thingsboard-developers

Changes

dao/pom.xml 176(+176 -0)

ui/.babelrc 9(+9 -0)

ui/.eslintrc 15(+15 -0)

ui/.gitignore 2(+2 -0)

ui/.jshintrc 13(+13 -0)

ui/package.json 123(+123 -0)

ui/pom.xml 142(+142 -0)

ui/server.js 75(+75 -0)

ui/src/app/app.js 105(+105 -0)

ui/src/app/app.run.js 166(+166 -0)

ui/src/index.html 37(+37 -0)

ui/src/scss/main.scss 402(+402 -0)

Details

diff --git a/application/src/main/java/org/thingsboard/server/config/JwtSettings.java b/application/src/main/java/org/thingsboard/server/config/JwtSettings.java
new file mode 100644
index 0000000..9a74785
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/config/JwtSettings.java
@@ -0,0 +1,75 @@
+/**
+ * Copyright © 2016 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.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.thingsboard.server.service.security.model.token.JwtToken;
+
+@Configuration
+@ConfigurationProperties(prefix = "security.jwt")
+public class JwtSettings {
+    /**
+     * {@link JwtToken} will expire after this time.
+     */
+    private Integer tokenExpirationTime;
+
+    /**
+     * Token issuer.
+     */
+    private String tokenIssuer;
+
+    /**
+     * Key is used to sign {@link JwtToken}.
+     */
+    private String tokenSigningKey;
+
+    /**
+     * {@link JwtToken} can be refreshed during this timeframe.
+     */
+    private Integer refreshTokenExpTime;
+
+    public Integer getRefreshTokenExpTime() {
+        return refreshTokenExpTime;
+    }
+
+    public void setRefreshTokenExpTime(Integer refreshTokenExpTime) {
+        this.refreshTokenExpTime = refreshTokenExpTime;
+    }
+
+    public Integer getTokenExpirationTime() {
+        return tokenExpirationTime;
+    }
+
+    public void setTokenExpirationTime(Integer tokenExpirationTime) {
+        this.tokenExpirationTime = tokenExpirationTime;
+    }
+
+    public String getTokenIssuer() {
+        return tokenIssuer;
+    }
+    public void setTokenIssuer(String tokenIssuer) {
+        this.tokenIssuer = tokenIssuer;
+    }
+
+    public String getTokenSigningKey() {
+        return tokenSigningKey;
+    }
+
+    public void setTokenSigningKey(String tokenSigningKey) {
+        this.tokenSigningKey = tokenSigningKey;
+    }
+}
\ No newline at end of file
diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardMessageConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardMessageConfiguration.java
new file mode 100644
index 0000000..99bec5b
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardMessageConfiguration.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright © 2016 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.config;
+
+import org.springframework.context.MessageSource;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.support.ResourceBundleMessageSource;
+
+@Configuration
+public class ThingsboardMessageConfiguration {
+
+    @Bean
+    public MessageSource messageSource() {
+        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
+        messageSource.setBasename("i18n/messages");
+        messageSource.setDefaultEncoding("UTF-8");
+        return messageSource;
+    }
+    
+}
diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
new file mode 100644
index 0000000..ec6ca81
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
@@ -0,0 +1,161 @@
+/**
+ * Copyright © 2016 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.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.security.SecurityProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
+import org.thingsboard.server.service.security.auth.rest.RestAuthenticationProvider;
+import org.thingsboard.server.service.security.auth.rest.RestLoginProcessingFilter;
+import org.thingsboard.server.service.security.auth.jwt.*;
+import org.thingsboard.server.service.security.auth.jwt.extractor.TokenExtractor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled=true)
+@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
+public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapter {
+
+    public static final String JWT_TOKEN_HEADER_PARAM = "X-Authorization";
+    public static final String JWT_TOKEN_QUERY_PARAM = "token";
+
+    public static final String DEVICE_API_ENTRY_POINT = "/api/v1/**";
+    public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login";
+    public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token";
+    public static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[] {"/index.html", "/static/**", "/api/noauth/**"};
+    public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**";
+    public static final String WS_TOKEN_BASED_AUTH_ENTRY_POINT = "/api/ws/**";
+
+    @Autowired private ThingsboardErrorResponseHandler restAccessDeniedHandler;
+    @Autowired private AuthenticationSuccessHandler successHandler;
+    @Autowired private AuthenticationFailureHandler failureHandler;
+    @Autowired private RestAuthenticationProvider restAuthenticationProvider;
+    @Autowired private JwtAuthenticationProvider jwtAuthenticationProvider;
+    @Autowired private RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider;
+
+    @Autowired
+    @Qualifier("jwtHeaderTokenExtractor")
+    private TokenExtractor jwtHeaderTokenExtractor;
+
+    @Autowired
+    @Qualifier("jwtQueryTokenExtractor")
+    private TokenExtractor jwtQueryTokenExtractor;
+
+    @Autowired private AuthenticationManager authenticationManager;
+
+    @Autowired private ObjectMapper objectMapper;
+
+    @Bean
+    protected RestLoginProcessingFilter buildRestLoginProcessingFilter() throws Exception {
+        RestLoginProcessingFilter filter = new RestLoginProcessingFilter(FORM_BASED_LOGIN_ENTRY_POINT, successHandler, failureHandler, objectMapper);
+        filter.setAuthenticationManager(this.authenticationManager);
+        return filter;
+    }
+
+    @Bean
+    protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception {
+        List<String> pathsToSkip = new ArrayList(Arrays.asList(NON_TOKEN_BASED_AUTH_ENTRY_POINTS));
+        pathsToSkip.addAll(Arrays.asList(WS_TOKEN_BASED_AUTH_ENTRY_POINT, TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT, DEVICE_API_ENTRY_POINT));
+        SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT);
+        JwtTokenAuthenticationProcessingFilter filter
+                = new JwtTokenAuthenticationProcessingFilter(failureHandler, jwtHeaderTokenExtractor, matcher);
+        filter.setAuthenticationManager(this.authenticationManager);
+        return filter;
+    }
+
+    @Bean
+    protected RefreshTokenProcessingFilter buildRefreshTokenProcessingFilter() throws Exception {
+        RefreshTokenProcessingFilter filter = new RefreshTokenProcessingFilter(TOKEN_REFRESH_ENTRY_POINT, successHandler, failureHandler, objectMapper);
+        filter.setAuthenticationManager(this.authenticationManager);
+        return filter;
+    }
+
+    @Bean
+    protected JwtTokenAuthenticationProcessingFilter buildWsJwtTokenAuthenticationProcessingFilter() throws Exception {
+        AntPathRequestMatcher matcher = new AntPathRequestMatcher(WS_TOKEN_BASED_AUTH_ENTRY_POINT);
+        JwtTokenAuthenticationProcessingFilter filter
+                = new JwtTokenAuthenticationProcessingFilter(failureHandler, jwtQueryTokenExtractor, matcher);
+        filter.setAuthenticationManager(this.authenticationManager);
+        return filter;
+    }
+
+    @Bean
+    @Override
+    public AuthenticationManager authenticationManagerBean() throws Exception {
+        return super.authenticationManagerBean();
+    }
+
+    @Override
+    protected void configure(AuthenticationManagerBuilder auth) {
+        auth.authenticationProvider(restAuthenticationProvider);
+        auth.authenticationProvider(jwtAuthenticationProvider);
+        auth.authenticationProvider(refreshTokenAuthenticationProvider);
+    }
+
+    @Bean
+    protected BCryptPasswordEncoder passwordEncoder() {
+        return new BCryptPasswordEncoder();
+    }
+
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        http.headers().frameOptions().disable()
+                .and()
+                .csrf().disable()
+                .exceptionHandling()
+                .and()
+                .sessionManagement()
+                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+                .and()
+                .authorizeRequests()
+                .antMatchers(DEVICE_API_ENTRY_POINT).permitAll() // Device HTTP Transport API
+                .antMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll() // Login end-point
+                .antMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll() // Token refresh end-point
+                .antMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS).permitAll() // static resources, user activation and password reset end-points
+                .and()
+                .authorizeRequests()
+                .antMatchers(WS_TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() // Protected WebSocket API End-points
+                .antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() // Protected API End-points
+                .and()
+                .exceptionHandling().accessDeniedHandler(restAccessDeniedHandler)
+                .and()
+                .addFilterBefore(buildRestLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
+                .addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
+                .addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
+                .addFilterBefore(buildWsJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/config/WebConfig.java b/application/src/main/java/org/thingsboard/server/config/WebConfig.java
new file mode 100644
index 0000000..3a2234a
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/config/WebConfig.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright © 2016 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.config;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@Controller
+public class WebConfig {
+
+    @RequestMapping(value = "/{path:^(?!api$)(?!static$)[^\\.]*}/**")
+    public String redirect() {
+        return "forward:/index.html";
+    }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java b/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java
new file mode 100644
index 0000000..b6b4d0e
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java
@@ -0,0 +1,96 @@
+/**
+ * Copyright © 2016 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.config;
+
+import java.util.Map;
+
+import org.thingsboard.server.exception.ThingsboardErrorCode;
+import org.thingsboard.server.exception.ThingsboardException;
+import org.thingsboard.server.controller.plugin.PluginWebSocketHandler;
+import org.thingsboard.server.service.security.model.SecurityUser;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.config.annotation.EnableWebSocket;
+import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
+import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
+import org.springframework.web.socket.server.HandshakeInterceptor;
+import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
+import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
+
+@Configuration
+@EnableWebSocket
+public class WebSocketConfiguration implements WebSocketConfigurer {
+
+    public static final String WS_PLUGIN_PREFIX = "/api/ws/plugins/";
+    public static final String WS_SECURITY_USER_ATTRIBUTE = "SECURITY_USER";
+    private static final String WS_PLUGIN_MAPPING = WS_PLUGIN_PREFIX + "**";
+
+    @Bean
+    public ServletServerContainerFactoryBean createWebSocketContainer() {
+        ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
+        container.setMaxTextMessageBufferSize(8192);
+        container.setMaxBinaryMessageBufferSize(8192);
+        return container;
+    }
+
+    @Override
+    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
+        registry.addHandler(pluginWsHandler(), WS_PLUGIN_MAPPING).setAllowedOrigins("*")
+                .addInterceptors(new HttpSessionHandshakeInterceptor(), new HandshakeInterceptor() {
+
+                    @Override
+                    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
+                            Map<String, Object> attributes) throws Exception {
+                        SecurityUser user = null;
+                        try {
+                            user = getCurrentUser();
+                        } catch (ThingsboardException ex) {}
+                        if (user == null) {
+                            response.setStatusCode(HttpStatus.UNAUTHORIZED);
+                            return false;
+                        } else {
+                            attributes.put(WS_SECURITY_USER_ATTRIBUTE, user);
+                            return true;
+                        }
+                    }
+
+                    @Override
+                    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
+                            Exception exception) {
+                    }
+                });
+    }
+
+    @Bean
+    public WebSocketHandler pluginWsHandler() {
+        return new PluginWebSocketHandler();
+    }
+
+    protected SecurityUser getCurrentUser() throws ThingsboardException {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        if (authentication != null && authentication.getPrincipal() instanceof SecurityUser) {
+            return (SecurityUser) authentication.getPrincipal();
+        } else {
+            throw new ThingsboardException("You aren't authorized to perform this operation!", ThingsboardErrorCode.AUTHENTICATION);
+        }
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/AdminController.java b/application/src/main/java/org/thingsboard/server/controller/AdminController.java
new file mode 100644
index 0000000..cace783
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/AdminController.java
@@ -0,0 +1,75 @@
+/**
+ * Copyright © 2016 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.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.thingsboard.server.common.data.AdminSettings;
+import org.thingsboard.server.dao.settings.AdminSettingsService;
+import org.thingsboard.server.exception.ThingsboardException;
+import org.thingsboard.server.service.mail.MailService;
+
+@RestController
+@RequestMapping("/api/admin")
+public class AdminController extends BaseController {
+
+    @Autowired
+    private MailService mailService;
+    
+    @Autowired
+    private AdminSettingsService adminSettingsService;
+
+    @PreAuthorize("hasAuthority('SYS_ADMIN')")
+    @RequestMapping(value = "/settings/{key}", method = RequestMethod.GET)
+    @ResponseBody
+    public AdminSettings getAdminSettings(@PathVariable("key") String key) throws ThingsboardException {
+        try {
+            return checkNotNull(adminSettingsService.findAdminSettingsByKey(key));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('SYS_ADMIN')")
+    @RequestMapping(value = "/settings", method = RequestMethod.POST)
+    @ResponseBody 
+    public AdminSettings saveAdminSettings(@RequestBody AdminSettings adminSettings) throws ThingsboardException {
+        try {
+            adminSettings = checkNotNull(adminSettingsService.saveAdminSettings(adminSettings));
+            if (adminSettings.getKey().equals("mail")) {
+                mailService.updateMailConfiguration();
+            }
+            return adminSettings;
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('SYS_ADMIN')")
+    @RequestMapping(value = "/settings/testMail", method = RequestMethod.POST)
+    public void sendTestMail(@RequestBody AdminSettings adminSettings) throws ThingsboardException {
+        try {
+            adminSettings = checkNotNull(adminSettings);
+            if (adminSettings.getKey().equals("mail")) {
+               String email = getCurrentUser().getEmail();
+               mailService.sendTestMail(adminSettings.getJsonValue(), email);
+            }
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java
new file mode 100644
index 0000000..b704c86
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java
@@ -0,0 +1,236 @@
+/**
+ * Copyright © 2016 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.controller;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.web.bind.annotation.*;
+import org.thingsboard.server.common.data.User;
+import org.thingsboard.server.common.data.security.UserCredentials;
+import org.thingsboard.server.dao.user.UserService;
+import org.thingsboard.server.exception.ThingsboardErrorCode;
+import org.thingsboard.server.exception.ThingsboardException;
+import org.thingsboard.server.service.mail.MailService;
+import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
+import org.thingsboard.server.service.security.model.SecurityUser;
+import org.thingsboard.server.service.security.model.token.JwtToken;
+import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+@RestController
+@RequestMapping("/api")
+@Slf4j
+public class AuthController extends BaseController {
+
+
+
+    @Autowired
+    private BCryptPasswordEncoder passwordEncoder;
+
+    @Autowired
+    private JwtTokenFactory tokenFactory;
+
+    @Autowired
+    private RefreshTokenRepository refreshTokenRepository;
+
+    @Autowired
+    private UserService userService;
+
+    @Autowired
+    private MailService mailService;
+
+    @PreAuthorize("isAuthenticated()")
+    @RequestMapping(value = "/auth/user", method = RequestMethod.GET)
+    public @ResponseBody User getUser() throws ThingsboardException {
+        try {
+            SecurityUser securityUser = getCurrentUser();
+            return userService.findUserById(securityUser.getId());
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    @RequestMapping(value = "/auth/changePassword", method = RequestMethod.POST)
+    @ResponseStatus(value = HttpStatus.OK)
+    public void changePassword (
+            @RequestParam(value = "currentPassword") String currentPassword,
+            @RequestParam(value = "newPassword") String newPassword) throws ThingsboardException {
+        try {
+            SecurityUser securityUser = getCurrentUser();
+            UserCredentials userCredentials = userService.findUserCredentialsByUserId(securityUser.getId());
+            if (!passwordEncoder.matches(currentPassword, userCredentials.getPassword())) {
+                throw new ThingsboardException("Current password doesn't match!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
+            }
+            userCredentials.setPassword(passwordEncoder.encode(newPassword));
+            userService.saveUserCredentials(userCredentials);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+    
+    @RequestMapping(value = "/noauth/activate", params = { "activateToken" }, method = RequestMethod.GET)
+    public ResponseEntity<String> checkActivateToken(
+            @RequestParam(value = "activateToken") String activateToken) {
+        HttpHeaders headers = new HttpHeaders();
+        HttpStatus responseStatus;
+        UserCredentials userCredentials = userService.findUserCredentialsByActivateToken(activateToken);
+        if (userCredentials != null) {
+            String createPasswordURI = "/login/createPassword";
+            try {
+                URI location = new URI(createPasswordURI + "?activateToken=" + activateToken);
+                headers.setLocation(location);
+                responseStatus = HttpStatus.PERMANENT_REDIRECT;
+            } catch (URISyntaxException e) {
+                log.error("Unable to create URI with address [{}]", createPasswordURI);
+                responseStatus = HttpStatus.BAD_REQUEST;
+            }
+        } else {
+            responseStatus = HttpStatus.CONFLICT;
+        }
+        return new ResponseEntity<>(headers, responseStatus);
+    }
+    
+    @RequestMapping(value = "/noauth/resetPasswordByEmail", method = RequestMethod.POST)
+    @ResponseStatus(value = HttpStatus.OK)
+    public void requestResetPasswordByEmail (
+            @RequestParam(value = "email") String email,
+            HttpServletRequest request) throws ThingsboardException {
+        try {
+            UserCredentials userCredentials = userService.requestPasswordReset(email);
+            
+            String baseUrl = String.format("%s://%s:%d",
+                    request.getScheme(),  
+                    request.getServerName(), 
+                    request.getServerPort());             
+            String resetPasswordUrl = String.format("%s/api/noauth/resetPassword?resetToken=%s", baseUrl,
+                    userCredentials.getResetToken());
+            
+            mailService.sendResetPasswordEmail(resetPasswordUrl, email);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+    
+    @RequestMapping(value = "/noauth/resetPassword", params = { "resetToken" }, method = RequestMethod.GET)
+    public ResponseEntity<String> checkResetToken(
+            @RequestParam(value = "resetToken") String resetToken) {
+        HttpHeaders headers = new HttpHeaders();
+        HttpStatus responseStatus;
+        String resetPasswordURI = "/login/resetPassword";
+        UserCredentials userCredentials = userService.findUserCredentialsByResetToken(resetToken);
+        if (userCredentials != null) {
+            try {
+                URI location = new URI(resetPasswordURI + "?resetToken=" + resetToken);
+                headers.setLocation(location);
+                responseStatus = HttpStatus.PERMANENT_REDIRECT;
+            } catch (URISyntaxException e) {
+                log.error("Unable to create URI with address [{}]", resetPasswordURI);
+                responseStatus = HttpStatus.BAD_REQUEST;
+            }
+        } else {
+            responseStatus = HttpStatus.CONFLICT;
+        }
+        return new ResponseEntity<>(headers, responseStatus);
+    }
+    
+    @RequestMapping(value = "/noauth/activate", method = RequestMethod.POST)
+    @ResponseStatus(value = HttpStatus.OK)
+    @ResponseBody
+    public JsonNode activateUser(
+            @RequestParam(value = "activateToken") String activateToken,
+            @RequestParam(value = "password") String password,
+            HttpServletRequest request) throws ThingsboardException {
+        try {
+            String encodedPassword = passwordEncoder.encode(password);
+            UserCredentials credentials = userService.activateUserCredentials(activateToken, encodedPassword);
+            User user = userService.findUserById(credentials.getUserId());
+            SecurityUser securityUser = new SecurityUser(user, credentials.isEnabled());
+            String baseUrl = String.format("%s://%s:%d",
+                    request.getScheme(),  
+                    request.getServerName(), 
+                    request.getServerPort());             
+            String loginUrl = String.format("%s/login", baseUrl);
+            String email = user.getEmail();
+            mailService.sendAccountActivatedEmail(loginUrl, email);
+
+            JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
+            JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
+
+            ObjectMapper objectMapper = new ObjectMapper();
+            ObjectNode tokenObject = objectMapper.createObjectNode();
+            tokenObject.put("token", accessToken.getToken());
+            tokenObject.put("refreshToken", refreshToken.getToken());
+            return tokenObject;
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+    
+    @RequestMapping(value = "/noauth/resetPassword", method = RequestMethod.POST)
+    @ResponseStatus(value = HttpStatus.OK)
+    @ResponseBody
+    public JsonNode resetPassword(
+            @RequestParam(value = "resetToken") String resetToken,
+            @RequestParam(value = "password") String password,
+            HttpServletRequest request) throws ThingsboardException {
+        try {
+            UserCredentials userCredentials = userService.findUserCredentialsByResetToken(resetToken);
+            if (userCredentials != null) {
+                String encodedPassword = passwordEncoder.encode(password);
+                userCredentials.setPassword(encodedPassword);
+                userCredentials.setResetToken(null);
+                userCredentials = userService.saveUserCredentials(userCredentials);
+                User user = userService.findUserById(userCredentials.getUserId());
+                SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled());
+                String baseUrl = String.format("%s://%s:%d",
+                        request.getScheme(),  
+                        request.getServerName(), 
+                        request.getServerPort());             
+                String loginUrl = String.format("%s/login", baseUrl);
+                String email = user.getEmail();
+                mailService.sendPasswordWasResetEmail(loginUrl, email);
+
+                JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
+                JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
+
+                ObjectMapper objectMapper = new ObjectMapper();
+                ObjectNode tokenObject = objectMapper.createObjectNode();
+                tokenObject.put("token", accessToken.getToken());
+                tokenObject.put("refreshToken", refreshToken.getToken());
+                return tokenObject;
+            } else {
+                throw new ThingsboardException("Invalid reset token!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
+            }
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
new file mode 100644
index 0000000..db6380b
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
@@ -0,0 +1,377 @@
+/**
+ * Copyright © 2016 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.controller;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.thingsboard.server.actors.service.ActorService;
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.Dashboard;
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.User;
+import org.thingsboard.server.common.data.id.*;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.page.TimePageLink;
+import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.common.data.plugin.PluginMetaData;
+import org.thingsboard.server.common.data.rule.RuleMetaData;
+import org.thingsboard.server.common.data.security.Authority;
+import org.thingsboard.server.common.data.widget.WidgetType;
+import org.thingsboard.server.common.data.widget.WidgetsBundle;
+import org.thingsboard.server.dao.customer.CustomerService;
+import org.thingsboard.server.dao.dashboard.DashboardService;
+import org.thingsboard.server.dao.device.DeviceCredentialsService;
+import org.thingsboard.server.dao.device.DeviceService;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.exception.IncorrectParameterException;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.dao.plugin.PluginService;
+import org.thingsboard.server.dao.rule.RuleService;
+import org.thingsboard.server.dao.user.UserService;
+import org.thingsboard.server.dao.widget.WidgetTypeService;
+import org.thingsboard.server.dao.widget.WidgetsBundleService;
+import org.thingsboard.server.exception.ThingsboardErrorCode;
+import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
+import org.thingsboard.server.exception.ThingsboardException;
+import org.thingsboard.server.service.component.ComponentDiscoveryService;
+import org.thingsboard.server.service.security.model.SecurityUser;
+
+import javax.mail.MessagingException;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+import static org.thingsboard.server.dao.service.Validator.validateId;
+
+@Slf4j
+public abstract class BaseController {
+
+    @Autowired
+    private ThingsboardErrorResponseHandler errorResponseHandler;
+
+    @Autowired
+    protected CustomerService customerService;
+
+    @Autowired
+    protected UserService userService;
+
+    @Autowired
+    protected DeviceService deviceService;
+
+    @Autowired
+    protected DeviceCredentialsService deviceCredentialsService;
+
+    @Autowired
+    protected WidgetsBundleService widgetsBundleService;
+
+    @Autowired
+    protected WidgetTypeService widgetTypeService;
+
+    @Autowired
+    protected DashboardService dashboardService;
+
+    @Autowired
+    protected ComponentDiscoveryService componentDescriptorService;
+
+    @Autowired
+    protected RuleService ruleService;
+
+    @Autowired
+    protected PluginService pluginService;
+
+    @Autowired
+    protected ActorService actorService;
+
+
+    @ExceptionHandler(ThingsboardException.class)
+    public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) {
+        errorResponseHandler.handle(ex, response);
+    }
+
+    ThingsboardException handleException(Exception exception) {
+        return handleException(exception, true);
+    }
+
+    private ThingsboardException handleException(Exception exception, boolean logException) {
+        if (logException) {
+            log.error("Error [{}]", exception.getMessage());
+        }
+
+        String cause = "";
+        if (exception.getCause() != null) {
+            cause = exception.getCause().getClass().getCanonicalName();
+        }
+
+        if (exception instanceof ThingsboardException) {
+            return (ThingsboardException) exception;
+        } else if (exception instanceof IllegalArgumentException || exception instanceof IncorrectParameterException
+                || exception instanceof DataValidationException || cause.contains("IncorrectParameterException")) {
+            return new ThingsboardException(exception.getMessage(), ThingsboardErrorCode.BAD_REQUEST_PARAMS);
+        } else if (exception instanceof MessagingException) {
+            return new ThingsboardException("Unable to send mail: " + exception.getMessage(), ThingsboardErrorCode.GENERAL);
+        } else {
+            return new ThingsboardException(exception.getMessage(), ThingsboardErrorCode.GENERAL);
+        }
+    }
+
+    <T> T checkNotNull(T reference) throws ThingsboardException {
+        if (reference == null) {
+            throw new ThingsboardException("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND);
+        }
+        return reference;
+    }
+
+    <T> T checkNotNull(Optional<T> reference) throws ThingsboardException {
+        if (reference.isPresent()) {
+            return reference.get();
+        } else {
+            throw new ThingsboardException("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND);
+        }
+    }
+
+    void checkParameter(String name, String param) throws ThingsboardException {
+        if (StringUtils.isEmpty(param)) {
+            throw new ThingsboardException("Parameter '" + name + "' can't be empty!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
+        }
+    }
+
+    UUID toUUID(String id) {
+        return UUID.fromString(id);
+    }
+
+    TimePageLink createPageLink(int limit, Long startTime, Long endTime, boolean ascOrder, String idOffset) {
+        UUID idOffsetUuid = null;
+        if (StringUtils.isNotEmpty(idOffset)) {
+            idOffsetUuid = toUUID(idOffset);
+        }
+        return new TimePageLink(limit, startTime, endTime, ascOrder, idOffsetUuid);
+    }
+
+
+    TextPageLink createPageLink(int limit, String textSearch, String idOffset, String textOffset) {
+        UUID idOffsetUuid = null;
+        if (StringUtils.isNotEmpty(idOffset)) {
+            idOffsetUuid = toUUID(idOffset);
+        }
+        return new TextPageLink(limit, textSearch, idOffsetUuid, textOffset);
+    }
+
+    protected SecurityUser getCurrentUser() throws ThingsboardException {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        if (authentication != null && authentication.getPrincipal() instanceof SecurityUser) {
+            return (SecurityUser) authentication.getPrincipal();
+        } else {
+            throw new ThingsboardException("You aren't authorized to perform this operation!", ThingsboardErrorCode.AUTHENTICATION);
+        }
+    }
+
+    void checkTenantId(TenantId tenantId) throws ThingsboardException {
+        validateId(tenantId, "Incorrect tenantId " + tenantId);
+        SecurityUser authUser = getCurrentUser();
+        if (authUser.getAuthority() != Authority.SYS_ADMIN &&
+                (authUser.getTenantId() == null || !authUser.getTenantId().equals(tenantId))) {
+            throw new ThingsboardException("You don't have permission to perform this operation!",
+                    ThingsboardErrorCode.PERMISSION_DENIED);
+        }
+    }
+
+    protected TenantId getTenantId() throws ThingsboardException {
+        return getCurrentUser().getTenantId();
+    }
+
+    Customer checkCustomerId(CustomerId customerId) throws ThingsboardException {
+        try {
+            validateId(customerId, "Incorrect customerId " + customerId);
+            SecurityUser authUser = getCurrentUser();
+            if (authUser.getAuthority() == Authority.SYS_ADMIN ||
+                    (authUser.getAuthority() != Authority.TENANT_ADMIN &&
+                            (authUser.getCustomerId() == null || !authUser.getCustomerId().equals(customerId)))) {
+                throw new ThingsboardException("You don't have permission to perform this operation!",
+                        ThingsboardErrorCode.PERMISSION_DENIED);
+            }
+            Customer customer = customerService.findCustomerById(customerId);
+            checkCustomer(customer);
+            return customer;
+        } catch (Exception e) {
+            throw handleException(e, false);
+        }
+    }
+
+    private void checkCustomer(Customer customer) throws ThingsboardException {
+        checkNotNull(customer);
+        checkTenantId(customer.getTenantId());
+    }
+
+    User checkUserId(UserId userId) throws ThingsboardException {
+        try {
+            validateId(userId, "Incorrect userId " + userId);
+            User user = userService.findUserById(userId);
+            checkUser(user);
+            return user;
+        } catch (Exception e) {
+            throw handleException(e, false);
+        }
+    }
+
+    private void checkUser(User user) throws ThingsboardException {
+        checkNotNull(user);
+        checkTenantId(user.getTenantId());
+        if (user.getAuthority() == Authority.CUSTOMER_USER) {
+            checkCustomerId(user.getCustomerId());
+        }
+    }
+
+    Device checkDeviceId(DeviceId deviceId) throws ThingsboardException {
+        try {
+            validateId(deviceId, "Incorrect deviceId " + deviceId);
+            Device device = deviceService.findDeviceById(deviceId);
+            checkDevice(device);
+            return device;
+        } catch (Exception e) {
+            throw handleException(e, false);
+        }
+    }
+
+    private void checkDevice(Device device) throws ThingsboardException {
+        checkNotNull(device);
+        checkTenantId(device.getTenantId());
+        if (device.getCustomerId() != null && !device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
+            checkCustomerId(device.getCustomerId());
+        }
+    }
+
+    WidgetsBundle checkWidgetsBundleId(WidgetsBundleId widgetsBundleId, boolean modify) throws ThingsboardException {
+        try {
+            validateId(widgetsBundleId, "Incorrect widgetsBundleId " + widgetsBundleId);
+            WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleById(widgetsBundleId);
+            checkWidgetsBundle(widgetsBundle, modify);
+            return widgetsBundle;
+        } catch (Exception e) {
+            throw handleException(e, false);
+        }
+    }
+
+    private void checkWidgetsBundle(WidgetsBundle widgetsBundle, boolean modify) throws ThingsboardException {
+        checkNotNull(widgetsBundle);
+        if (widgetsBundle.getTenantId() != null && !widgetsBundle.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
+            checkTenantId(widgetsBundle.getTenantId());
+        } else if (modify && getCurrentUser().getAuthority() != Authority.SYS_ADMIN) {
+            throw new ThingsboardException("You don't have permission to perform this operation!",
+                    ThingsboardErrorCode.PERMISSION_DENIED);
+        }
+    }
+
+    WidgetType checkWidgetTypeId(WidgetTypeId widgetTypeId, boolean modify) throws ThingsboardException {
+        try {
+            validateId(widgetTypeId, "Incorrect widgetTypeId " + widgetTypeId);
+            WidgetType widgetType = widgetTypeService.findWidgetTypeById(widgetTypeId);
+            checkWidgetType(widgetType, modify);
+            return widgetType;
+        } catch (Exception e) {
+            throw handleException(e, false);
+        }
+    }
+
+    void checkWidgetType(WidgetType widgetType, boolean modify) throws ThingsboardException {
+        checkNotNull(widgetType);
+        if (widgetType.getTenantId() != null && !widgetType.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
+            checkTenantId(widgetType.getTenantId());
+        } else if (modify && getCurrentUser().getAuthority() != Authority.SYS_ADMIN) {
+            throw new ThingsboardException("You don't have permission to perform this operation!",
+                    ThingsboardErrorCode.PERMISSION_DENIED);
+        }
+    }
+
+    Dashboard checkDashboardId(DashboardId dashboardId) throws ThingsboardException {
+        try {
+            validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
+            Dashboard dashboard = dashboardService.findDashboardById(dashboardId);
+            checkDashboard(dashboard);
+            return dashboard;
+        } catch (Exception e) {
+            throw handleException(e, false);
+        }
+    }
+
+    private void checkDashboard(Dashboard dashboard) throws ThingsboardException {
+        checkNotNull(dashboard);
+        checkTenantId(dashboard.getTenantId());
+        if (dashboard.getCustomerId() != null && !dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
+            checkCustomerId(dashboard.getCustomerId());
+        }
+    }
+
+    ComponentDescriptor checkComponentDescriptorByClazz(String clazz) throws ThingsboardException {
+        try {
+            log.debug("[{}] Lookup component descriptor", clazz);
+            ComponentDescriptor componentDescriptor = checkNotNull(componentDescriptorService.getComponent(clazz));
+            return componentDescriptor;
+        } catch (Exception e) {
+            throw handleException(e, false);
+        }
+    }
+
+    List<ComponentDescriptor> checkComponentDescriptorsByType(ComponentType type) throws ThingsboardException {
+        try {
+            log.debug("[{}] Lookup component descriptors", type);
+            return componentDescriptorService.getComponents(type);
+        } catch (Exception e) {
+            throw handleException(e, false);
+        }
+    }
+
+    List<ComponentDescriptor> checkPluginActionsByPluginClazz(String pluginClazz) throws ThingsboardException {
+        try {
+            checkComponentDescriptorByClazz(pluginClazz);
+            log.debug("[{}] Lookup plugin actions", pluginClazz);
+            return componentDescriptorService.getPluginActions(pluginClazz);
+        } catch (Exception e) {
+            throw handleException(e, false);
+        }
+    }
+
+    protected PluginMetaData checkPlugin(PluginMetaData plugin) throws ThingsboardException {
+        checkNotNull(plugin);
+        SecurityUser authUser = getCurrentUser();
+        TenantId tenantId = plugin.getTenantId();
+        validateId(tenantId, "Incorrect tenantId " + tenantId);
+        if (authUser.getAuthority() != Authority.SYS_ADMIN) {
+            if (authUser.getTenantId() == null ||
+                    !tenantId.getId().equals(ModelConstants.NULL_UUID) && !authUser.getTenantId().equals(tenantId)) {
+                throw new ThingsboardException("You don't have permission to perform this operation!",
+                        ThingsboardErrorCode.PERMISSION_DENIED);
+
+            } else if (tenantId.getId().equals(ModelConstants.NULL_UUID)) {
+                plugin.setConfiguration(null);
+            }
+        }
+        return plugin;
+    }
+
+    protected RuleMetaData checkRule(RuleMetaData rule) throws ThingsboardException {
+        checkNotNull(rule);
+        checkTenantId(rule.getTenantId());
+        return rule;
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java b/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java
new file mode 100644
index 0000000..0744dd1
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java
@@ -0,0 +1,66 @@
+/**
+ * Copyright © 2016 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.controller;
+
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.exception.ThingsboardException;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/api")
+public class ComponentDescriptorController extends BaseController {
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')")
+    @RequestMapping(value = "/component/{componentDescriptorClazz:.+}", method = RequestMethod.GET)
+    @ResponseBody
+    public ComponentDescriptor getComponentDescriptorByClazz(@PathVariable("componentDescriptorClazz") String strComponentDescriptorClazz) throws ThingsboardException {
+        checkParameter("strComponentDescriptorClazz", strComponentDescriptorClazz);
+        try {
+            return checkComponentDescriptorByClazz(strComponentDescriptorClazz);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')")
+    @RequestMapping(value = "/components/{componentType}", method = RequestMethod.GET)
+    @ResponseBody
+    public List<ComponentDescriptor> getComponentDescriptorsByType(@PathVariable("componentType") String strComponentType) throws ThingsboardException {
+        checkParameter("componentType", strComponentType);
+        try {
+            return checkComponentDescriptorsByType(ComponentType.valueOf(strComponentType));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')")
+    @RequestMapping(value = "/components/actions/{pluginClazz:.+}", method = RequestMethod.GET)
+    @ResponseBody
+    public List<ComponentDescriptor> getPluginActionsByPluginClazz(@PathVariable("pluginClazz") String pluginClazz) throws ThingsboardException {
+        checkParameter("pluginClazz", pluginClazz);
+        try {
+            return checkPluginActionsByPluginClazz(pluginClazz);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java
new file mode 100644
index 0000000..93b4267
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java
@@ -0,0 +1,87 @@
+/**
+ * Copyright © 2016 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.controller;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.exception.ThingsboardException;
+
+@RestController
+@RequestMapping("/api")
+public class CustomerController extends BaseController {
+
+    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+    @RequestMapping(value = "/customer/{customerId}", method = RequestMethod.GET)
+    @ResponseBody
+    public Customer getCustomerById(@PathVariable("customerId") String strCustomerId) throws ThingsboardException {
+        checkParameter("customerId", strCustomerId);
+        try {
+            CustomerId customerId = new CustomerId(toUUID(strCustomerId));
+            return checkCustomerId(customerId);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/customer", method = RequestMethod.POST)
+    @ResponseBody 
+    public Customer saveCustomer(@RequestBody Customer customer) throws ThingsboardException {
+        try {
+            customer.setTenantId(getCurrentUser().getTenantId());
+            return checkNotNull(customerService.saveCustomer(customer));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/customer/{customerId}", method = RequestMethod.DELETE)
+    @ResponseStatus(value = HttpStatus.OK)
+    public void deleteCustomer(@PathVariable("customerId") String strCustomerId) throws ThingsboardException {
+        checkParameter("customerId", strCustomerId);
+        try {
+            CustomerId customerId = new CustomerId(toUUID(strCustomerId));
+            checkCustomerId(customerId);
+            customerService.deleteCustomer(customerId);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/customers", params = { "limit" }, method = RequestMethod.GET)
+    @ResponseBody
+    public TextPageData<Customer> getCustomers(@RequestParam int limit,
+                                               @RequestParam(required = false) String textSearch,
+                                               @RequestParam(required = false) String idOffset,
+                                               @RequestParam(required = false) String textOffset) throws ThingsboardException {
+        try {
+            TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
+            TenantId tenantId = getCurrentUser().getTenantId();
+            return checkNotNull(customerService.findCustomersByTenantId(tenantId, pageLink));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java
new file mode 100644
index 0000000..6ce92c9
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java
@@ -0,0 +1,148 @@
+/**
+ * Copyright © 2016 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.controller;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.thingsboard.server.common.data.Dashboard;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.DashboardId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.exception.IncorrectParameterException;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.exception.ThingsboardException;
+
+@RestController
+@RequestMapping("/api")
+public class DashboardController extends BaseController {
+
+    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+    @RequestMapping(value = "/dashboard/{dashboardId}", method = RequestMethod.GET)
+    @ResponseBody
+    public Dashboard getDashboardById(@PathVariable("dashboardId") String strDashboardId) throws ThingsboardException {
+        checkParameter("dashboardId", strDashboardId);
+        try {
+            DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
+            return checkDashboardId(dashboardId);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/dashboard", method = RequestMethod.POST)
+    @ResponseBody 
+    public Dashboard saveDashboard(@RequestBody Dashboard dashboard) throws ThingsboardException {
+        try {
+            dashboard.setTenantId(getCurrentUser().getTenantId());
+            return checkNotNull(dashboardService.saveDashboard(dashboard));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/dashboard/{dashboardId}", method = RequestMethod.DELETE)
+    @ResponseStatus(value = HttpStatus.OK)
+    public void deleteDashboard(@PathVariable("dashboardId") String strDashboardId) throws ThingsboardException {
+        checkParameter("dashboardId", strDashboardId);
+        try {
+            DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
+            checkDashboardId(dashboardId);
+            dashboardService.deleteDashboard(dashboardId);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.POST)
+    @ResponseBody 
+    public Dashboard assignDashboardToCustomer(@PathVariable("customerId") String strCustomerId,
+                                         @PathVariable("dashboardId") String strDashboardId) throws ThingsboardException {
+        checkParameter("customerId", strCustomerId);
+        checkParameter("dashboardId", strDashboardId);
+        try {
+            CustomerId customerId = new CustomerId(toUUID(strCustomerId));
+            checkCustomerId(customerId);
+
+            DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
+            checkDashboardId(dashboardId);
+            
+            return checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/customer/dashboard/{dashboardId}", method = RequestMethod.DELETE)
+    @ResponseBody 
+    public Dashboard unassignDashboardFromCustomer(@PathVariable("dashboardId") String strDashboardId) throws ThingsboardException {
+        checkParameter("dashboardId", strDashboardId);
+        try {
+            DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
+            Dashboard dashboard = checkDashboardId(dashboardId);
+            if (dashboard.getCustomerId() == null || dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
+                throw new IncorrectParameterException("Dashboard isn't assigned to any customer!");
+            }
+            return checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/tenant/dashboards", params = { "limit" }, method = RequestMethod.GET)
+    @ResponseBody
+    public TextPageData<Dashboard> getTenantDashboards(
+            @RequestParam int limit,
+            @RequestParam(required = false) String textSearch,
+            @RequestParam(required = false) String idOffset,
+            @RequestParam(required = false) String textOffset) throws ThingsboardException {
+        try {
+            TenantId tenantId = getCurrentUser().getTenantId();
+            TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
+            return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+    @RequestMapping(value = "/customer/{customerId}/dashboards", params = { "limit" }, method = RequestMethod.GET)
+    @ResponseBody
+    public TextPageData<Dashboard> getCustomerDashboards(
+            @PathVariable("customerId") String strCustomerId,
+            @RequestParam int limit,
+            @RequestParam(required = false) String textSearch,
+            @RequestParam(required = false) String idOffset,
+            @RequestParam(required = false) String textOffset) throws ThingsboardException {
+        checkParameter("customerId", strCustomerId);
+        try {
+            TenantId tenantId = getCurrentUser().getTenantId();
+            CustomerId customerId = new CustomerId(toUUID(strCustomerId));
+            checkCustomerId(customerId);
+            TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
+            return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
new file mode 100644
index 0000000..1c0a7be
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
@@ -0,0 +1,176 @@
+/**
+ * Copyright © 2016 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.controller;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.security.DeviceCredentials;
+import org.thingsboard.server.dao.exception.IncorrectParameterException;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.exception.ThingsboardException;
+
+@RestController
+@RequestMapping("/api")
+public class DeviceController extends BaseController {
+
+    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+    @RequestMapping(value = "/device/{deviceId}", method = RequestMethod.GET)
+    @ResponseBody
+    public Device getDeviceById(@PathVariable("deviceId") String strDeviceId) throws ThingsboardException {
+        checkParameter("deviceId", strDeviceId);
+        try {
+            DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
+            return checkDeviceId(deviceId);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/device", method = RequestMethod.POST)
+    @ResponseBody 
+    public Device saveDevice(@RequestBody Device device) throws ThingsboardException {
+        try {
+            device.setTenantId(getCurrentUser().getTenantId());
+            return checkNotNull(deviceService.saveDevice(device));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/device/{deviceId}", method = RequestMethod.DELETE)
+    @ResponseStatus(value = HttpStatus.OK)
+    public void deleteDevice(@PathVariable("deviceId") String strDeviceId) throws ThingsboardException {
+        checkParameter("deviceId", strDeviceId);
+        try {
+            DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
+            checkDeviceId(deviceId);
+            deviceService.deleteDevice(deviceId);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/customer/{customerId}/device/{deviceId}", method = RequestMethod.POST)
+    @ResponseBody 
+    public Device assignDeviceToCustomer(@PathVariable("customerId") String strCustomerId,
+                                         @PathVariable("deviceId") String strDeviceId) throws ThingsboardException {
+        checkParameter("customerId", strCustomerId);
+        checkParameter("deviceId", strDeviceId);
+        try {
+            CustomerId customerId = new CustomerId(toUUID(strCustomerId));
+            checkCustomerId(customerId);
+
+            DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
+            checkDeviceId(deviceId);
+            
+            return checkNotNull(deviceService.assignDeviceToCustomer(deviceId, customerId));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/customer/device/{deviceId}", method = RequestMethod.DELETE)
+    @ResponseBody 
+    public Device unassignDeviceFromCustomer(@PathVariable("deviceId") String strDeviceId) throws ThingsboardException {
+        checkParameter("deviceId", strDeviceId);
+        try {
+            DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
+            Device device = checkDeviceId(deviceId);
+            if (device.getCustomerId() == null || device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
+                throw new IncorrectParameterException("Device isn't assigned to any customer!");
+            }
+            return checkNotNull(deviceService.unassignDeviceFromCustomer(deviceId));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+    @RequestMapping(value = "/device/{deviceId}/credentials", method = RequestMethod.GET)
+    @ResponseBody
+    public DeviceCredentials getDeviceCredentialsByDeviceId(@PathVariable("deviceId") String strDeviceId) throws ThingsboardException {
+        checkParameter("deviceId", strDeviceId);
+        try {
+            DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
+            checkDeviceId(deviceId);
+            return checkNotNull(deviceCredentialsService.findDeviceCredentialsByDeviceId(deviceId));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/device/credentials", method = RequestMethod.POST)
+    @ResponseBody 
+    public DeviceCredentials saveDeviceCredentials(@RequestBody DeviceCredentials deviceCredentials) throws ThingsboardException {
+        checkNotNull(deviceCredentials);
+        try {
+            checkDeviceId(deviceCredentials.getDeviceId());
+            return checkNotNull(deviceCredentialsService.updateDeviceCredentials(deviceCredentials));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/tenant/devices", params = { "limit" }, method = RequestMethod.GET)
+    @ResponseBody
+    public TextPageData<Device> getTenantDevices(
+            @RequestParam int limit,
+            @RequestParam(required = false) String textSearch,
+            @RequestParam(required = false) String idOffset,
+            @RequestParam(required = false) String textOffset) throws ThingsboardException {
+        try {
+            TenantId tenantId = getCurrentUser().getTenantId();
+            TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
+            return checkNotNull(deviceService.findDevicesByTenantId(tenantId, pageLink));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+    @RequestMapping(value = "/customer/{customerId}/devices", params = { "limit" }, method = RequestMethod.GET)
+    @ResponseBody
+    public TextPageData<Device> getCustomerDevices(
+            @PathVariable("customerId") String strCustomerId,
+            @RequestParam int limit,
+            @RequestParam(required = false) String textSearch,
+            @RequestParam(required = false) String idOffset,
+            @RequestParam(required = false) String textOffset) throws ThingsboardException {
+        checkParameter("customerId", strCustomerId);
+        try {
+            TenantId tenantId = getCurrentUser().getTenantId();
+            CustomerId customerId = new CustomerId(toUUID(strCustomerId));
+            checkCustomerId(customerId);
+            TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
+            return checkNotNull(deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/EventController.java b/application/src/main/java/org/thingsboard/server/controller/EventController.java
new file mode 100644
index 0000000..2ff1a73
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/EventController.java
@@ -0,0 +1,116 @@
+/**
+ * Copyright © 2016 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.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.Event;
+import org.thingsboard.server.common.data.id.*;
+import org.thingsboard.server.common.data.page.TimePageData;
+import org.thingsboard.server.common.data.page.TimePageLink;
+import org.thingsboard.server.dao.event.EventService;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.exception.ThingsboardErrorCode;
+import org.thingsboard.server.exception.ThingsboardException;
+
+@RestController
+@RequestMapping("/api")
+public class EventController extends BaseController {
+
+    @Autowired
+    private EventService eventService;
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
+    @RequestMapping(value = "/events/{entityType}/{entityId}/{eventType}", method = RequestMethod.GET)
+    @ResponseBody
+    public TimePageData<Event> getEvents(
+            @PathVariable("entityType") String strEntityType,
+            @PathVariable("entityId") String strEntityId,
+            @PathVariable("eventType") String eventType,
+            @RequestParam("tenantId") String strTenantId,
+            @RequestParam int limit,
+            @RequestParam(required = false) Long startTime,
+            @RequestParam(required = false) Long endTime,
+            @RequestParam(required = false, defaultValue = "false") boolean ascOrder,
+            @RequestParam(required = false) String offset
+    ) throws ThingsboardException {
+        checkParameter("EntityId", strEntityId);
+        checkParameter("EntityType", strEntityType);
+        try {
+            TenantId tenantId = new TenantId(toUUID(strTenantId));
+            if (!tenantId.getId().equals(ModelConstants.NULL_UUID) &&
+                    !tenantId.equals(getCurrentUser().getTenantId())) {
+                throw new ThingsboardException("You don't have permission to perform this operation!",
+                        ThingsboardErrorCode.PERMISSION_DENIED);
+            }
+            TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
+            return checkNotNull(eventService.findEvents(tenantId, getEntityId(strEntityType, strEntityId), eventType, pageLink));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
+    @RequestMapping(value = "/events/{entityType}/{entityId}", method = RequestMethod.GET)
+    @ResponseBody
+    public TimePageData<Event> getEvents(
+            @PathVariable("entityType") String strEntityType,
+            @PathVariable("entityId") String strEntityId,
+            @RequestParam("tenantId") String strTenantId,
+            @RequestParam int limit,
+            @RequestParam(required = false) Long startTime,
+            @RequestParam(required = false) Long endTime,
+            @RequestParam(required = false, defaultValue = "false") boolean ascOrder,
+            @RequestParam(required = false) String offset
+    ) throws ThingsboardException {
+        checkParameter("EntityId", strEntityId);
+        checkParameter("EntityType", strEntityType);
+        try {
+            TenantId tenantId = new TenantId(toUUID(strTenantId));
+            if (!tenantId.getId().equals(ModelConstants.NULL_UUID) &&
+                    !tenantId.equals(getCurrentUser().getTenantId())) {
+                throw new ThingsboardException("You don't have permission to perform this operation!",
+                        ThingsboardErrorCode.PERMISSION_DENIED);
+            }
+            TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
+            return checkNotNull(eventService.findEvents(tenantId, getEntityId(strEntityType, strEntityId), pageLink));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+
+    private EntityId getEntityId(String strEntityType, String strEntityId) throws ThingsboardException {
+        EntityId entityId;
+        EntityType entityType = EntityType.valueOf(strEntityType);
+        switch (entityType) {
+            case RULE:
+                entityId = new RuleId(toUUID(strEntityId));
+                break;
+            case PLUGIN:
+                entityId = new PluginId(toUUID(strEntityId));
+                break;
+            case DEVICE:
+                entityId = new DeviceId(toUUID(strEntityId));
+                break;
+            default:
+                throw new ThingsboardException("EntityType ['" + entityType + "'] is incorrect!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
+        }
+        return entityId;
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/PluginApiController.java b/application/src/main/java/org/thingsboard/server/controller/plugin/PluginApiController.java
new file mode 100644
index 0000000..d9b0ab4
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/plugin/PluginApiController.java
@@ -0,0 +1,108 @@
+/**
+ * Copyright © 2016 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.controller.plugin;
+
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.RequestEntity;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.context.request.async.DeferredResult;
+import org.thingsboard.server.actors.service.ActorService;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.plugin.PluginMetaData;
+import org.thingsboard.server.controller.BaseController;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.dao.plugin.PluginService;
+import org.thingsboard.server.exception.ThingsboardException;
+import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext;
+import org.thingsboard.server.extensions.api.plugins.PluginConstants;
+import org.thingsboard.server.extensions.api.plugins.rest.BasicPluginRestMsg;
+import org.thingsboard.server.extensions.api.plugins.rest.RestRequest;
+
+import javax.servlet.http.HttpServletRequest;
+
+@RestController
+@RequestMapping(PluginConstants.PLUGIN_URL_PREFIX)
+@Slf4j
+public class PluginApiController extends BaseController {
+
+    @Autowired
+    private ActorService actorService;
+
+    @Autowired
+    private PluginService pluginService;
+
+    @SuppressWarnings("rawtypes")
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
+    @RequestMapping(value = "/{pluginToken}/**")
+    @ResponseStatus(value = HttpStatus.OK)
+    public DeferredResult<ResponseEntity> processRequest(
+            @PathVariable("pluginToken") String pluginToken,
+            RequestEntity<byte[]> requestEntity,
+            HttpServletRequest request)
+            throws ThingsboardException {
+        log.debug("[{}] Going to process requst uri: {}", pluginToken, requestEntity.getUrl());
+        DeferredResult<ResponseEntity> result = new DeferredResult<ResponseEntity>();
+        PluginMetaData pluginMd = pluginService.findPluginByApiToken(pluginToken);
+        if (pluginMd == null) {
+            result.setErrorResult(new PluginNotFoundException("Plugin with token: " + pluginToken + " not found!"));
+        } else {
+            TenantId tenantId = getCurrentUser().getTenantId();
+            CustomerId customerId = getCurrentUser().getCustomerId();
+            if (validatePluginAccess(pluginMd, tenantId, customerId)) {
+                if(ModelConstants.NULL_UUID.equals(tenantId.getId())){
+                    tenantId = null;
+                }
+                PluginApiCallSecurityContext securityCtx = new PluginApiCallSecurityContext(pluginMd.getTenantId(), pluginMd.getId(), tenantId, customerId);
+                actorService.process(new BasicPluginRestMsg(securityCtx, new RestRequest(requestEntity, request), result));
+            } else {
+                result.setResult(new ResponseEntity<>(HttpStatus.FORBIDDEN));
+            }
+
+        }
+        return result;
+    }
+
+    public static boolean validatePluginAccess(PluginMetaData pluginMd, TenantId tenantId, CustomerId customerId) {
+        boolean systemAdministrator = tenantId == null || ModelConstants.NULL_UUID.equals(tenantId.getId());
+        boolean tenantAdministrator = !systemAdministrator && (customerId == null || ModelConstants.NULL_UUID.equals(customerId.getId()));
+        boolean systemPlugin = ModelConstants.NULL_UUID.equals(pluginMd.getTenantId().getId());
+
+        boolean validUser = false;
+        if (systemPlugin) {
+            if (pluginMd.isPublicAccess() || systemAdministrator) {
+                // All users can access public system plugins. Only system
+                // users can access private system plugins
+                validUser = true;
+            }
+        } else {
+            if ((pluginMd.isPublicAccess() || tenantAdministrator) && tenantId.equals(pluginMd.getTenantId())) {
+                // All tenant users can access public tenant plugins. Only tenant
+                // administrator can access private tenant plugins
+                validUser = true;
+            }
+        }
+        return validUser;
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/PluginNotFoundException.java b/application/src/main/java/org/thingsboard/server/controller/plugin/PluginNotFoundException.java
new file mode 100644
index 0000000..b105bb8
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/plugin/PluginNotFoundException.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright © 2016 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.controller.plugin;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(HttpStatus.NOT_FOUND)
+public class PluginNotFoundException extends RuntimeException {
+
+    private static final long serialVersionUID = 1L;
+    
+    public PluginNotFoundException(String message){
+        super(message);
+    }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/PluginWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/PluginWebSocketHandler.java
new file mode 100644
index 0000000..dbc1c81
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/plugin/PluginWebSocketHandler.java
@@ -0,0 +1,203 @@
+/**
+ * Copyright © 2016 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.controller.plugin;
+
+import java.io.IOException;
+import java.net.URI;
+import java.security.InvalidParameterException;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.thingsboard.server.actors.service.ActorService;
+import org.thingsboard.server.config.WebSocketConfiguration;
+import org.thingsboard.server.extensions.api.plugins.PluginConstants;
+import org.thingsboard.server.service.security.model.SecurityUser;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.plugin.PluginMetaData;
+import org.thingsboard.server.dao.plugin.PluginService;
+import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext;
+import org.thingsboard.server.extensions.api.plugins.ws.BasicPluginWebsocketSessionRef;
+import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef;
+import org.thingsboard.server.extensions.api.plugins.ws.SessionEvent;
+import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
+import org.thingsboard.server.extensions.api.plugins.ws.msg.SessionEventPluginWebSocketMsg;
+import org.thingsboard.server.extensions.api.plugins.ws.msg.TextPluginWebSocketMsg;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.socket.CloseStatus;
+import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketSession;
+import org.springframework.web.socket.handler.TextWebSocketHandler;
+
+@Service
+@Slf4j
+public class PluginWebSocketHandler extends TextWebSocketHandler implements PluginWebSocketMsgEndpoint {
+
+    private static final ConcurrentMap<String, SessionMetaData> internalSessionMap = new ConcurrentHashMap<>();
+    private static final ConcurrentMap<String, String> externalSessionMap = new ConcurrentHashMap<>();
+
+    @Autowired @Lazy
+    private ActorService actorService;
+
+    @Autowired @Lazy
+    private PluginService pluginService;
+
+    @Override
+    public void handleTextMessage(WebSocketSession session, TextMessage message) {
+        try {
+            log.info("[{}] Processing {}", session.getId(), message);
+            SessionMetaData sessionMd = internalSessionMap.get(session.getId());
+            if (sessionMd != null) {
+                actorService.process(new TextPluginWebSocketMsg(sessionMd.sessionRef, message.getPayload()));
+            } else {
+                log.warn("[{}] Failed to find session", session.getId());
+                session.close(CloseStatus.SERVER_ERROR.withReason("Session not found!"));
+            }
+            session.sendMessage(message);
+        } catch (IOException e) {
+            log.warn("IO error", e);
+        }
+    }
+
+    @Override
+    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
+        super.afterConnectionEstablished(session);
+        try {
+            String internalSessionId = session.getId();
+            PluginWebsocketSessionRef sessionRef = toRef(session);
+            String externalSessionId = sessionRef.getSessionId();
+            internalSessionMap.put(internalSessionId, new SessionMetaData(session, sessionRef));
+            externalSessionMap.put(externalSessionId, internalSessionId);
+            actorService.process(new SessionEventPluginWebSocketMsg(sessionRef, SessionEvent.onEstablished()));
+            log.info("[{}][{}] Session is started", externalSessionId, session.getId());
+        } catch (InvalidParameterException e) {
+            log.warn("[[{}] Failed to start session", session.getId(), e);
+            session.close(CloseStatus.BAD_DATA.withReason(e.getMessage()));
+        } catch (Exception e) {
+            log.warn("[{}] Failed to start session", session.getId(), e);
+            session.close(CloseStatus.SERVER_ERROR.withReason(e.getMessage()));
+        }
+    }
+
+    @Override
+    public void handleTransportError(WebSocketSession session, Throwable tError) throws Exception {
+        super.handleTransportError(session, tError);
+        SessionMetaData sessionMd = internalSessionMap.get(session.getId());
+        if (sessionMd != null) {
+            actorService.process(new SessionEventPluginWebSocketMsg(sessionMd.sessionRef, SessionEvent.onError(tError)));
+        } else {
+            log.warn("[{}] Failed to find session", session.getId());
+        }
+        log.trace("[{}] Session transport error", session.getId(), tError);
+    }
+
+    @Override
+    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
+        super.afterConnectionClosed(session, closeStatus);
+        SessionMetaData sessionMd = internalSessionMap.remove(session.getId());
+        if (sessionMd != null) {
+            externalSessionMap.remove(sessionMd.sessionRef.getSessionId());
+            actorService.process(new SessionEventPluginWebSocketMsg(sessionMd.sessionRef, SessionEvent.onClosed()));
+        }
+        log.info("[{}] Session is closed", session.getId());
+    }
+
+    private PluginWebsocketSessionRef toRef(WebSocketSession session) throws IOException {
+        URI sessionUri = session.getUri();
+        String path = sessionUri.getPath();
+        path = path.substring(WebSocketConfiguration.WS_PLUGIN_PREFIX.length());
+        if (path.length() == 0) {
+            throw new IllegalArgumentException("URL should contain plugin token!");
+        }
+        String[] pathElements = path.split("/");
+        String pluginToken = pathElements[0];
+        // TODO: cache
+        PluginMetaData pluginMd = pluginService.findPluginByApiToken(pluginToken);
+        if (pluginMd == null) {
+            throw new InvalidParameterException("Can't find plugin with specified token!");
+        } else {
+            SecurityUser currentUser = (SecurityUser) session.getAttributes().get(WebSocketConfiguration.WS_SECURITY_USER_ATTRIBUTE);
+            TenantId tenantId = currentUser.getTenantId();
+            CustomerId customerId = currentUser.getCustomerId();
+            if (PluginApiController.validatePluginAccess(pluginMd, tenantId, customerId)) {
+                PluginApiCallSecurityContext securityCtx = new PluginApiCallSecurityContext(pluginMd.getTenantId(), pluginMd.getId(), tenantId,
+                        currentUser.getCustomerId());
+                return new BasicPluginWebsocketSessionRef(UUID.randomUUID().toString(), securityCtx, session.getUri(), session.getAttributes(),
+                        session.getLocalAddress(), session.getRemoteAddress());
+            } else {
+                throw new SecurityException("Current user is not allowed to use this plugin!");
+            }
+        }
+    }
+
+    private static class SessionMetaData {
+        private final WebSocketSession session;
+        private final PluginWebsocketSessionRef sessionRef;
+
+        public SessionMetaData(WebSocketSession session, PluginWebsocketSessionRef sessionRef) {
+            super();
+            this.session = session;
+            this.sessionRef = sessionRef;
+        }
+    }
+
+    @Override
+    public void send(PluginWebsocketMsg<?> wsMsg) throws IOException {
+        PluginWebsocketSessionRef sessionRef = wsMsg.getSessionRef();
+        String externalId = sessionRef.getSessionId();
+        log.debug("[{}] Processing {}", externalId, wsMsg);
+        String internalId = externalSessionMap.get(externalId);
+        if (internalId != null) {
+            SessionMetaData sessionMd = internalSessionMap.get(internalId);
+            if (sessionMd != null) {
+                if (wsMsg instanceof TextPluginWebSocketMsg) {
+                    String payload = ((TextPluginWebSocketMsg) wsMsg).getPayload();
+                    sessionMd.session.sendMessage(new TextMessage(payload));
+                }
+            } else {
+                log.warn("[{}][{}] Failed to find session by internal id", externalId, internalId);
+            }
+        } else {
+            log.warn("[{}] Failed to find session by external id", externalId);
+        }
+    }
+
+    @Override
+    public void close(PluginWebsocketSessionRef sessionRef) throws IOException {
+        String externalId = sessionRef.getSessionId();
+        log.debug("[{}] Processing close request", externalId);
+        String internalId = externalSessionMap.get(externalId);
+        if (internalId != null) {
+            SessionMetaData sessionMd = internalSessionMap.get(internalId);
+            if (sessionMd != null) {
+                sessionMd.session.close(CloseStatus.NORMAL);
+            } else {
+                log.warn("[{}][{}] Failed to find session by internal id", externalId, internalId);
+            }
+        } else {
+            log.warn("[{}] Failed to find session by external id", externalId);
+        }
+    }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/PluginWebSocketMsgEndpoint.java b/application/src/main/java/org/thingsboard/server/controller/plugin/PluginWebSocketMsgEndpoint.java
new file mode 100644
index 0000000..75b6012
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/plugin/PluginWebSocketMsgEndpoint.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright © 2016 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.controller.plugin;
+
+import java.io.IOException;
+
+import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef;
+import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
+
+public interface PluginWebSocketMsgEndpoint {
+
+    void send(PluginWebsocketMsg<?> wsMsg) throws IOException;
+
+    void close(PluginWebsocketSessionRef sessionRef) throws IOException;
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/PluginController.java b/application/src/main/java/org/thingsboard/server/controller/PluginController.java
new file mode 100644
index 0000000..6f06eb3
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/PluginController.java
@@ -0,0 +1,197 @@
+/**
+ * Copyright © 2016 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.controller;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.thingsboard.server.common.data.id.PluginId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
+import org.thingsboard.server.common.data.plugin.PluginMetaData;
+import org.thingsboard.server.common.data.security.Authority;
+import org.thingsboard.server.common.data.widget.WidgetsBundle;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.exception.ThingsboardException;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/api")
+public class PluginController extends BaseController {
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/plugin/{pluginId}", method = RequestMethod.GET)
+    @ResponseBody
+    public PluginMetaData getPluginById(@PathVariable("pluginId") String strPluginId) throws ThingsboardException {
+        checkParameter("pluginId", strPluginId);
+        try {
+            PluginId pluginId = new PluginId(toUUID(strPluginId));
+            return checkPlugin(pluginService.findPluginById(pluginId));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/plugin/token/{pluginToken}", method = RequestMethod.GET)
+    @ResponseBody
+    public PluginMetaData getPluginByToken(@PathVariable("pluginToken") String pluginToken) throws ThingsboardException {
+        checkParameter("pluginToken", pluginToken);
+        try {
+            return checkPlugin(pluginService.findPluginByApiToken(pluginToken));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/plugin", method = RequestMethod.POST)
+    @ResponseBody
+    public PluginMetaData savePlugin(@RequestBody PluginMetaData source) throws ThingsboardException {
+        try {
+            boolean created = source.getId() == null;
+            source.setTenantId(getCurrentUser().getTenantId());
+            PluginMetaData plugin = checkNotNull(pluginService.savePlugin(source));
+            actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(),
+                    created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
+            return plugin;
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/plugin/{pluginId}/activate", method = RequestMethod.POST)
+    @ResponseStatus(value = HttpStatus.OK)
+    public void activatePluginById(@PathVariable("pluginId") String strPluginId) throws ThingsboardException {
+        checkParameter("pluginId", strPluginId);
+        try {
+            PluginId pluginId = new PluginId(toUUID(strPluginId));
+            PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId));
+            pluginService.activatePluginById(pluginId);
+            actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.ACTIVATED);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/plugin/{pluginId}/suspend", method = RequestMethod.POST)
+    @ResponseStatus(value = HttpStatus.OK)
+    public void suspendPluginById(@PathVariable("pluginId") String strPluginId) throws ThingsboardException {
+        checkParameter("pluginId", strPluginId);
+        try {
+            PluginId pluginId = new PluginId(toUUID(strPluginId));
+            PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId));
+            pluginService.suspendPluginById(pluginId);
+            actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.SUSPENDED);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('SYS_ADMIN')")
+    @RequestMapping(value = "/plugin/system", params = {"limit"}, method = RequestMethod.GET)
+    @ResponseBody
+    public TextPageData<PluginMetaData> getSystemPlugins(
+            @RequestParam int limit,
+            @RequestParam(required = false) String textSearch,
+            @RequestParam(required = false) String idOffset,
+            @RequestParam(required = false) String textOffset) throws ThingsboardException {
+        try {
+            TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
+            return checkNotNull(pluginService.findSystemPlugins(pageLink));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('SYS_ADMIN')")
+    @RequestMapping(value = "/plugin/tenant/{tenantId}", params = {"limit"}, method = RequestMethod.GET)
+    @ResponseBody
+    public TextPageData<PluginMetaData> getTenantPlugins(
+            @PathVariable("tenantId") String strTenantId,
+            @RequestParam int limit,
+            @RequestParam(required = false) String textSearch,
+            @RequestParam(required = false) String idOffset,
+            @RequestParam(required = false) String textOffset) throws ThingsboardException {
+        checkParameter("tenantId", strTenantId);
+        try {
+            TenantId tenantId = new TenantId(toUUID(strTenantId));
+            TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
+            return checkNotNull(pluginService.findTenantPlugins(tenantId, pageLink));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/plugins", method = RequestMethod.GET)
+    @ResponseBody
+    public List<PluginMetaData> getPlugins() throws ThingsboardException {
+        try {
+            if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN) {
+                return checkNotNull(pluginService.findSystemPlugins());
+            } else {
+                TenantId tenantId = getCurrentUser().getTenantId();
+                List<PluginMetaData> plugins = checkNotNull(pluginService.findAllTenantPluginsByTenantId(tenantId));
+                plugins.stream()
+                        .filter(plugin -> plugin.getTenantId().getId().equals(ModelConstants.NULL_UUID))
+                        .forEach(plugin -> plugin.setConfiguration(null));
+                return plugins;
+            }
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/plugin", params = {"limit"}, method = RequestMethod.GET)
+    @ResponseBody
+    public TextPageData<PluginMetaData> getTenantPlugins(
+            @RequestParam int limit,
+            @RequestParam(required = false) String textSearch,
+            @RequestParam(required = false) String idOffset,
+            @RequestParam(required = false) String textOffset) throws ThingsboardException {
+        try {
+            TenantId tenantId = getCurrentUser().getTenantId();
+            TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
+            return checkNotNull(pluginService.findTenantPlugins(tenantId, pageLink));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/plugin/{pluginId}", method = RequestMethod.DELETE)
+    @ResponseStatus(value = HttpStatus.OK)
+    public void deletePlugin(@PathVariable("pluginId") String strPluginId) throws ThingsboardException {
+        checkParameter("pluginId", strPluginId);
+        try {
+            PluginId pluginId = new PluginId(toUUID(strPluginId));
+            PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId));
+            pluginService.deletePluginById(pluginId);
+            actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.DELETED);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleController.java b/application/src/main/java/org/thingsboard/server/controller/RuleController.java
new file mode 100644
index 0000000..3c04619
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/RuleController.java
@@ -0,0 +1,193 @@
+/**
+ * Copyright © 2016 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.controller;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.thingsboard.server.common.data.id.RuleId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
+import org.thingsboard.server.common.data.plugin.PluginMetaData;
+import org.thingsboard.server.common.data.rule.RuleMetaData;
+import org.thingsboard.server.common.data.security.Authority;
+import org.thingsboard.server.exception.ThingsboardException;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/api")
+public class RuleController extends BaseController {
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/rule/{ruleId}", method = RequestMethod.GET)
+    @ResponseBody
+    public RuleMetaData getRuleById(@PathVariable("ruleId") String strRuleId) throws ThingsboardException {
+        checkParameter("ruleId", strRuleId);
+        try {
+            RuleId ruleId = new RuleId(toUUID(strRuleId));
+            return checkRule(ruleService.findRuleById(ruleId));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/rule/token/{pluginToken}", method = RequestMethod.GET)
+    @ResponseBody
+    public List<RuleMetaData> getRulesByPluginToken(@PathVariable("pluginToken") String pluginToken) throws ThingsboardException {
+        checkParameter("pluginToken", pluginToken);
+        try {
+            PluginMetaData plugin = checkPlugin(pluginService.findPluginByApiToken(pluginToken));
+            return ruleService.findPluginRules(plugin.getApiToken());
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/rule", method = RequestMethod.POST)
+    @ResponseBody
+    public RuleMetaData saveRule(@RequestBody RuleMetaData source) throws ThingsboardException {
+        try {
+            boolean created = source.getId() == null;
+            source.setTenantId(getCurrentUser().getTenantId());
+            RuleMetaData rule = checkNotNull(ruleService.saveRule(source));
+            actorService.onRuleStateChange(rule.getTenantId(), rule.getId(),
+                    created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
+            return rule;
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/rule/{ruleId}/activate", method = RequestMethod.POST)
+    @ResponseStatus(value = HttpStatus.OK)
+    public void activateRuleById(@PathVariable("ruleId") String strRuleId) throws ThingsboardException {
+        checkParameter("ruleId", strRuleId);
+        try {
+            RuleId ruleId = new RuleId(toUUID(strRuleId));
+            RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId));
+            ruleService.activateRuleById(ruleId);
+            actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.ACTIVATED);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/rule/{ruleId}/suspend", method = RequestMethod.POST)
+    @ResponseStatus(value = HttpStatus.OK)
+    public void suspendRuleById(@PathVariable("ruleId") String strRuleId) throws ThingsboardException {
+        checkParameter("ruleId", strRuleId);
+        try {
+            RuleId ruleId = new RuleId(toUUID(strRuleId));
+            RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId));
+            ruleService.suspendRuleById(ruleId);
+            actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.SUSPENDED);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('SYS_ADMIN')")
+    @RequestMapping(value = "/rule/system", params = {"limit"}, method = RequestMethod.GET)
+    @ResponseBody
+    public TextPageData<RuleMetaData> getSystemRules(
+            @RequestParam int limit,
+            @RequestParam(required = false) String textSearch,
+            @RequestParam(required = false) String idOffset,
+            @RequestParam(required = false) String textOffset) throws ThingsboardException {
+        try {
+            TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
+            return checkNotNull(ruleService.findSystemRules(pageLink));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('SYS_ADMIN')")
+    @RequestMapping(value = "/rule/tenant/{tenantId}", params = {"limit"}, method = RequestMethod.GET)
+    @ResponseBody
+    public TextPageData<RuleMetaData> getTenantRules(
+            @PathVariable("tenantId") String strTenantId,
+            @RequestParam int limit,
+            @RequestParam(required = false) String textSearch,
+            @RequestParam(required = false) String idOffset,
+            @RequestParam(required = false) String textOffset) throws ThingsboardException {
+        checkParameter("tenantId", strTenantId);
+        try {
+            TenantId tenantId = new TenantId(toUUID(strTenantId));
+            TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
+            return checkNotNull(ruleService.findTenantRules(tenantId, pageLink));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/rules", method = RequestMethod.GET)
+    @ResponseBody
+    public List<RuleMetaData> getRules() throws ThingsboardException {
+        try {
+            if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN) {
+                return checkNotNull(ruleService.findSystemRules());
+            } else {
+                TenantId tenantId = getCurrentUser().getTenantId();
+                return checkNotNull(ruleService.findAllTenantRulesByTenantId(tenantId));
+            }
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/rule", params = {"limit"}, method = RequestMethod.GET)
+    @ResponseBody
+    public TextPageData<RuleMetaData> getTenantRules(
+            @RequestParam int limit,
+            @RequestParam(required = false) String textSearch,
+            @RequestParam(required = false) String idOffset,
+            @RequestParam(required = false) String textOffset) throws ThingsboardException {
+        try {
+            TenantId tenantId = getCurrentUser().getTenantId();
+            TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
+            return checkNotNull(ruleService.findTenantRules(tenantId, pageLink));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/rule/{ruleId}", method = RequestMethod.DELETE)
+    @ResponseStatus(value = HttpStatus.OK)
+    public void deleteRule(@PathVariable("ruleId") String strRuleId) throws ThingsboardException {
+        checkParameter("ruleId", strRuleId);
+        try {
+            RuleId ruleId = new RuleId(toUUID(strRuleId));
+            RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId));
+            ruleService.deleteRuleById(ruleId);
+            actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.DELETED);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantController.java b/application/src/main/java/org/thingsboard/server/controller/TenantController.java
new file mode 100644
index 0000000..6dd38a3
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/TenantController.java
@@ -0,0 +1,89 @@
+/**
+ * Copyright © 2016 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.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.tenant.TenantService;
+import org.thingsboard.server.exception.ThingsboardException;
+
+@RestController
+@RequestMapping("/api")
+public class TenantController extends BaseController {
+    
+    @Autowired
+    private TenantService tenantService;
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/tenant/{tenantId}", method = RequestMethod.GET)
+    @ResponseBody
+    public Tenant getTenantById(@PathVariable("tenantId") String strTenantId) throws ThingsboardException {
+        checkParameter("tenantId", strTenantId);
+        try {
+            TenantId tenantId = new TenantId(toUUID(strTenantId));
+            checkTenantId(tenantId);
+            return checkNotNull(tenantService.findTenantById(tenantId));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('SYS_ADMIN')")
+    @RequestMapping(value = "/tenant", method = RequestMethod.POST)
+    @ResponseBody 
+    public Tenant saveTenant(@RequestBody Tenant tenant) throws ThingsboardException {
+        try {
+            return checkNotNull(tenantService.saveTenant(tenant));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('SYS_ADMIN')")
+    @RequestMapping(value = "/tenant/{tenantId}", method = RequestMethod.DELETE)
+    @ResponseStatus(value = HttpStatus.OK)
+    public void deleteTenant(@PathVariable("tenantId") String strTenantId) throws ThingsboardException {
+        checkParameter("tenantId", strTenantId);
+        try {
+            TenantId tenantId = new TenantId(toUUID(strTenantId));
+            tenantService.deleteTenant(tenantId);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('SYS_ADMIN')")
+    @RequestMapping(value = "/tenants", params = { "limit" }, method = RequestMethod.GET)
+    @ResponseBody
+    public TextPageData<Tenant> getTenants(@RequestParam int limit,
+                                           @RequestParam(required = false) String textSearch,
+                                           @RequestParam(required = false) String idOffset,
+                                           @RequestParam(required = false) String textOffset) throws ThingsboardException {
+        try {
+            TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
+            return checkNotNull(tenantService.findTenants(pageLink));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+    
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java
new file mode 100644
index 0000000..2c436f0
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java
@@ -0,0 +1,179 @@
+/**
+ * Copyright © 2016 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.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.thingsboard.server.common.data.User;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.id.UserId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.security.Authority;
+import org.thingsboard.server.common.data.security.UserCredentials;
+import org.thingsboard.server.exception.ThingsboardErrorCode;
+import org.thingsboard.server.exception.ThingsboardException;
+import org.thingsboard.server.service.mail.MailService;
+import org.thingsboard.server.service.security.model.SecurityUser;
+
+import javax.servlet.http.HttpServletRequest;
+
+@RestController
+@RequestMapping("/api")
+public class UserController extends BaseController {
+
+    @Autowired
+    private MailService mailService;
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
+    @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET)
+    @ResponseBody
+    public User getUserById(@PathVariable("userId") String strUserId) throws ThingsboardException {
+        checkParameter("userId", strUserId);
+        try {
+            UserId userId = new UserId(toUUID(strUserId));
+            SecurityUser authUser = getCurrentUser();
+            if (authUser.getAuthority() == Authority.CUSTOMER_USER && !authUser.getId().equals(userId)) {
+                throw new ThingsboardException("You don't have permission to perform this operation!",
+                        ThingsboardErrorCode.PERMISSION_DENIED);
+            }
+            return checkUserId(userId);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
+    @RequestMapping(value = "/user", method = RequestMethod.POST)
+    @ResponseBody 
+    public User saveUser(@RequestBody User user,
+            HttpServletRequest request) throws ThingsboardException {
+        try {
+            SecurityUser authUser = getCurrentUser();
+            if (authUser.getAuthority() == Authority.CUSTOMER_USER && !authUser.getId().equals(user.getId())) {
+                throw new ThingsboardException("You don't have permission to perform this operation!",
+                        ThingsboardErrorCode.PERMISSION_DENIED);
+            }
+            boolean sendEmail = user.getId() == null;
+            if (getCurrentUser().getAuthority() == Authority.TENANT_ADMIN) {
+                user.setTenantId(getCurrentUser().getTenantId());
+            }
+            User savedUser = checkNotNull(userService.saveUser(user));
+            if (sendEmail) {
+                UserCredentials userCredentials = userService.findUserCredentialsByUserId(savedUser.getId());
+                String baseUrl = String.format("%s://%s:%d",
+                        request.getScheme(),  
+                        request.getServerName(), 
+                        request.getServerPort());             
+                String activateUrl = String.format("%s/api/noauth/activate?activateToken=%s", baseUrl,
+                        userCredentials.getActivateToken());
+                String email = savedUser.getEmail();
+                try {
+                    mailService.sendActivationEmail(activateUrl, email);
+                } catch (ThingsboardException e) {
+                    userService.deleteUser(savedUser.getId());
+                    throw e;
+                }
+            }
+            return savedUser;
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/user/sendActivationMail", method = RequestMethod.POST)
+    @ResponseStatus(value = HttpStatus.OK)
+    public void sendActivationEmail(
+            @RequestParam(value = "email") String email,
+            HttpServletRequest request) throws ThingsboardException {
+        try {
+            User user = checkNotNull(userService.findUserByEmail(email));
+            UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getId());
+            if (!userCredentials.isEnabled()) {
+                String baseUrl = String.format("%s://%s:%d",
+                        request.getScheme(),  
+                        request.getServerName(), 
+                        request.getServerPort());             
+                String activateUrl = String.format("%s/api/noauth/activate?activateToken=%s", baseUrl,
+                        userCredentials.getActivateToken());
+                mailService.sendActivationEmail(activateUrl, email);
+            } else {
+                throw new ThingsboardException("User is already active!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
+            }
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/user/{userId}", method = RequestMethod.DELETE)
+    @ResponseStatus(value = HttpStatus.OK)
+    public void deleteUser(@PathVariable("userId") String strUserId) throws ThingsboardException {
+        checkParameter("userId", strUserId);
+        try {
+            UserId userId = new UserId(toUUID(strUserId));
+            checkUserId(userId);
+            userService.deleteUser(userId);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('SYS_ADMIN')")
+    @RequestMapping(value = "/tenant/{tenantId}/users", params = { "limit" }, method = RequestMethod.GET)
+    @ResponseBody
+    public TextPageData<User> getTenantAdmins(
+            @PathVariable("tenantId") String strTenantId,
+            @RequestParam int limit,
+            @RequestParam(required = false) String textSearch,
+            @RequestParam(required = false) String idOffset,
+            @RequestParam(required = false) String textOffset) throws ThingsboardException {
+        checkParameter("tenantId", strTenantId);
+        try {
+            TenantId tenantId = new TenantId(toUUID(strTenantId));
+            TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
+            return checkNotNull(userService.findTenantAdmins(tenantId, pageLink));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/customer/{customerId}/users", params = { "limit" }, method = RequestMethod.GET)
+    @ResponseBody
+    public TextPageData<User> getCustomerUsers(
+            @PathVariable("customerId") String strCustomerId,
+            @RequestParam int limit,
+            @RequestParam(required = false) String textSearch,
+            @RequestParam(required = false) String idOffset,
+            @RequestParam(required = false) String textOffset) throws ThingsboardException {
+        checkParameter("customerId", strCustomerId);
+        try {
+            CustomerId customerId = new CustomerId(toUUID(strCustomerId));
+            checkCustomerId(customerId);
+            TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
+            TenantId tenantId = getCurrentUser().getTenantId();
+            return checkNotNull(userService.findCustomerUsers(tenantId, customerId, pageLink));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+    
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java b/application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java
new file mode 100644
index 0000000..e06962b
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java
@@ -0,0 +1,116 @@
+/**
+ * Copyright © 2016 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.controller;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.id.WidgetsBundleId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.security.Authority;
+import org.thingsboard.server.common.data.widget.WidgetsBundle;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.exception.ThingsboardException;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/api")
+public class WidgetsBundleController extends BaseController {
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/widgetsBundle/{widgetsBundleId}", method = RequestMethod.GET)
+    @ResponseBody
+    public WidgetsBundle getWidgetsBundleById(@PathVariable("widgetsBundleId") String strWidgetsBundleId) throws ThingsboardException {
+        checkParameter("widgetsBundleId", strWidgetsBundleId);
+        try {
+            WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId));
+            return checkWidgetsBundleId(widgetsBundleId, false);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/widgetsBundle", method = RequestMethod.POST)
+    @ResponseBody
+    public WidgetsBundle saveWidgetsBundle(@RequestBody WidgetsBundle widgetsBundle) throws ThingsboardException {
+        try {
+            if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN) {
+                widgetsBundle.setTenantId(new TenantId(ModelConstants.NULL_UUID));
+            } else {
+                widgetsBundle.setTenantId(getCurrentUser().getTenantId());
+            }
+            return checkNotNull(widgetsBundleService.saveWidgetsBundle(widgetsBundle));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/widgetsBundle/{widgetsBundleId}", method = RequestMethod.DELETE)
+    @ResponseStatus(value = HttpStatus.OK)
+    public void deleteWidgetsBundle(@PathVariable("widgetsBundleId") String strWidgetsBundleId) throws ThingsboardException {
+        checkParameter("widgetsBundleId", strWidgetsBundleId);
+        try {
+            WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId));
+            checkWidgetsBundleId(widgetsBundleId, true);
+            widgetsBundleService.deleteWidgetsBundle(widgetsBundleId);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/widgetsBundles", params = { "limit" }, method = RequestMethod.GET)
+    @ResponseBody
+    public TextPageData<WidgetsBundle> getWidgetsBundles(
+            @RequestParam int limit,
+            @RequestParam(required = false) String textSearch,
+            @RequestParam(required = false) String idOffset,
+            @RequestParam(required = false) String textOffset) throws ThingsboardException {
+        try {
+            TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
+            if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN) {
+                return checkNotNull(widgetsBundleService.findSystemWidgetsBundlesByPageLink(pageLink));
+            } else {
+                TenantId tenantId = getCurrentUser().getTenantId();
+                return checkNotNull(widgetsBundleService.findAllTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, pageLink));
+            }
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/widgetsBundles", method = RequestMethod.GET)
+    @ResponseBody
+    public List<WidgetsBundle> getWidgetsBundles() throws ThingsboardException {
+        try {
+            if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN) {
+                return checkNotNull(widgetsBundleService.findSystemWidgetsBundles());
+            } else {
+                TenantId tenantId = getCurrentUser().getTenantId();
+                return checkNotNull(widgetsBundleService.findAllTenantWidgetsBundlesByTenantId(tenantId));
+            }
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java b/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java
new file mode 100644
index 0000000..c3ded12
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java
@@ -0,0 +1,118 @@
+/**
+ * Copyright © 2016 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.controller;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.id.WidgetTypeId;
+import org.thingsboard.server.common.data.security.Authority;
+import org.thingsboard.server.common.data.widget.WidgetType;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.exception.ThingsboardException;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/api")
+public class WidgetTypeController extends BaseController {
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/widgetType/{widgetTypeId}", method = RequestMethod.GET)
+    @ResponseBody
+    public WidgetType getWidgetTypeById(@PathVariable("widgetTypeId") String strWidgetTypeId) throws ThingsboardException {
+        checkParameter("widgetTypeId", strWidgetTypeId);
+        try {
+            WidgetTypeId widgetTypeId = new WidgetTypeId(toUUID(strWidgetTypeId));
+            return checkWidgetTypeId(widgetTypeId, false);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/widgetType", method = RequestMethod.POST)
+    @ResponseBody
+    public WidgetType saveWidgetType(@RequestBody WidgetType widgetType) throws ThingsboardException {
+        try {
+            if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN) {
+                widgetType.setTenantId(new TenantId(ModelConstants.NULL_UUID));
+            } else {
+                widgetType.setTenantId(getCurrentUser().getTenantId());
+            }
+            return checkNotNull(widgetTypeService.saveWidgetType(widgetType));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/widgetType/{widgetTypeId}", method = RequestMethod.DELETE)
+    @ResponseStatus(value = HttpStatus.OK)
+    public void deleteWidgetType(@PathVariable("widgetTypeId") String strWidgetTypeId) throws ThingsboardException {
+        checkParameter("widgetTypeId", strWidgetTypeId);
+        try {
+            WidgetTypeId widgetTypeId = new WidgetTypeId(toUUID(strWidgetTypeId));
+            checkWidgetTypeId(widgetTypeId, true);
+            widgetTypeService.deleteWidgetType(widgetTypeId);
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
+    @RequestMapping(value = "/widgetTypes", params = { "isSystem", "bundleAlias"}, method = RequestMethod.GET)
+    @ResponseBody
+    public List<WidgetType> getBundleWidgetTypes(
+            @RequestParam boolean isSystem,
+            @RequestParam String bundleAlias) throws ThingsboardException {
+        try {
+            TenantId tenantId;
+            if (isSystem) {
+                tenantId = new TenantId(ModelConstants.NULL_UUID);
+            } else {
+                tenantId = getCurrentUser().getTenantId();
+            }
+            return checkNotNull(widgetTypeService.findWidgetTypesByTenantIdAndBundleAlias(tenantId, bundleAlias));
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
+    @RequestMapping(value = "/widgetType", params = { "isSystem", "bundleAlias", "alias" }, method = RequestMethod.GET)
+    @ResponseBody
+    public WidgetType getWidgetType(
+            @RequestParam boolean isSystem,
+            @RequestParam String bundleAlias,
+            @RequestParam String alias) throws ThingsboardException {
+        try {
+            TenantId tenantId;
+            if (isSystem) {
+                tenantId = new TenantId(ModelConstants.NULL_UUID);
+            } else {
+                tenantId = getCurrentUser().getTenantId();
+            }
+            WidgetType widgetType = widgetTypeService.findWidgetTypeByTenantIdBundleAliasAndAlias(tenantId, bundleAlias, alias);
+            checkWidgetType(widgetType, false);
+            return widgetType;
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorCode.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorCode.java
new file mode 100644
index 0000000..9c505f6
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorCode.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright © 2016 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.exception;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+
+public enum ThingsboardErrorCode {
+
+    GENERAL(2),
+    AUTHENTICATION(10),
+    JWT_TOKEN_EXPIRED(11),
+    PERMISSION_DENIED(20),
+    INVALID_ARGUMENTS(30),
+    BAD_REQUEST_PARAMS(31),
+    ITEM_NOT_FOUND(32);
+
+    private int errorCode;
+
+    ThingsboardErrorCode(int errorCode) {
+        this.errorCode = errorCode;
+    }
+
+    @JsonValue
+    public int getErrorCode() {
+        return errorCode;
+    }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java
new file mode 100644
index 0000000..9960a56
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright © 2016 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.exception;
+
+import org.springframework.http.HttpStatus;
+
+import java.util.Date;
+
+public class ThingsboardErrorResponse {
+    // HTTP Response Status Code
+    private final HttpStatus status;
+
+    // General Error message
+    private final String message;
+
+    // Error code
+    private final ThingsboardErrorCode errorCode;
+
+    private final Date timestamp;
+
+    protected ThingsboardErrorResponse(final String message, final ThingsboardErrorCode errorCode, HttpStatus status) {
+        this.message = message;
+        this.errorCode = errorCode;
+        this.status = status;
+        this.timestamp = new java.util.Date();
+    }
+
+    public static ThingsboardErrorResponse of(final String message, final ThingsboardErrorCode errorCode, HttpStatus status) {
+        return new ThingsboardErrorResponse(message, errorCode, status);
+    }
+
+    public Integer getStatus() {
+        return status.value();
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public ThingsboardErrorCode getErrorCode() {
+        return errorCode;
+    }
+
+    public Date getTimestamp() {
+        return timestamp;
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java
new file mode 100644
index 0000000..ad60ea2
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java
@@ -0,0 +1,133 @@
+/**
+ * Copyright © 2016 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.exception;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.access.AccessDeniedHandler;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.service.security.exception.AuthMethodNotSupportedException;
+import org.thingsboard.server.service.security.exception.JwtExpiredTokenException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+@Component
+@Slf4j
+public class ThingsboardErrorResponseHandler implements AccessDeniedHandler {
+
+    @Autowired
+    private ObjectMapper mapper;
+
+    @Override
+    public void handle(HttpServletRequest request, HttpServletResponse response,
+                       AccessDeniedException accessDeniedException) throws IOException,
+            ServletException {
+        if (!response.isCommitted()) {
+            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+            response.setStatus(HttpStatus.FORBIDDEN.value());
+            mapper.writeValue(response.getWriter(),
+                    ThingsboardErrorResponse.of("You don't have permission to perform this operation!",
+                            ThingsboardErrorCode.PERMISSION_DENIED, HttpStatus.FORBIDDEN));
+        }
+    }
+
+    public void handle(Exception exception, HttpServletResponse response) {
+        log.debug("Processing exception {}", exception.getMessage(), exception);
+        if (!response.isCommitted()) {
+            try {
+                response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+
+                if (exception instanceof ThingsboardException) {
+                    handleThingsboardException((ThingsboardException) exception, response);
+                } else if (exception instanceof AccessDeniedException) {
+                    handleAccessDeniedException(response);
+                } else if (exception instanceof AuthenticationException) {
+                    handleAuthenticationException((AuthenticationException) exception, response);
+                } else {
+                    response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
+                    mapper.writeValue(response.getWriter(), ThingsboardErrorResponse.of(exception.getMessage(),
+                            ThingsboardErrorCode.GENERAL, HttpStatus.INTERNAL_SERVER_ERROR));
+                }
+            } catch (IOException e) {
+                log.error("Can't handle exception", e);
+            }
+        }
+    }
+
+    private void handleThingsboardException(ThingsboardException thingsboardException, HttpServletResponse response) throws IOException {
+
+        ThingsboardErrorCode errorCode = thingsboardException.getErrorCode();
+        HttpStatus status;
+
+        switch (errorCode) {
+            case AUTHENTICATION:
+                status = HttpStatus.UNAUTHORIZED;
+                break;
+            case PERMISSION_DENIED:
+                status = HttpStatus.FORBIDDEN;
+                break;
+            case INVALID_ARGUMENTS:
+                status = HttpStatus.BAD_REQUEST;
+                break;
+            case ITEM_NOT_FOUND:
+                status = HttpStatus.NOT_FOUND;
+                break;
+            case BAD_REQUEST_PARAMS:
+                status = HttpStatus.BAD_REQUEST;
+                break;
+            case GENERAL:
+                status = HttpStatus.INTERNAL_SERVER_ERROR;
+                break;
+            default:
+                status = HttpStatus.INTERNAL_SERVER_ERROR;
+                break;
+        }
+
+        response.setStatus(status.value());
+        mapper.writeValue(response.getWriter(), ThingsboardErrorResponse.of(thingsboardException.getMessage(), errorCode, status));
+    }
+
+    private void handleAccessDeniedException(HttpServletResponse response) throws IOException {
+        response.setStatus(HttpStatus.FORBIDDEN.value());
+        mapper.writeValue(response.getWriter(),
+                ThingsboardErrorResponse.of("You don't have permission to perform this operation!",
+                        ThingsboardErrorCode.PERMISSION_DENIED, HttpStatus.FORBIDDEN));
+
+    }
+
+    private void handleAuthenticationException(AuthenticationException authenticationException, HttpServletResponse response) throws IOException {
+        response.setStatus(HttpStatus.UNAUTHORIZED.value());
+        if (authenticationException instanceof BadCredentialsException) {
+            mapper.writeValue(response.getWriter(), ThingsboardErrorResponse.of("Invalid username or password", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
+        } else if (authenticationException instanceof JwtExpiredTokenException) {
+            mapper.writeValue(response.getWriter(), ThingsboardErrorResponse.of("Token has expired", ThingsboardErrorCode.JWT_TOKEN_EXPIRED, HttpStatus.UNAUTHORIZED));
+        } else if (authenticationException instanceof AuthMethodNotSupportedException) {
+            mapper.writeValue(response.getWriter(), ThingsboardErrorResponse.of(authenticationException.getMessage(), ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
+        }
+        mapper.writeValue(response.getWriter(), ThingsboardErrorResponse.of("Authentication failed", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
+    }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardException.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardException.java
new file mode 100644
index 0000000..0da0414
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardException.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright © 2016 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.exception;
+
+public class ThingsboardException extends Exception {
+
+    private static final long serialVersionUID = 1L;
+
+    private ThingsboardErrorCode errorCode;
+
+    public ThingsboardException() {
+        super();
+    }
+
+    public ThingsboardException(ThingsboardErrorCode errorCode) {
+        this.errorCode = errorCode;
+    }
+
+    public ThingsboardException(String message, ThingsboardErrorCode errorCode) {
+        super(message);
+        this.errorCode = errorCode;
+    }
+
+    public ThingsboardException(String message, Throwable cause, ThingsboardErrorCode errorCode) {
+        super(message, cause);
+        this.errorCode = errorCode;
+    }
+
+    public ThingsboardException(Throwable cause, ThingsboardErrorCode errorCode) {
+        super(cause);
+        this.errorCode = errorCode;
+    }
+
+    public ThingsboardErrorCode getErrorCode() {
+        return errorCode;
+    }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/environment/EnvironmentLogService.java b/application/src/main/java/org/thingsboard/server/service/environment/EnvironmentLogService.java
new file mode 100644
index 0000000..aae2d8d
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/environment/EnvironmentLogService.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright © 2016 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.service.environment;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.zookeeper.Environment;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * Created by igor on 11/24/16.
+ */
+
+@Service("environmentLogService")
+@ConditionalOnProperty(prefix = "zk", value = "enabled", havingValue = "false", matchIfMissing = true)
+@Slf4j
+public class EnvironmentLogService {
+
+    @PostConstruct
+    public void init() {
+        Environment.logEnv("Thingsboard server environment: ", log);
+    }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java
new file mode 100644
index 0000000..b11517c
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java
@@ -0,0 +1,208 @@
+/**
+ * Copyright © 2016 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.service.mail;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.annotation.PostConstruct;
+import javax.mail.internet.MimeMessage;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.velocity.app.VelocityEngine;
+import org.thingsboard.server.exception.ThingsboardErrorCode;
+import org.thingsboard.server.exception.ThingsboardException;
+import org.thingsboard.server.common.data.AdminSettings;
+import org.thingsboard.server.dao.settings.AdminSettingsService;
+import org.thingsboard.server.dao.exception.IncorrectParameterException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.MessageSource;
+import org.springframework.core.NestedRuntimeException;
+import org.springframework.mail.javamail.JavaMailSenderImpl;
+import org.springframework.mail.javamail.MimeMessageHelper;
+import org.springframework.stereotype.Service;
+import org.springframework.ui.velocity.VelocityEngineUtils;
+
+import com.fasterxml.jackson.databind.JsonNode;
+@Service
+@Slf4j
+public class DefaultMailService implements MailService {
+
+    @Autowired
+    private MessageSource messages;
+    
+    @Autowired
+    private VelocityEngine engine;
+    
+    private JavaMailSenderImpl mailSender;
+    
+    private String mailFrom;
+    
+    @Autowired
+    private AdminSettingsService adminSettingsService; 
+    
+    @PostConstruct
+    private void init() {
+        updateMailConfiguration();
+    }
+
+    @Override
+    public void updateMailConfiguration() {
+        AdminSettings settings = adminSettingsService.findAdminSettingsByKey("mail");
+        JsonNode jsonConfig = settings.getJsonValue();
+        mailSender = createMailSender(jsonConfig);
+        mailFrom = jsonConfig.get("mailFrom").asText();
+    }
+    
+    private JavaMailSenderImpl createMailSender(JsonNode jsonConfig) {
+        JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
+        mailSender.setHost(jsonConfig.get("smtpHost").asText());
+        mailSender.setPort(parsePort(jsonConfig.get("smtpPort").asText()));
+        mailSender.setUsername(jsonConfig.get("username").asText());
+        mailSender.setPassword(jsonConfig.get("password").asText());
+        mailSender.setJavaMailProperties(createJavaMailProperties(jsonConfig));
+        return mailSender;
+    }
+
+    private Properties createJavaMailProperties(JsonNode jsonConfig) {
+        Properties javaMailProperties = new Properties();
+        String protocol = jsonConfig.get("smtpProtocol").asText();
+        javaMailProperties.put("mail.transport.protocol", protocol);
+        javaMailProperties.put("mail." + protocol + ".host", jsonConfig.get("smtpHost").asText());
+        javaMailProperties.put("mail." + protocol + ".port", jsonConfig.get("smtpPort").asText());
+        javaMailProperties.put("mail." + protocol + ".timeout", jsonConfig.get("timeout").asText());
+        javaMailProperties.put("mail." + protocol + ".auth", String.valueOf(StringUtils.isNotEmpty(jsonConfig.get("username").asText())));
+        javaMailProperties.put("mail." + protocol + ".starttls.enable", jsonConfig.get("enableTls"));
+        return javaMailProperties;
+    }
+    
+    private int parsePort(String strPort) {
+        try {
+            return Integer.valueOf(strPort);
+        } catch (NumberFormatException e) {
+            throw new IncorrectParameterException(String.format("Invalid smtp port value: %s", strPort));
+        }
+    }
+    
+    @Override
+    public void sendTestMail(JsonNode jsonConfig, String email) throws ThingsboardException {
+        JavaMailSenderImpl testMailSender = createMailSender(jsonConfig);
+        String mailFrom = jsonConfig.get("mailFrom").asText();
+        String subject = messages.getMessage("test.message.subject", null, Locale.US);
+        
+        Map<String, Object> model = new HashMap<String, Object>();
+        model.put("targetEmail", email);
+        
+        String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine,
+                "test.vm", "UTF-8", model);
+        
+        sendMail(testMailSender, mailFrom, email, subject, message); 
+    }
+
+    @Override
+    public void sendActivationEmail(String activationLink, String email) throws ThingsboardException {
+        
+        String subject = messages.getMessage("activation.subject", null, Locale.US);
+        
+        Map<String, Object> model = new HashMap<String, Object>();
+        model.put("activationLink", activationLink);
+        model.put("targetEmail", email);
+        
+        String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine,
+                "activation.vm", "UTF-8", model);
+        
+        sendMail(mailSender, mailFrom, email, subject, message); 
+    }
+    
+    @Override
+    public void sendAccountActivatedEmail(String loginLink, String email) throws ThingsboardException {
+        
+        String subject = messages.getMessage("account.activated.subject", null, Locale.US);
+        
+        Map<String, Object> model = new HashMap<String, Object>();
+        model.put("loginLink", loginLink);
+        model.put("targetEmail", email);
+        
+        String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine,
+                "account.activated.vm", "UTF-8", model);
+        
+        sendMail(mailSender, mailFrom, email, subject, message); 
+    }
+
+    @Override
+    public void sendResetPasswordEmail(String passwordResetLink, String email) throws ThingsboardException {
+        
+        String subject = messages.getMessage("reset.password.subject", null, Locale.US);
+        
+        Map<String, Object> model = new HashMap<String, Object>();
+        model.put("passwordResetLink", passwordResetLink);
+        model.put("targetEmail", email);
+        
+        String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine,
+                "reset.password.vm", "UTF-8", model);
+        
+        sendMail(mailSender, mailFrom, email, subject, message); 
+    }
+    
+    @Override
+    public void sendPasswordWasResetEmail(String loginLink, String email) throws ThingsboardException {
+        
+        String subject = messages.getMessage("password.was.reset.subject", null, Locale.US);
+        
+        Map<String, Object> model = new HashMap<String, Object>();
+        model.put("loginLink", loginLink);
+        model.put("targetEmail", email);
+        
+        String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine,
+                "password.was.reset.vm", "UTF-8", model);
+        
+        sendMail(mailSender, mailFrom, email, subject, message); 
+    }
+
+
+    private void sendMail(JavaMailSenderImpl mailSender, 
+            String mailFrom, String email, 
+            String subject, String message) throws ThingsboardException {
+        try {
+            MimeMessage mimeMsg = mailSender.createMimeMessage();
+            MimeMessageHelper helper = new MimeMessageHelper(mimeMsg, "UTF-8");
+            helper.setFrom(mailFrom);
+            helper.setTo(email);
+            helper.setSubject(subject);
+            helper.setText(message, true);
+            mailSender.send(helper.getMimeMessage());
+        } catch (Exception e) {
+            throw handleException(e);
+        }
+    }
+
+    protected ThingsboardException handleException(Exception exception) {
+        String message;
+        if (exception instanceof NestedRuntimeException) {
+            message = ((NestedRuntimeException)exception).getMostSpecificCause().getMessage();
+        } else {
+            message = exception.getMessage();
+        }
+        return new ThingsboardException(String.format("Unable to send mail: %s", message),
+                ThingsboardErrorCode.GENERAL);
+    }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/mail/MailService.java b/application/src/main/java/org/thingsboard/server/service/mail/MailService.java
new file mode 100644
index 0000000..e135253
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/mail/MailService.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright © 2016 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.service.mail;
+
+import org.thingsboard.server.exception.ThingsboardException;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+public interface MailService {
+
+    void updateMailConfiguration();
+    
+    void sendTestMail(JsonNode config, String email) throws ThingsboardException;
+    
+    void sendActivationEmail(String activationLink, String email) throws ThingsboardException;
+    
+    void sendAccountActivatedEmail(String loginLink, String email) throws ThingsboardException;
+    
+    void sendResetPasswordEmail(String passwordResetLink, String email) throws ThingsboardException;
+    
+    void sendPasswordWasResetEmail(String loginLink, String email) throws ThingsboardException;
+    
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/AbstractJwtAuthenticationToken.java b/application/src/main/java/org/thingsboard/server/service/security/auth/AbstractJwtAuthenticationToken.java
new file mode 100644
index 0000000..eb6541f
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/AbstractJwtAuthenticationToken.java
@@ -0,0 +1,66 @@
+/**
+ * Copyright © 2016 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.service.security.auth;
+
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.thingsboard.server.service.security.model.SecurityUser;
+import org.thingsboard.server.service.security.model.token.RawAccessJwtToken;
+
+public abstract class AbstractJwtAuthenticationToken extends AbstractAuthenticationToken {
+
+    private static final long serialVersionUID = -6212297506742428406L;
+
+    private RawAccessJwtToken rawAccessToken;
+    private SecurityUser securityUser;
+
+    public AbstractJwtAuthenticationToken(RawAccessJwtToken unsafeToken) {
+        super(null);
+        this.rawAccessToken = unsafeToken;
+        this.setAuthenticated(false);
+    }
+
+    public AbstractJwtAuthenticationToken(SecurityUser securityUser) {
+        super(securityUser.getAuthorities());
+        this.eraseCredentials();
+        this.securityUser = securityUser;
+        super.setAuthenticated(true);
+    }
+
+    @Override
+    public void setAuthenticated(boolean authenticated) {
+        if (authenticated) {
+            throw new IllegalArgumentException(
+                    "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
+        }
+        super.setAuthenticated(false);
+    }
+
+    @Override
+    public Object getCredentials() {
+        return rawAccessToken;
+    }
+
+    @Override
+    public Object getPrincipal() {
+        return this.securityUser;
+    }
+
+    @Override
+    public void eraseCredentials() {
+        super.eraseCredentials();
+        this.rawAccessToken = null;
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java
new file mode 100644
index 0000000..9b60882
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright © 2016 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.service.security.auth.jwt.extractor;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.config.ThingsboardSecurityConfiguration;
+
+import javax.servlet.http.HttpServletRequest;
+
+@Component(value="jwtHeaderTokenExtractor")
+public class JwtHeaderTokenExtractor implements TokenExtractor {
+    public static String HEADER_PREFIX = "Bearer ";
+
+    @Override
+    public String extract(HttpServletRequest request) {
+        String header = request.getHeader(ThingsboardSecurityConfiguration.JWT_TOKEN_HEADER_PARAM);
+        if (StringUtils.isBlank(header)) {
+            throw new AuthenticationServiceException("Authorization header cannot be blank!");
+        }
+
+        if (header.length() < HEADER_PREFIX.length()) {
+            throw new AuthenticationServiceException("Invalid authorization header size.");
+        }
+
+        return header.substring(HEADER_PREFIX.length(), header.length());
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtQueryTokenExtractor.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtQueryTokenExtractor.java
new file mode 100644
index 0000000..d8a8370
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtQueryTokenExtractor.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright © 2016 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.service.security.auth.jwt.extractor;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.config.ThingsboardSecurityConfiguration;
+
+import javax.servlet.http.HttpServletRequest;
+
+@Component(value="jwtQueryTokenExtractor")
+public class JwtQueryTokenExtractor implements TokenExtractor {
+
+    @Override
+    public String extract(HttpServletRequest request) {
+        String token = null;
+        if (request.getParameterMap() != null && !request.getParameterMap().isEmpty()) {
+            String[] tokenParamValue = request.getParameterMap().get(ThingsboardSecurityConfiguration.JWT_TOKEN_QUERY_PARAM);
+            if (tokenParamValue != null && tokenParamValue.length == 1) {
+                token = tokenParamValue[0];
+            }
+        }
+        if (StringUtils.isBlank(token)) {
+            throw new AuthenticationServiceException("Authorization query parameter cannot be blank!");
+        }
+
+        return token;
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/TokenExtractor.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/TokenExtractor.java
new file mode 100644
index 0000000..74c6dea
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/TokenExtractor.java
@@ -0,0 +1,22 @@
+/**
+ * Copyright © 2016 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.service.security.auth.jwt.extractor;
+
+import javax.servlet.http.HttpServletRequest;
+
+public interface TokenExtractor {
+    public String extract(HttpServletRequest request);
+}
\ No newline at end of file
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtAuthenticationProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtAuthenticationProvider.java
new file mode 100644
index 0000000..d02019c
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtAuthenticationProvider.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright © 2016 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.service.security.auth.jwt;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jws;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.config.JwtSettings;
+import org.thingsboard.server.service.security.auth.JwtAuthenticationToken;
+import org.thingsboard.server.service.security.model.SecurityUser;
+import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
+import org.thingsboard.server.service.security.model.token.RawAccessJwtToken;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Component
+@SuppressWarnings("unchecked")
+public class JwtAuthenticationProvider implements AuthenticationProvider {
+
+    private final JwtTokenFactory tokenFactory;
+
+    @Autowired
+    public JwtAuthenticationProvider(JwtTokenFactory tokenFactory) {
+        this.tokenFactory = tokenFactory;
+    }
+
+    @Override
+    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+        RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials();
+        SecurityUser securityUser = tokenFactory.parseAccessJwtToken(rawAccessToken);
+        return new JwtAuthenticationToken(securityUser);
+    }
+
+    @Override
+    public boolean supports(Class<?> authentication) {
+        return (JwtAuthenticationToken.class.isAssignableFrom(authentication));
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java
new file mode 100644
index 0000000..523a670
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java
@@ -0,0 +1,71 @@
+/**
+ * Copyright © 2016 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.service.security.auth.jwt;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+import org.springframework.security.web.util.matcher.RequestMatcher;
+import org.thingsboard.server.config.ThingsboardSecurityConfiguration;
+import org.thingsboard.server.service.security.auth.JwtAuthenticationToken;
+import org.thingsboard.server.service.security.auth.jwt.extractor.TokenExtractor;
+import org.thingsboard.server.service.security.model.token.RawAccessJwtToken;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+public class JwtTokenAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
+    private final AuthenticationFailureHandler failureHandler;
+    private final TokenExtractor tokenExtractor;
+
+    @Autowired
+    public JwtTokenAuthenticationProcessingFilter(AuthenticationFailureHandler failureHandler,
+                                                  TokenExtractor tokenExtractor, RequestMatcher matcher) {
+        super(matcher);
+        this.failureHandler = failureHandler;
+        this.tokenExtractor = tokenExtractor;
+    }
+
+    @Override
+    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
+            throws AuthenticationException, IOException, ServletException {
+        RawAccessJwtToken token = new RawAccessJwtToken(tokenExtractor.extract(request));
+        return getAuthenticationManager().authenticate(new JwtAuthenticationToken(token));
+    }
+
+    @Override
+    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
+                                            Authentication authResult) throws IOException, ServletException {
+        SecurityContext context = SecurityContextHolder.createEmptyContext();
+        context.setAuthentication(authResult);
+        SecurityContextHolder.setContext(context);
+        chain.doFilter(request, response);
+    }
+
+    @Override
+    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
+                                              AuthenticationException failed) throws IOException, ServletException {
+        SecurityContextHolder.clearContext();
+        failureHandler.onAuthenticationFailure(request, response, failed);
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java
new file mode 100644
index 0000000..e044ef4
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java
@@ -0,0 +1,79 @@
+/**
+ * Copyright © 2016 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.service.security.auth.jwt;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.DisabledException;
+import org.springframework.security.authentication.InsufficientAuthenticationException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Component;
+import org.springframework.util.Assert;
+import org.thingsboard.server.common.data.User;
+import org.thingsboard.server.common.data.security.UserCredentials;
+import org.thingsboard.server.dao.user.UserService;
+import org.thingsboard.server.service.security.auth.RefreshAuthenticationToken;
+import org.thingsboard.server.service.security.model.SecurityUser;
+import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
+import org.thingsboard.server.service.security.model.token.RawAccessJwtToken;
+
+@Component
+public class RefreshTokenAuthenticationProvider implements AuthenticationProvider {
+
+    private final JwtTokenFactory tokenFactory;
+    private final UserService userService;
+
+    @Autowired
+    public RefreshTokenAuthenticationProvider(final UserService userService, final JwtTokenFactory tokenFactory) {
+        this.userService = userService;
+        this.tokenFactory = tokenFactory;
+    }
+
+    @Override
+    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+        Assert.notNull(authentication, "No authentication data provided");
+        RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials();
+        SecurityUser unsafeUser = tokenFactory.parseRefreshToken(rawAccessToken);
+
+        User user = userService.findUserById(unsafeUser.getId());
+        if (user == null) {
+            throw new UsernameNotFoundException("User not found by refresh token");
+        }
+
+        UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getId());
+        if (userCredentials == null) {
+            throw new UsernameNotFoundException("User credentials not found");
+        }
+
+        if (!userCredentials.isEnabled()) {
+            throw new DisabledException("User is not active");
+        }
+
+        if (user.getAuthority() == null) throw new InsufficientAuthenticationException("User has no authority assigned");
+
+        SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled());
+
+        return new RefreshAuthenticationToken(securityUser);
+    }
+
+    @Override
+    public boolean supports(Class<?> authentication) {
+        return (RefreshAuthenticationToken.class.isAssignableFrom(authentication));
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenProcessingFilter.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenProcessingFilter.java
new file mode 100644
index 0000000..354b68e
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenProcessingFilter.java
@@ -0,0 +1,94 @@
+/**
+ * Copyright © 2016 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.service.security.auth.jwt;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
+import org.thingsboard.server.service.security.auth.RefreshAuthenticationToken;
+import org.thingsboard.server.service.security.exception.AuthMethodNotSupportedException;
+import org.thingsboard.server.service.security.model.token.RawAccessJwtToken;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+public class RefreshTokenProcessingFilter extends AbstractAuthenticationProcessingFilter {
+    private static Logger logger = LoggerFactory.getLogger(RefreshTokenProcessingFilter.class);
+
+    private final AuthenticationSuccessHandler successHandler;
+    private final AuthenticationFailureHandler failureHandler;
+
+    private final ObjectMapper objectMapper;
+
+    public RefreshTokenProcessingFilter(String defaultProcessUrl, AuthenticationSuccessHandler successHandler,
+                                        AuthenticationFailureHandler failureHandler, ObjectMapper mapper) {
+        super(defaultProcessUrl);
+        this.successHandler = successHandler;
+        this.failureHandler = failureHandler;
+        this.objectMapper = mapper;
+    }
+
+    @Override
+    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
+            throws AuthenticationException, IOException, ServletException {
+        if (!HttpMethod.POST.name().equals(request.getMethod())) {
+            if(logger.isDebugEnabled()) {
+                logger.debug("Authentication method not supported. Request method: " + request.getMethod());
+            }
+            throw new AuthMethodNotSupportedException("Authentication method not supported");
+        }
+
+        RefreshTokenRequest refreshTokenRequest;
+        try {
+            refreshTokenRequest = objectMapper.readValue(request.getReader(), RefreshTokenRequest.class);
+        } catch (Exception e) {
+            throw new AuthenticationServiceException("Invalid refresh token request payload");
+        }
+
+        if (StringUtils.isBlank(refreshTokenRequest.getRefreshToken())) {
+            throw new AuthenticationServiceException("Refresh token is not provided");
+        }
+
+        RawAccessJwtToken token = new RawAccessJwtToken(refreshTokenRequest.getRefreshToken());
+
+        return this.getAuthenticationManager().authenticate(new RefreshAuthenticationToken(token));
+    }
+
+    @Override
+    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
+                                            Authentication authResult) throws IOException, ServletException {
+        successHandler.onAuthenticationSuccess(request, response, authResult);
+    }
+
+    @Override
+    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
+                                              AuthenticationException failed) throws IOException, ServletException {
+        SecurityContextHolder.clearContext();
+        failureHandler.onAuthenticationFailure(request, response, failed);
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenRepository.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenRepository.java
new file mode 100644
index 0000000..06237d8
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenRepository.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright © 2016 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.service.security.auth.jwt;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.service.security.model.SecurityUser;
+import org.thingsboard.server.service.security.model.token.JwtToken;
+import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
+
+@Component
+public class RefreshTokenRepository {
+
+    private final JwtTokenFactory tokenFactory;
+
+    @Autowired
+    public RefreshTokenRepository(final JwtTokenFactory tokenFactory) {
+        this.tokenFactory = tokenFactory;
+    }
+
+    public JwtToken requestRefreshToken(SecurityUser user) {
+        return tokenFactory.createRefreshToken(user);
+    }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenRequest.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenRequest.java
new file mode 100644
index 0000000..4a14972
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenRequest.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright © 2016 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.service.security.auth.jwt;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class RefreshTokenRequest {
+    private String refreshToken;
+
+    @JsonCreator
+    public RefreshTokenRequest(@JsonProperty("refreshToken") String refreshToken) {
+        this.refreshToken = refreshToken;
+    }
+
+    public String getRefreshToken() {
+        return refreshToken;
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/SkipPathRequestMatcher.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/SkipPathRequestMatcher.java
new file mode 100644
index 0000000..9710cf6
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/SkipPathRequestMatcher.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright © 2016 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.service.security.auth.jwt;
+
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+import org.springframework.security.web.util.matcher.OrRequestMatcher;
+import org.springframework.security.web.util.matcher.RequestMatcher;
+import org.springframework.util.Assert;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class SkipPathRequestMatcher implements RequestMatcher {
+    private OrRequestMatcher matchers;
+    private RequestMatcher processingMatcher;
+
+    public SkipPathRequestMatcher(List<String> pathsToSkip, String processingPath) {
+        Assert.notNull(pathsToSkip);
+        List<RequestMatcher> m = pathsToSkip.stream().map(path -> new AntPathRequestMatcher(path)).collect(Collectors.toList());
+        matchers = new OrRequestMatcher(m);
+        processingMatcher = new AntPathRequestMatcher(processingPath);
+    }
+
+    @Override
+    public boolean matches(HttpServletRequest request) {
+        if (matchers.matches(request)) {
+            return false;
+        }
+        return processingMatcher.matches(request) ? true : false;
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/JwtAuthenticationToken.java b/application/src/main/java/org/thingsboard/server/service/security/auth/JwtAuthenticationToken.java
new file mode 100644
index 0000000..688ca19
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/JwtAuthenticationToken.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright © 2016 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.service.security.auth;
+
+import org.thingsboard.server.service.security.model.SecurityUser;
+import org.thingsboard.server.service.security.model.token.RawAccessJwtToken;
+
+public class JwtAuthenticationToken extends AbstractJwtAuthenticationToken {
+
+    private static final long serialVersionUID = -8487219769037942225L;
+
+    public JwtAuthenticationToken(RawAccessJwtToken unsafeToken) {
+        super(unsafeToken);
+    }
+
+    public JwtAuthenticationToken(SecurityUser securityUser) {
+        super(securityUser);
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/RefreshAuthenticationToken.java b/application/src/main/java/org/thingsboard/server/service/security/auth/RefreshAuthenticationToken.java
new file mode 100644
index 0000000..6f927c1
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/RefreshAuthenticationToken.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright © 2016 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.service.security.auth;
+
+import org.thingsboard.server.service.security.model.SecurityUser;
+import org.thingsboard.server.service.security.model.token.RawAccessJwtToken;
+
+public class RefreshAuthenticationToken extends AbstractJwtAuthenticationToken {
+
+    private static final long serialVersionUID = -1311042791508924523L;
+
+    public RefreshAuthenticationToken(RawAccessJwtToken unsafeToken) {
+        super(unsafeToken);
+    }
+
+    public RefreshAuthenticationToken(SecurityUser securityUser) {
+        super(securityUser);
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/LoginRequest.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/LoginRequest.java
new file mode 100644
index 0000000..b00506f
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/LoginRequest.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright © 2016 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.service.security.auth.rest;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class LoginRequest {
+    private String username;
+    private String password;
+
+    @JsonCreator
+    public LoginRequest(@JsonProperty("username") String username, @JsonProperty("password") String password) {
+        this.username = username;
+        this.password = password;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationProvider.java
new file mode 100644
index 0000000..bcc9588
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationProvider.java
@@ -0,0 +1,79 @@
+/**
+ * Copyright © 2016 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.service.security.auth.rest;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.*;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.stereotype.Component;
+import org.springframework.util.Assert;
+import org.thingsboard.server.common.data.User;
+import org.thingsboard.server.common.data.security.UserCredentials;
+import org.thingsboard.server.dao.user.UserService;
+import org.thingsboard.server.service.security.model.SecurityUser;
+
+@Component
+public class RestAuthenticationProvider implements AuthenticationProvider {
+
+    private final BCryptPasswordEncoder encoder;
+    private final UserService userService;
+
+    @Autowired
+    public RestAuthenticationProvider(final UserService userService, final BCryptPasswordEncoder encoder) {
+        this.userService = userService;
+        this.encoder = encoder;
+    }
+
+    @Override
+    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+        Assert.notNull(authentication, "No authentication data provided");
+
+        String username = (String) authentication.getPrincipal();
+        String password = (String) authentication.getCredentials();
+
+        User user = userService.findUserByEmail(username);
+        if (user == null) {
+            throw new UsernameNotFoundException("User not found: " + username);
+        }
+
+        UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getId());
+        if (userCredentials == null) {
+            throw new UsernameNotFoundException("User credentials not found");
+        }
+
+        if (!userCredentials.isEnabled()) {
+            throw new DisabledException("User is not active");
+        }
+
+        if (!encoder.matches(password, userCredentials.getPassword())) {
+            throw new BadCredentialsException("Authentication Failed. Username or Password not valid.");
+        }
+
+        if (user.getAuthority() == null) throw new InsufficientAuthenticationException("User has no authority assigned");
+
+        SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled());
+
+        return new UsernamePasswordAuthenticationToken(securityUser, null, securityUser.getAuthorities());
+    }
+
+    @Override
+    public boolean supports(Class<?> authentication) {
+        return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationFailureHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationFailureHandler.java
new file mode 100644
index 0000000..879a17c
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationFailureHandler.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright © 2016 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.service.security.auth.rest;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@Component
+public class RestAwareAuthenticationFailureHandler implements AuthenticationFailureHandler {
+
+    private final ThingsboardErrorResponseHandler errorResponseHandler;
+
+    @Autowired
+    public RestAwareAuthenticationFailureHandler(ThingsboardErrorResponseHandler errorResponseHandler) {
+        this.errorResponseHandler = errorResponseHandler;
+    }
+
+    @Override
+    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
+                                        AuthenticationException e) throws IOException, ServletException {
+        errorResponseHandler.handle(e, response);
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java
new file mode 100644
index 0000000..72c7719
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java
@@ -0,0 +1,85 @@
+/**
+ * Copyright © 2016 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.service.security.auth.rest;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.WebAttributes;
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
+import org.thingsboard.server.service.security.model.SecurityUser;
+import org.thingsboard.server.service.security.model.token.JwtToken;
+import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+public class RestAwareAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
+    private final ObjectMapper mapper;
+    private final JwtTokenFactory tokenFactory;
+    private final RefreshTokenRepository refreshTokenRepository;
+
+    @Autowired
+    public RestAwareAuthenticationSuccessHandler(final ObjectMapper mapper, final JwtTokenFactory tokenFactory, final RefreshTokenRepository refreshTokenRepository) {
+        this.mapper = mapper;
+        this.tokenFactory = tokenFactory;
+        this.refreshTokenRepository = refreshTokenRepository;
+    }
+
+    @Override
+    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
+                                        Authentication authentication) throws IOException, ServletException {
+        SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
+
+        JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
+        JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
+
+        Map<String, String> tokenMap = new HashMap<String, String>();
+        tokenMap.put("token", accessToken.getToken());
+        tokenMap.put("refreshToken", refreshToken.getToken());
+
+        response.setStatus(HttpStatus.OK.value());
+        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+        mapper.writeValue(response.getWriter(), tokenMap);
+
+        clearAuthenticationAttributes(request);
+    }
+
+    /**
+     * Removes temporary authentication-related data which may have been stored
+     * in the session during the authentication process..
+     *
+     */
+    protected final void clearAuthenticationAttributes(HttpServletRequest request) {
+        HttpSession session = request.getSession(false);
+
+        if (session == null) {
+            return;
+        }
+
+        session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestLoginProcessingFilter.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestLoginProcessingFilter.java
new file mode 100644
index 0000000..ba6431e
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestLoginProcessingFilter.java
@@ -0,0 +1,93 @@
+/**
+ * Copyright © 2016 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.service.security.auth.rest;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
+import org.thingsboard.server.service.security.exception.AuthMethodNotSupportedException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+public class RestLoginProcessingFilter extends AbstractAuthenticationProcessingFilter {
+    private static Logger logger = LoggerFactory.getLogger(RestLoginProcessingFilter.class);
+
+    private final AuthenticationSuccessHandler successHandler;
+    private final AuthenticationFailureHandler failureHandler;
+
+    private final ObjectMapper objectMapper;
+
+    public RestLoginProcessingFilter(String defaultProcessUrl, AuthenticationSuccessHandler successHandler,
+                                     AuthenticationFailureHandler failureHandler, ObjectMapper mapper) {
+        super(defaultProcessUrl);
+        this.successHandler = successHandler;
+        this.failureHandler = failureHandler;
+        this.objectMapper = mapper;
+    }
+
+    @Override
+    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
+            throws AuthenticationException, IOException, ServletException {
+        if (!HttpMethod.POST.name().equals(request.getMethod())) {
+            if(logger.isDebugEnabled()) {
+                logger.debug("Authentication method not supported. Request method: " + request.getMethod());
+            }
+            throw new AuthMethodNotSupportedException("Authentication method not supported");
+        }
+
+        LoginRequest loginRequest;
+        try {
+            loginRequest = objectMapper.readValue(request.getReader(), LoginRequest.class);
+        } catch (Exception e) {
+            throw new AuthenticationServiceException("Invalid login request payload");
+        }
+
+        if (StringUtils.isBlank(loginRequest.getUsername()) || StringUtils.isBlank(loginRequest.getPassword())) {
+            throw new AuthenticationServiceException("Username or Password not provided");
+        }
+
+        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
+
+        return this.getAuthenticationManager().authenticate(token);
+    }
+
+    @Override
+    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
+                                            Authentication authResult) throws IOException, ServletException {
+        successHandler.onAuthenticationSuccess(request, response, authResult);
+    }
+
+    @Override
+    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
+                                              AuthenticationException failed) throws IOException, ServletException {
+        SecurityContextHolder.clearContext();
+        failureHandler.onAuthenticationFailure(request, response, failed);
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/device/DefaultDeviceAuthService.java b/application/src/main/java/org/thingsboard/server/service/security/device/DefaultDeviceAuthService.java
new file mode 100644
index 0000000..cef73fe
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/device/DefaultDeviceAuthService.java
@@ -0,0 +1,70 @@
+/**
+ * Copyright © 2016 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.service.security.device;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.security.DeviceCredentials;
+import org.thingsboard.server.common.data.security.DeviceCredentialsFilter;
+import org.thingsboard.server.common.transport.auth.DeviceAuthResult;
+import org.thingsboard.server.common.transport.auth.DeviceAuthService;
+import org.thingsboard.server.dao.device.DeviceCredentialsService;
+import org.thingsboard.server.dao.device.DeviceService;
+
+import java.util.Optional;
+
+@Service
+@Slf4j
+public class DefaultDeviceAuthService implements DeviceAuthService {
+
+    @Autowired
+    DeviceService deviceService;
+
+    @Autowired
+    DeviceCredentialsService deviceCredentialsService;
+
+    @Override
+    public DeviceAuthResult process(DeviceCredentialsFilter credentialsFilter) {
+        log.trace("Lookup device credentials using filter {}", credentialsFilter);
+        DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(credentialsFilter.getCredentialsId());
+        if (credentials != null) {
+            log.trace("Credentials found {}", credentials);
+            if (credentials.getCredentialsType() == credentialsFilter.getCredentialsType()) {
+                switch (credentials.getCredentialsType()) {
+                    case ACCESS_TOKEN:
+                        // Credentials ID matches Credentials value in this
+                        // primitive case;
+                        return DeviceAuthResult.of(credentials.getDeviceId());
+                    default:
+                        return DeviceAuthResult.of("Credentials Type is not supported yet!");
+                }
+            } else {
+                return DeviceAuthResult.of("Credentials Type mismatch!");
+            }
+        } else {
+            log.trace("Credentials not found!");
+            return DeviceAuthResult.of("Credentials Not Found!");
+        }
+    }
+
+    @Override
+    public Optional<Device> findDeviceById(DeviceId deviceId) {
+        return Optional.ofNullable(deviceService.findDeviceById(deviceId));
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/exception/AuthMethodNotSupportedException.java b/application/src/main/java/org/thingsboard/server/service/security/exception/AuthMethodNotSupportedException.java
new file mode 100644
index 0000000..4444870
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/exception/AuthMethodNotSupportedException.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright © 2016 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.service.security.exception;
+
+import org.springframework.security.authentication.AuthenticationServiceException;
+
+public class AuthMethodNotSupportedException extends AuthenticationServiceException {
+    private static final long serialVersionUID = 3705043083010304496L;
+
+    public AuthMethodNotSupportedException(String msg) {
+        super(msg);
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/exception/JwtExpiredTokenException.java b/application/src/main/java/org/thingsboard/server/service/security/exception/JwtExpiredTokenException.java
new file mode 100644
index 0000000..a3c522c
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/exception/JwtExpiredTokenException.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright © 2016 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.service.security.exception;
+
+import org.springframework.security.core.AuthenticationException;
+import org.thingsboard.server.service.security.model.token.JwtToken;
+
+public class JwtExpiredTokenException extends AuthenticationException {
+    private static final long serialVersionUID = -5959543783324224864L;
+
+    private JwtToken token;
+
+    public JwtExpiredTokenException(String msg) {
+        super(msg);
+    }
+
+    public JwtExpiredTokenException(JwtToken token, String msg, Throwable t) {
+        super(msg, t);
+        this.token = token;
+    }
+
+    public String token() {
+        return this.token.getToken();
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java b/application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java
new file mode 100644
index 0000000..83b87ab
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java
@@ -0,0 +1,64 @@
+/**
+ * Copyright © 2016 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.service.security.model;
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.thingsboard.server.common.data.User;
+import org.thingsboard.server.common.data.id.UserId;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+public class SecurityUser extends User {
+
+    private static final long serialVersionUID = -797397440703066079L;
+
+    private Collection<GrantedAuthority> authorities;
+    private boolean enabled;
+
+    public SecurityUser() {
+        super();
+    }
+
+    public SecurityUser(UserId id) {
+        super(id);
+    }
+
+    public SecurityUser(User user, boolean enabled) {
+        super(user);
+        this.enabled = enabled;
+    }
+
+    public Collection<? extends GrantedAuthority> getAuthorities() {
+        if (authorities == null) {
+            authorities = Arrays.asList(SecurityUser.this.getAuthority()).stream()
+                    .map(authority -> new SimpleGrantedAuthority(authority.name()))
+                    .collect(Collectors.toList());
+        }
+        return authorities;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/token/AccessJwtToken.java b/application/src/main/java/org/thingsboard/server/service/security/model/token/AccessJwtToken.java
new file mode 100644
index 0000000..9145937
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/model/token/AccessJwtToken.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright © 2016 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.service.security.model.token;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.jsonwebtoken.Claims;
+
+public final class AccessJwtToken implements JwtToken {
+    private final String rawToken;
+    @JsonIgnore
+    private Claims claims;
+
+    protected AccessJwtToken(final String token, Claims claims) {
+        this.rawToken = token;
+        this.claims = claims;
+    }
+
+    public String getToken() {
+        return this.rawToken;
+    }
+
+    public Claims getClaims() {
+        return claims;
+    }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtToken.java b/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtToken.java
new file mode 100644
index 0000000..0e43fdf
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtToken.java
@@ -0,0 +1,20 @@
+/**
+ * Copyright © 2016 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.service.security.model.token;
+
+public interface JwtToken {
+    String getToken();
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java b/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java
new file mode 100644
index 0000000..0ce6181
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java
@@ -0,0 +1,158 @@
+/**
+ * Copyright © 2016 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.service.security.model.token;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jws;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import org.apache.commons.lang3.StringUtils;
+import org.joda.time.DateTime;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.id.UserId;
+import org.thingsboard.server.common.data.security.Authority;
+import org.thingsboard.server.config.JwtSettings;
+import org.thingsboard.server.service.security.model.SecurityUser;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+@Component
+public class JwtTokenFactory {
+
+    private static final String SCOPES = "scopes";
+    private static final String USER_ID = "userId";
+    private static final String FIRST_NAME = "firstName";
+    private static final String LAST_NAME = "lastName";
+    private static final String ENABLED = "enabled";
+    private static final String TENANT_ID = "tenantId";
+    private static final String CUSTOMER_ID = "customerId";
+
+    private final JwtSettings settings;
+
+    @Autowired
+    public JwtTokenFactory(JwtSettings settings) {
+        this.settings = settings;
+    }
+
+    /**
+     * Factory method for issuing new JWT Tokens.
+     */
+    public AccessJwtToken createAccessJwtToken(SecurityUser securityUser) {
+        if (StringUtils.isBlank(securityUser.getEmail()))
+            throw new IllegalArgumentException("Cannot create JWT Token without username/email");
+
+        if (securityUser.getAuthority() == null)
+            throw new IllegalArgumentException("User doesn't have any privileges");
+
+        Claims claims = Jwts.claims().setSubject(securityUser.getEmail());
+        claims.put(SCOPES, securityUser.getAuthorities().stream().map(s -> s.getAuthority()).collect(Collectors.toList()));
+        claims.put(USER_ID, securityUser.getId().getId().toString());
+        claims.put(FIRST_NAME, securityUser.getFirstName());
+        claims.put(LAST_NAME, securityUser.getLastName());
+        claims.put(ENABLED, securityUser.isEnabled());
+        if (securityUser.getTenantId() != null) {
+            claims.put(TENANT_ID, securityUser.getTenantId().getId().toString());
+        }
+        if (securityUser.getCustomerId() != null) {
+            claims.put(CUSTOMER_ID, securityUser.getCustomerId().getId().toString());
+        }
+
+        DateTime currentTime = new DateTime();
+
+        String token = Jwts.builder()
+                .setClaims(claims)
+                .setIssuer(settings.getTokenIssuer())
+                .setIssuedAt(currentTime.toDate())
+                .setExpiration(currentTime.plusSeconds(settings.getTokenExpirationTime()).toDate())
+                .signWith(SignatureAlgorithm.HS512, settings.getTokenSigningKey())
+                .compact();
+
+        return new AccessJwtToken(token, claims);
+    }
+
+    public SecurityUser parseAccessJwtToken(RawAccessJwtToken rawAccessToken) {
+        Jws<Claims> jwsClaims = rawAccessToken.parseClaims(settings.getTokenSigningKey());
+        Claims claims = jwsClaims.getBody();
+        String subject = claims.getSubject();
+        List<String> scopes = claims.get(SCOPES, List.class);
+        if (scopes == null || scopes.isEmpty()) {
+            throw new IllegalArgumentException("JWT Token doesn't have any scopes");
+        }
+
+        SecurityUser securityUser = new SecurityUser(new UserId(UUID.fromString(claims.get(USER_ID, String.class))));
+        securityUser.setEmail(subject);
+        securityUser.setAuthority(Authority.parse(scopes.get(0)));
+        securityUser.setFirstName(claims.get(FIRST_NAME, String.class));
+        securityUser.setLastName(claims.get(LAST_NAME, String.class));
+        securityUser.setEnabled(claims.get(ENABLED, Boolean.class));
+        String tenantId = claims.get(TENANT_ID, String.class);
+        if (tenantId != null) {
+            securityUser.setTenantId(new TenantId(UUID.fromString(tenantId)));
+        }
+        String customerId = claims.get(CUSTOMER_ID, String.class);
+        if (customerId != null) {
+            securityUser.setCustomerId(new CustomerId(UUID.fromString(customerId)));
+        }
+
+        return securityUser;
+    }
+
+    public JwtToken createRefreshToken(SecurityUser securityUser) {
+        if (StringUtils.isBlank(securityUser.getEmail())) {
+            throw new IllegalArgumentException("Cannot create JWT Token without username/email");
+        }
+
+        DateTime currentTime = new DateTime();
+
+        Claims claims = Jwts.claims().setSubject(securityUser.getEmail());
+        claims.put(SCOPES, Arrays.asList(Authority.REFRESH_TOKEN.name()));
+        claims.put(USER_ID, securityUser.getId().getId().toString());
+
+        String token = Jwts.builder()
+                .setClaims(claims)
+                .setIssuer(settings.getTokenIssuer())
+                .setId(UUID.randomUUID().toString())
+                .setIssuedAt(currentTime.toDate())
+                .setExpiration(currentTime.plusSeconds(settings.getRefreshTokenExpTime()).toDate())
+                .signWith(SignatureAlgorithm.HS512, settings.getTokenSigningKey())
+                .compact();
+
+        return new AccessJwtToken(token, claims);
+    }
+
+    public SecurityUser parseRefreshToken(RawAccessJwtToken rawAccessToken) {
+        Jws<Claims> jwsClaims = rawAccessToken.parseClaims(settings.getTokenSigningKey());
+        Claims claims = jwsClaims.getBody();
+        String subject = claims.getSubject();
+        List<String> scopes = claims.get(SCOPES, List.class);
+        if (scopes == null || scopes.isEmpty()) {
+            throw new IllegalArgumentException("Refresh Token doesn't have any scopes");
+        }
+        if (!scopes.get(0).equals(Authority.REFRESH_TOKEN.name())) {
+            throw new IllegalArgumentException("Invalid Refresh Token scope");
+        }
+        SecurityUser securityUser = new SecurityUser(new UserId(UUID.fromString(claims.get(USER_ID, String.class))));
+        securityUser.setEmail(subject);
+        return securityUser;
+    }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/token/RawAccessJwtToken.java b/application/src/main/java/org/thingsboard/server/service/security/model/token/RawAccessJwtToken.java
new file mode 100644
index 0000000..cf32374
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/security/model/token/RawAccessJwtToken.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright © 2016 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.service.security.model.token;
+
+import io.jsonwebtoken.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.thingsboard.server.service.security.exception.JwtExpiredTokenException;
+
+public class RawAccessJwtToken implements JwtToken {
+    private static Logger logger = LoggerFactory.getLogger(RawAccessJwtToken.class);
+
+    private String token;
+
+    public RawAccessJwtToken(String token) {
+        this.token = token;
+    }
+
+    /**
+     * Parses and validates JWT Token signature.
+     *
+     * @throws BadCredentialsException
+     * @throws JwtExpiredTokenException
+     *
+     */
+    public Jws<Claims> parseClaims(String signingKey) {
+        try {
+            return Jwts.parser().setSigningKey(signingKey).parseClaimsJws(this.token);
+        } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) {
+            logger.error("Invalid JWT Token", ex);
+            throw new BadCredentialsException("Invalid JWT token: ", ex);
+        } catch (ExpiredJwtException expiredEx) {
+            logger.info("JWT Token is expired", expiredEx);
+            throw new JwtExpiredTokenException(this, "JWT Token expired", expiredEx);
+        }
+    }
+
+    @Override
+    public String getToken() {
+        return token;
+    }
+}

dao/pom.xml 176(+176 -0)

diff --git a/dao/pom.xml b/dao/pom.xml
new file mode 100644
index 0000000..f9f0d56
--- /dev/null
+++ b/dao/pom.xml
@@ -0,0 +1,176 @@
+<!--
+
+    Copyright © 2016 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.thingsboard</groupId>
+        <version>0.0.1-SNAPSHOT</version>
+        <artifactId>server</artifactId>
+    </parent>
+    <groupId>org.thingsboard.server</groupId>
+    <artifactId>dao</artifactId>
+    <packaging>jar</packaging>
+
+    <name>Thingsboard Server DAO Layer</name>
+    <url>http://thingsboard.org</url>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <main.dir>${basedir}/..</main.dir>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.thingsboard.server.common</groupId>
+            <artifactId>data</artifactId>
+        </dependency>    
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>log4j-over-slf4j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-test</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <scope>test</scope>
+        </dependency>
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-lang3</artifactId>
+		</dependency>        
+		<dependency>
+			<groupId>commons-validator</groupId>
+			<artifactId>commons-validator</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.fasterxml.jackson.core</groupId>
+			<artifactId>jackson-databind</artifactId>
+		</dependency>
+        <dependency>
+            <groupId>com.github.fge</groupId>
+            <artifactId>json-schema-validator</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
+        </dependency>
+		<dependency>
+			<groupId>org.springframework</groupId>
+			<artifactId>spring-tx</artifactId>
+		</dependency>       
+		<dependency>
+			<groupId>com.datastax.cassandra</groupId>
+			<artifactId>cassandra-driver-core</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.datastax.cassandra</groupId>
+			<artifactId>cassandra-driver-mapping</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.datastax.cassandra</groupId>
+			<artifactId>cassandra-driver-extras</artifactId>
+		</dependency>
+        <dependency>
+            <groupId>io.takari.junit</groupId>
+            <artifactId>takari-cpsuite</artifactId>
+            <scope>test</scope>
+        </dependency>        
+		<dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.cassandraunit</groupId>
+            <artifactId>cassandra-unit</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-log4j12</artifactId>
+                </exclusion>
+            </exclusions>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.curator</groupId>
+            <artifactId>curator-x-discovery</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.hazelcast</groupId>
+            <artifactId>hazelcast-zookeeper</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.hazelcast</groupId>
+            <artifactId>hazelcast</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.hazelcast</groupId>
+            <artifactId>hazelcast-spring</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-autoconfigure</artifactId>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>${surfire.version}</version>
+                <configuration>
+                    <includes>
+                        <include>**/*TestSuite.java</include>
+                    </includes>
+                </configuration>
+            </plugin>
+		    <plugin>
+		       <groupId>org.apache.maven.plugins</groupId>
+		       <artifactId>maven-jar-plugin</artifactId>
+                <version>${jar-plugin.version}</version>
+		       <executions>
+		         <execution>
+		           <goals>
+		             <goal>test-jar</goal>
+		           </goals>
+		         </execution>
+		       </executions>
+		    </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/dao/src/main/java/org/thingsboard/server/dao/AbstractDao.java b/dao/src/main/java/org/thingsboard/server/dao/AbstractDao.java
new file mode 100644
index 0000000..b927f14
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/AbstractDao.java
@@ -0,0 +1,93 @@
+/**
+ * Copyright © 2016 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;
+
+import com.datastax.driver.core.*;
+import com.datastax.driver.core.exceptions.CodecNotFoundException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.thingsboard.server.dao.cassandra.CassandraCluster;
+import org.thingsboard.server.dao.model.type.*;
+
+@Slf4j
+public abstract class AbstractDao {
+
+    @Autowired
+    protected CassandraCluster cluster;
+
+    private Session session;
+
+    private ConsistencyLevel defaultReadLevel;
+    private ConsistencyLevel defaultWriteLevel;
+
+    protected Session getSession() {
+        if (session == null) {
+            session = cluster.getSession();
+            defaultReadLevel = cluster.getDefaultReadConsistencyLevel();
+            defaultWriteLevel = cluster.getDefaultWriteConsistencyLevel();
+            CodecRegistry registry = session.getCluster().getConfiguration().getCodecRegistry();
+            registerCodecIfNotFound(registry, new JsonCodec());
+            registerCodecIfNotFound(registry, new DeviceCredentialsTypeCodec());
+            registerCodecIfNotFound(registry, new AuthorityCodec());
+            registerCodecIfNotFound(registry, new ComponentLifecycleStateCodec());
+            registerCodecIfNotFound(registry, new ComponentTypeCodec());
+            registerCodecIfNotFound(registry, new ComponentScopeCodec());
+            registerCodecIfNotFound(registry, new EntityTypeCodec());
+        }
+        return session;
+    }
+
+    private void registerCodecIfNotFound(CodecRegistry registry, TypeCodec<?> codec) {
+        try {
+            registry.codecFor(codec.getCqlType(), codec.getJavaType());
+        } catch (CodecNotFoundException e) {
+            registry.register(codec);
+        }
+    }
+
+    protected ResultSet executeRead(Statement statement) {
+        return execute(statement, defaultReadLevel);
+    }
+
+    protected ResultSet executeWrite(Statement statement) {
+        return execute(statement, defaultWriteLevel);
+    }
+
+
+    protected ResultSetFuture executeAsyncRead(Statement statement) {
+        return executeAsync(statement, defaultReadLevel);
+    }
+
+    protected ResultSetFuture executeAsyncWrite(Statement statement) {
+        return executeAsync(statement, defaultWriteLevel);
+    }
+
+    private ResultSet execute(Statement statement, ConsistencyLevel level) {
+        log.debug("Execute cassandra statement {}", statement);
+        if (statement.getConsistencyLevel() == null) {
+            statement.setConsistencyLevel(level);
+        }
+        return getSession().execute(statement);
+    }
+
+    private ResultSetFuture executeAsync(Statement statement, ConsistencyLevel level) {
+        log.debug("Execute cassandra async statement {}", statement);
+        if (statement.getConsistencyLevel() == null) {
+            statement.setConsistencyLevel(level);
+        }
+        return getSession().executeAsync(statement);
+    }
+}
\ No newline at end of file
diff --git a/dao/src/main/java/org/thingsboard/server/dao/AbstractModelDao.java b/dao/src/main/java/org/thingsboard/server/dao/AbstractModelDao.java
new file mode 100644
index 0000000..229cd03
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/AbstractModelDao.java
@@ -0,0 +1,114 @@
+/**
+ * Copyright © 2016 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;
+
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.Statement;
+import com.datastax.driver.core.querybuilder.QueryBuilder;
+import com.datastax.driver.core.querybuilder.Select;
+import com.datastax.driver.core.utils.UUIDs;
+import com.datastax.driver.mapping.Mapper;
+import com.datastax.driver.mapping.Result;
+import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.server.dao.model.BaseEntity;
+import org.thingsboard.server.dao.model.wrapper.EntityResultSet;
+import org.thingsboard.server.dao.model.ModelConstants;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.lt;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
+
+@Slf4j
+public abstract class AbstractModelDao<T extends BaseEntity<?>> extends AbstractDao implements Dao<T> {
+
+    protected abstract Class<T> getColumnFamilyClass();
+
+    protected abstract String getColumnFamilyName();
+
+    protected Mapper<T> getMapper() {
+        return cluster.getMapper(getColumnFamilyClass());
+    }
+
+    protected List<T> findListByStatement(Statement statement) {
+        List<T> list = Collections.emptyList();
+        if (statement != null) {
+            statement.setConsistencyLevel(cluster.getDefaultReadConsistencyLevel());
+            ResultSet resultSet = getSession().execute(statement);
+            Result<T> result = getMapper().map(resultSet);
+            if (result != null) {
+                list = result.all();
+            }
+        }
+        return list;
+    }
+
+    protected T findOneByStatement(Statement statement) {
+        T object = null;
+        if (statement != null) {
+            statement.setConsistencyLevel(cluster.getDefaultReadConsistencyLevel());
+            ResultSet resultSet = getSession().execute(statement);
+            Result<T> result = getMapper().map(resultSet);
+            if (result != null) {
+                object = result.one();
+            }
+        }
+        return object;
+    }
+
+    protected Statement getSaveQuery(T dto) {
+        return getMapper().saveQuery(dto);
+    }
+
+    protected EntityResultSet<T> saveWithResult(T entity) {
+        log.debug("Save entity {}", entity);
+        if (entity.getId() == null) {
+            entity.setId(UUIDs.timeBased());
+        } else {
+            removeById(entity.getId());
+        }
+        Statement saveStatement = getSaveQuery(entity);
+        saveStatement.setConsistencyLevel(cluster.getDefaultWriteConsistencyLevel());
+        ResultSet resultSet = executeWrite(saveStatement);
+        return new EntityResultSet<>(resultSet, entity);
+    }
+
+    public T save(T entity) {
+        return saveWithResult(entity).getEntity();
+    }
+
+    public T findById(UUID key) {
+        log.debug("Get entity by key {}", key);
+        Select.Where query = select().from(getColumnFamilyName()).where(eq(ModelConstants.ID_PROPERTY, key));
+        log.trace("Execute query {}", query);
+        return findOneByStatement(query);
+    }
+
+    public ResultSet removeById(UUID key) {
+        Statement delete = QueryBuilder.delete().all().from(getColumnFamilyName()).where(eq(ModelConstants.ID_PROPERTY, key));
+        log.debug("Remove request: {}", delete.toString());
+        return getSession().execute(delete);
+    }
+
+
+    public List<T> find() {
+        log.debug("Get all entities from column family {}", getColumnFamilyName());
+        return findListByStatement(QueryBuilder.select().all().from(getColumnFamilyName()).setConsistencyLevel(cluster.getDefaultReadConsistencyLevel()));
+    }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/AbstractSearchTextDao.java b/dao/src/main/java/org/thingsboard/server/dao/AbstractSearchTextDao.java
new file mode 100644
index 0000000..d2a93e3
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/AbstractSearchTextDao.java
@@ -0,0 +1,78 @@
+/**
+ * Copyright © 2016 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;
+
+import com.datastax.driver.core.querybuilder.Clause;
+import com.datastax.driver.core.querybuilder.QueryBuilder;
+import com.datastax.driver.core.querybuilder.Select;
+import com.datastax.driver.core.querybuilder.Select.Where;
+import org.apache.commons.lang3.StringUtils;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.model.SearchTextEntity;
+import org.thingsboard.server.dao.model.ModelConstants;
+
+import java.util.List;
+
+import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.gt;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.gte;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.lt;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
+
+public abstract class AbstractSearchTextDao<T extends SearchTextEntity<?>> extends AbstractModelDao<T> {
+
+    public T save(T entity) {
+        entity.setSearchText(entity.getSearchTextSource().toLowerCase());
+        return super.save(entity);
+    }
+    
+    protected List<T> findPageWithTextSearch(String searchView, List<Clause> clauses, TextPageLink pageLink) {
+        Select select = select().from(searchView);
+        Where query = select.where();
+        for (Clause clause : clauses) {
+            query.and(clause);
+        }       
+        query.limit(pageLink.getLimit());
+        if (!StringUtils.isEmpty(pageLink.getTextOffset())) {
+            query.and(eq(ModelConstants.SEARCH_TEXT_PROPERTY, pageLink.getTextOffset()));
+            query.and(QueryBuilder.lt(ModelConstants.ID_PROPERTY, pageLink.getIdOffset()));
+            List<T> result = findListByStatement(query);
+            if (result.size() < pageLink.getLimit()) {
+                select = select().from(searchView);
+                query = select.where();
+                for (Clause clause : clauses) {
+                    query.and(clause);
+                }      
+                query.and(QueryBuilder.gt(ModelConstants.SEARCH_TEXT_PROPERTY, pageLink.getTextOffset()));
+                if (!StringUtils.isEmpty(pageLink.getTextSearch())) {
+                    query.and(QueryBuilder.lt(ModelConstants.SEARCH_TEXT_PROPERTY, pageLink.getTextSearchBound()));
+                }
+                int limit = pageLink.getLimit() - result.size();
+                query.limit(limit);
+                result.addAll(findListByStatement(query));
+            }
+            return result;
+        } else if (!StringUtils.isEmpty(pageLink.getTextSearch())) {
+            query.and(QueryBuilder.gte(ModelConstants.SEARCH_TEXT_PROPERTY, pageLink.getTextSearch()));
+            query.and(QueryBuilder.lt(ModelConstants.SEARCH_TEXT_PROPERTY, pageLink.getTextSearchBound()));
+            return findListByStatement(query);
+        } else {
+            return findListByStatement(query);
+        }
+    }
+
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/AbstractSearchTimeDao.java b/dao/src/main/java/org/thingsboard/server/dao/AbstractSearchTimeDao.java
new file mode 100644
index 0000000..dcdbbbd
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/AbstractSearchTimeDao.java
@@ -0,0 +1,90 @@
+/**
+ * Copyright © 2016 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;
+
+import com.datastax.driver.core.querybuilder.Clause;
+import com.datastax.driver.core.querybuilder.Ordering;
+import com.datastax.driver.core.querybuilder.QueryBuilder;
+import com.datastax.driver.core.querybuilder.Select;
+import com.datastax.driver.core.querybuilder.Select.Where;
+import com.datastax.driver.core.utils.UUIDs;
+import org.apache.commons.lang3.StringUtils;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.page.TimePageLink;
+import org.thingsboard.server.dao.model.BaseEntity;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.dao.model.SearchTextEntity;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
+
+public abstract class AbstractSearchTimeDao<T extends BaseEntity<?>> extends AbstractModelDao<T> {
+
+
+    protected List<T> findPageWithTimeSearch(String searchView, List<Clause> clauses, TimePageLink pageLink) {
+        return findPageWithTimeSearch(searchView, clauses, Collections.emptyList(), pageLink);
+    }
+
+    protected List<T> findPageWithTimeSearch(String searchView, List<Clause> clauses, Ordering ordering, TimePageLink pageLink) {
+        return findPageWithTimeSearch(searchView, clauses, Collections.singletonList(ordering), pageLink);
+    }
+
+
+    protected List<T> findPageWithTimeSearch(String searchView, List<Clause> clauses, List<Ordering> topLevelOrderings, TimePageLink pageLink) {
+        Select select = select().from(searchView);
+        Where query = select.where();
+        for (Clause clause : clauses) {
+            query.and(clause);
+        }
+        query.limit(pageLink.getLimit());
+        if (pageLink.isAscOrder()) {
+            if (pageLink.getIdOffset() != null) {
+                query.and(QueryBuilder.gt(ModelConstants.ID_PROPERTY, pageLink.getIdOffset()));
+            } else if (pageLink.getStartTime() != null) {
+                final UUID startOf = UUIDs.startOf(pageLink.getStartTime());
+                query.and(QueryBuilder.gte(ModelConstants.ID_PROPERTY, startOf));
+            }
+            if (pageLink.getEndTime() != null) {
+                final UUID endOf = UUIDs.endOf(pageLink.getEndTime());
+                query.and(QueryBuilder.lte(ModelConstants.ID_PROPERTY, endOf));
+            }
+        } else {
+            if (pageLink.getIdOffset() != null) {
+                query.and(QueryBuilder.lt(ModelConstants.ID_PROPERTY, pageLink.getIdOffset()));
+            } else if (pageLink.getEndTime() != null) {
+                final UUID endOf = UUIDs.endOf(pageLink.getEndTime());
+                query.and(QueryBuilder.lte(ModelConstants.ID_PROPERTY, endOf));
+            }
+            if (pageLink.getStartTime() != null) {
+                final UUID startOf = UUIDs.startOf(pageLink.getStartTime());
+                query.and(QueryBuilder.gte(ModelConstants.ID_PROPERTY, startOf));
+            }
+        }
+        List<Ordering> orderings = new ArrayList<>(topLevelOrderings);
+        if (pageLink.isAscOrder()) {
+            orderings.add(QueryBuilder.asc(ModelConstants.ID_PROPERTY));
+        } else {
+            orderings.add(QueryBuilder.desc(ModelConstants.ID_PROPERTY));
+        }
+        query.orderBy(orderings.toArray(new Ordering[orderings.size()]));
+        return findListByStatement(query);
+    }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraCluster.java b/dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraCluster.java
new file mode 100644
index 0000000..3af7748
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraCluster.java
@@ -0,0 +1,159 @@
+/**
+ * Copyright © 2016 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.cassandra;
+
+
+import com.datastax.driver.core.Cluster;
+import com.datastax.driver.core.ConsistencyLevel;
+import com.datastax.driver.core.ProtocolOptions.Compression;
+import com.datastax.driver.core.Session;
+import com.datastax.driver.core.exceptions.NoHostAvailableException;
+import com.datastax.driver.mapping.Mapper;
+import com.datastax.driver.mapping.MappingManager;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.dao.exception.DatabaseException;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.io.Closeable;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.StringTokenizer;
+
+@Component
+@Slf4j
+@Data
+public class CassandraCluster {
+
+    private static final String COMMA = ",";
+    private static final String COLON = ":";
+
+    @Value("${cassandra.cluster_name}")
+    private String clusterName;
+    @Value("${cassandra.keyspace_name}")
+    private String keyspaceName;
+    @Value("${cassandra.url}")
+    private String url;
+    @Value("${cassandra.compression}")
+    private String compression;
+    @Value("${cassandra.ssl}")
+    private Boolean ssl;
+    @Value("${cassandra.jmx}")
+    private Boolean jmx;
+    @Value("${cassandra.metrics}")
+    private Boolean metrics;
+    @Value("${cassandra.credentials}")
+    private Boolean credentials;
+    @Value("${cassandra.username}")
+    private String username;
+    @Value("${cassandra.password}")
+    private String password;
+    @Value("${cassandra.init_timeout_ms}")
+    private long initTimeout;
+    @Value("${cassandra.init_retry_interval_ms}")
+    private long initRetryInterval;
+
+    @Autowired
+    private CassandraSocketOptions socketOpts;
+
+    @Autowired
+    private CassandraQueryOptions queryOpts;
+
+    private Cluster cluster;
+    private Session session;
+    private MappingManager mappingManager;
+
+    public <T> Mapper<T> getMapper(Class<T> clazz) {
+        return mappingManager.mapper(clazz);
+    }
+
+    @PostConstruct
+    public void init() {
+        long endTime = System.currentTimeMillis() + initTimeout;
+        while (System.currentTimeMillis() < endTime) {
+            try {
+                Cluster.Builder builder = Cluster.builder()
+                        .addContactPointsWithPorts(getContactPoints(url))
+                        .withClusterName(clusterName)
+                        .withSocketOptions(socketOpts.getOpts());
+                builder.withQueryOptions(queryOpts.getOpts());
+                builder.withCompression(StringUtils.isEmpty(compression) ? Compression.NONE : Compression.valueOf(compression.toUpperCase()));
+                if (ssl) {
+                    builder.withSSL();
+                }
+                if (!jmx) {
+                    builder.withoutJMXReporting();
+                }
+                if (!metrics) {
+                    builder.withoutMetrics();
+                }
+                if (credentials) {
+                    builder.withCredentials(username, password);
+                }
+                cluster = builder.build();
+                cluster.init();
+                session = cluster.connect(keyspaceName);
+                mappingManager = new MappingManager(session);
+                break;
+            } catch (Exception e) {
+                log.warn("Failed to initialize cassandra cluster due to {}. Will retry in {} ms", e.getMessage(), initRetryInterval);
+                try {
+                    Thread.sleep(initRetryInterval);
+                } catch (InterruptedException ie) {
+                    log.warn("Failed to wait until retry", ie);
+                }
+            }
+        }
+    }
+
+    @PreDestroy
+    public void close() {
+        if (cluster != null) {
+            cluster.close();
+        }
+    }
+
+    private List<InetSocketAddress> getContactPoints(String url) {
+        List<InetSocketAddress> result;
+        if (StringUtils.isBlank(url)) {
+            result = Collections.emptyList();
+        } else {
+            result = new ArrayList<>();
+            for (String hostPort : url.split(COMMA)) {
+                String host = hostPort.split(COLON)[0];
+                Integer port = Integer.valueOf(hostPort.split(COLON)[1]);
+                result.add(new InetSocketAddress(host, port));
+            }
+        }
+        return result;
+    }
+
+    public ConsistencyLevel getDefaultReadConsistencyLevel() {
+        return queryOpts.getDefaultReadConsistencyLevel();
+    }
+
+    public ConsistencyLevel getDefaultWriteConsistencyLevel() {
+        return queryOpts.getDefaultWriteConsistencyLevel();
+    }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraQueryOptions.java b/dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraQueryOptions.java
new file mode 100644
index 0000000..291f2da
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraQueryOptions.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright © 2016 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.cassandra;
+
+import com.datastax.driver.core.ConsistencyLevel;
+import com.datastax.driver.core.QueryOptions;
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+import javax.annotation.PostConstruct;
+
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
+
+@Component
+@Configuration
+@Data
+public class CassandraQueryOptions {
+
+    @Value("${cassandra.query.default_fetch_size}")
+    private Integer defaultFetchSize;
+    @Value("${cassandra.query.read_consistency_level}")
+    private String readConsistencyLevel;
+    @Value("${cassandra.query.write_consistency_level}")
+    private String writeConsistencyLevel;
+
+    private QueryOptions opts;
+
+    private ConsistencyLevel defaultReadConsistencyLevel;
+    private ConsistencyLevel defaultWriteConsistencyLevel;
+
+    @PostConstruct
+    public void initOpts(){
+        opts = new QueryOptions();
+        opts.setFetchSize(defaultFetchSize);
+    }
+
+    protected ConsistencyLevel getDefaultReadConsistencyLevel() {
+        if (defaultReadConsistencyLevel == null) {
+            if (readConsistencyLevel != null) {
+                defaultReadConsistencyLevel = ConsistencyLevel.valueOf(readConsistencyLevel.toUpperCase());
+            } else {
+                defaultReadConsistencyLevel = ConsistencyLevel.ONE;
+            }
+        }
+        return defaultReadConsistencyLevel;
+    }
+
+    protected ConsistencyLevel getDefaultWriteConsistencyLevel() {
+        if (defaultWriteConsistencyLevel == null) {
+            if (writeConsistencyLevel != null) {
+                defaultWriteConsistencyLevel = ConsistencyLevel.valueOf(writeConsistencyLevel.toUpperCase());
+            } else {
+                defaultWriteConsistencyLevel = ConsistencyLevel.ONE;
+            }
+        }
+        return defaultWriteConsistencyLevel;
+    }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraSocketOptions.java b/dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraSocketOptions.java
new file mode 100644
index 0000000..b2cf918
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraSocketOptions.java
@@ -0,0 +1,75 @@
+/**
+ * Copyright © 2016 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.cassandra;
+
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Component;
+
+import com.datastax.driver.core.SocketOptions;
+
+import javax.annotation.PostConstruct;
+
+@Component
+@Configuration
+@Data
+public class CassandraSocketOptions {
+
+    @Value("${cassandra.socket.connect_timeout}")
+    private int connectTimeoutMillis;
+    @Value("${cassandra.socket.read_timeout}")
+    private int readTimeoutMillis;
+    @Value("${cassandra.socket.keep_alive}")
+    private Boolean keepAlive;
+    @Value("${cassandra.socket.reuse_address}")
+    private Boolean reuseAddress;
+    @Value("${cassandra.socket.so_linger}")
+    private Integer soLinger;
+    @Value("${cassandra.socket.tcp_no_delay}")
+    private Boolean tcpNoDelay;
+    @Value("${cassandra.socket.receive_buffer_size}")
+    private Integer receiveBufferSize;
+    @Value("${cassandra.socket.send_buffer_size}")
+    private Integer sendBufferSize;
+
+    private SocketOptions opts;
+
+    @PostConstruct
+    public void initOpts() {
+        opts = new SocketOptions();
+        opts.setConnectTimeoutMillis(connectTimeoutMillis);
+        opts.setReadTimeoutMillis(readTimeoutMillis);
+        if (keepAlive != null) {
+            opts.setKeepAlive(keepAlive);
+        }
+        if (reuseAddress != null) {
+            opts.setReuseAddress(reuseAddress);
+        }
+        if (soLinger != null) {
+            opts.setSoLinger(soLinger);
+        }
+        if (tcpNoDelay != null) {
+            opts.setTcpNoDelay(tcpNoDelay);
+        }
+        if (receiveBufferSize != null) {
+            opts.setReceiveBufferSize(receiveBufferSize);
+        }
+        if (sendBufferSize != null) {
+            opts.setSendBufferSize(sendBufferSize);
+        }
+    }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorDao.java b/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorDao.java
new file mode 100644
index 0000000..8e07bbf
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorDao.java
@@ -0,0 +1,169 @@
+/**
+ * Copyright © 2016 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.component;
+
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.Statement;
+import com.datastax.driver.core.querybuilder.QueryBuilder;
+import com.datastax.driver.core.querybuilder.Select;
+import com.datastax.driver.core.utils.UUIDs;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.id.ComponentDescriptorId;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
+import org.thingsboard.server.common.data.plugin.ComponentScope;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.dao.AbstractSearchTextDao;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.dao.model.ComponentDescriptorEntity;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
+
+/**
+ * @author Andrew Shvayka
+ */
+@Component
+@Slf4j
+public class BaseComponentDescriptorDao extends AbstractSearchTextDao<ComponentDescriptorEntity> implements ComponentDescriptorDao {
+
+    @Override
+    protected Class<ComponentDescriptorEntity> getColumnFamilyClass() {
+        return ComponentDescriptorEntity.class;
+    }
+
+    @Override
+    protected String getColumnFamilyName() {
+        return ModelConstants.COMPONENT_DESCRIPTOR_COLUMN_FAMILY_NAME;
+    }
+
+    @Override
+    public Optional<ComponentDescriptorEntity> save(ComponentDescriptor component) {
+        ComponentDescriptorEntity entity = new ComponentDescriptorEntity(component);
+        log.debug("Save component entity [{}]", entity);
+        Optional<ComponentDescriptorEntity> result = saveIfNotExist(entity);
+        if (log.isTraceEnabled()) {
+            log.trace("Saved result: [{}] for component entity [{}]", result.isPresent(), result.orElse(null));
+        } else {
+            log.debug("Saved result: [{}]", result.isPresent());
+        }
+        return result;
+    }
+
+    @Override
+    public ComponentDescriptorEntity findById(ComponentDescriptorId componentId) {
+        log.debug("Search component entity by id [{}]", componentId);
+        ComponentDescriptorEntity entity = super.findById(componentId.getId());
+        if (log.isTraceEnabled()) {
+            log.trace("Search result: [{}] for component entity [{}]", entity != null, entity);
+        } else {
+            log.debug("Search result: [{}]", entity != null);
+        }
+        return entity;
+    }
+
+    @Override
+    public ComponentDescriptorEntity findByClazz(String clazz) {
+        log.debug("Search component entity by clazz [{}]", clazz);
+        Select.Where query = select().from(getColumnFamilyName()).where(eq(ModelConstants.COMPONENT_DESCRIPTOR_CLASS_PROPERTY, clazz));
+        log.trace("Execute query [{}]", query);
+        ComponentDescriptorEntity entity = findOneByStatement(query);
+        if (log.isTraceEnabled()) {
+            log.trace("Search result: [{}] for component entity [{}]", entity != null, entity);
+        } else {
+            log.debug("Search result: [{}]", entity != null);
+        }
+        return entity;
+    }
+
+    @Override
+    public List<ComponentDescriptorEntity> findByTypeAndPageLink(ComponentType type, TextPageLink pageLink) {
+        log.debug("Try to find component by type [{}] and pageLink [{}]", type, pageLink);
+        List<ComponentDescriptorEntity> entities = findPageWithTextSearch(ModelConstants.COMPONENT_DESCRIPTOR_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
+                Arrays.asList(eq(ModelConstants.COMPONENT_DESCRIPTOR_TYPE_PROPERTY, type.name())), pageLink);
+        if (log.isTraceEnabled()) {
+            log.trace("Search result: [{}]", Arrays.toString(entities.toArray()));
+        } else {
+            log.debug("Search result: [{}]", entities.size());
+        }
+        return entities;
+    }
+
+    @Override
+    public List<ComponentDescriptorEntity> findByScopeAndTypeAndPageLink(ComponentScope scope, ComponentType type, TextPageLink pageLink) {
+        log.debug("Try to find component by scope [{}] and type [{}] and pageLink [{}]", scope, type, pageLink);
+        List<ComponentDescriptorEntity> entities = findPageWithTextSearch(ModelConstants.COMPONENT_DESCRIPTOR_BY_SCOPE_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
+                Arrays.asList(eq(ModelConstants.COMPONENT_DESCRIPTOR_TYPE_PROPERTY, type.name()),
+                        eq(ModelConstants.COMPONENT_DESCRIPTOR_SCOPE_PROPERTY, scope.name())), pageLink);
+        if (log.isTraceEnabled()) {
+            log.trace("Search result: [{}]", Arrays.toString(entities.toArray()));
+        } else {
+            log.debug("Search result: [{}]", entities.size());
+        }
+        return entities;
+    }
+
+    public ResultSet removeById(UUID key) {
+        Statement delete = QueryBuilder.delete().all().from(ModelConstants.COMPONENT_DESCRIPTOR_BY_ID).where(eq(ModelConstants.ID_PROPERTY, key));
+        log.debug("Remove request: {}", delete.toString());
+        return getSession().execute(delete);
+    }
+
+    @Override
+    public void deleteById(ComponentDescriptorId id) {
+        log.debug("Delete plugin meta-data entity by id [{}]", id);
+        ResultSet resultSet = removeById(id.getId());
+        log.debug("Delete result: [{}]", resultSet.wasApplied());
+    }
+
+    @Override
+    public void deleteByClazz(String clazz) {
+        log.debug("Delete plugin meta-data entity by id [{}]", clazz);
+        Statement delete = QueryBuilder.delete().all().from(getColumnFamilyName()).where(eq(ModelConstants.COMPONENT_DESCRIPTOR_CLASS_PROPERTY, clazz));
+        log.debug("Remove request: {}", delete.toString());
+        ResultSet resultSet = getSession().execute(delete);
+        log.debug("Delete result: [{}]", resultSet.wasApplied());
+    }
+
+    private Optional<ComponentDescriptorEntity> saveIfNotExist(ComponentDescriptorEntity entity) {
+        if (entity.getId() == null) {
+            entity.setId(UUIDs.timeBased());
+        }
+
+        ResultSet rs = executeRead(QueryBuilder.insertInto(getColumnFamilyName())
+                .value(ModelConstants.ID_PROPERTY, entity.getId())
+                .value(ModelConstants.COMPONENT_DESCRIPTOR_NAME_PROPERTY, entity.getName())
+                .value(ModelConstants.COMPONENT_DESCRIPTOR_CLASS_PROPERTY, entity.getClazz())
+                .value(ModelConstants.COMPONENT_DESCRIPTOR_TYPE_PROPERTY, entity.getType())
+                .value(ModelConstants.COMPONENT_DESCRIPTOR_SCOPE_PROPERTY, entity.getScope())
+                .value(ModelConstants.COMPONENT_DESCRIPTOR_CONFIGURATION_DESCRIPTOR_PROPERTY, entity.getConfigurationDescriptor())
+                .value(ModelConstants.COMPONENT_DESCRIPTOR_ACTIONS_PROPERTY, entity.getActions())
+                .value(ModelConstants.SEARCH_TEXT_PROPERTY, entity.getSearchText())
+                .ifNotExists()
+        );
+        if (rs.wasApplied()) {
+            return Optional.of(entity);
+        } else {
+            return Optional.empty();
+        }
+    }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java b/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java
new file mode 100644
index 0000000..23a53cf
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java
@@ -0,0 +1,133 @@
+/**
+ * Copyright © 2016 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.component;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.github.fge.jsonschema.core.exceptions.ProcessingException;
+import com.github.fge.jsonschema.core.report.ProcessingReport;
+import com.github.fge.jsonschema.main.JsonSchemaFactory;
+import com.github.fge.jsonschema.main.JsonValidator;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.common.data.id.ComponentDescriptorId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
+import org.thingsboard.server.common.data.plugin.ComponentScope;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.exception.IncorrectParameterException;
+import org.thingsboard.server.dao.model.ComponentDescriptorEntity;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.service.Validator;
+
+import java.util.List;
+import java.util.Optional;
+
+import static org.thingsboard.server.dao.DaoUtil.convertDataList;
+import static org.thingsboard.server.dao.DaoUtil.getData;
+
+/**
+ * @author Andrew Shvayka
+ */
+@Service
+@Slf4j
+public class BaseComponentDescriptorService implements ComponentDescriptorService {
+
+    @Autowired
+    private ComponentDescriptorDao componentDescriptorDao;
+
+    @Override
+    public ComponentDescriptor saveComponent(ComponentDescriptor component) {
+        componentValidator.validate(component);
+        Optional<ComponentDescriptorEntity> result = componentDescriptorDao.save(component);
+        if (result.isPresent()) {
+            return getData(result.get());
+        } else {
+            return getData(componentDescriptorDao.findByClazz(component.getClazz()));
+        }
+    }
+
+    @Override
+    public ComponentDescriptor findById(ComponentDescriptorId componentId) {
+        Validator.validateId(componentId, "Incorrect component id for search request.");
+        return getData(componentDescriptorDao.findById(componentId));
+    }
+
+    @Override
+    public ComponentDescriptor findByClazz(String clazz) {
+        Validator.validateString(clazz, "Incorrect clazz for search request.");
+        return getData(componentDescriptorDao.findByClazz(clazz));
+    }
+
+    @Override
+    public TextPageData<ComponentDescriptor> findByTypeAndPageLink(ComponentType type, TextPageLink pageLink) {
+        Validator.validatePageLink(pageLink, "Incorrect PageLink object for search plugin components request.");
+        List<ComponentDescriptorEntity> pluginEntities = componentDescriptorDao.findByTypeAndPageLink(type, pageLink);
+        List<ComponentDescriptor> components = convertDataList(pluginEntities);
+        return new TextPageData<>(components, pageLink);
+    }
+
+    @Override
+    public TextPageData<ComponentDescriptor> findByScopeAndTypeAndPageLink(ComponentScope scope, ComponentType type, TextPageLink pageLink) {
+        Validator.validatePageLink(pageLink, "Incorrect PageLink object for search plugin components request.");
+        List<ComponentDescriptorEntity> pluginEntities = componentDescriptorDao.findByScopeAndTypeAndPageLink(scope, type, pageLink);
+        List<ComponentDescriptor> components = convertDataList(pluginEntities);
+        return new TextPageData<>(components, pageLink);
+    }
+
+    @Override
+    public void deleteByClazz(String clazz) {
+        Validator.validateString(clazz, "Incorrect clazz for delete request.");
+        componentDescriptorDao.deleteByClazz(clazz);
+    }
+
+    @Override
+    public boolean validate(ComponentDescriptor component, JsonNode configuration) {
+        JsonValidator validator = JsonSchemaFactory.byDefault().getValidator();
+        try {
+            if (!component.getConfigurationDescriptor().has("schema")) {
+                throw new DataValidationException("Configuration descriptor doesn't contain schema property!");
+            }
+            JsonNode configurationSchema = component.getConfigurationDescriptor().get("schema");
+            ProcessingReport report = validator.validate(configurationSchema, configuration);
+            return report.isSuccess();
+        } catch (ProcessingException e) {
+            throw new IncorrectParameterException(e.getMessage(), e);
+        }
+    }
+
+    private DataValidator<ComponentDescriptor> componentValidator =
+            new DataValidator<ComponentDescriptor>() {
+                @Override
+                protected void validateDataImpl(ComponentDescriptor plugin) {
+                    if (plugin.getType() == null) {
+                        throw new DataValidationException("Component type should be specified!.");
+                    }
+                    if (plugin.getScope() == null) {
+                        throw new DataValidationException("Component scope should be specified!.");
+                    }
+                    if (StringUtils.isEmpty(plugin.getName())) {
+                        throw new DataValidationException("Component name should be specified!.");
+                    }
+                    if (StringUtils.isEmpty(plugin.getClazz())) {
+                        throw new DataValidationException("Component clazz should be specified!.");
+                    }
+                }
+            };
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/component/ComponentDescriptorDao.java b/dao/src/main/java/org/thingsboard/server/dao/component/ComponentDescriptorDao.java
new file mode 100644
index 0000000..c78103c
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/component/ComponentDescriptorDao.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright © 2016 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.component;
+
+import org.thingsboard.server.common.data.id.ComponentDescriptorId;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
+import org.thingsboard.server.common.data.plugin.ComponentScope;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.dao.Dao;
+import org.thingsboard.server.dao.model.ComponentDescriptorEntity;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author Andrew Shvayka
+ */
+public interface ComponentDescriptorDao extends Dao<ComponentDescriptorEntity> {
+
+    Optional<ComponentDescriptorEntity> save(ComponentDescriptor component);
+
+    ComponentDescriptorEntity findById(ComponentDescriptorId componentId);
+
+    ComponentDescriptorEntity findByClazz(String clazz);
+
+    List<ComponentDescriptorEntity> findByTypeAndPageLink(ComponentType type, TextPageLink pageLink);
+
+    List<ComponentDescriptorEntity> findByScopeAndTypeAndPageLink(ComponentScope scope, ComponentType type, TextPageLink pageLink);
+
+    void deleteById(ComponentDescriptorId componentId);
+
+    void deleteByClazz(String clazz);
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/component/ComponentDescriptorService.java b/dao/src/main/java/org/thingsboard/server/dao/component/ComponentDescriptorService.java
new file mode 100644
index 0000000..9ec81e7
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/component/ComponentDescriptorService.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright © 2016 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.component;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.thingsboard.server.common.data.id.ComponentDescriptorId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
+import org.thingsboard.server.common.data.plugin.ComponentScope;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+
+/**
+ * @author Andrew Shvayka
+ */
+public interface ComponentDescriptorService {
+
+    ComponentDescriptor saveComponent(ComponentDescriptor component);
+
+    ComponentDescriptor findById(ComponentDescriptorId componentId);
+
+    ComponentDescriptor findByClazz(String clazz);
+
+    TextPageData<ComponentDescriptor> findByTypeAndPageLink(ComponentType type, TextPageLink pageLink);
+
+    TextPageData<ComponentDescriptor> findByScopeAndTypeAndPageLink(ComponentScope scope, ComponentType type, TextPageLink pageLink);
+
+    boolean validate(ComponentDescriptor component, JsonNode configuration);
+
+    void deleteByClazz(String clazz);
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java
new file mode 100644
index 0000000..de950a2
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright © 2016 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.customer;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.Dao;
+import org.thingsboard.server.dao.model.CustomerEntity;
+
+/**
+ * The Interface CustomerDao.
+ */
+public interface CustomerDao extends Dao<CustomerEntity> {
+
+    /**
+     * Save or update customer object
+     *
+     * @param customer the customer object
+     * @return saved customer object
+     */
+    CustomerEntity save(Customer customer);
+    
+    /**
+     * Find customers by tenant id and page link.
+     *
+     * @param tenantId the tenant id
+     * @param pageLink the page link
+     * @return the list of customer objects
+     */
+    List<CustomerEntity> findCustomersByTenantId(UUID tenantId, TextPageLink pageLink);
+    
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDaoImpl.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDaoImpl.java
new file mode 100644
index 0000000..719be97
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDaoImpl.java
@@ -0,0 +1,63 @@
+/**
+ * Copyright © 2016 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.customer;
+
+import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.AbstractSearchTextDao;
+import org.thingsboard.server.dao.model.CustomerEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.thingsboard.server.dao.model.ModelConstants;
+@Component
+@Slf4j
+public class CustomerDaoImpl extends AbstractSearchTextDao<CustomerEntity> implements CustomerDao {
+
+    @Override
+    protected Class<CustomerEntity> getColumnFamilyClass() {
+        return CustomerEntity.class;
+    }
+
+    @Override
+    protected String getColumnFamilyName() {
+        return ModelConstants.CUSTOMER_COLUMN_FAMILY_NAME;
+    }
+    
+    @Override
+    public CustomerEntity save(Customer customer) {
+        log.debug("Save customer [{}] ", customer);
+        return save(new CustomerEntity(customer));
+    }
+
+    @Override
+    public List<CustomerEntity> findCustomersByTenantId(UUID tenantId, TextPageLink pageLink) {
+        log.debug("Try to find customers by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
+        List<CustomerEntity> customerEntities = findPageWithTextSearch(ModelConstants.CUSTOMER_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
+                Arrays.asList(eq(ModelConstants.CUSTOMER_TENANT_ID_PROPERTY, tenantId)),
+                pageLink); 
+        log.trace("Found customers [{}] by tenantId [{}] and pageLink [{}]", customerEntities, tenantId, pageLink);
+        return customerEntities;
+    }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerService.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerService.java
new file mode 100644
index 0000000..bf5ff6e
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerService.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright © 2016 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.customer;
+
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+
+public interface CustomerService {
+
+    public Customer findCustomerById(CustomerId customerId);
+    
+    public Customer saveCustomer(Customer customer);
+    
+    public void deleteCustomer(CustomerId customerId);
+    
+    public TextPageData<Customer> findCustomersByTenantId(TenantId tenantId, TextPageLink pageLink);
+    
+    public void deleteCustomersByTenantId(TenantId tenantId);
+    
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
new file mode 100644
index 0000000..ffb985c
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
@@ -0,0 +1,145 @@
+/**
+ * Copyright © 2016 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.customer;
+
+import static org.thingsboard.server.dao.DaoUtil.convertDataList;
+import static org.thingsboard.server.dao.DaoUtil.getData;
+
+import java.util.List;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.dashboard.DashboardService;
+import org.thingsboard.server.dao.device.DeviceService;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.exception.IncorrectParameterException;
+import org.thingsboard.server.dao.model.CustomerEntity;
+import org.thingsboard.server.dao.model.TenantEntity;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.service.PaginatedRemover;
+import org.thingsboard.server.dao.tenant.TenantDao;
+import org.thingsboard.server.dao.user.UserService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.dao.service.Validator;
+@Service
+@Slf4j
+public class CustomerServiceImpl implements CustomerService {
+
+    @Autowired
+    private CustomerDao customerDao;
+    
+    @Autowired
+    private UserService userService;
+    
+    @Autowired
+    private TenantDao tenantDao;
+    
+    @Autowired
+    private DeviceService deviceService;
+    
+    @Autowired
+    private DashboardService dashboardService;
+    
+    @Override
+    public Customer findCustomerById(CustomerId customerId) {
+        log.trace("Executing findCustomerById [{}]", customerId);
+        Validator.validateId(customerId, "Incorrect customerId " + customerId);
+        CustomerEntity customerEntity = customerDao.findById(customerId.getId());
+        return getData(customerEntity);
+    }
+
+    @Override
+    public Customer saveCustomer(Customer customer) {
+        log.trace("Executing saveCustomer [{}]", customer);
+        customerValidator.validate(customer);
+        CustomerEntity customerEntity = customerDao.save(customer);
+        return getData(customerEntity);
+    }
+
+    @Override
+    public void deleteCustomer(CustomerId customerId) {
+        log.trace("Executing deleteCustomer [{}]", customerId);
+        Validator.validateId(customerId, "Incorrect tenantId " + customerId);
+        Customer customer = findCustomerById(customerId);
+        if (customer == null) {
+            throw new IncorrectParameterException("Unable to delete non-existent customer.");
+        }
+        dashboardService.unassignCustomerDashboards(customer.getTenantId(), customerId);
+        deviceService.unassignCustomerDevices(customer.getTenantId(), customerId);
+        userService.deleteCustomerUsers(customer.getTenantId(), customerId);               
+        customerDao.removeById(customerId.getId());
+    }
+
+    @Override
+    public TextPageData<Customer> findCustomersByTenantId(TenantId tenantId, TextPageLink pageLink) {
+        log.trace("Executing findCustomersByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
+        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
+        Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
+        List<CustomerEntity> customerEntities = customerDao.findCustomersByTenantId(tenantId.getId(), pageLink);
+        List<Customer> customers = convertDataList(customerEntities);
+        return new TextPageData<Customer>(customers, pageLink);
+    }
+
+    @Override
+    public void deleteCustomersByTenantId(TenantId tenantId) {
+        log.trace("Executing deleteCustomersByTenantId, tenantId [{}]", tenantId);
+        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
+        customersByTenantRemover.removeEntitites(tenantId);
+    }
+    
+    private DataValidator<Customer> customerValidator =
+            new DataValidator<Customer>() {
+                @Override
+                protected void validateDataImpl(Customer customer) {
+                    if (StringUtils.isEmpty(customer.getTitle())) {
+                        throw new DataValidationException("Customer title should be specified!");
+                    }
+                    if (!StringUtils.isEmpty(customer.getEmail())) {
+                        validateEmail(customer.getEmail());
+                    }
+                    if (customer.getTenantId() == null) {
+                        throw new DataValidationException("Customer should be assigned to tenant!");
+                    } else {
+                        TenantEntity tenant = tenantDao.findById(customer.getTenantId().getId());
+                        if (tenant == null) {
+                            throw new DataValidationException("Customer is referencing to non-existent tenant!");
+                        }
+                    }
+                }
+    };
+
+    private PaginatedRemover<TenantId, CustomerEntity> customersByTenantRemover =
+            new PaginatedRemover<TenantId, CustomerEntity>() {
+        
+        @Override
+        protected List<CustomerEntity> findEntities(TenantId id, TextPageLink pageLink) {
+            return customerDao.findCustomersByTenantId(id.getId(), pageLink);
+        }
+
+        @Override
+        protected void removeEntity(CustomerEntity entity) {
+            deleteCustomer(new CustomerId(entity.getId()));
+        }
+    };
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/Dao.java b/dao/src/main/java/org/thingsboard/server/dao/Dao.java
new file mode 100644
index 0000000..c34472b
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/Dao.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright © 2016 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;
+
+import com.datastax.driver.core.ResultSet;
+
+import java.util.List;
+import java.util.UUID;
+
+public interface Dao<T> {
+
+    List<T> find();
+
+    T findById(UUID id);
+
+    T save(T t);
+
+    ResultSet removeById(UUID id);
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java
new file mode 100644
index 0000000..da1a42f
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java
@@ -0,0 +1,61 @@
+/**
+ * Copyright © 2016 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;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+import org.thingsboard.server.common.data.id.UUIDBased;
+import org.thingsboard.server.dao.model.ToData;
+
+public abstract class DaoUtil {
+
+    private DaoUtil() {
+    }
+
+    public static <T> List<T> convertDataList(Collection<? extends ToData<T>> toDataList) {
+        List<T> list = Collections.emptyList();
+        if (toDataList != null && !toDataList.isEmpty()) {
+            list = new ArrayList<>();
+            for (ToData<T> object : toDataList) {
+                list.add(object.toData());
+            }
+        }
+        return list;
+    }
+
+    public static <T> T getData(ToData<T> data) {
+        T object = null;
+        if (data != null) {
+            object = data.toData();
+        }
+        return object;
+    }
+
+    public static UUID getId(UUIDBased idBased) {
+        UUID id = null;
+        if (idBased != null) {
+            id = idBased.getId();
+        }
+        return id;
+    }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java
new file mode 100644
index 0000000..056e901
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright © 2016 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.dashboard;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.thingsboard.server.common.data.Dashboard;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.Dao;
+import org.thingsboard.server.dao.model.DashboardEntity;
+
+/**
+ * The Interface DashboardDao.
+ *
+ * @param <T> the generic type
+ */
+public interface DashboardDao extends Dao<DashboardEntity> {
+
+    /**
+     * Save or update dashboard object
+     *
+     * @param dashboard the dashboard object
+     * @return saved dashboard object
+     */
+    DashboardEntity save(Dashboard dashboard);
+
+    /**
+     * Find dashboards by tenantId and page link.
+     *
+     * @param tenantId the tenantId
+     * @param pageLink the page link
+     * @return the list of dashboard objects
+     */
+    List<DashboardEntity> findDashboardsByTenantId(UUID tenantId, TextPageLink pageLink);
+    
+    /**
+     * Find dashboards by tenantId, customerId and page link.
+     *
+     * @param tenantId the tenantId
+     * @param customerId the customerId
+     * @param pageLink the page link
+     * @return the list of dashboard objects
+     */
+    List<DashboardEntity> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink);
+    
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDaoImpl.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDaoImpl.java
new file mode 100644
index 0000000..54a32ea
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDaoImpl.java
@@ -0,0 +1,83 @@
+/**
+ * Copyright © 2016 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.dashboard;
+
+import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
+import static org.thingsboard.server.dao.model.ModelConstants.DASHBOARD_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME;
+import static org.thingsboard.server.dao.model.ModelConstants.DASHBOARD_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME;
+import static org.thingsboard.server.dao.model.ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME;
+import static org.thingsboard.server.dao.model.ModelConstants.DASHBOARD_CUSTOMER_ID_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.DASHBOARD_TENANT_ID_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.Dashboard;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.AbstractSearchTextDao;
+import org.thingsboard.server.dao.model.DashboardEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component
+@Slf4j
+public class DashboardDaoImpl extends AbstractSearchTextDao<DashboardEntity> implements DashboardDao {
+
+    @Override
+    protected Class<DashboardEntity> getColumnFamilyClass() {
+        return DashboardEntity.class;
+    }
+
+    @Override
+    protected String getColumnFamilyName() {
+        return DASHBOARD_COLUMN_FAMILY_NAME;
+    }
+
+    @Override
+    public DashboardEntity save(Dashboard dashboard) {
+        log.debug("Save dashboard [{}] ", dashboard);
+        return save(new DashboardEntity(dashboard));
+    }
+
+    @Override
+    public List<DashboardEntity> findDashboardsByTenantId(UUID tenantId, TextPageLink pageLink) {
+        log.debug("Try to find dashboards by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
+        List<DashboardEntity> dashboardEntities = findPageWithTextSearch(DASHBOARD_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, 
+                Arrays.asList(eq(DASHBOARD_TENANT_ID_PROPERTY, tenantId),
+                              eq(DASHBOARD_CUSTOMER_ID_PROPERTY, NULL_UUID)), 
+                pageLink); 
+        
+        log.trace("Found dashboards [{}] by tenantId [{}] and pageLink [{}]", dashboardEntities, tenantId, pageLink);
+        return dashboardEntities;
+    }
+
+    @Override
+    public List<DashboardEntity> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) {
+        log.debug("Try to find dashboards by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink);
+        List<DashboardEntity> dashboardEntities = findPageWithTextSearch(DASHBOARD_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, 
+                Arrays.asList(eq(DASHBOARD_CUSTOMER_ID_PROPERTY, customerId),
+                              eq(DASHBOARD_TENANT_ID_PROPERTY, tenantId)), 
+                pageLink); 
+        
+        log.trace("Found dashboards [{}] by tenantId [{}], customerId [{}] and pageLink [{}]", dashboardEntities, tenantId, customerId, pageLink);
+        return dashboardEntities;
+    }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java
new file mode 100644
index 0000000..773f39f
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright © 2016 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.dashboard;
+
+import org.thingsboard.server.common.data.Dashboard;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.DashboardId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+
+public interface DashboardService {
+    
+    public Dashboard findDashboardById(DashboardId dashboardId);
+    
+    public Dashboard saveDashboard(Dashboard dashboard);
+    
+    public Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId);
+
+    public Dashboard unassignDashboardFromCustomer(DashboardId dashboardId);
+
+    public void deleteDashboard(DashboardId dashboardId);
+    
+    public TextPageData<Dashboard> findDashboardsByTenantId(TenantId tenantId, TextPageLink pageLink);
+
+    public void deleteDashboardsByTenantId(TenantId tenantId);
+    
+    public TextPageData<Dashboard> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink);
+
+    public void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId);
+    
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
new file mode 100644
index 0000000..33d61dc
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
@@ -0,0 +1,195 @@
+/**
+ * Copyright © 2016 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.dashboard;
+
+import static org.thingsboard.server.dao.DaoUtil.convertDataList;
+import static org.thingsboard.server.dao.DaoUtil.getData;
+
+import java.util.List;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.thingsboard.server.common.data.Dashboard;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.DashboardId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.customer.CustomerDao;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.model.CustomerEntity;
+import org.thingsboard.server.dao.model.DashboardEntity;
+import org.thingsboard.server.dao.model.TenantEntity;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.service.PaginatedRemover;
+import org.thingsboard.server.dao.tenant.TenantDao;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.dao.service.Validator;
+
+@Service
+@Slf4j
+public class DashboardServiceImpl implements DashboardService {
+
+    @Autowired
+    private DashboardDao dashboardDao;
+    
+    @Autowired
+    private TenantDao tenantDao;
+    
+    @Autowired
+    private CustomerDao customerDao;
+    
+    @Override
+    public Dashboard findDashboardById(DashboardId dashboardId) {
+        log.trace("Executing findDashboardById [{}]", dashboardId);
+        Validator.validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
+        DashboardEntity dashboardEntity = dashboardDao.findById(dashboardId.getId());
+        return getData(dashboardEntity);
+    }
+
+    @Override
+    public Dashboard saveDashboard(Dashboard dashboard) {
+        log.trace("Executing saveDashboard [{}]", dashboard);
+        dashboardValidator.validate(dashboard);
+        DashboardEntity dashboardEntity = dashboardDao.save(dashboard);
+        return getData(dashboardEntity);
+    }
+    
+    @Override
+    public Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId) {
+        Dashboard dashboard = findDashboardById(dashboardId);
+        dashboard.setCustomerId(customerId);
+        return saveDashboard(dashboard);
+    }
+
+    @Override
+    public Dashboard unassignDashboardFromCustomer(DashboardId dashboardId) {
+        Dashboard dashboard = findDashboardById(dashboardId);
+        dashboard.setCustomerId(null);
+        return saveDashboard(dashboard);
+    }
+
+    @Override
+    public void deleteDashboard(DashboardId dashboardId) {
+        log.trace("Executing deleteDashboard [{}]", dashboardId);
+        Validator.validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
+        dashboardDao.removeById(dashboardId.getId());
+    }
+
+    @Override
+    public TextPageData<Dashboard> findDashboardsByTenantId(TenantId tenantId, TextPageLink pageLink) {
+        log.trace("Executing findDashboardsByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
+        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
+        Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
+        List<DashboardEntity> dashboardEntities = dashboardDao.findDashboardsByTenantId(tenantId.getId(), pageLink);
+        List<Dashboard> dashboards = convertDataList(dashboardEntities);
+        return new TextPageData<Dashboard>(dashboards, pageLink);
+    }
+
+    @Override
+    public void deleteDashboardsByTenantId(TenantId tenantId) {
+        log.trace("Executing deleteDashboardsByTenantId, tenantId [{}]", tenantId);
+        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
+        tenantDashboardsRemover.removeEntitites(tenantId);
+    }
+
+    @Override
+    public TextPageData<Dashboard> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink) {
+        log.trace("Executing findDashboardsByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink);
+        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
+        Validator.validateId(customerId, "Incorrect customerId " + customerId);
+        Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
+        List<DashboardEntity> dashboardEntities = dashboardDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink);
+        List<Dashboard> dashboards = convertDataList(dashboardEntities);
+        return new TextPageData<Dashboard>(dashboards, pageLink);
+    }
+
+    @Override
+    public void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId) {
+        log.trace("Executing unassignCustomerDashboards, tenantId [{}], customerId [{}]", tenantId, customerId);
+        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
+        Validator.validateId(customerId, "Incorrect customerId " + customerId);
+        new CustomerDashboardsUnassigner(tenantId).removeEntitites(customerId);
+    }
+    
+    private DataValidator<Dashboard> dashboardValidator =
+            new DataValidator<Dashboard>() {
+                @Override
+                protected void validateDataImpl(Dashboard dashboard) {
+                    if (StringUtils.isEmpty(dashboard.getTitle())) {
+                        throw new DataValidationException("Dashboard title should be specified!");
+                    }
+                    if (dashboard.getTenantId() == null) {
+                        throw new DataValidationException("Dashboard should be assigned to tenant!");
+                    } else {
+                        TenantEntity tenant = tenantDao.findById(dashboard.getTenantId().getId());
+                        if (tenant == null) {
+                            throw new DataValidationException("Dashboard is referencing to non-existent tenant!");
+                        }
+                    }
+                    if (dashboard.getCustomerId() == null) {
+                        dashboard.setCustomerId(new CustomerId(ModelConstants.NULL_UUID));
+                    } else if (!dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
+                        CustomerEntity customer = customerDao.findById(dashboard.getCustomerId().getId());
+                        if (customer == null) {
+                            throw new DataValidationException("Can't assign dashboard to non-existent customer!");
+                        }
+                        if (!customer.getTenantId().equals(dashboard.getTenantId().getId())) {
+                            throw new DataValidationException("Can't assign dashboard to customer from different tenant!");
+                        }
+                    }
+                }
+    };
+    
+    private PaginatedRemover<TenantId, DashboardEntity> tenantDashboardsRemover =
+            new PaginatedRemover<TenantId, DashboardEntity>() {
+        
+        @Override
+        protected List<DashboardEntity> findEntities(TenantId id, TextPageLink pageLink) {
+            return dashboardDao.findDashboardsByTenantId(id.getId(), pageLink);
+        }
+
+        @Override
+        protected void removeEntity(DashboardEntity entity) {
+            deleteDashboard(new DashboardId(entity.getId()));
+        }
+    };
+    
+    class CustomerDashboardsUnassigner extends PaginatedRemover<CustomerId, DashboardEntity> {
+        
+        private TenantId tenantId;
+        
+        CustomerDashboardsUnassigner(TenantId tenantId) {
+            this.tenantId = tenantId;
+        }
+
+        @Override
+        protected List<DashboardEntity> findEntities(CustomerId id, TextPageLink pageLink) {
+            return dashboardDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink);
+        }
+
+        @Override
+        protected void removeEntity(DashboardEntity entity) {
+            unassignDashboardFromCustomer(new DashboardId(entity.getId()));
+        }
+        
+    }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsDao.java
new file mode 100644
index 0000000..5390e95
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsDao.java
@@ -0,0 +1,55 @@
+/**
+ * Copyright © 2016 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 java.util.UUID;
+
+import org.thingsboard.server.common.data.security.DeviceCredentials;
+import org.thingsboard.server.dao.Dao;
+import org.thingsboard.server.dao.model.DeviceCredentialsEntity;
+
+/**
+ * The Interface DeviceCredentialsDao.
+ *
+ * @param <T> the generic type
+ */
+public interface DeviceCredentialsDao extends Dao<DeviceCredentialsEntity> {
+
+    /**
+     * Save or update device credentials object
+     *
+     * @param deviceCredentials the device credentials object
+     * @return saved device credentials object
+     */
+    DeviceCredentialsEntity save(DeviceCredentials deviceCredentials);
+
+    /**
+     * Find device credentials by device id.
+     *
+     * @param deviceId the device id
+     * @return the device credentials object
+     */
+    DeviceCredentialsEntity findByDeviceId(UUID deviceId);
+
+    /**
+     * Find device credentials by credentials id.
+     *
+     * @param credentialsId the credentials id
+     * @return the device credentials object
+     */
+    DeviceCredentialsEntity findByCredentialsId(String credentialsId);
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsDaoImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsDaoImpl.java
new file mode 100644
index 0000000..c38ea8c
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsDaoImpl.java
@@ -0,0 +1,77 @@
+/**
+ * Copyright © 2016 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 static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
+
+import java.util.UUID;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.security.DeviceCredentials;
+import org.thingsboard.server.dao.AbstractModelDao;
+import org.thingsboard.server.dao.model.DeviceCredentialsEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Repository;
+
+import com.datastax.driver.core.querybuilder.Select.Where;
+import org.thingsboard.server.dao.model.ModelConstants;
+
+@Component
+@Slf4j
+public class DeviceCredentialsDaoImpl extends AbstractModelDao<DeviceCredentialsEntity> implements DeviceCredentialsDao {
+
+    @Override
+    protected Class<DeviceCredentialsEntity> getColumnFamilyClass() {
+        return DeviceCredentialsEntity.class;
+    }
+
+    @Override
+    protected String getColumnFamilyName() {
+        return ModelConstants.DEVICE_CREDENTIALS_COLUMN_FAMILY_NAME;
+    }
+
+    @Override
+    public DeviceCredentialsEntity findByDeviceId(UUID deviceId) {
+        log.debug("Try to find device credentials by deviceId [{}] ", deviceId);
+        Where query = select().from(ModelConstants.DEVICE_CREDENTIALS_BY_DEVICE_COLUMN_FAMILY_NAME)
+                .where(eq(ModelConstants.DEVICE_CREDENTIALS_DEVICE_ID_PROPERTY, deviceId));
+        log.trace("Execute query {}", query);
+        DeviceCredentialsEntity deviceCredentialsEntity = findOneByStatement(query);
+        log.trace("Found device credentials [{}] by deviceId [{}]", deviceCredentialsEntity, deviceId);
+        return deviceCredentialsEntity;
+    }
+    
+    @Override
+    public DeviceCredentialsEntity findByCredentialsId(String credentialsId) {
+        log.debug("Try to find device credentials by credentialsId [{}] ", credentialsId);
+        Where query = select().from(ModelConstants.DEVICE_CREDENTIALS_BY_CREDENTIALS_ID_COLUMN_FAMILY_NAME)
+                .where(eq(ModelConstants.DEVICE_CREDENTIALS_CREDENTIALS_ID_PROPERTY, credentialsId));
+        log.trace("Execute query {}", query);
+        DeviceCredentialsEntity deviceCredentialsEntity = findOneByStatement(query);
+        log.trace("Found device credentials [{}] by credentialsId [{}]", deviceCredentialsEntity, credentialsId);
+        return deviceCredentialsEntity;
+    }
+
+    @Override
+    public DeviceCredentialsEntity save(DeviceCredentials deviceCredentials) {
+        log.debug("Save device credentials [{}] ", deviceCredentials);
+        return save(new DeviceCredentialsEntity(deviceCredentials));
+    }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java
new file mode 100644
index 0000000..43d2442
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsService.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright © 2016 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 org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.security.DeviceCredentials;
+
+import static org.thingsboard.server.common.data.CacheConstants.DEVICE_CREDENTIALS_CACHE;
+
+public interface DeviceCredentialsService {
+
+    DeviceCredentials findDeviceCredentialsByDeviceId(DeviceId deviceId);
+
+    @Cacheable(cacheNames = DEVICE_CREDENTIALS_CACHE, unless="#result == null")
+    DeviceCredentials findDeviceCredentialsByCredentialsId(String credentialsId);
+
+    @CacheEvict(cacheNames = DEVICE_CREDENTIALS_CACHE, keyGenerator="previousDeviceCredentialsId", beforeInvocation = true)
+    DeviceCredentials updateDeviceCredentials(DeviceCredentials deviceCredentials);
+
+    DeviceCredentials createDeviceCredentials(DeviceCredentials deviceCredentials);
+
+    @CacheEvict(cacheNames = DEVICE_CREDENTIALS_CACHE, key="#deviceCredentials.credentialsId")
+    void deleteDeviceCredentials(DeviceCredentials deviceCredentials);
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java
new file mode 100644
index 0000000..19ad2d1
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java
@@ -0,0 +1,134 @@
+/**
+ * Copyright © 2016 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.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.security.DeviceCredentials;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.model.DeviceCredentialsEntity;
+import org.thingsboard.server.dao.service.DataValidator;
+
+import java.util.Optional;
+
+import static org.thingsboard.server.dao.DaoUtil.getData;
+import static org.thingsboard.server.dao.service.Validator.validateId;
+import static org.thingsboard.server.dao.service.Validator.validateString;
+
+@Service
+@Slf4j
+public class DeviceCredentialsServiceImpl implements DeviceCredentialsService {
+
+    @Autowired
+    private DeviceCredentialsDao deviceCredentialsDao;
+
+    @Autowired
+    private DeviceService deviceService;
+
+    @Override
+    public DeviceCredentials findDeviceCredentialsByDeviceId(DeviceId deviceId) {
+        log.trace("Executing findDeviceCredentialsByDeviceId [{}]", deviceId);
+        validateId(deviceId, "Incorrect deviceId " + deviceId);
+        DeviceCredentialsEntity deviceCredentialsEntity = deviceCredentialsDao.findByDeviceId(deviceId.getId());
+        return getData(deviceCredentialsEntity);
+    }
+
+    @Override
+    public DeviceCredentials findDeviceCredentialsByCredentialsId(String credentialsId) {
+        log.trace("Executing findDeviceCredentialsByCredentialsId [{}]", credentialsId);
+        validateString(credentialsId, "Incorrect credentialsId " + credentialsId);
+        DeviceCredentialsEntity deviceCredentialsEntity = deviceCredentialsDao.findByCredentialsId(credentialsId);
+        return getData(deviceCredentialsEntity);
+    }
+
+    @Override
+    public DeviceCredentials updateDeviceCredentials(DeviceCredentials deviceCredentials) {
+        return saveOrUpdare(deviceCredentials);
+    }
+
+    @Override
+    public DeviceCredentials createDeviceCredentials(DeviceCredentials deviceCredentials) {
+        return saveOrUpdare(deviceCredentials);
+    }
+
+    private DeviceCredentials saveOrUpdare(DeviceCredentials deviceCredentials) {
+        log.trace("Executing updateDeviceCredentials [{}]", deviceCredentials);
+        credentialsValidator.validate(deviceCredentials);
+        return getData(deviceCredentialsDao.save(deviceCredentials));
+    }
+
+    @Override
+    public void deleteDeviceCredentials(DeviceCredentials deviceCredentials) {
+        log.trace("Executing deleteDeviceCredentials [{}]", deviceCredentials);
+        deviceCredentialsDao.removeById(deviceCredentials.getUuidId());
+    }
+
+    private DataValidator<DeviceCredentials> credentialsValidator =
+            new DataValidator<DeviceCredentials>() {
+
+                @Override
+                protected void validateCreate(DeviceCredentials deviceCredentials) {
+                    DeviceCredentialsEntity existingCredentialsEntity = deviceCredentialsDao.findByCredentialsId(deviceCredentials.getCredentialsId());
+                    if (existingCredentialsEntity != null) {
+                        throw new DataValidationException("Create of existent device credentials!");
+                    }
+                }
+
+                @Override
+                protected void validateUpdate(DeviceCredentials deviceCredentials) {
+                    DeviceCredentialsEntity existingCredentialsEntity = deviceCredentialsDao.findById(deviceCredentials.getUuidId());
+                    if (existingCredentialsEntity == null) {
+                        throw new DataValidationException("Unable to update non-existent device credentials!");
+                    }
+                    DeviceCredentialsEntity sameCredentialsIdEntity = deviceCredentialsDao.findByCredentialsId(deviceCredentials.getCredentialsId());
+                    if (sameCredentialsIdEntity != null && !sameCredentialsIdEntity.getId().equals(deviceCredentials.getUuidId())) {
+                        throw new DataValidationException("Specified credentials are already registered!");
+                    }
+                }
+
+                @Override
+                protected void validateDataImpl(DeviceCredentials deviceCredentials) {
+                    if (deviceCredentials.getDeviceId() == null) {
+                        throw new DataValidationException("Device credentials should be assigned to device!");
+                    }
+                    if (deviceCredentials.getCredentialsType() == null) {
+                        throw new DataValidationException("Device credentials type should be specified!");
+                    }
+                    if (StringUtils.isEmpty(deviceCredentials.getCredentialsId())) {
+                        throw new DataValidationException("Device credentials id should be specified!");
+                    }
+                    switch (deviceCredentials.getCredentialsType()) {
+                        case ACCESS_TOKEN:
+                            if (deviceCredentials.getCredentialsId().length() < 1 || deviceCredentials.getCredentialsId().length() > 20) {
+                                throw new DataValidationException("Incorrect access token length [" + deviceCredentials.getCredentialsId().length() + "]!");
+                            }
+                            break;
+                        default:
+                            break;
+                    }
+                    Device device = deviceService.findDeviceById(deviceCredentials.getDeviceId());
+                    if (device == null) {
+                        throw new DataValidationException("Can't assign device credentials to non-existent device!");
+                    }
+                }
+            };
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java
new file mode 100644
index 0000000..0f00340
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright © 2016 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 java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.Dao;
+import org.thingsboard.server.dao.model.DeviceEntity;
+
+/**
+ * The Interface DeviceDao.
+ *
+ */
+public interface DeviceDao extends Dao<DeviceEntity> {
+
+    /**
+     * Save or update device object
+     *
+     * @param device the device object
+     * @return saved device object
+     */
+    DeviceEntity save(Device device);
+
+    /**
+     * Find devices by tenantId and page link.
+     *
+     * @param tenantId the tenantId
+     * @param pageLink the page link
+     * @return the list of device objects
+     */
+    List<DeviceEntity> findDevicesByTenantId(UUID tenantId, TextPageLink pageLink);
+    
+    /**
+     * Find devices by tenantId, customerId and page link.
+     *
+     * @param tenantId the tenantId
+     * @param customerId the customerId
+     * @param pageLink the page link
+     * @return the list of device objects
+     */
+    List<DeviceEntity> findDevicesByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink);
+
+    /**
+     * Find devices by tenantId and device name.
+     *
+     * @param tenantId the tenantId
+     * @param name the device name
+     * @return the optional device object
+     */
+    Optional<DeviceEntity> findDevicesByTenantIdAndName(UUID tenantId, String name);
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDaoImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDaoImpl.java
new file mode 100644
index 0000000..719ad37
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDaoImpl.java
@@ -0,0 +1,85 @@
+/**
+ * Copyright © 2016 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 static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
+import static org.thingsboard.server.dao.model.ModelConstants.*;
+
+import java.util.*;
+
+import com.datastax.driver.core.querybuilder.Select;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.AbstractSearchTextDao;
+import org.thingsboard.server.dao.model.DeviceEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component
+@Slf4j
+public class DeviceDaoImpl extends AbstractSearchTextDao<DeviceEntity> implements DeviceDao {
+
+    @Override
+    protected Class<DeviceEntity> getColumnFamilyClass() {
+        return DeviceEntity.class;
+    }
+
+    @Override
+    protected String getColumnFamilyName() {
+        return DEVICE_COLUMN_FAMILY_NAME;
+    }
+
+    @Override
+    public DeviceEntity save(Device device) {
+        log.debug("Save device [{}] ", device);
+        return save(new DeviceEntity(device));
+    }
+
+    @Override
+    public List<DeviceEntity> findDevicesByTenantId(UUID tenantId, TextPageLink pageLink) {
+        log.debug("Try to find devices by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
+        List<DeviceEntity> deviceEntities = findPageWithTextSearch(DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
+                Collections.singletonList(eq(DEVICE_TENANT_ID_PROPERTY, tenantId)), pageLink);
+
+        log.trace("Found devices [{}] by tenantId [{}] and pageLink [{}]", deviceEntities, tenantId, pageLink);
+        return deviceEntities;
+    }
+
+    @Override
+    public List<DeviceEntity> findDevicesByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) {
+        log.debug("Try to find devices by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink);
+        List<DeviceEntity> deviceEntities = findPageWithTextSearch(DEVICE_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
+                Arrays.asList(eq(DEVICE_CUSTOMER_ID_PROPERTY, customerId),
+                        eq(DEVICE_TENANT_ID_PROPERTY, tenantId)),
+                pageLink);
+
+        log.trace("Found devices [{}] by tenantId [{}], customerId [{}] and pageLink [{}]", deviceEntities, tenantId, customerId, pageLink);
+        return deviceEntities;
+    }
+
+    @Override
+    public Optional<DeviceEntity> findDevicesByTenantIdAndName(UUID tenantId, String deviceName) {
+        Select select = select().from(DEVICE_BY_TENANT_AND_NAME_VIEW_NAME);
+        Select.Where query = select.where();
+        query.and(eq(DEVICE_TENANT_ID_PROPERTY, tenantId));
+        query.and(eq(DEVICE_NAME_PROPERTY, deviceName));
+        return Optional.ofNullable(findOneByStatement(query));
+    }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceService.java
new file mode 100644
index 0000000..eb3cb28
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceService.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright © 2016 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 org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+
+public interface DeviceService {
+    
+    Device findDeviceById(DeviceId deviceId);
+
+    Device saveDevice(Device device);
+
+    Device assignDeviceToCustomer(DeviceId deviceId, CustomerId customerId);
+
+    Device unassignDeviceFromCustomer(DeviceId deviceId);
+
+    void deleteDevice(DeviceId deviceId);
+
+    TextPageData<Device> findDevicesByTenantId(TenantId tenantId, TextPageLink pageLink);
+
+    void deleteDevicesByTenantId(TenantId tenantId);
+
+    TextPageData<Device> findDevicesByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink);
+
+    void unassignCustomerDevices(TenantId tenantId, CustomerId customerId);
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
new file mode 100644
index 0000000..d321bf4
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
@@ -0,0 +1,231 @@
+/**
+ * Copyright © 2016 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.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.security.DeviceCredentials;
+import org.thingsboard.server.common.data.security.DeviceCredentialsType;
+import org.thingsboard.server.dao.customer.CustomerDao;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.model.CustomerEntity;
+import org.thingsboard.server.dao.model.DeviceEntity;
+import org.thingsboard.server.dao.model.TenantEntity;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.service.PaginatedRemover;
+import org.thingsboard.server.dao.tenant.TenantDao;
+
+import java.util.List;
+
+import static org.thingsboard.server.dao.DaoUtil.convertDataList;
+import static org.thingsboard.server.dao.DaoUtil.getData;
+import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
+import static org.thingsboard.server.dao.service.Validator.validateId;
+import static org.thingsboard.server.dao.service.Validator.validatePageLink;
+
+@Service
+@Slf4j
+public class DeviceServiceImpl implements DeviceService {
+
+    @Autowired
+    private DeviceDao deviceDao;
+
+    @Autowired
+    private TenantDao tenantDao;
+
+    @Autowired
+    private CustomerDao customerDao;
+
+    @Autowired
+    private DeviceCredentialsService deviceCredentialsService;
+
+    @Override
+    public Device findDeviceById(DeviceId deviceId) {
+        log.trace("Executing findDeviceById [{}]", deviceId);
+        validateId(deviceId, "Incorrect deviceId " + deviceId);
+        DeviceEntity deviceEntity = deviceDao.findById(deviceId.getId());
+        return getData(deviceEntity);
+    }
+
+    @Override
+    public Device saveDevice(Device device) {
+        log.trace("Executing saveDevice [{}]", device);
+        deviceValidator.validate(device);
+        DeviceEntity deviceEntity = deviceDao.save(device);
+        if (device.getId() == null) {
+            DeviceCredentials deviceCredentials = new DeviceCredentials();
+            deviceCredentials.setDeviceId(new DeviceId(deviceEntity.getId()));
+            deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
+            deviceCredentials.setCredentialsId(RandomStringUtils.randomAlphanumeric(20));
+            deviceCredentialsService.createDeviceCredentials(deviceCredentials);
+        }
+        return getData(deviceEntity);
+    }
+
+    @Override
+    public Device assignDeviceToCustomer(DeviceId deviceId, CustomerId customerId) {
+        Device device = findDeviceById(deviceId);
+        device.setCustomerId(customerId);
+        return saveDevice(device);
+    }
+
+    @Override
+    public Device unassignDeviceFromCustomer(DeviceId deviceId) {
+        Device device = findDeviceById(deviceId);
+        device.setCustomerId(null);
+        return saveDevice(device);
+    }
+
+    @Override
+    public void deleteDevice(DeviceId deviceId) {
+        log.trace("Executing deleteDevice [{}]", deviceId);
+        validateId(deviceId, "Incorrect deviceId " + deviceId);
+        DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(deviceId);
+        if (deviceCredentials != null) {
+            deviceCredentialsService.deleteDeviceCredentials(deviceCredentials);
+        }
+        deviceDao.removeById(deviceId.getId());
+    }
+
+    @Override
+    public TextPageData<Device> findDevicesByTenantId(TenantId tenantId, TextPageLink pageLink) {
+        log.trace("Executing findDevicesByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
+        validateId(tenantId, "Incorrect tenantId " + tenantId);
+        validatePageLink(pageLink, "Incorrect page link " + pageLink);
+        List<DeviceEntity> deviceEntities = deviceDao.findDevicesByTenantId(tenantId.getId(), pageLink);
+        List<Device> devices = convertDataList(deviceEntities);
+        return new TextPageData<Device>(devices, pageLink);
+    }
+
+    @Override
+    public void deleteDevicesByTenantId(TenantId tenantId) {
+        log.trace("Executing deleteDevicesByTenantId, tenantId [{}]", tenantId);
+        validateId(tenantId, "Incorrect tenantId " + tenantId);
+        tenantDevicesRemover.removeEntitites(tenantId);
+    }
+
+    @Override
+    public TextPageData<Device> findDevicesByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink) {
+        log.trace("Executing findDevicesByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink);
+        validateId(tenantId, "Incorrect tenantId " + tenantId);
+        validateId(customerId, "Incorrect customerId " + customerId);
+        validatePageLink(pageLink, "Incorrect page link " + pageLink);
+        List<DeviceEntity> deviceEntities = deviceDao.findDevicesByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink);
+        List<Device> devices = convertDataList(deviceEntities);
+        return new TextPageData<Device>(devices, pageLink);
+    }
+
+    @Override
+    public void unassignCustomerDevices(TenantId tenantId, CustomerId customerId) {
+        log.trace("Executing unassignCustomerDevices, tenantId [{}], customerId [{}]", tenantId, customerId);
+        validateId(tenantId, "Incorrect tenantId " + tenantId);
+        validateId(customerId, "Incorrect customerId " + customerId);
+        new CustomerDevicesUnassigner(tenantId).removeEntitites(customerId);
+    }
+
+    private DataValidator<Device> deviceValidator =
+            new DataValidator<Device>() {
+
+                @Override
+                protected void validateCreate(Device device) {
+                    deviceDao.findDevicesByTenantIdAndName(device.getTenantId().getId(), device.getName()).ifPresent(
+                            d -> {
+                                throw new DataValidationException("Device with such name already exists!");
+                            }
+                    );
+                }
+
+                @Override
+                protected void validateUpdate(Device device) {
+                    deviceDao.findDevicesByTenantIdAndName(device.getTenantId().getId(), device.getName()).ifPresent(
+                            d -> {
+                                if (!d.getId().equals(device.getUuidId())) {
+                                    throw new DataValidationException("Device with such name already exists!");
+                                }
+                            }
+                    );
+                }
+
+                @Override
+                protected void validateDataImpl(Device device) {
+                    if (StringUtils.isEmpty(device.getName())) {
+                        throw new DataValidationException("Device name should be specified!");
+                    }
+                    if (device.getTenantId() == null) {
+                        throw new DataValidationException("Device should be assigned to tenant!");
+                    } else {
+                        TenantEntity tenant = tenantDao.findById(device.getTenantId().getId());
+                        if (tenant == null) {
+                            throw new DataValidationException("Device is referencing to non-existent tenant!");
+                        }
+                    }
+                    if (device.getCustomerId() == null) {
+                        device.setCustomerId(new CustomerId(NULL_UUID));
+                    } else if (!device.getCustomerId().getId().equals(NULL_UUID)) {
+                        CustomerEntity customer = customerDao.findById(device.getCustomerId().getId());
+                        if (customer == null) {
+                            throw new DataValidationException("Can't assign device to non-existent customer!");
+                        }
+                        if (!customer.getTenantId().equals(device.getTenantId().getId())) {
+                            throw new DataValidationException("Can't assign device to customer from different tenant!");
+                        }
+                    }
+                }
+            };
+
+    private PaginatedRemover<TenantId, DeviceEntity> tenantDevicesRemover =
+            new PaginatedRemover<TenantId, DeviceEntity>() {
+
+                @Override
+                protected List<DeviceEntity> findEntities(TenantId id, TextPageLink pageLink) {
+                    return deviceDao.findDevicesByTenantId(id.getId(), pageLink);
+                }
+
+                @Override
+                protected void removeEntity(DeviceEntity entity) {
+                    deleteDevice(new DeviceId(entity.getId()));
+                }
+            };
+
+    class CustomerDevicesUnassigner extends PaginatedRemover<CustomerId, DeviceEntity> {
+
+        private TenantId tenantId;
+
+        CustomerDevicesUnassigner(TenantId tenantId) {
+            this.tenantId = tenantId;
+        }
+
+        @Override
+        protected List<DeviceEntity> findEntities(CustomerId id, TextPageLink pageLink) {
+            return deviceDao.findDevicesByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink);
+        }
+
+        @Override
+        protected void removeEntity(DeviceEntity entity) {
+            unassignDeviceFromCustomer(new DeviceId(entity.getId()));
+        }
+
+    }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventDao.java
new file mode 100644
index 0000000..736b825
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventDao.java
@@ -0,0 +1,136 @@
+/**
+ * Copyright © 2016 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.event;
+
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.querybuilder.Insert;
+import com.datastax.driver.core.querybuilder.QueryBuilder;
+import com.datastax.driver.core.querybuilder.Select;
+import com.datastax.driver.core.utils.UUIDs;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.Event;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.page.TimePageLink;
+import org.thingsboard.server.dao.AbstractSearchTimeDao;
+import org.thingsboard.server.dao.model.EventEntity;
+import org.thingsboard.server.dao.model.ModelConstants;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
+import static org.thingsboard.server.dao.model.ModelConstants.EVENT_BY_ID_VIEW_NAME;
+import static org.thingsboard.server.dao.model.ModelConstants.EVENT_BY_TYPE_AND_ID_VIEW_NAME;
+import static org.thingsboard.server.dao.model.ModelConstants.EVENT_COLUMN_FAMILY_NAME;
+
+@Component
+@Slf4j
+public class BaseEventDao extends AbstractSearchTimeDao<EventEntity> implements EventDao {
+
+    @Override
+    protected Class<EventEntity> getColumnFamilyClass() {
+        return EventEntity.class;
+    }
+
+    @Override
+    protected String getColumnFamilyName() {
+        return EVENT_COLUMN_FAMILY_NAME;
+    }
+
+    @Override
+    public EventEntity save(Event event) {
+        log.debug("Save event [{}] ", event);
+        return save(new EventEntity(event), false).orElse(null);
+    }
+
+    @Override
+    public Optional<EventEntity> saveIfNotExists(Event event) {
+        return save(new EventEntity(event), true);
+    }
+
+    @Override
+    public EventEntity findEvent(UUID tenantId, EntityId entityId, String eventType, String eventUid) {
+        log.debug("Search event entity by [{}][{}][{}][{}]", tenantId, entityId, eventType, eventUid);
+        Select.Where query = select().from(getColumnFamilyName()).where(
+                eq(ModelConstants.EVENT_TENANT_ID_PROPERTY, tenantId))
+                .and(eq(ModelConstants.EVENT_ENTITY_TYPE_PROPERTY, entityId.getEntityType()))
+                .and(eq(ModelConstants.EVENT_ENTITY_ID_PROPERTY, entityId.getId()))
+                .and(eq(ModelConstants.EVENT_TYPE_PROPERTY, eventType))
+                .and(eq(ModelConstants.EVENT_UID_PROPERTY, eventUid));
+        log.trace("Execute query [{}]", query);
+        EventEntity entity = findOneByStatement(query);
+        if (log.isTraceEnabled()) {
+            log.trace("Search result: [{}] for event entity [{}]", entity != null, entity);
+        } else {
+            log.debug("Search result: [{}]", entity != null);
+        }
+        return entity;
+    }
+
+    @Override
+    public List<EventEntity> findEvents(UUID tenantId, EntityId entityId, TimePageLink pageLink) {
+        log.trace("Try to find events by tenant [{}], entity [{}]and pageLink [{}]", tenantId, entityId, pageLink);
+        List<EventEntity> entities = findPageWithTimeSearch(EVENT_BY_ID_VIEW_NAME,
+                Arrays.asList(eq(ModelConstants.EVENT_TENANT_ID_PROPERTY, tenantId),
+                        eq(ModelConstants.EVENT_ENTITY_TYPE_PROPERTY, entityId.getEntityType()),
+                        eq(ModelConstants.EVENT_ENTITY_ID_PROPERTY, entityId.getId())),
+                pageLink);
+        log.trace("Found events by tenant [{}], entity [{}] and pageLink [{}]", tenantId, entityId, pageLink);
+        return entities;
+    }
+
+    @Override
+    public List<EventEntity> findEvents(UUID tenantId, EntityId entityId, String eventType, TimePageLink pageLink) {
+        log.trace("Try to find events by tenant [{}], entity [{}], type [{}] and pageLink [{}]", tenantId, entityId, eventType, pageLink);
+        List<EventEntity> entities = findPageWithTimeSearch(EVENT_BY_TYPE_AND_ID_VIEW_NAME,
+                Arrays.asList(eq(ModelConstants.EVENT_TENANT_ID_PROPERTY, tenantId),
+                        eq(ModelConstants.EVENT_ENTITY_TYPE_PROPERTY, entityId.getEntityType()),
+                        eq(ModelConstants.EVENT_ENTITY_ID_PROPERTY, entityId.getId()),
+                        eq(ModelConstants.EVENT_TYPE_PROPERTY, eventType)),
+                pageLink.isAscOrder() ? QueryBuilder.asc(ModelConstants.EVENT_TYPE_PROPERTY) :
+                        QueryBuilder.desc(ModelConstants.EVENT_TYPE_PROPERTY),
+                pageLink);
+        log.trace("Found events by tenant [{}], entity [{}], type [{}] and pageLink [{}]", tenantId, entityId, eventType, pageLink);
+        return entities;
+    }
+
+    private Optional<EventEntity> save(EventEntity entity, boolean ifNotExists) {
+        if (entity.getId() == null) {
+            entity.setId(UUIDs.timeBased());
+        }
+        Insert insert = QueryBuilder.insertInto(getColumnFamilyName())
+                .value(ModelConstants.ID_PROPERTY, entity.getId())
+                .value(ModelConstants.EVENT_TENANT_ID_PROPERTY, entity.getTenantId())
+                .value(ModelConstants.EVENT_ENTITY_TYPE_PROPERTY, entity.getEntityType())
+                .value(ModelConstants.EVENT_ENTITY_ID_PROPERTY, entity.getEntityId())
+                .value(ModelConstants.EVENT_TYPE_PROPERTY, entity.getEventType())
+                .value(ModelConstants.EVENT_UID_PROPERTY, entity.getEventUId())
+                .value(ModelConstants.EVENT_BODY_PROPERTY, entity.getBody());
+        if (ifNotExists) {
+            insert = insert.ifNotExists();
+        }
+        ResultSet rs = executeWrite(insert);
+        if (rs.wasApplied()) {
+            return Optional.of(entity);
+        } else {
+            return Optional.empty();
+        }
+    }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java
new file mode 100644
index 0000000..9e86a94
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java
@@ -0,0 +1,130 @@
+/**
+ * Copyright © 2016 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.event;
+
+import com.datastax.driver.core.utils.UUIDs;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.common.data.Event;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.EventId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TimePageData;
+import org.thingsboard.server.common.data.page.TimePageLink;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.model.EventEntity;
+import org.thingsboard.server.dao.service.DataValidator;
+
+import java.util.List;
+import java.util.Optional;
+
+import static org.thingsboard.server.dao.DaoUtil.convertDataList;
+import static org.thingsboard.server.dao.DaoUtil.getData;
+import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
+
+@Service
+@Slf4j
+public class BaseEventService implements EventService {
+
+    private final TenantId systemTenantId = new TenantId(NULL_UUID);
+
+    @Autowired
+    public EventDao eventDao;
+
+    @Override
+    public Event save(Event event) {
+        eventValidator.validate(event);
+        if (event.getTenantId() == null) {
+            log.trace("Save system event with predefined id {}", systemTenantId);
+            event.setTenantId(systemTenantId);
+        }
+        if (event.getId() == null) {
+            event.setId(new EventId(UUIDs.timeBased()));
+        }
+        if (StringUtils.isEmpty(event.getUid())) {
+            event.setUid(event.getId().toString());
+        }
+        return getData(eventDao.save(event));
+    }
+
+    @Override
+    public Optional<Event> saveIfNotExists(Event event) {
+        eventValidator.validate(event);
+        if (StringUtils.isEmpty(event.getUid())) {
+            throw new DataValidationException("Event uid should be specified!.");
+        }
+        if (event.getTenantId() == null) {
+            log.trace("Save system event with predefined id {}", systemTenantId);
+            event.setTenantId(systemTenantId);
+        }
+        if (event.getId() == null) {
+            event.setId(new EventId(UUIDs.timeBased()));
+        }
+        Optional<EventEntity> result = eventDao.saveIfNotExists(event);
+        return result.isPresent() ? Optional.of(getData(result.get())) : Optional.empty();
+    }
+
+    @Override
+    public Optional<Event> findEvent(TenantId tenantId, EntityId entityId, String eventType, String eventUid) {
+        if (tenantId == null) {
+            throw new DataValidationException("Tenant id should be specified!.");
+        }
+        if (entityId == null) {
+            throw new DataValidationException("Entity id should be specified!.");
+        }
+        if (StringUtils.isEmpty(eventType)) {
+            throw new DataValidationException("Event type should be specified!.");
+        }
+        if (StringUtils.isEmpty(eventUid)) {
+            throw new DataValidationException("Event uid should be specified!.");
+        }
+        EventEntity entity = eventDao.findEvent(tenantId.getId(), entityId, eventType, eventUid);
+        return entity != null ? Optional.of(getData(entity)) : Optional.empty();
+    }
+
+    @Override
+    public TimePageData<Event> findEvents(TenantId tenantId, EntityId entityId, TimePageLink pageLink) {
+        List<EventEntity> entities = eventDao.findEvents(tenantId.getId(), entityId, pageLink);
+        List<Event> events = convertDataList(entities);
+        return new TimePageData<Event>(events, pageLink);
+    }
+
+
+    @Override
+    public TimePageData<Event> findEvents(TenantId tenantId, EntityId entityId, String eventType, TimePageLink pageLink) {
+        List<EventEntity> entities = eventDao.findEvents(tenantId.getId(), entityId, eventType, pageLink);
+        List<Event> events = convertDataList(entities);
+        return new TimePageData<Event>(events, pageLink);
+    }
+
+    private DataValidator<Event> eventValidator =
+            new DataValidator<Event>() {
+                @Override
+                protected void validateDataImpl(Event event) {
+                    if (event.getEntityId() == null) {
+                        throw new DataValidationException("Entity id should be specified!.");
+                    }
+                    if (StringUtils.isEmpty(event.getType())) {
+                        throw new DataValidationException("Event type should be specified!.");
+                    }
+                    if (event.getBody() == null) {
+                        throw new DataValidationException("Event body should be specified!.");
+                    }
+                }
+            };
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java b/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java
new file mode 100644
index 0000000..4d7b1f6
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java
@@ -0,0 +1,82 @@
+/**
+ * Copyright © 2016 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.event;
+
+import org.thingsboard.server.common.data.Event;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.page.TimePageLink;
+import org.thingsboard.server.dao.Dao;
+import org.thingsboard.server.dao.model.EventEntity;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+/**
+ * The Interface DeviceDao.
+ *
+ * @param <T> the generic type
+ */
+public interface EventDao extends Dao<EventEntity> {
+
+    /**
+     * Save or update event object
+     *
+     * @param event the event object
+     * @return saved event object
+     */
+    EventEntity save(Event event);
+
+    /**
+     * Save event object if it is not yet saved
+     *
+     * @param event the event object
+     * @return saved event object
+     */
+    Optional<EventEntity> saveIfNotExists(Event event);
+
+    /**
+     * Find event by tenantId, entityId and eventUid.
+     *
+     * @param tenantId the tenantId
+     * @param entityId the entityId
+     * @param eventType the eventType
+     * @param eventUid the eventUid
+     * @return the event
+     */
+    EventEntity findEvent(UUID tenantId, EntityId entityId, String eventType, String eventUid);
+
+    /**
+     * Find events by tenantId, entityId and pageLink.
+     *
+     * @param tenantId the tenantId
+     * @param entityId the entityId
+     * @param pageLink the pageLink
+     * @return the event list
+     */
+    List<EventEntity> findEvents(UUID tenantId, EntityId entityId, TimePageLink pageLink);
+
+    /**
+     * Find events by tenantId, entityId, eventType and pageLink.
+     *
+     * @param tenantId the tenantId
+     * @param entityId the entityId
+     * @param eventType the eventType
+     * @param pageLink the pageLink
+     * @return the event list
+     */
+    List<EventEntity> findEvents(UUID tenantId, EntityId entityId, String eventType, TimePageLink pageLink);
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/event/EventService.java b/dao/src/main/java/org/thingsboard/server/dao/event/EventService.java
new file mode 100644
index 0000000..386f894
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/event/EventService.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright © 2016 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.event;
+
+import org.thingsboard.server.common.data.Event;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TimePageData;
+import org.thingsboard.server.common.data.page.TimePageLink;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface EventService {
+
+    Event save(Event event);
+
+    Optional<Event> saveIfNotExists(Event event);
+
+    Optional<Event> findEvent(TenantId tenantId, EntityId entityId, String eventType, String eventUid);
+
+    TimePageData<Event> findEvents(TenantId tenantId, EntityId entityId, TimePageLink pageLink);
+
+    TimePageData<Event> findEvents(TenantId tenantId, EntityId entityId, String eventType, TimePageLink pageLink);
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/exception/DatabaseException.java b/dao/src/main/java/org/thingsboard/server/dao/exception/DatabaseException.java
new file mode 100644
index 0000000..9cd6b04
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/exception/DatabaseException.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright © 2016 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.exception;
+
+public class DatabaseException extends RuntimeException {
+
+	private static final long serialVersionUID = 3463762014441887103L;
+
+	public DatabaseException() {
+		super();
+	}
+
+	public DatabaseException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+	public DatabaseException(String message) {
+		super(message);
+	}
+
+	public DatabaseException(Throwable cause) {
+		super(cause);
+	}
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/exception/DataValidationException.java b/dao/src/main/java/org/thingsboard/server/dao/exception/DataValidationException.java
new file mode 100644
index 0000000..4a69ec7
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/exception/DataValidationException.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright © 2016 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.exception;
+
+public class DataValidationException extends RuntimeException {
+
+    private static final long serialVersionUID = 7659985660312721830L;
+
+    public DataValidationException(String message) {
+        super(message);
+    }
+
+    public DataValidationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/exception/IncorrectParameterException.java b/dao/src/main/java/org/thingsboard/server/dao/exception/IncorrectParameterException.java
new file mode 100644
index 0000000..7ee8fb1
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/exception/IncorrectParameterException.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright © 2016 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.exception;
+
+
+public class IncorrectParameterException extends RuntimeException {
+
+	private static final long serialVersionUID = 601995650578985289L;
+
+	public IncorrectParameterException(String message) {
+        super(message);
+    }
+
+    public IncorrectParameterException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/AdminSettingsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/AdminSettingsEntity.java
new file mode 100644
index 0000000..3e72fcb
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/AdminSettingsEntity.java
@@ -0,0 +1,147 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model;
+
+import static org.thingsboard.server.dao.model.ModelConstants.ADMIN_SETTINGS_COLUMN_FAMILY_NAME;
+import static org.thingsboard.server.dao.model.ModelConstants.ADMIN_SETTINGS_JSON_VALUE_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.ADMIN_SETTINGS_KEY_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY;
+
+import java.util.UUID;
+
+import org.thingsboard.server.common.data.AdminSettings;
+import org.thingsboard.server.common.data.id.AdminSettingsId;
+import org.thingsboard.server.dao.model.type.JsonCodec;
+
+import com.datastax.driver.core.utils.UUIDs;
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+import com.datastax.driver.mapping.annotations.Transient;
+import com.fasterxml.jackson.databind.JsonNode;
+
+@Table(name = ADMIN_SETTINGS_COLUMN_FAMILY_NAME)
+public final class AdminSettingsEntity implements BaseEntity<AdminSettings> {
+
+    @Transient
+    private static final long serialVersionUID = 899117723388310403L;
+    
+    @PartitionKey(value = 0)
+    @Column(name = ID_PROPERTY)
+    private UUID id;
+    
+    @Column(name = ADMIN_SETTINGS_KEY_PROPERTY)
+    private String key;
+
+    @Column(name = ADMIN_SETTINGS_JSON_VALUE_PROPERTY, codec = JsonCodec.class)
+    private JsonNode jsonValue;
+
+    public AdminSettingsEntity() {
+        super();
+    }
+
+    public AdminSettingsEntity(AdminSettings adminSettings) {
+        if (adminSettings.getId() != null) {
+            this.id = adminSettings.getId().getId();
+        }
+        this.key = adminSettings.getKey();
+        this.jsonValue = adminSettings.getJsonValue();
+    }
+    
+    public UUID getId() {
+        return id;
+    }
+
+    public void setId(UUID id) {
+        this.id = id;
+    }
+    
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public JsonNode getJsonValue() {
+        return jsonValue;
+    }
+
+    public void setJsonValue(JsonNode jsonValue) {
+        this.jsonValue = jsonValue;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((id == null) ? 0 : id.hashCode());
+        result = prime * result + ((jsonValue == null) ? 0 : jsonValue.hashCode());
+        result = prime * result + ((key == null) ? 0 : key.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        AdminSettingsEntity other = (AdminSettingsEntity) obj;
+        if (id == null) {
+            if (other.id != null)
+                return false;
+        } else if (!id.equals(other.id))
+            return false;
+        if (jsonValue == null) {
+            if (other.jsonValue != null)
+                return false;
+        } else if (!jsonValue.equals(other.jsonValue))
+            return false;
+        if (key == null) {
+            if (other.key != null)
+                return false;
+        } else if (!key.equals(other.key))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("AdminSettingsEntity [id=");
+        builder.append(id);
+        builder.append(", key=");
+        builder.append(key);
+        builder.append(", jsonValue=");
+        builder.append(jsonValue);
+        builder.append("]");
+        return builder.toString();
+    }
+
+    @Override
+    public AdminSettings toData() {
+        AdminSettings adminSettings = new AdminSettings(new AdminSettingsId(id));
+        adminSettings.setCreatedTime(UUIDs.unixTimestamp(id));
+        adminSettings.setKey(key);
+        adminSettings.setJsonValue(jsonValue);
+        return adminSettings;
+    }
+
+}
\ No newline at end of file
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/BaseEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/BaseEntity.java
new file mode 100644
index 0000000..02828ec
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/BaseEntity.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model;
+
+import java.io.Serializable;
+import java.util.UUID;
+
+public interface BaseEntity<D> extends ToData<D>, Serializable {
+
+    UUID getId();
+
+    void setId(UUID id);
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ComponentDescriptorEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/ComponentDescriptorEntity.java
new file mode 100644
index 0000000..99851de
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/ComponentDescriptorEntity.java
@@ -0,0 +1,162 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model;
+
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.thingsboard.server.common.data.id.ComponentDescriptorId;
+import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
+import org.thingsboard.server.common.data.plugin.ComponentScope;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.dao.model.type.JsonCodec;
+
+import java.util.UUID;
+
+/**
+ * @author Andrew Shvayka
+ */
+@Table(name = ModelConstants.COMPONENT_DESCRIPTOR_COLUMN_FAMILY_NAME)
+public class ComponentDescriptorEntity implements SearchTextEntity<ComponentDescriptor> {
+
+    private static final long serialVersionUID = 1L;
+
+    @PartitionKey
+    @Column(name = ModelConstants.ID_PROPERTY)
+    private UUID id;
+
+    @Column(name = ModelConstants.COMPONENT_DESCRIPTOR_TYPE_PROPERTY)
+    private ComponentType type;
+
+    @Column(name = ModelConstants.COMPONENT_DESCRIPTOR_SCOPE_PROPERTY)
+    private ComponentScope scope;
+
+    @Column(name = ModelConstants.COMPONENT_DESCRIPTOR_NAME_PROPERTY)
+    private String name;
+
+    @Column(name = ModelConstants.COMPONENT_DESCRIPTOR_CLASS_PROPERTY)
+    private String clazz;
+
+    @Column(name = ModelConstants.COMPONENT_DESCRIPTOR_CONFIGURATION_DESCRIPTOR_PROPERTY, codec = JsonCodec.class)
+    private JsonNode configurationDescriptor;
+
+    @Column(name = ModelConstants.COMPONENT_DESCRIPTOR_ACTIONS_PROPERTY)
+    private String actions;
+
+    @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
+    private String searchText;
+
+    public ComponentDescriptorEntity() {
+    }
+
+    public ComponentDescriptorEntity(ComponentDescriptor component) {
+        if (component.getId() != null) {
+            this.id = component.getId().getId();
+        }
+        this.actions = component.getActions();
+        this.type = component.getType();
+        this.scope = component.getScope();
+        this.name = component.getName();
+        this.clazz = component.getClazz();
+        this.configurationDescriptor = component.getConfigurationDescriptor();
+        this.searchText = component.getName();
+    }
+
+    @Override
+    public ComponentDescriptor toData() {
+        ComponentDescriptor data = new ComponentDescriptor(new ComponentDescriptorId(id));
+        data.setType(type);
+        data.setScope(scope);
+        data.setName(this.getName());
+        data.setClazz(this.getClazz());
+        data.setActions(this.getActions());
+        data.setConfigurationDescriptor(this.getConfigurationDescriptor());
+        return data;
+    }
+
+    @Override
+    public UUID getId() {
+        return id;
+    }
+
+    @Override
+    public void setId(UUID id) {
+        this.id = id;
+    }
+
+    public String getActions() {
+        return actions;
+    }
+
+    public void setActions(String actions) {
+        this.actions = actions;
+    }
+
+    public ComponentType getType() {
+        return type;
+    }
+
+    public void setType(ComponentType type) {
+        this.type = type;
+    }
+
+    public ComponentScope getScope() {
+        return scope;
+    }
+
+    public void setScope(ComponentScope scope) {
+        this.scope = scope;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getClazz() {
+        return clazz;
+    }
+
+    public void setClazz(String clazz) {
+        this.clazz = clazz;
+    }
+
+    public JsonNode getConfigurationDescriptor() {
+        return configurationDescriptor;
+    }
+
+    public void setConfigurationDescriptor(JsonNode configurationDescriptor) {
+        this.configurationDescriptor = configurationDescriptor;
+    }
+
+    public String getSearchText() {
+        return searchText;
+    }
+
+    @Override
+    public void setSearchText(String searchText) {
+        this.searchText = searchText;
+    }
+
+    @Override
+    public String getSearchTextSource() {
+        return searchText;
+    }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/CustomerEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/CustomerEntity.java
new file mode 100644
index 0000000..5c26189
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/CustomerEntity.java
@@ -0,0 +1,365 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model;
+
+import static org.thingsboard.server.dao.model.ModelConstants.ADDRESS2_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.ADDRESS_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.CITY_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.COUNTRY_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_ADDITIONAL_INFO_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_COLUMN_FAMILY_NAME;
+import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_TENANT_ID_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_TITLE_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.EMAIL_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.PHONE_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.STATE_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.ZIP_PROPERTY;
+
+import java.util.UUID;
+
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.dao.model.type.JsonCodec;
+
+import com.datastax.driver.core.utils.UUIDs;
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+import com.datastax.driver.mapping.annotations.Transient;
+import com.fasterxml.jackson.databind.JsonNode;
+
+@Table(name = CUSTOMER_COLUMN_FAMILY_NAME)
+public final class CustomerEntity implements SearchTextEntity<Customer> {
+
+    @Transient
+    private static final long serialVersionUID = -7732527103760948490L;
+    
+    @PartitionKey(value = 0)
+    @Column(name = ID_PROPERTY)
+    private UUID id;
+    
+    @PartitionKey(value = 1)
+    @Column(name = CUSTOMER_TENANT_ID_PROPERTY)
+    private UUID tenantId;
+    
+    @Column(name = CUSTOMER_TITLE_PROPERTY)
+    private String title;
+    
+    @Column(name = SEARCH_TEXT_PROPERTY)
+    private String searchText;
+    
+    @Column(name = COUNTRY_PROPERTY)
+    private String country;
+    
+    @Column(name = STATE_PROPERTY)
+    private String state;
+
+    @Column(name = CITY_PROPERTY)
+    private String city;
+
+    @Column(name = ADDRESS_PROPERTY)
+    private String address;
+
+    @Column(name = ADDRESS2_PROPERTY)
+    private String address2;
+
+    @Column(name = ZIP_PROPERTY)
+    private String zip;
+
+    @Column(name = PHONE_PROPERTY)
+    private String phone;
+
+    @Column(name = EMAIL_PROPERTY)
+    private String email;
+
+    @Column(name = CUSTOMER_ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class)
+    private JsonNode additionalInfo;
+
+    public CustomerEntity() {
+        super();
+    }
+
+    public CustomerEntity(Customer customer) {
+        if (customer.getId() != null) {
+            this.id = customer.getId().getId();
+        }
+        this.tenantId = customer.getTenantId().getId();
+        this.title = customer.getTitle();
+        this.country = customer.getCountry();
+        this.state = customer.getState();
+        this.city = customer.getCity();
+        this.address = customer.getAddress();
+        this.address2 = customer.getAddress2();
+        this.zip = customer.getZip();
+        this.phone = customer.getPhone();
+        this.email = customer.getEmail();
+        this.additionalInfo = customer.getAdditionalInfo();
+    }
+    
+    public UUID getId() {
+        return id;
+    }
+
+    public void setId(UUID id) {
+        this.id = id;
+    }
+
+    public UUID getTenantId() {
+        return tenantId;
+    }
+
+    public void setTenantId(UUID tenantId) {
+        this.tenantId = tenantId;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getCountry() {
+        return country;
+    }
+
+    public void setCountry(String country) {
+        this.country = country;
+    }
+
+    public String getState() {
+        return state;
+    }
+
+    public void setState(String state) {
+        this.state = state;
+    }
+
+    public String getCity() {
+        return city;
+    }
+
+    public void setCity(String city) {
+        this.city = city;
+    }
+
+    public String getAddress() {
+        return address;
+    }
+
+    public void setAddress(String address) {
+        this.address = address;
+    }
+
+    public String getAddress2() {
+        return address2;
+    }
+
+    public void setAddress2(String address2) {
+        this.address2 = address2;
+    }
+
+    public String getZip() {
+        return zip;
+    }
+
+    public void setZip(String zip) {
+        this.zip = zip;
+    }
+
+    public String getPhone() {
+        return phone;
+    }
+
+    public void setPhone(String phone) {
+        this.phone = phone;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    public JsonNode getAdditionalInfo() {
+        return additionalInfo;
+    }
+
+    public void setAdditionalInfo(JsonNode additionalInfo) {
+        this.additionalInfo = additionalInfo;
+    }
+    
+    @Override
+    public String getSearchTextSource() {
+        return title;
+    }
+
+    @Override
+    public void setSearchText(String searchText) {
+        this.searchText = searchText;
+    }
+    
+    public String getSearchText() {
+        return searchText;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((additionalInfo == null) ? 0 : additionalInfo.hashCode());
+        result = prime * result + ((address == null) ? 0 : address.hashCode());
+        result = prime * result + ((address2 == null) ? 0 : address2.hashCode());
+        result = prime * result + ((city == null) ? 0 : city.hashCode());
+        result = prime * result + ((country == null) ? 0 : country.hashCode());
+        result = prime * result + ((email == null) ? 0 : email.hashCode());
+        result = prime * result + ((id == null) ? 0 : id.hashCode());
+        result = prime * result + ((phone == null) ? 0 : phone.hashCode());
+        result = prime * result + ((state == null) ? 0 : state.hashCode());
+        result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
+        result = prime * result + ((title == null) ? 0 : title.hashCode());
+        result = prime * result + ((zip == null) ? 0 : zip.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        CustomerEntity other = (CustomerEntity) obj;
+        if (additionalInfo == null) {
+            if (other.additionalInfo != null)
+                return false;
+        } else if (!additionalInfo.equals(other.additionalInfo))
+            return false;
+        if (address == null) {
+            if (other.address != null)
+                return false;
+        } else if (!address.equals(other.address))
+            return false;
+        if (address2 == null) {
+            if (other.address2 != null)
+                return false;
+        } else if (!address2.equals(other.address2))
+            return false;
+        if (city == null) {
+            if (other.city != null)
+                return false;
+        } else if (!city.equals(other.city))
+            return false;
+        if (country == null) {
+            if (other.country != null)
+                return false;
+        } else if (!country.equals(other.country))
+            return false;
+        if (email == null) {
+            if (other.email != null)
+                return false;
+        } else if (!email.equals(other.email))
+            return false;
+        if (id == null) {
+            if (other.id != null)
+                return false;
+        } else if (!id.equals(other.id))
+            return false;
+        if (phone == null) {
+            if (other.phone != null)
+                return false;
+        } else if (!phone.equals(other.phone))
+            return false;
+        if (state == null) {
+            if (other.state != null)
+                return false;
+        } else if (!state.equals(other.state))
+            return false;
+        if (tenantId == null) {
+            if (other.tenantId != null)
+                return false;
+        } else if (!tenantId.equals(other.tenantId))
+            return false;
+        if (title == null) {
+            if (other.title != null)
+                return false;
+        } else if (!title.equals(other.title))
+            return false;
+        if (zip == null) {
+            if (other.zip != null)
+                return false;
+        } else if (!zip.equals(other.zip))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("CustomerEntity [id=");
+        builder.append(id);
+        builder.append(", tenantId=");
+        builder.append(tenantId);
+        builder.append(", title=");
+        builder.append(title);
+        builder.append(", country=");
+        builder.append(country);
+        builder.append(", state=");
+        builder.append(state);
+        builder.append(", city=");
+        builder.append(city);
+        builder.append(", address=");
+        builder.append(address);
+        builder.append(", address2=");
+        builder.append(address2);
+        builder.append(", zip=");
+        builder.append(zip);
+        builder.append(", phone=");
+        builder.append(phone);
+        builder.append(", email=");
+        builder.append(email);
+        builder.append(", additionalInfo=");
+        builder.append(additionalInfo);
+        builder.append("]");
+        return builder.toString();
+    }
+
+    @Override
+    public Customer toData() {
+        Customer customer = new Customer(new CustomerId(id));
+        customer.setCreatedTime(UUIDs.unixTimestamp(id));
+        customer.setTenantId(new TenantId(tenantId));
+        customer.setTitle(title);
+        customer.setCountry(country);
+        customer.setState(state);
+        customer.setCity(city);
+        customer.setAddress(address);
+        customer.setAddress2(address2);
+        customer.setZip(zip);
+        customer.setPhone(phone);
+        customer.setEmail(email);
+        customer.setAdditionalInfo(additionalInfo);
+        return customer;
+    }
+
+}
\ No newline at end of file
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/DashboardEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/DashboardEntity.java
new file mode 100644
index 0000000..cf74d82
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/DashboardEntity.java
@@ -0,0 +1,221 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model;
+
+import java.util.UUID;
+
+import org.thingsboard.server.common.data.Dashboard;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.DashboardId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.dao.model.type.JsonCodec;
+
+import com.datastax.driver.core.utils.UUIDs;
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+import com.datastax.driver.mapping.annotations.Transient;
+import com.fasterxml.jackson.databind.JsonNode;
+
+@Table(name = ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME)
+public final class DashboardEntity implements SearchTextEntity<Dashboard> {
+    
+    @Transient
+    private static final long serialVersionUID = 2998395951247446191L;
+
+    @PartitionKey(value = 0)
+    @Column(name = ModelConstants.ID_PROPERTY)
+    private UUID id;
+    
+    @PartitionKey(value = 1)
+    @Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY)
+    private UUID tenantId;
+
+    @PartitionKey(value = 2)
+    @Column(name = ModelConstants.DASHBOARD_CUSTOMER_ID_PROPERTY)
+    private UUID customerId;
+
+    @Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY)
+    private String title;
+    
+    @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
+    private String searchText;
+    
+    @Column(name = ModelConstants.DASHBOARD_CONFIGURATION_PROPERTY, codec = JsonCodec.class)
+    private JsonNode configuration;
+
+    public DashboardEntity() {
+        super();
+    }
+
+    public DashboardEntity(Dashboard dashboard) {
+        if (dashboard.getId() != null) {
+            this.id = dashboard.getId().getId();
+        }
+        if (dashboard.getTenantId() != null) {
+            this.tenantId = dashboard.getTenantId().getId();
+        }
+        if (dashboard.getCustomerId() != null) {
+            this.customerId = dashboard.getCustomerId().getId();
+        }
+        this.title = dashboard.getTitle();
+        this.configuration = dashboard.getConfiguration();
+    }
+    
+    public UUID getId() {
+        return id;
+    }
+
+    public void setId(UUID id) {
+        this.id = id;
+    }
+
+    public UUID getTenantId() {
+        return tenantId;
+    }
+
+    public void setTenantId(UUID tenantId) {
+        this.tenantId = tenantId;
+    }
+
+    public UUID getCustomerId() {
+        return customerId;
+    }
+
+    public void setCustomerId(UUID customerId) {
+        this.customerId = customerId;
+    }
+    
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public JsonNode getConfiguration() {
+        return configuration;
+    }
+
+    public void setConfiguration(JsonNode configuration) {
+        this.configuration = configuration;
+    }
+    
+    @Override
+    public String getSearchTextSource() {
+        return title;
+    }
+
+    @Override
+    public void setSearchText(String searchText) {
+        this.searchText = searchText;
+    }
+    
+    public String getSearchText() {
+        return searchText;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((configuration == null) ? 0 : configuration.hashCode());
+        result = prime * result + ((customerId == null) ? 0 : customerId.hashCode());
+        result = prime * result + ((id == null) ? 0 : id.hashCode());
+        result = prime * result + ((searchText == null) ? 0 : searchText.hashCode());
+        result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
+        result = prime * result + ((title == null) ? 0 : title.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        DashboardEntity other = (DashboardEntity) obj;
+        if (configuration == null) {
+            if (other.configuration != null)
+                return false;
+        } else if (!configuration.equals(other.configuration))
+            return false;
+        if (customerId == null) {
+            if (other.customerId != null)
+                return false;
+        } else if (!customerId.equals(other.customerId))
+            return false;
+        if (id == null) {
+            if (other.id != null)
+                return false;
+        } else if (!id.equals(other.id))
+            return false;
+        if (searchText == null) {
+            if (other.searchText != null)
+                return false;
+        } else if (!searchText.equals(other.searchText))
+            return false;
+        if (tenantId == null) {
+            if (other.tenantId != null)
+                return false;
+        } else if (!tenantId.equals(other.tenantId))
+            return false;
+        if (title == null) {
+            if (other.title != null)
+                return false;
+        } else if (!title.equals(other.title))
+            return false;
+        return true;
+    }
+    
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("DashboardEntity [id=");
+        builder.append(id);
+        builder.append(", tenantId=");
+        builder.append(tenantId);
+        builder.append(", customerId=");
+        builder.append(customerId);
+        builder.append(", title=");
+        builder.append(title);
+        builder.append(", searchText=");
+        builder.append(searchText);
+        builder.append(", configuration=");
+        builder.append(configuration);
+        builder.append("]");
+        return builder.toString();
+    }
+
+    @Override
+    public Dashboard toData() {
+        Dashboard dashboard = new Dashboard(new DashboardId(id));
+        dashboard.setCreatedTime(UUIDs.unixTimestamp(id));
+        if (tenantId != null) {
+            dashboard.setTenantId(new TenantId(tenantId));
+        }
+        if (customerId != null) {
+            dashboard.setCustomerId(new CustomerId(customerId));
+        }
+        dashboard.setTitle(title);
+        dashboard.setConfiguration(configuration);
+        return dashboard;
+    }
+
+}
\ No newline at end of file
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/DeviceCredentialsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/DeviceCredentialsEntity.java
new file mode 100644
index 0000000..5a1fd28
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/DeviceCredentialsEntity.java
@@ -0,0 +1,186 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model;
+
+import java.util.UUID;
+
+import org.thingsboard.server.common.data.id.DeviceCredentialsId;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.security.DeviceCredentials;
+import org.thingsboard.server.common.data.security.DeviceCredentialsType;
+import org.thingsboard.server.dao.model.type.DeviceCredentialsTypeCodec;
+
+import com.datastax.driver.core.utils.UUIDs;
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+import com.datastax.driver.mapping.annotations.Transient;
+
+@Table(name = ModelConstants.DEVICE_CREDENTIALS_COLUMN_FAMILY_NAME)
+public final class DeviceCredentialsEntity implements BaseEntity<DeviceCredentials> {
+
+    @Transient
+    private static final long serialVersionUID = -2667310560260623272L;
+    
+    @PartitionKey(value = 0)
+    @Column(name = ModelConstants.ID_PROPERTY)
+    private UUID id;
+    
+    @Column(name = ModelConstants.DEVICE_CREDENTIALS_DEVICE_ID_PROPERTY)
+    private UUID deviceId;
+    
+    @Column(name = ModelConstants.DEVICE_CREDENTIALS_CREDENTIALS_TYPE_PROPERTY, codec = DeviceCredentialsTypeCodec.class)
+    private DeviceCredentialsType credentialsType;
+
+    @Column(name = ModelConstants.DEVICE_CREDENTIALS_CREDENTIALS_ID_PROPERTY)
+    private String credentialsId;
+
+    @Column(name = ModelConstants.DEVICE_CREDENTIALS_CREDENTIALS_VALUE_PROPERTY)
+    private String credentialsValue;
+
+    public DeviceCredentialsEntity() {
+        super();
+    }
+
+    public DeviceCredentialsEntity(DeviceCredentials deviceCredentials) {
+        if (deviceCredentials.getId() != null) {
+            this.id = deviceCredentials.getId().getId();
+        }
+        if (deviceCredentials.getDeviceId() != null) {
+            this.deviceId = deviceCredentials.getDeviceId().getId();
+        }
+        this.credentialsType = deviceCredentials.getCredentialsType();
+        this.credentialsId = deviceCredentials.getCredentialsId();
+        this.credentialsValue = deviceCredentials.getCredentialsValue(); 
+    }
+    
+    public UUID getId() {
+        return id;
+    }
+
+    public void setId(UUID id) {
+        this.id = id;
+    }
+
+    public UUID getDeviceId() {
+        return deviceId;
+    }
+
+    public void setDeviceId(UUID deviceId) {
+        this.deviceId = deviceId;
+    }
+
+    public DeviceCredentialsType getCredentialsType() {
+        return credentialsType;
+    }
+
+    public void setCredentialsType(DeviceCredentialsType credentialsType) {
+        this.credentialsType = credentialsType;
+    }
+
+    public String getCredentialsId() {
+        return credentialsId;
+    }
+
+    public void setCredentialsId(String credentialsId) {
+        this.credentialsId = credentialsId;
+    }
+
+    public String getCredentialsValue() {
+        return credentialsValue;
+    }
+
+    public void setCredentialsValue(String credentialsValue) {
+        this.credentialsValue = credentialsValue;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((credentialsId == null) ? 0 : credentialsId.hashCode());
+        result = prime * result + ((credentialsType == null) ? 0 : credentialsType.hashCode());
+        result = prime * result + ((credentialsValue == null) ? 0 : credentialsValue.hashCode());
+        result = prime * result + ((deviceId == null) ? 0 : deviceId.hashCode());
+        result = prime * result + ((id == null) ? 0 : id.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        DeviceCredentialsEntity other = (DeviceCredentialsEntity) obj;
+        if (credentialsId == null) {
+            if (other.credentialsId != null)
+                return false;
+        } else if (!credentialsId.equals(other.credentialsId))
+            return false;
+        if (credentialsType != other.credentialsType)
+            return false;
+        if (credentialsValue == null) {
+            if (other.credentialsValue != null)
+                return false;
+        } else if (!credentialsValue.equals(other.credentialsValue))
+            return false;
+        if (deviceId == null) {
+            if (other.deviceId != null)
+                return false;
+        } else if (!deviceId.equals(other.deviceId))
+            return false;
+        if (id == null) {
+            if (other.id != null)
+                return false;
+        } else if (!id.equals(other.id))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("DeviceCredentialsEntity [id=");
+        builder.append(id);
+        builder.append(", deviceId=");
+        builder.append(deviceId);
+        builder.append(", credentialsType=");
+        builder.append(credentialsType);
+        builder.append(", credentialsId=");
+        builder.append(credentialsId);
+        builder.append(", credentialsValue=");
+        builder.append(credentialsValue);
+        builder.append("]");
+        return builder.toString();
+    }
+
+    @Override
+    public DeviceCredentials toData() {
+        DeviceCredentials deviceCredentials = new DeviceCredentials(new DeviceCredentialsId(id));
+        deviceCredentials.setCreatedTime(UUIDs.unixTimestamp(id));
+        if (deviceId != null) {
+            deviceCredentials.setDeviceId(new DeviceId(deviceId));
+        }
+        deviceCredentials.setCredentialsType(credentialsType);
+        deviceCredentials.setCredentialsId(credentialsId);
+        deviceCredentials.setCredentialsValue(credentialsValue);
+        return deviceCredentials;
+    }
+
+}
\ No newline at end of file
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/DeviceEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/DeviceEntity.java
new file mode 100644
index 0000000..4fc1ea2
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/DeviceEntity.java
@@ -0,0 +1,214 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model;
+
+import com.datastax.driver.core.utils.UUIDs;
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+import com.datastax.driver.mapping.annotations.Transient;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.dao.model.type.JsonCodec;
+
+import java.util.UUID;
+
+import static org.thingsboard.server.dao.model.ModelConstants.*;
+
+@Table(name = DEVICE_COLUMN_FAMILY_NAME)
+public final class DeviceEntity implements SearchTextEntity<Device> {
+
+    @Transient
+    private static final long serialVersionUID = -1265181166886910152L;
+    
+    @PartitionKey(value = 0)
+    @Column(name = ID_PROPERTY)
+    private UUID id;
+    
+    @PartitionKey(value = 1)
+    @Column(name = DEVICE_TENANT_ID_PROPERTY)
+    private UUID tenantId;
+
+    @PartitionKey(value = 2)
+    @Column(name = DEVICE_CUSTOMER_ID_PROPERTY)
+    private UUID customerId;
+
+    @Column(name = DEVICE_NAME_PROPERTY)
+    private String name;
+    
+    @Column(name = SEARCH_TEXT_PROPERTY)
+    private String searchText;
+    
+    @Column(name = DEVICE_ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class)
+    private JsonNode additionalInfo;
+
+    public DeviceEntity() {
+        super();
+    }
+
+    public DeviceEntity(Device device) {
+        if (device.getId() != null) {
+            this.id = device.getId().getId();
+        }
+        if (device.getTenantId() != null) {
+            this.tenantId = device.getTenantId().getId();
+        }
+        if (device.getCustomerId() != null) {
+            this.customerId = device.getCustomerId().getId();
+        }
+        this.name = device.getName();
+        this.additionalInfo = device.getAdditionalInfo();
+    }
+    
+    public UUID getId() {
+        return id;
+    }
+
+    public void setId(UUID id) {
+        this.id = id;
+    }
+
+    public UUID getTenantId() {
+        return tenantId;
+    }
+
+    public void setTenantId(UUID tenantId) {
+        this.tenantId = tenantId;
+    }
+
+    public UUID getCustomerId() {
+        return customerId;
+    }
+
+    public void setCustomerId(UUID customerId) {
+        this.customerId = customerId;
+    }
+    
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public JsonNode getAdditionalInfo() {
+        return additionalInfo;
+    }
+
+    public void setAdditionalInfo(JsonNode additionalInfo) {
+        this.additionalInfo = additionalInfo;
+    }
+    
+    @Override
+    public String getSearchTextSource() {
+        return name;
+    }
+
+    @Override
+    public void setSearchText(String searchText) {
+        this.searchText = searchText;
+    }
+    
+    public String getSearchText() {
+        return searchText;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((additionalInfo == null) ? 0 : additionalInfo.hashCode());
+        result = prime * result + ((customerId == null) ? 0 : customerId.hashCode());
+        result = prime * result + ((id == null) ? 0 : id.hashCode());
+        result = prime * result + ((name == null) ? 0 : name.hashCode());
+        result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        DeviceEntity other = (DeviceEntity) obj;
+        if (additionalInfo == null) {
+            if (other.additionalInfo != null)
+                return false;
+        } else if (!additionalInfo.equals(other.additionalInfo))
+            return false;
+        if (customerId == null) {
+            if (other.customerId != null)
+                return false;
+        } else if (!customerId.equals(other.customerId))
+            return false;
+        if (id == null) {
+            if (other.id != null)
+                return false;
+        } else if (!id.equals(other.id))
+            return false;
+        if (name == null) {
+            if (other.name != null)
+                return false;
+        } else if (!name.equals(other.name))
+            return false;
+        if (tenantId == null) {
+            if (other.tenantId != null)
+                return false;
+        } else if (!tenantId.equals(other.tenantId))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("DeviceEntity [id=");
+        builder.append(id);
+        builder.append(", tenantId=");
+        builder.append(tenantId);
+        builder.append(", customerId=");
+        builder.append(customerId);
+        builder.append(", name=");
+        builder.append(name);
+        builder.append(", additionalInfo=");
+        builder.append(additionalInfo);
+        builder.append("]");
+        return builder.toString();
+    }
+
+    @Override
+    public Device toData() {
+        Device device = new Device(new DeviceId(id));
+        device.setCreatedTime(UUIDs.unixTimestamp(id));
+        if (tenantId != null) {
+            device.setTenantId(new TenantId(tenantId));
+        }
+        if (customerId != null) {
+            device.setCustomerId(new CustomerId(customerId));
+        }
+        device.setName(name);
+        device.setAdditionalInfo(additionalInfo);
+        return device;
+    }
+
+}
\ No newline at end of file
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/EventEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/EventEntity.java
new file mode 100644
index 0000000..a655daf
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/EventEntity.java
@@ -0,0 +1,123 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model;
+
+import com.datastax.driver.core.utils.UUIDs;
+import com.datastax.driver.mapping.annotations.*;
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.Event;
+import org.thingsboard.server.common.data.id.*;
+import org.thingsboard.server.dao.model.type.EntityTypeCodec;
+import org.thingsboard.server.dao.model.type.JsonCodec;
+
+import java.util.UUID;
+
+import static org.thingsboard.server.dao.model.ModelConstants.*;
+
+/**
+ * @author Andrew Shvayka
+ */
+@Data
+@NoArgsConstructor
+@Table(name = DEVICE_COLUMN_FAMILY_NAME)
+public class EventEntity implements BaseEntity<Event> {
+
+    @Transient
+    private static final long serialVersionUID = -1265181166886910153L;
+
+    @Column(name = ID_PROPERTY)
+    private UUID id;
+
+    @PartitionKey()
+    @Column(name = EVENT_TENANT_ID_PROPERTY)
+    private UUID tenantId;
+
+    @PartitionKey(value = 1)
+    @Column(name = EVENT_ENTITY_TYPE_PROPERTY, codec = EntityTypeCodec.class)
+    private EntityType entityType;
+
+    @PartitionKey(value = 2)
+    @Column(name = EVENT_ENTITY_ID_PROPERTY)
+    private UUID entityId;
+
+    @ClusteringColumn()
+    @Column(name = EVENT_TYPE_PROPERTY)
+    private String eventType;
+
+    @ClusteringColumn(value = 1)
+    @Column(name = EVENT_UID_PROPERTY)
+    private String eventUId;
+
+    @Column(name = EVENT_BODY_PROPERTY, codec = JsonCodec.class)
+    private JsonNode body;
+
+    public EventEntity(Event event) {
+        if (event.getId() != null) {
+            this.id = event.getId().getId();
+        }
+        if (event.getTenantId() != null) {
+            this.tenantId = event.getTenantId().getId();
+        }
+        if (event.getEntityId() != null) {
+            this.entityType = event.getEntityId().getEntityType();
+            this.entityId = event.getEntityId().getId();
+        }
+        this.eventType = event.getType();
+        this.eventUId = event.getUid();
+        this.body = event.getBody();
+    }
+
+    @Override
+    public UUID getId() {
+        return id;
+    }
+
+    @Override
+    public void setId(UUID id) {
+        this.id = id;
+    }
+
+    @Override
+    public Event toData() {
+        Event event = new Event(new EventId(id));
+        event.setCreatedTime(UUIDs.unixTimestamp(id));
+        event.setTenantId(new TenantId(tenantId));
+        switch (entityType) {
+            case TENANT:
+                event.setEntityId(new TenantId(entityId));
+                break;
+            case DEVICE:
+                event.setEntityId(new DeviceId(entityId));
+                break;
+            case CUSTOMER:
+                event.setEntityId(new CustomerId(entityId));
+                break;
+            case RULE:
+                event.setEntityId(new RuleId(entityId));
+                break;
+            case PLUGIN:
+                event.setEntityId(new PluginId(entityId));
+                break;
+        }
+        event.setBody(body);
+        event.setType(eventType);
+        event.setUid(eventUId);
+        return event;
+    }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
new file mode 100644
index 0000000..b14e99f
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
@@ -0,0 +1,262 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model;
+
+import java.util.UUID;
+
+import com.datastax.driver.core.utils.UUIDs;
+
+public class ModelConstants {
+
+    private ModelConstants() {
+    }
+    
+    public static UUID NULL_UUID = UUIDs.startOf(0);
+    
+    /**
+     * Generic constants.
+     */
+    public static final String ID_PROPERTY = "id";
+    public static final String USER_ID_PROPERTY = "user_id";
+    public static final String TENTANT_ID_PROPERTY = "tenant_id";
+    public static final String CUSTOMER_ID_PROPERTY = "customer_id";
+    public static final String DEVICE_ID_PROPERTY = "device_id";
+    public static final String TITLE_PROPERTY = "title";
+    public static final String ALIAS_PROPERTY = "alias";
+    public static final String SEARCH_TEXT_PROPERTY = "search_text";
+    public static final String ADDITIONAL_INFO_PROPERTY = "additional_info";
+    
+    /**
+     * Cassandra user constants.
+     */
+    public static final String USER_COLUMN_FAMILY_NAME = "user";
+    public static final String USER_TENANT_ID_PROPERTY = TENTANT_ID_PROPERTY;
+    public static final String USER_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
+    public static final String USER_EMAIL_PROPERTY = "email";
+    public static final String USER_AUTHORITY_PROPERTY = "authority";
+    public static final String USER_FIRST_NAME_PROPERTY = "first_name";
+    public static final String USER_LAST_NAME_PROPERTY = "last_name";
+    public static final String USER_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
+    
+    public static final String USER_BY_EMAIL_COLUMN_FAMILY_NAME = "user_by_email";
+    public static final String USER_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "user_by_tenant_and_search_text";
+    public static final String USER_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "user_by_customer_and_search_text";
+    
+    /**
+     * Cassandra user_credentials constants.
+     */
+    public static final String USER_CREDENTIALS_COLUMN_FAMILY_NAME = "user_credentials";
+    public static final String USER_CREDENTIALS_USER_ID_PROPERTY = USER_ID_PROPERTY;
+    public static final String USER_CREDENTIALS_ENABLED_PROPERTY = "enabled";
+    public static final String USER_CREDENTIALS_PASSWORD_PROPERTY = "password";
+    public static final String USER_CREDENTIALS_ACTIVATE_TOKEN_PROPERTY = "activate_token";
+    public static final String USER_CREDENTIALS_RESET_TOKEN_PROPERTY = "reset_token";
+    
+    public static final String USER_CREDENTIALS_BY_USER_COLUMN_FAMILY_NAME = "user_credentials_by_user";
+    public static final String USER_CREDENTIALS_BY_ACTIVATE_TOKEN_COLUMN_FAMILY_NAME = "user_credentials_by_activate_token";
+    public static final String USER_CREDENTIALS_BY_RESET_TOKEN_COLUMN_FAMILY_NAME = "user_credentials_by_reset_token";
+    
+    /**
+     * Cassandra admin_settings constants.
+     */
+    public static final String ADMIN_SETTINGS_COLUMN_FAMILY_NAME = "admin_settings";
+    public static final String ADMIN_SETTINGS_KEY_PROPERTY = "key";
+    public static final String ADMIN_SETTINGS_JSON_VALUE_PROPERTY = "json_value";
+    
+    public static final String ADMIN_SETTINGS_BY_KEY_COLUMN_FAMILY_NAME = "admin_settings_by_key";
+    
+    /**
+     * Cassandra contact constants.
+     */
+    public static final String COUNTRY_PROPERTY = "country";
+    public static final String STATE_PROPERTY = "state";
+    public static final String CITY_PROPERTY = "city";
+    public static final String ADDRESS_PROPERTY = "address";
+    public static final String ADDRESS2_PROPERTY = "address2";
+    public static final String ZIP_PROPERTY = "zip";
+    public static final String PHONE_PROPERTY = "phone";
+    public static final String EMAIL_PROPERTY = "email";
+
+    /**
+     * Cassandra tenant constants.
+     */
+    public static final String TENANT_COLUMN_FAMILY_NAME = "tenant";
+    public static final String TENANT_TITLE_PROPERTY = TITLE_PROPERTY;
+    public static final String TENANT_REGION_PROPERTY = "region";
+    public static final String TENANT_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
+    
+    public static final String TENANT_BY_REGION_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "tenant_by_region_and_search_text";
+    
+    /**
+     * Cassandra customer constants.
+     */
+    public static final String CUSTOMER_COLUMN_FAMILY_NAME = "customer";
+    public static final String CUSTOMER_TENANT_ID_PROPERTY = TENTANT_ID_PROPERTY;
+    public static final String CUSTOMER_TITLE_PROPERTY = TITLE_PROPERTY;
+    public static final String CUSTOMER_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
+    
+    public static final String CUSTOMER_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "customer_by_tenant_and_search_text";
+    
+    /**
+     * Cassandra device constants.
+     */
+    public static final String DEVICE_COLUMN_FAMILY_NAME = "device";
+    public static final String DEVICE_TENANT_ID_PROPERTY = TENTANT_ID_PROPERTY;
+    public static final String DEVICE_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
+    public static final String DEVICE_NAME_PROPERTY = "name";
+    public static final String DEVICE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
+    
+    public static final String DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_and_search_text";
+    public static final String DEVICE_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_customer_and_search_text";
+    public static final String DEVICE_BY_TENANT_AND_NAME_VIEW_NAME = "device_by_tenant_and_name";
+
+    
+    /**
+     * Cassandra device_credentials constants.
+     */
+    public static final String DEVICE_CREDENTIALS_COLUMN_FAMILY_NAME = "device_credentials";
+    public static final String DEVICE_CREDENTIALS_DEVICE_ID_PROPERTY = DEVICE_ID_PROPERTY;
+    public static final String DEVICE_CREDENTIALS_CREDENTIALS_TYPE_PROPERTY = "credentials_type";
+    public static final String DEVICE_CREDENTIALS_CREDENTIALS_ID_PROPERTY = "credentials_id";
+    public static final String DEVICE_CREDENTIALS_CREDENTIALS_VALUE_PROPERTY = "credentials_value";
+    
+    public static final String DEVICE_CREDENTIALS_BY_DEVICE_COLUMN_FAMILY_NAME = "device_credentials_by_device";
+    public static final String DEVICE_CREDENTIALS_BY_CREDENTIALS_ID_COLUMN_FAMILY_NAME = "device_credentials_by_credentials_id";
+
+    /**
+     * Cassandra widgets_bundle constants.
+     */
+    public static final String WIDGETS_BUNDLE_COLUMN_FAMILY_NAME = "widgets_bundle";
+    public static final String WIDGETS_BUNDLE_TENANT_ID_PROPERTY = TENTANT_ID_PROPERTY;
+    public static final String WIDGETS_BUNDLE_ALIAS_PROPERTY = ALIAS_PROPERTY;
+    public static final String WIDGETS_BUNDLE_TITLE_PROPERTY = TITLE_PROPERTY;
+    public static final String WIDGETS_BUNDLE_IMAGE_PROPERTY = "image";
+
+    public static final String WIDGETS_BUNDLE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "widgets_bundle_by_tenant_and_search_text";
+    public static final String WIDGETS_BUNDLE_BY_TENANT_AND_ALIAS_COLUMN_FAMILY_NAME = "widgets_bundle_by_tenant_and_alias";
+
+    /**
+     * Cassandra widget_type constants.
+     */
+    public static final String WIDGET_TYPE_COLUMN_FAMILY_NAME = "widget_type";
+    public static final String WIDGET_TYPE_TENANT_ID_PROPERTY = TENTANT_ID_PROPERTY;
+    public static final String WIDGET_TYPE_BUNDLE_ALIAS_PROPERTY = "bundle_alias";
+    public static final String WIDGET_TYPE_ALIAS_PROPERTY = ALIAS_PROPERTY;
+    public static final String WIDGET_TYPE_NAME_PROPERTY = "name";
+    public static final String WIDGET_TYPE_DESCRIPTOR_PROPERTY = "descriptor";
+
+    public static final String WIDGET_TYPE_BY_TENANT_AND_ALIASES_COLUMN_FAMILY_NAME = "widget_type_by_tenant_and_aliases";
+
+    /**
+     * Cassandra dashboard constants.
+     */
+    public static final String DASHBOARD_COLUMN_FAMILY_NAME = "dashboard";
+    public static final String DASHBOARD_TENANT_ID_PROPERTY = TENTANT_ID_PROPERTY;
+    public static final String DASHBOARD_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
+    public static final String DASHBOARD_TITLE_PROPERTY = TITLE_PROPERTY;
+    public static final String DASHBOARD_CONFIGURATION_PROPERTY = "configuration";
+
+    public static final String DASHBOARD_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "dashboard_by_tenant_and_search_text";
+    public static final String DASHBOARD_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "dashboard_by_customer_and_search_text";
+
+
+    /**
+     * Cassandra plugin metadata constants.
+     */
+    public static final String PLUGIN_COLUMN_FAMILY_NAME = "plugin";
+    public static final String PLUGIN_TENANT_ID_PROPERTY = TENTANT_ID_PROPERTY;
+    public static final String PLUGIN_NAME_PROPERTY = "name";
+    public static final String PLUGIN_API_TOKEN_PROPERTY = "api_token";
+    public static final String PLUGIN_CLASS_PROPERTY = "plugin_class";
+    public static final String PLUGIN_ACCESS_PROPERTY = "public_access";
+    public static final String PLUGIN_STATE_PROPERTY = "state";
+    public static final String PLUGIN_CONFIGURATION_PROPERTY = "configuration";
+
+    public static final String PLUGIN_BY_API_TOKEN_COLUMN_FAMILY_NAME = "plugin_by_api_token";
+    public static final String PLUGIN_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "plugin_by_tenant_and_search_text";
+
+    /**
+     * Cassandra plugin component metadata constants.
+     */
+    public static final String COMPONENT_DESCRIPTOR_COLUMN_FAMILY_NAME = "component_descriptor";
+    public static final String COMPONENT_DESCRIPTOR_TYPE_PROPERTY = "type";
+    public static final String COMPONENT_DESCRIPTOR_SCOPE_PROPERTY = "scope";
+    public static final String COMPONENT_DESCRIPTOR_NAME_PROPERTY = "name";
+    public static final String COMPONENT_DESCRIPTOR_CLASS_PROPERTY = "clazz";
+    public static final String COMPONENT_DESCRIPTOR_CONFIGURATION_DESCRIPTOR_PROPERTY = "configuration_descriptor";
+    public static final String COMPONENT_DESCRIPTOR_ACTIONS_PROPERTY = "actions";
+
+    public static final String COMPONENT_DESCRIPTOR_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "component_desc_by_type_search_text";
+    public static final String COMPONENT_DESCRIPTOR_BY_SCOPE_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "component_desc_by_scope_type_search_text";
+    public static final String COMPONENT_DESCRIPTOR_BY_ID = "component_desc_by_id";
+
+  /**
+   * Cassandra rule metadata constants.
+   */
+    public static final String RULE_COLUMN_FAMILY_NAME = "rule";
+    public static final String RULE_TENANT_ID_PROPERTY = TENTANT_ID_PROPERTY;
+    public static final String RULE_NAME_PROPERTY = "name";
+    public static final String RULE_STATE_PROPERTY = "state";
+    public static final String RULE_WEIGHT_PROPERTY = "weight";
+    public static final String RULE_PLUGIN_TOKEN_PROPERTY = "plugin_token";
+    public static final String RULE_FILTERS = "filters";
+    public static final String RULE_PROCESSOR = "processor";
+    public static final String RULE_ACTION = "action";
+
+    public static final String RULE_BY_PLUGIN_TOKEN = "rule_by_plugin_token";
+    public static final String RULE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "rule_by_tenant_and_search_text";
+
+    /**
+     * Cassandra event constants.
+     */
+    public static final String EVENT_COLUMN_FAMILY_NAME = "event";
+    public static final String EVENT_TENANT_ID_PROPERTY = TENTANT_ID_PROPERTY;
+    public static final String EVENT_TYPE_PROPERTY = "event_type";
+    public static final String EVENT_UID_PROPERTY = "event_uid";
+    public static final String EVENT_ENTITY_TYPE_PROPERTY = "entity_type";
+    public static final String EVENT_ENTITY_ID_PROPERTY = "entity_id";
+    public static final String EVENT_BODY_PROPERTY = "body";
+
+    public static final String EVENT_BY_TYPE_AND_ID_VIEW_NAME = "event_by_type_and_id";
+    public static final String EVENT_BY_ID_VIEW_NAME = "event_by_id";
+
+    /**
+     * Cassandra attributes and timeseries constants.
+     */
+    public static final String ATTRIBUTES_KV_CF = "attributes_kv_cf";
+    public static final String TS_KV_CF = "ts_kv_cf";
+    public static final String TS_KV_PARTITIONS_CF = "ts_kv_partitions_cf";
+    public static final String TS_KV_LATEST_CF = "ts_kv_latest_cf";
+
+
+    public static final String ENTITY_TYPE_COLUMN = "entity_type";
+    public static final String ENTITY_ID_COLUMN = "entity_id";
+    public static final String ATTRIBUTE_TYPE_COLUMN = "attribute_type";
+    public static final String ATTRIBUTE_KEY_COLUMN = "attribute_key";
+    public static final String LAST_UPDATE_TS_COLUMN = "last_update_ts";
+
+    public static final String PARTITION_COLUMN = "partition";
+    public static final String KEY_COLUMN = "key";
+    public static final String TS_COLUMN = "ts";
+
+    /**
+     * Main names of cassandra key-value columns storage.
+     */
+    public static final String BOOLEAN_VALUE_COLUMN = "bool_v";
+    public static final String STRING_VALUE_COLUMN = "str_v";
+    public static final String LONG_VALUE_COLUMN = "long_v";
+    public static final String DOUBLE_VALUE_COLUMN = "dbl_v";
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/PluginMetaDataEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/PluginMetaDataEntity.java
new file mode 100644
index 0000000..c36bc28
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/PluginMetaDataEntity.java
@@ -0,0 +1,218 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model;
+
+import com.datastax.driver.core.utils.UUIDs;
+import com.datastax.driver.mapping.annotations.ClusteringColumn;
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.thingsboard.server.common.data.id.PluginId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
+import org.thingsboard.server.common.data.plugin.PluginMetaData;
+import org.thingsboard.server.dao.model.type.ComponentLifecycleStateCodec;
+import org.thingsboard.server.dao.model.type.JsonCodec;
+
+import java.util.Objects;
+import java.util.UUID;
+
+import static org.thingsboard.server.dao.model.ModelConstants.ADDITIONAL_INFO_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.RULE_ACTION;
+
+@Table(name = ModelConstants.PLUGIN_COLUMN_FAMILY_NAME)
+public class PluginMetaDataEntity implements SearchTextEntity<PluginMetaData> {
+
+    private static final long serialVersionUID = 1L;
+
+    @PartitionKey
+    @Column(name = ModelConstants.ID_PROPERTY)
+    private UUID id;
+
+    @Column(name = ModelConstants.PLUGIN_API_TOKEN_PROPERTY)
+    private String apiToken;
+
+    @ClusteringColumn
+    @Column(name = ModelConstants.PLUGIN_TENANT_ID_PROPERTY)
+    private UUID tenantId;
+
+    @Column(name = ModelConstants.PLUGIN_NAME_PROPERTY)
+    private String name;
+
+    @Column(name = ModelConstants.PLUGIN_CLASS_PROPERTY)
+    private String clazz;
+
+    @Column(name = ModelConstants.PLUGIN_ACCESS_PROPERTY)
+    private boolean publicAccess;
+
+    @Column(name = ModelConstants.PLUGIN_STATE_PROPERTY, codec = ComponentLifecycleStateCodec.class)
+    private ComponentLifecycleState state;
+
+    @Column(name = ModelConstants.PLUGIN_CONFIGURATION_PROPERTY, codec = JsonCodec.class)
+    private JsonNode configuration;
+
+    @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
+    private String searchText;
+
+    @Column(name = ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class)
+    private JsonNode additionalInfo;
+
+    public PluginMetaDataEntity() {
+    }
+
+    public PluginMetaDataEntity(PluginMetaData pluginMetaData) {
+        if (pluginMetaData.getId() != null) {
+            this.id = pluginMetaData.getId().getId();
+        }
+        this.tenantId = pluginMetaData.getTenantId().getId();
+        this.apiToken = pluginMetaData.getApiToken();
+        this.clazz = pluginMetaData.getClazz();
+        this.name = pluginMetaData.getName();
+        this.publicAccess = pluginMetaData.isPublicAccess();
+        this.state = pluginMetaData.getState();
+        this.configuration = pluginMetaData.getConfiguration();
+        this.searchText = pluginMetaData.getName();
+        this.additionalInfo = pluginMetaData.getAdditionalInfo();
+    }
+
+    @Override
+    public String getSearchTextSource() {
+        return searchText;
+    }
+
+    @Override
+    public void setSearchText(String searchText) {
+        this.searchText = searchText;
+    }
+
+    @Override
+    public UUID getId() {
+        return id;
+    }
+
+    @Override
+    public void setId(UUID id) {
+        this.id = id;
+    }
+
+    public String getApiToken() {
+        return apiToken;
+    }
+
+    public void setApiToken(String apiToken) {
+        this.apiToken = apiToken;
+    }
+
+    public UUID getTenantId() {
+        return tenantId;
+    }
+
+    public void setTenantId(UUID tenantId) {
+        this.tenantId = tenantId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getClazz() {
+        return clazz;
+    }
+
+    public void setClazz(String clazz) {
+        this.clazz = clazz;
+    }
+
+    public JsonNode getConfiguration() {
+        return configuration;
+    }
+
+    public void setConfiguration(JsonNode configuration) {
+        this.configuration = configuration;
+    }
+
+    public boolean isPublicAccess() {
+        return publicAccess;
+    }
+
+    public void setPublicAccess(boolean publicAccess) {
+        this.publicAccess = publicAccess;
+    }
+
+    public ComponentLifecycleState getState() {
+        return state;
+    }
+
+    public void setState(ComponentLifecycleState state) {
+        this.state = state;
+    }
+
+    public String getSearchText() {
+        return searchText;
+    }
+
+    public JsonNode getAdditionalInfo() {
+        return additionalInfo;
+    }
+
+    public void setAdditionalInfo(JsonNode additionalInfo) {
+        this.additionalInfo = additionalInfo;
+    }
+
+    @Override
+    public PluginMetaData toData() {
+        PluginMetaData data = new PluginMetaData(new PluginId(id));
+        data.setTenantId(new TenantId(tenantId));
+        data.setCreatedTime(UUIDs.unixTimestamp(id));
+        data.setName(name);
+        data.setConfiguration(configuration);
+        data.setClazz(clazz);
+        data.setPublicAccess(publicAccess);
+        data.setState(state);
+        data.setApiToken(apiToken);
+        data.setAdditionalInfo(additionalInfo);
+        return data;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+        PluginMetaDataEntity entity = (PluginMetaDataEntity) o;
+        return Objects.equals(id, entity.id) && Objects.equals(apiToken, entity.apiToken) && Objects.equals(tenantId, entity.tenantId)
+                && Objects.equals(name, entity.name) && Objects.equals(clazz, entity.clazz) && Objects.equals(state, entity.state)
+                && Objects.equals(configuration, entity.configuration)
+                && Objects.equals(searchText, entity.searchText);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, apiToken, tenantId, name, clazz, state, configuration, searchText);
+    }
+
+    @Override
+    public String toString() {
+        return "PluginMetaDataEntity{" + "id=" + id + ", apiToken='" + apiToken + '\'' + ", tenantId=" + tenantId + ", name='" + name + '\'' + ", clazz='"
+                + clazz + '\'' + ", state=" + state + ", configuration=" + configuration + ", searchText='" + searchText + '\'' + '}';
+    }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/RuleMetaDataEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/RuleMetaDataEntity.java
new file mode 100644
index 0000000..dd2c55e
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/RuleMetaDataEntity.java
@@ -0,0 +1,227 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model;
+
+import com.datastax.driver.core.utils.UUIDs;
+import com.datastax.driver.mapping.annotations.ClusteringColumn;
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.thingsboard.server.common.data.id.RuleId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
+import org.thingsboard.server.common.data.rule.RuleMetaData;
+import org.thingsboard.server.dao.DaoUtil;
+import org.thingsboard.server.dao.model.type.ComponentLifecycleStateCodec;
+import org.thingsboard.server.dao.model.type.JsonCodec;
+
+import java.util.Objects;
+import java.util.UUID;
+
+import static org.thingsboard.server.dao.model.ModelConstants.*;
+
+@Table(name = RULE_COLUMN_FAMILY_NAME)
+public class RuleMetaDataEntity implements SearchTextEntity<RuleMetaData> {
+
+    @PartitionKey
+    @Column(name = ID_PROPERTY)
+    private UUID id;
+    @ClusteringColumn
+    @Column(name = RULE_TENANT_ID_PROPERTY)
+    private UUID tenantId;
+    @Column(name = RULE_NAME_PROPERTY)
+    private String name;
+    @Column(name = ModelConstants.RULE_STATE_PROPERTY, codec = ComponentLifecycleStateCodec.class)
+    private ComponentLifecycleState state;
+    @Column(name = RULE_WEIGHT_PROPERTY)
+    private int weight;
+    @Column(name = SEARCH_TEXT_PROPERTY)
+    private String searchText;
+    @Column(name = RULE_PLUGIN_TOKEN_PROPERTY)
+    private String pluginToken;
+    @Column(name = RULE_FILTERS, codec = JsonCodec.class)
+    private JsonNode filters;
+    @Column(name = RULE_PROCESSOR, codec = JsonCodec.class)
+    private JsonNode processor;
+    @Column(name = RULE_ACTION, codec = JsonCodec.class)
+    private JsonNode action;
+    @Column(name = ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class)
+    private JsonNode additionalInfo;
+
+    public RuleMetaDataEntity() {
+    }
+
+    public RuleMetaDataEntity(RuleMetaData rule) {
+        if (rule.getId() != null) {
+            this.id = rule.getUuidId();
+        }
+        this.tenantId = DaoUtil.getId(rule.getTenantId());
+        this.name = rule.getName();
+        this.pluginToken = rule.getPluginToken();
+        this.state = rule.getState();
+        this.weight = rule.getWeight();
+        this.searchText = rule.getName();
+        this.filters = rule.getFilters();
+        this.processor = rule.getProcessor();
+        this.action = rule.getAction();
+        this.additionalInfo = rule.getAdditionalInfo();
+    }
+
+    @Override
+    public String getSearchTextSource() {
+        return searchText;
+    }
+
+    @Override
+    public void setSearchText(String searchText) {
+        this.searchText = searchText;
+    }
+
+    @Override
+    public UUID getId() {
+        return id;
+    }
+
+    @Override
+    public void setId(UUID id) {
+        this.id = id;
+    }
+
+    public UUID getTenantId() {
+        return tenantId;
+    }
+
+    public void setTenantId(UUID tenantId) {
+        this.tenantId = tenantId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public ComponentLifecycleState getState() {
+        return state;
+    }
+
+    public void setState(ComponentLifecycleState state) {
+        this.state = state;
+    }
+
+    public int getWeight() {
+        return weight;
+    }
+
+    public void setWeight(int weight) {
+        this.weight = weight;
+    }
+
+    public String getPluginToken() {
+        return pluginToken;
+    }
+
+    public void setPluginToken(String pluginToken) {
+        this.pluginToken = pluginToken;
+    }
+
+    public String getSearchText() {
+        return searchText;
+    }
+
+    public JsonNode getFilters() {
+        return filters;
+    }
+
+    public void setFilters(JsonNode filters) {
+        this.filters = filters;
+    }
+
+    public JsonNode getProcessor() {
+        return processor;
+    }
+
+    public void setProcessor(JsonNode processor) {
+        this.processor = processor;
+    }
+
+    public JsonNode getAction() {
+        return action;
+    }
+
+    public void setAction(JsonNode action) {
+        this.action = action;
+    }
+
+    public JsonNode getAdditionalInfo() {
+        return additionalInfo;
+    }
+
+    public void setAdditionalInfo(JsonNode additionalInfo) {
+        this.additionalInfo = additionalInfo;
+    }
+
+    @Override
+    public RuleMetaData toData() {
+        RuleMetaData rule = new RuleMetaData(new RuleId(id));
+        rule.setTenantId(new TenantId(tenantId));
+        rule.setName(name);
+        rule.setState(state);
+        rule.setWeight(weight);
+        rule.setCreatedTime(UUIDs.unixTimestamp(id));
+        rule.setPluginToken(pluginToken);
+        rule.setFilters(filters);
+        rule.setProcessor(processor);
+        rule.setAction(action);
+        rule.setAdditionalInfo(additionalInfo);
+        return rule;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        RuleMetaDataEntity that = (RuleMetaDataEntity) o;
+        return weight == that.weight &&
+                Objects.equals(id, that.id) &&
+                Objects.equals(tenantId, that.tenantId) &&
+                Objects.equals(name, that.name) &&
+                Objects.equals(pluginToken, that.pluginToken) &&
+                Objects.equals(state, that.state) &&
+                Objects.equals(searchText, that.searchText);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, tenantId, name, pluginToken, state, weight, searchText);
+    }
+
+    @Override
+    public String toString() {
+        return "RuleMetaDataEntity{" +
+                "id=" + id +
+                ", tenantId=" + tenantId +
+                ", name='" + name + '\'' +
+                ", pluginToken='" + pluginToken + '\'' +
+                ", state='" + state + '\'' +
+                ", weight=" + weight +
+                ", searchText='" + searchText + '\'' +
+                '}';
+    }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/SearchTextEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/SearchTextEntity.java
new file mode 100644
index 0000000..9233dd3
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/SearchTextEntity.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model;
+
+public interface SearchTextEntity<D> extends BaseEntity<D> {
+
+    String getSearchTextSource();
+    
+    void setSearchText(String searchText);
+    
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/TenantEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/TenantEntity.java
new file mode 100644
index 0000000..7069cca
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/TenantEntity.java
@@ -0,0 +1,364 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model;
+
+import static org.thingsboard.server.dao.model.ModelConstants.ADDRESS2_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.ADDRESS_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.CITY_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.COUNTRY_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.EMAIL_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.PHONE_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.STATE_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.TENANT_ADDITIONAL_INFO_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.TENANT_COLUMN_FAMILY_NAME;
+import static org.thingsboard.server.dao.model.ModelConstants.TENANT_REGION_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.TENANT_TITLE_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.ZIP_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY;
+
+import java.util.UUID;
+
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.dao.model.type.JsonCodec;
+
+import com.datastax.driver.core.utils.UUIDs;
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+import com.datastax.driver.mapping.annotations.Transient;
+import com.fasterxml.jackson.databind.JsonNode;
+
+@Table(name = TENANT_COLUMN_FAMILY_NAME)
+public final class TenantEntity implements SearchTextEntity<Tenant> {
+
+    @Transient
+    private static final long serialVersionUID = -6198635547142409206L;
+    
+    @PartitionKey(value = 0)
+    @Column(name = ID_PROPERTY)
+    private UUID id;
+
+    @Column(name = TENANT_TITLE_PROPERTY)
+    private String title;
+    
+    @Column(name = SEARCH_TEXT_PROPERTY)
+    private String searchText;
+
+    @Column(name = TENANT_REGION_PROPERTY)
+    private String region;
+    
+    @Column(name = COUNTRY_PROPERTY)
+    private String country;
+    
+    @Column(name = STATE_PROPERTY)
+    private String state;
+
+    @Column(name = CITY_PROPERTY)
+    private String city;
+
+    @Column(name = ADDRESS_PROPERTY)
+    private String address;
+
+    @Column(name = ADDRESS2_PROPERTY)
+    private String address2;
+
+    @Column(name = ZIP_PROPERTY)
+    private String zip;
+
+    @Column(name = PHONE_PROPERTY)
+    private String phone;
+
+    @Column(name = EMAIL_PROPERTY)
+    private String email;
+
+    @Column(name = TENANT_ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class)
+    private JsonNode additionalInfo;
+
+    public TenantEntity() {
+        super();
+    }
+
+    public TenantEntity(Tenant tenant) {
+        if (tenant.getId() != null) {
+            this.id = tenant.getId().getId();
+        }
+        this.title = tenant.getTitle();
+        this.region = tenant.getRegion();
+        this.country = tenant.getCountry();
+        this.state = tenant.getState();
+        this.city = tenant.getCity();
+        this.address = tenant.getAddress();
+        this.address2 = tenant.getAddress2();
+        this.zip = tenant.getZip();
+        this.phone = tenant.getPhone();
+        this.email = tenant.getEmail();
+        this.additionalInfo = tenant.getAdditionalInfo();
+    }
+    
+    public UUID getId() {
+        return id;
+    }
+
+    public void setId(UUID id) {
+        this.id = id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getRegion() {
+        return region;
+    }
+
+    public void setRegion(String region) {
+        this.region = region;
+    }
+
+    public String getCountry() {
+        return country;
+    }
+
+    public void setCountry(String country) {
+        this.country = country;
+    }
+
+    public String getState() {
+        return state;
+    }
+
+    public void setState(String state) {
+        this.state = state;
+    }
+
+    public String getCity() {
+        return city;
+    }
+
+    public void setCity(String city) {
+        this.city = city;
+    }
+
+    public String getAddress() {
+        return address;
+    }
+
+    public void setAddress(String address) {
+        this.address = address;
+    }
+
+    public String getAddress2() {
+        return address2;
+    }
+
+    public void setAddress2(String address2) {
+        this.address2 = address2;
+    }
+
+    public String getZip() {
+        return zip;
+    }
+
+    public void setZip(String zip) {
+        this.zip = zip;
+    }
+
+    public String getPhone() {
+        return phone;
+    }
+
+    public void setPhone(String phone) {
+        this.phone = phone;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    public JsonNode getAdditionalInfo() {
+        return additionalInfo;
+    }
+
+    public void setAdditionalInfo(JsonNode additionalInfo) {
+        this.additionalInfo = additionalInfo;
+    }
+
+    @Override
+    public String getSearchTextSource() {
+        return title;
+    }
+
+    @Override
+    public void setSearchText(String searchText) {
+        this.searchText = searchText;
+    }
+    
+    public String getSearchText() {
+        return searchText;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((additionalInfo == null) ? 0 : additionalInfo.hashCode());
+        result = prime * result + ((address == null) ? 0 : address.hashCode());
+        result = prime * result + ((address2 == null) ? 0 : address2.hashCode());
+        result = prime * result + ((city == null) ? 0 : city.hashCode());
+        result = prime * result + ((country == null) ? 0 : country.hashCode());
+        result = prime * result + ((email == null) ? 0 : email.hashCode());
+        result = prime * result + ((id == null) ? 0 : id.hashCode());
+        result = prime * result + ((phone == null) ? 0 : phone.hashCode());
+        result = prime * result + ((region == null) ? 0 : region.hashCode());
+        result = prime * result + ((state == null) ? 0 : state.hashCode());
+        result = prime * result + ((title == null) ? 0 : title.hashCode());
+        result = prime * result + ((zip == null) ? 0 : zip.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        TenantEntity other = (TenantEntity) obj;
+        if (additionalInfo == null) {
+            if (other.additionalInfo != null)
+                return false;
+        } else if (!additionalInfo.equals(other.additionalInfo))
+            return false;
+        if (address == null) {
+            if (other.address != null)
+                return false;
+        } else if (!address.equals(other.address))
+            return false;
+        if (address2 == null) {
+            if (other.address2 != null)
+                return false;
+        } else if (!address2.equals(other.address2))
+            return false;
+        if (city == null) {
+            if (other.city != null)
+                return false;
+        } else if (!city.equals(other.city))
+            return false;
+        if (country == null) {
+            if (other.country != null)
+                return false;
+        } else if (!country.equals(other.country))
+            return false;
+        if (email == null) {
+            if (other.email != null)
+                return false;
+        } else if (!email.equals(other.email))
+            return false;
+        if (id == null) {
+            if (other.id != null)
+                return false;
+        } else if (!id.equals(other.id))
+            return false;
+        if (phone == null) {
+            if (other.phone != null)
+                return false;
+        } else if (!phone.equals(other.phone))
+            return false;
+        if (region == null) {
+            if (other.region != null)
+                return false;
+        } else if (!region.equals(other.region))
+            return false;
+        if (state == null) {
+            if (other.state != null)
+                return false;
+        } else if (!state.equals(other.state))
+            return false;
+        if (title == null) {
+            if (other.title != null)
+                return false;
+        } else if (!title.equals(other.title))
+            return false;
+        if (zip == null) {
+            if (other.zip != null)
+                return false;
+        } else if (!zip.equals(other.zip))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("TenantEntity [id=");
+        builder.append(id);
+        builder.append(", title=");
+        builder.append(title);
+        builder.append(", region=");
+        builder.append(region);
+        builder.append(", country=");
+        builder.append(country);
+        builder.append(", state=");
+        builder.append(state);
+        builder.append(", city=");
+        builder.append(city);
+        builder.append(", address=");
+        builder.append(address);
+        builder.append(", address2=");
+        builder.append(address2);
+        builder.append(", zip=");
+        builder.append(zip);
+        builder.append(", phone=");
+        builder.append(phone);
+        builder.append(", email=");
+        builder.append(email);
+        builder.append(", additionalInfo=");
+        builder.append(additionalInfo);
+        builder.append("]");
+        return builder.toString();
+    }
+
+    @Override
+    public Tenant toData() {
+        Tenant tenant = new Tenant(new TenantId(id));
+        tenant.setCreatedTime(UUIDs.unixTimestamp(id));
+        tenant.setTitle(title);
+        tenant.setRegion(region);
+        tenant.setCountry(country);
+        tenant.setState(state);
+        tenant.setCity(city);
+        tenant.setAddress(address);
+        tenant.setAddress2(address2);
+        tenant.setZip(zip);
+        tenant.setPhone(phone);
+        tenant.setEmail(email);
+        tenant.setAdditionalInfo(additionalInfo);
+        return tenant;
+    }
+
+
+}
\ No newline at end of file
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ToData.java b/dao/src/main/java/org/thingsboard/server/dao/model/ToData.java
new file mode 100644
index 0000000..53dd9c9
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/ToData.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model;
+
+/**
+ * The interface To dto.
+ *
+ * @param <T> the type parameter
+ */
+public interface ToData<T> {
+
+    /**
+     * This method convert domain model object to data transfer object.
+     *
+     * @return the dto object
+     */
+    T toData();
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/type/AuthorityCodec.java b/dao/src/main/java/org/thingsboard/server/dao/model/type/AuthorityCodec.java
new file mode 100644
index 0000000..35f708d
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/type/AuthorityCodec.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model.type;
+
+import org.thingsboard.server.common.data.security.Authority;
+
+import com.datastax.driver.extras.codecs.enums.EnumNameCodec;
+
+public class AuthorityCodec extends EnumNameCodec<Authority> {
+
+    public AuthorityCodec() {
+        super(Authority.class);
+    }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/type/ComponentLifecycleStateCodec.java b/dao/src/main/java/org/thingsboard/server/dao/model/type/ComponentLifecycleStateCodec.java
new file mode 100644
index 0000000..fb6d309
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/type/ComponentLifecycleStateCodec.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model.type;
+
+import com.datastax.driver.extras.codecs.enums.EnumNameCodec;
+import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
+import org.thingsboard.server.common.data.security.Authority;
+
+public class ComponentLifecycleStateCodec extends EnumNameCodec<ComponentLifecycleState> {
+
+    public ComponentLifecycleStateCodec() {
+        super(ComponentLifecycleState.class);
+    }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/type/ComponentScopeCodec.java b/dao/src/main/java/org/thingsboard/server/dao/model/type/ComponentScopeCodec.java
new file mode 100644
index 0000000..7a3d4db
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/type/ComponentScopeCodec.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model.type;
+
+import com.datastax.driver.extras.codecs.enums.EnumNameCodec;
+import org.thingsboard.server.common.data.plugin.ComponentScope;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+
+public class ComponentScopeCodec extends EnumNameCodec<ComponentScope> {
+
+    public ComponentScopeCodec() {
+        super(ComponentScope.class);
+    }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/type/ComponentTypeCodec.java b/dao/src/main/java/org/thingsboard/server/dao/model/type/ComponentTypeCodec.java
new file mode 100644
index 0000000..9953e8b
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/type/ComponentTypeCodec.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model.type;
+
+import com.datastax.driver.extras.codecs.enums.EnumNameCodec;
+import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+
+public class ComponentTypeCodec extends EnumNameCodec<ComponentType> {
+
+    public ComponentTypeCodec() {
+        super(ComponentType.class);
+    }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/type/DeviceCredentialsTypeCodec.java b/dao/src/main/java/org/thingsboard/server/dao/model/type/DeviceCredentialsTypeCodec.java
new file mode 100644
index 0000000..25a8a27
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/type/DeviceCredentialsTypeCodec.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model.type;
+
+import org.thingsboard.server.common.data.security.DeviceCredentialsType;
+
+import com.datastax.driver.extras.codecs.enums.EnumNameCodec;
+
+public class DeviceCredentialsTypeCodec extends EnumNameCodec<DeviceCredentialsType> {
+
+    public DeviceCredentialsTypeCodec() {
+        super(DeviceCredentialsType.class);
+    }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/type/EntityTypeCodec.java b/dao/src/main/java/org/thingsboard/server/dao/model/type/EntityTypeCodec.java
new file mode 100644
index 0000000..5f19d72
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/type/EntityTypeCodec.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model.type;
+
+import com.datastax.driver.extras.codecs.enums.EnumNameCodec;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+
+public class EntityTypeCodec extends EnumNameCodec<EntityType> {
+
+    public EntityTypeCodec() {
+        super(EntityType.class);
+    }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/type/JsonCodec.java b/dao/src/main/java/org/thingsboard/server/dao/model/type/JsonCodec.java
new file mode 100644
index 0000000..5dc9d10
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/type/JsonCodec.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model.type;
+
+import com.datastax.driver.extras.codecs.json.JacksonJsonCodec;
+import com.fasterxml.jackson.databind.JsonNode;
+
+public class JsonCodec extends JacksonJsonCodec<JsonNode> {
+
+    public JsonCodec() {
+        super(JsonNode.class);
+    }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/UserCredentialsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/UserCredentialsEntity.java
new file mode 100644
index 0000000..ebf914d
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/UserCredentialsEntity.java
@@ -0,0 +1,194 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model;
+
+import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.USER_CREDENTIALS_ACTIVATE_TOKEN_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.USER_CREDENTIALS_COLUMN_FAMILY_NAME;
+import static org.thingsboard.server.dao.model.ModelConstants.USER_CREDENTIALS_PASSWORD_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.USER_CREDENTIALS_USER_ID_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.USER_CREDENTIALS_ENABLED_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.USER_CREDENTIALS_RESET_TOKEN_PROPERTY;
+
+import java.util.UUID;
+
+import org.thingsboard.server.common.data.id.UserCredentialsId;
+import org.thingsboard.server.common.data.id.UserId;
+import org.thingsboard.server.common.data.security.UserCredentials;
+
+import com.datastax.driver.core.utils.UUIDs;
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+import com.datastax.driver.mapping.annotations.Transient;
+
+@Table(name = USER_CREDENTIALS_COLUMN_FAMILY_NAME)
+public final class UserCredentialsEntity implements BaseEntity<UserCredentials> {
+
+    @Transient
+    private static final long serialVersionUID = 1348221414123438374L;
+    
+    @PartitionKey(value = 0)
+    @Column(name = ID_PROPERTY)
+    private UUID id;
+    
+    @Column(name = USER_CREDENTIALS_USER_ID_PROPERTY)
+    private UUID userId;
+
+    @Column(name = USER_CREDENTIALS_ENABLED_PROPERTY)
+    private boolean enabled;
+
+    @Column(name = USER_CREDENTIALS_PASSWORD_PROPERTY)
+    private String password;
+
+    @Column(name = USER_CREDENTIALS_ACTIVATE_TOKEN_PROPERTY)
+    private String activateToken;
+
+    @Column(name = USER_CREDENTIALS_RESET_TOKEN_PROPERTY)
+    private String resetToken;
+
+    public UserCredentialsEntity() {
+        super();
+    }
+
+    public UserCredentialsEntity(UserCredentials userCredentials) {
+        if (userCredentials.getId() != null) {
+            this.id = userCredentials.getId().getId();
+        }
+        if (userCredentials.getUserId() != null) {
+            this.userId = userCredentials.getUserId().getId();
+        }
+        this.enabled = userCredentials.isEnabled();
+        this.password = userCredentials.getPassword();
+        this.activateToken = userCredentials.getActivateToken();
+        this.resetToken = userCredentials.getResetToken();
+    }
+    
+    public UUID getId() {
+        return id;
+    }
+
+    public void setId(UUID id) {
+        this.id = id;
+    }
+
+    public UUID getUserId() {
+        return userId;
+    }
+
+    public void setUserId(UUID userId) {
+        this.userId = userId;
+    }
+    
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public String getActivateToken() {
+        return activateToken;
+    }
+
+    public void setActivateToken(String activateToken) {
+        this.activateToken = activateToken;
+    }
+
+    public String getResetToken() {
+        return resetToken;
+    }
+
+    public void setResetToken(String resetToken) {
+        this.resetToken = resetToken;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((activateToken == null) ? 0 : activateToken.hashCode());
+        result = prime * result + (enabled ? 1231 : 1237);
+        result = prime * result + ((id == null) ? 0 : id.hashCode());
+        result = prime * result + ((password == null) ? 0 : password.hashCode());
+        result = prime * result + ((resetToken == null) ? 0 : resetToken.hashCode());
+        result = prime * result + ((userId == null) ? 0 : userId.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        UserCredentialsEntity other = (UserCredentialsEntity) obj;
+        if (activateToken == null) {
+            if (other.activateToken != null)
+                return false;
+        } else if (!activateToken.equals(other.activateToken))
+            return false;
+        if (enabled != other.enabled)
+            return false;
+        if (id == null) {
+            if (other.id != null)
+                return false;
+        } else if (!id.equals(other.id))
+            return false;
+        if (password == null) {
+            if (other.password != null)
+                return false;
+        } else if (!password.equals(other.password))
+            return false;
+        if (resetToken == null) {
+            if (other.resetToken != null)
+                return false;
+        } else if (!resetToken.equals(other.resetToken))
+            return false;
+        if (userId == null) {
+            if (other.userId != null)
+                return false;
+        } else if (!userId.equals(other.userId))
+            return false;
+        return true;
+    }
+
+    @Override
+    public UserCredentials toData() {
+        UserCredentials userCredentials = new UserCredentials(new UserCredentialsId(id));
+        userCredentials.setCreatedTime(UUIDs.unixTimestamp(id));
+        if (userId != null) {
+            userCredentials.setUserId(new UserId(userId));
+        }
+        userCredentials.setEnabled(enabled);
+        userCredentials.setPassword(password);
+        userCredentials.setActivateToken(activateToken);
+        userCredentials.setResetToken(resetToken);
+        return userCredentials;
+    }
+
+}
\ No newline at end of file
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/UserEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/UserEntity.java
new file mode 100644
index 0000000..12dec6d
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/UserEntity.java
@@ -0,0 +1,287 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model;
+
+import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.USER_ADDITIONAL_INFO_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.USER_AUTHORITY_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.USER_COLUMN_FAMILY_NAME;
+import static org.thingsboard.server.dao.model.ModelConstants.USER_CUSTOMER_ID_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.USER_EMAIL_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.USER_FIRST_NAME_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.USER_LAST_NAME_PROPERTY;
+import static org.thingsboard.server.dao.model.ModelConstants.USER_TENANT_ID_PROPERTY;
+
+import java.util.UUID;
+
+import org.thingsboard.server.common.data.User;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.id.UserId;
+import org.thingsboard.server.common.data.security.Authority;
+import org.thingsboard.server.dao.model.type.AuthorityCodec;
+import org.thingsboard.server.dao.model.type.JsonCodec;
+
+import com.datastax.driver.core.utils.UUIDs;
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+import com.datastax.driver.mapping.annotations.Transient;
+import com.fasterxml.jackson.databind.JsonNode;
+
+@Table(name = USER_COLUMN_FAMILY_NAME)
+public final class UserEntity implements SearchTextEntity<User> {
+
+	@Transient
+	private static final long serialVersionUID = -7740338274987723489L;
+    
+    @PartitionKey(value = 0)
+    @Column(name = ID_PROPERTY)
+    private UUID id;
+
+    @PartitionKey(value = 1)
+    @Column(name = USER_TENANT_ID_PROPERTY)
+    private UUID tenantId;
+
+    @PartitionKey(value = 2)
+    @Column(name = USER_CUSTOMER_ID_PROPERTY)
+    private UUID customerId;
+
+    @PartitionKey(value = 3)
+    @Column(name = USER_AUTHORITY_PROPERTY, codec = AuthorityCodec.class)
+    private Authority authority;
+
+    @Column(name = USER_EMAIL_PROPERTY)
+    private String email;
+    
+    @Column(name = SEARCH_TEXT_PROPERTY)
+    private String searchText;
+    
+    @Column(name = USER_FIRST_NAME_PROPERTY)
+    private String firstName;
+    
+    @Column(name = USER_LAST_NAME_PROPERTY)
+    private String lastName;
+
+    @Column(name = USER_ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class)
+    private JsonNode additionalInfo;
+
+    public UserEntity() {
+        super();
+    }
+
+    public UserEntity(User user) {
+        if (user.getId() != null) {
+            this.id = user.getId().getId();
+        }
+        this.authority = user.getAuthority();
+        if (user.getTenantId() != null) {
+        	this.tenantId = user.getTenantId().getId();
+        }
+        if (user.getCustomerId() != null) {
+        	this.customerId = user.getCustomerId().getId();
+        }
+        this.email = user.getEmail();
+        this.firstName = user.getFirstName();
+        this.lastName = user.getLastName();
+        this.additionalInfo = user.getAdditionalInfo();
+    }
+    
+	public UUID getId() {
+        return id;
+    }
+
+    public void setId(UUID id) {
+        this.id = id;
+    }
+
+    public Authority getAuthority() {
+		return authority;
+	}
+
+	public void setAuthority(Authority authority) {
+		this.authority = authority;
+	}
+
+	public UUID getTenantId() {
+		return tenantId;
+	}
+
+	public void setTenantId(UUID tenantId) {
+		this.tenantId = tenantId;
+	}
+
+	public UUID getCustomerId() {
+		return customerId;
+	}
+
+	public void setCustomerId(UUID customerId) {
+		this.customerId = customerId;
+	}
+
+	public String getEmail() {
+		return email;
+	}
+
+	public void setEmail(String email) {
+		this.email = email;
+	}
+
+	public String getFirstName() {
+		return firstName;
+	}
+
+	public void setFirstName(String firstName) {
+		this.firstName = firstName;
+	}
+
+	public String getLastName() {
+		return lastName;
+	}
+
+	public void setLastName(String lastName) {
+		this.lastName = lastName;
+	}
+
+	public JsonNode getAdditionalInfo() {
+		return additionalInfo;
+	}
+
+	public void setAdditionalInfo(JsonNode additionalInfo) {
+		this.additionalInfo = additionalInfo;
+	}
+	
+    @Override
+    public String getSearchTextSource() {
+        return email;
+    }
+
+    @Override
+    public void setSearchText(String searchText) {
+        this.searchText = searchText;
+    }
+    
+    public String getSearchText() {
+        return searchText;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((additionalInfo == null) ? 0 : additionalInfo.hashCode());
+        result = prime * result + ((authority == null) ? 0 : authority.hashCode());
+        result = prime * result + ((customerId == null) ? 0 : customerId.hashCode());
+        result = prime * result + ((email == null) ? 0 : email.hashCode());
+        result = prime * result + ((firstName == null) ? 0 : firstName.hashCode());
+        result = prime * result + ((id == null) ? 0 : id.hashCode());
+        result = prime * result + ((lastName == null) ? 0 : lastName.hashCode());
+        result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        UserEntity other = (UserEntity) obj;
+        if (additionalInfo == null) {
+            if (other.additionalInfo != null)
+                return false;
+        } else if (!additionalInfo.equals(other.additionalInfo))
+            return false;
+        if (authority != other.authority)
+            return false;
+        if (customerId == null) {
+            if (other.customerId != null)
+                return false;
+        } else if (!customerId.equals(other.customerId))
+            return false;
+        if (email == null) {
+            if (other.email != null)
+                return false;
+        } else if (!email.equals(other.email))
+            return false;
+        if (firstName == null) {
+            if (other.firstName != null)
+                return false;
+        } else if (!firstName.equals(other.firstName))
+            return false;
+        if (id == null) {
+            if (other.id != null)
+                return false;
+        } else if (!id.equals(other.id))
+            return false;
+        if (lastName == null) {
+            if (other.lastName != null)
+                return false;
+        } else if (!lastName.equals(other.lastName))
+            return false;
+        if (tenantId == null) {
+            if (other.tenantId != null)
+                return false;
+        } else if (!tenantId.equals(other.tenantId))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("UserEntity [id=");
+        builder.append(id);
+        builder.append(", authority=");
+        builder.append(authority);
+        builder.append(", tenantId=");
+        builder.append(tenantId);
+        builder.append(", customerId=");
+        builder.append(customerId);
+        builder.append(", email=");
+        builder.append(email);
+        builder.append(", firstName=");
+        builder.append(firstName);
+        builder.append(", lastName=");
+        builder.append(lastName);
+        builder.append(", additionalInfo=");
+        builder.append(additionalInfo);
+        builder.append("]");
+        return builder.toString();
+    }
+
+    @Override
+    public User toData() {
+		User user = new User(new UserId(id));
+		user.setCreatedTime(UUIDs.unixTimestamp(id));
+		user.setAuthority(authority);
+		if (tenantId != null) {
+			user.setTenantId(new TenantId(tenantId));
+		}
+		if (customerId != null) {
+			user.setCustomerId(new CustomerId(customerId));
+		}
+		user.setEmail(email);
+		user.setFirstName(firstName);
+		user.setLastName(lastName);
+		user.setAdditionalInfo(additionalInfo);
+        return user;
+    }
+
+}
\ No newline at end of file
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/WidgetsBundleEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/WidgetsBundleEntity.java
new file mode 100644
index 0000000..60889b6
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/WidgetsBundleEntity.java
@@ -0,0 +1,187 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model;
+
+
+import com.datastax.driver.core.utils.UUIDs;
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+import com.datastax.driver.mapping.annotations.Transient;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.id.WidgetsBundleId;
+import org.thingsboard.server.common.data.widget.WidgetsBundle;
+
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+@Table(name = ModelConstants.WIDGETS_BUNDLE_COLUMN_FAMILY_NAME)
+public final class WidgetsBundleEntity implements SearchTextEntity<WidgetsBundle> {
+
+    @Transient
+    private static final long serialVersionUID = -8842195928585650849L;
+
+    @PartitionKey(value = 0)
+    @Column(name = ModelConstants.ID_PROPERTY)
+    private UUID id;
+
+    @PartitionKey(value = 1)
+    @Column(name = ModelConstants.WIDGETS_BUNDLE_TENANT_ID_PROPERTY)
+    private UUID tenantId;
+
+    @Column(name = ModelConstants.WIDGETS_BUNDLE_ALIAS_PROPERTY)
+    private String alias;
+
+    @Column(name = ModelConstants.WIDGETS_BUNDLE_TITLE_PROPERTY)
+    private String title;
+
+    @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
+    private String searchText;
+
+    @Column(name = ModelConstants.WIDGETS_BUNDLE_IMAGE_PROPERTY)
+    private ByteBuffer image;
+
+    public WidgetsBundleEntity() {
+        super();
+    }
+
+    public WidgetsBundleEntity(WidgetsBundle widgetsBundle) {
+        if (widgetsBundle.getId() != null) {
+            this.id = widgetsBundle.getId().getId();
+        }
+        if (widgetsBundle.getTenantId() != null) {
+            this.tenantId = widgetsBundle.getTenantId().getId();
+        }
+        this.alias = widgetsBundle.getAlias();
+        this.title = widgetsBundle.getTitle();
+        if (widgetsBundle.getImage() != null) {
+            this.image = ByteBuffer.wrap(widgetsBundle.getImage());
+        }
+    }
+
+    @Override
+    public UUID getId() {
+        return id;
+    }
+
+    @Override
+    public void setId(UUID id) {
+        this.id = id;
+    }
+
+    public UUID getTenantId() {
+        return tenantId;
+    }
+
+    public void setTenantId(UUID tenantId) {
+        this.tenantId = tenantId;
+    }
+
+    public String getAlias() {
+        return alias;
+    }
+
+    public void setAlias(String alias) {
+        this.alias = alias;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public ByteBuffer getImage() {
+        return image;
+    }
+
+    public void setImage(ByteBuffer image) {
+        this.image = image;
+    }
+
+    @Override
+    public String getSearchTextSource() {
+        return title;
+    }
+
+    @Override
+    public void setSearchText(String searchText) {
+        this.searchText = searchText;
+    }
+
+    public String getSearchText() {
+        return searchText;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = id != null ? id.hashCode() : 0;
+        result = 31 * result + (tenantId != null ? tenantId.hashCode() : 0);
+        result = 31 * result + (alias != null ? alias.hashCode() : 0);
+        result = 31 * result + (title != null ? title.hashCode() : 0);
+        result = 31 * result + (searchText != null ? searchText.hashCode() : 0);
+        result = 31 * result + (image != null ? image.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        WidgetsBundleEntity that = (WidgetsBundleEntity) o;
+
+        if (id != null ? !id.equals(that.id) : that.id != null) return false;
+        if (tenantId != null ? !tenantId.equals(that.tenantId) : that.tenantId != null) return false;
+        if (alias != null ? !alias.equals(that.alias) : that.alias != null) return false;
+        if (title != null ? !title.equals(that.title) : that.title != null) return false;
+        if (searchText != null ? !searchText.equals(that.searchText) : that.searchText != null) return false;
+        return image != null ? image.equals(that.image) : that.image == null;
+
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("WidgetsBundleEntity{");
+        sb.append("id=").append(id);
+        sb.append(", tenantId=").append(tenantId);
+        sb.append(", alias='").append(alias).append('\'');
+        sb.append(", title='").append(title).append('\'');
+        sb.append(", searchText='").append(searchText).append('\'');
+        sb.append(", image=").append(image);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public WidgetsBundle toData() {
+        WidgetsBundle widgetsBundle = new WidgetsBundle(new WidgetsBundleId(id));
+        widgetsBundle.setCreatedTime(UUIDs.unixTimestamp(id));
+        if (tenantId != null) {
+            widgetsBundle.setTenantId(new TenantId(tenantId));
+        }
+        widgetsBundle.setAlias(alias);
+        widgetsBundle.setTitle(title);
+        if (image != null) {
+            byte[] imageByteArray = new byte[image.remaining()];
+            image.get(imageByteArray);
+            widgetsBundle.setImage(imageByteArray);
+        }
+        return widgetsBundle;
+    }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/WidgetTypeEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/WidgetTypeEntity.java
new file mode 100644
index 0000000..df33948
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/WidgetTypeEntity.java
@@ -0,0 +1,178 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model;
+
+import com.datastax.driver.core.utils.UUIDs;
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+import com.datastax.driver.mapping.annotations.Transient;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.id.WidgetTypeId;
+import org.thingsboard.server.common.data.widget.WidgetType;
+import org.thingsboard.server.dao.model.type.JsonCodec;
+
+import java.util.UUID;
+
+@Table(name = ModelConstants.WIDGET_TYPE_COLUMN_FAMILY_NAME)
+public final class WidgetTypeEntity implements BaseEntity<WidgetType> {
+
+    @Transient
+    private static final long serialVersionUID = 3591054897680176342L;
+
+    @PartitionKey(value = 0)
+    @Column(name = ModelConstants.ID_PROPERTY)
+    private UUID id;
+
+    @PartitionKey(value = 1)
+    @Column(name = ModelConstants.WIDGET_TYPE_TENANT_ID_PROPERTY)
+    private UUID tenantId;
+
+    @PartitionKey(value = 2)
+    @Column(name = ModelConstants.WIDGET_TYPE_BUNDLE_ALIAS_PROPERTY)
+    private String bundleAlias;
+
+    @Column(name = ModelConstants.WIDGET_TYPE_ALIAS_PROPERTY)
+    private String alias;
+
+    @Column(name = ModelConstants.WIDGET_TYPE_NAME_PROPERTY)
+    private String name;
+
+    @Column(name = ModelConstants.WIDGET_TYPE_DESCRIPTOR_PROPERTY, codec = JsonCodec.class)
+    private JsonNode descriptor;
+
+    public WidgetTypeEntity() {
+        super();
+    }
+
+    public WidgetTypeEntity(WidgetType widgetType) {
+        if (widgetType.getId() != null) {
+            this.id = widgetType.getId().getId();
+        }
+        if (widgetType.getTenantId() != null) {
+            this.tenantId = widgetType.getTenantId().getId();
+        }
+        this.bundleAlias = widgetType.getBundleAlias();
+        this.alias = widgetType.getAlias();
+        this.name = widgetType.getName();
+        this.descriptor = widgetType.getDescriptor();
+    }
+
+    @Override
+    public UUID getId() {
+        return id;
+    }
+
+    @Override
+    public void setId(UUID id) {
+        this.id = id;
+    }
+
+    public UUID getTenantId() {
+        return tenantId;
+    }
+
+    public void setTenantId(UUID tenantId) {
+        this.tenantId = tenantId;
+    }
+
+    public String getBundleAlias() {
+        return bundleAlias;
+    }
+
+    public void setBundleAlias(String bundleAlias) {
+        this.bundleAlias = bundleAlias;
+    }
+
+    public String getAlias() {
+        return alias;
+    }
+
+    public void setAlias(String alias) {
+        this.alias = alias;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public JsonNode getDescriptor() {
+        return descriptor;
+    }
+
+    public void setDescriptor(JsonNode descriptor) {
+        this.descriptor = descriptor;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = id != null ? id.hashCode() : 0;
+        result = 31 * result + (tenantId != null ? tenantId.hashCode() : 0);
+        result = 31 * result + (bundleAlias != null ? bundleAlias.hashCode() : 0);
+        result = 31 * result + (alias != null ? alias.hashCode() : 0);
+        result = 31 * result + (name != null ? name.hashCode() : 0);
+        result = 31 * result + (descriptor != null ? descriptor.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        WidgetTypeEntity that = (WidgetTypeEntity) o;
+
+        if (id != null ? !id.equals(that.id) : that.id != null) return false;
+        if (tenantId != null ? !tenantId.equals(that.tenantId) : that.tenantId != null) return false;
+        if (bundleAlias != null ? !bundleAlias.equals(that.bundleAlias) : that.bundleAlias != null) return false;
+        if (alias != null ? !alias.equals(that.alias) : that.alias != null) return false;
+        if (name != null ? !name.equals(that.name) : that.name != null) return false;
+        return descriptor != null ? descriptor.equals(that.descriptor) : that.descriptor == null;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("WidgetTypeEntity{");
+        sb.append("id=").append(id);
+        sb.append(", tenantId=").append(tenantId);
+        sb.append(", bundleAlias='").append(bundleAlias).append('\'');
+        sb.append(", alias='").append(alias).append('\'');
+        sb.append(", name='").append(name).append('\'');
+        sb.append(", descriptor=").append(descriptor);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public WidgetType toData() {
+        WidgetType widgetType = new WidgetType(new WidgetTypeId(id));
+        widgetType.setCreatedTime(UUIDs.unixTimestamp(id));
+        if (tenantId != null) {
+            widgetType.setTenantId(new TenantId(tenantId));
+        }
+        widgetType.setBundleAlias(bundleAlias);
+        widgetType.setAlias(alias);
+        widgetType.setName(name);
+        widgetType.setDescriptor(descriptor);
+        return widgetType;
+    }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/wrapper/EntityResultSet.java b/dao/src/main/java/org/thingsboard/server/dao/model/wrapper/EntityResultSet.java
new file mode 100644
index 0000000..be02f72
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/wrapper/EntityResultSet.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.model.wrapper;
+
+
+import com.datastax.driver.core.ResultSet;
+
+public class EntityResultSet<T> {
+
+  private ResultSet resultSet;
+  private T entity;
+
+  public EntityResultSet(ResultSet resultSet, T entity) {
+    this.resultSet = resultSet;
+    this.entity = entity;
+  }
+
+  public T getEntity() {
+    return entity;
+  }
+
+  public boolean wasApplied() {
+    return resultSet.wasApplied();
+  }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/plugin/BasePluginDao.java b/dao/src/main/java/org/thingsboard/server/dao/plugin/BasePluginDao.java
new file mode 100644
index 0000000..2bed72c
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/plugin/BasePluginDao.java
@@ -0,0 +1,123 @@
+/**
+ * Copyright © 2016 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.plugin;
+
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.querybuilder.Select;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.id.PluginId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.plugin.PluginMetaData;
+import org.thingsboard.server.dao.AbstractSearchTextDao;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.dao.model.PluginMetaDataEntity;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+import static com.datastax.driver.core.querybuilder.QueryBuilder.*;
+import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
+
+@Component
+@Slf4j
+public class BasePluginDao extends AbstractSearchTextDao<PluginMetaDataEntity> implements PluginDao {
+
+    @Override
+    protected Class<PluginMetaDataEntity> getColumnFamilyClass() {
+        return PluginMetaDataEntity.class;
+    }
+
+    @Override
+    protected String getColumnFamilyName() {
+        return ModelConstants.PLUGIN_COLUMN_FAMILY_NAME;
+    }
+
+    @Override
+    public PluginMetaDataEntity save(PluginMetaData plugin) {
+        return save(new PluginMetaDataEntity(plugin));
+    }
+
+    @Override
+    public PluginMetaDataEntity findById(PluginId pluginId) {
+        log.debug("Search plugin meta-data entity by id [{}]", pluginId);
+        PluginMetaDataEntity entity = super.findById(pluginId.getId());
+        if (log.isTraceEnabled()) {
+            log.trace("Search result: [{}] for plugin entity [{}]", entity != null, entity);
+        } else {
+            log.debug("Search result: [{}]", entity != null);
+        }
+        return entity;
+    }
+
+    @Override
+    public PluginMetaDataEntity findByApiToken(String apiToken) {
+        log.debug("Search plugin meta-data entity by api token [{}]", apiToken);
+        Select.Where query = select().from(ModelConstants.PLUGIN_BY_API_TOKEN_COLUMN_FAMILY_NAME).where(eq(ModelConstants.PLUGIN_API_TOKEN_PROPERTY, apiToken));
+        log.trace("Execute query [{}]", query);
+        PluginMetaDataEntity entity = findOneByStatement(query);
+        if (log.isTraceEnabled()) {
+            log.trace("Search result: [{}] for plugin entity [{}]", entity != null, entity);
+        } else {
+            log.debug("Search result: [{}]", entity != null);
+        }
+        return entity;
+    }
+
+    @Override
+    public void deleteById(UUID id) {
+        log.debug("Delete plugin meta-data entity by id [{}]", id);
+        ResultSet resultSet = removeById(id);
+        log.debug("Delete result: [{}]", resultSet.wasApplied());
+    }
+
+    @Override
+    public void deleteById(PluginId pluginId) {
+        deleteById(pluginId.getId());
+    }
+
+    @Override
+    public List<PluginMetaDataEntity> findByTenantIdAndPageLink(TenantId tenantId, TextPageLink pageLink) {
+        log.debug("Try to find plugins by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
+        List<PluginMetaDataEntity> entities = findPageWithTextSearch(ModelConstants.PLUGIN_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
+                Arrays.asList(eq(ModelConstants.PLUGIN_TENANT_ID_PROPERTY, tenantId.getId())), pageLink);
+        if (log.isTraceEnabled()) {
+            log.trace("Search result: [{}]", Arrays.toString(entities.toArray()));
+        } else {
+            log.debug("Search result: [{}]", entities.size());
+        }
+        return entities;
+    }
+
+    @Override
+    public List<PluginMetaDataEntity> findAllTenantPluginsByTenantId(UUID tenantId, TextPageLink pageLink) {
+        log.debug("Try to find all tenant plugins by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
+        List<PluginMetaDataEntity> pluginEntities = findPageWithTextSearch(ModelConstants.PLUGIN_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
+                Arrays.asList(in(ModelConstants.PLUGIN_TENANT_ID_PROPERTY, Arrays.asList(NULL_UUID, tenantId))),
+                pageLink);
+        if (log.isTraceEnabled()) {
+            log.trace("Search result: [{}]", Arrays.toString(pluginEntities.toArray()));
+        } else {
+            log.debug("Search result: [{}]", pluginEntities.size());
+        }
+        return pluginEntities;
+    }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/plugin/BasePluginService.java b/dao/src/main/java/org/thingsboard/server/dao/plugin/BasePluginService.java
new file mode 100644
index 0000000..732d957
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/plugin/BasePluginService.java
@@ -0,0 +1,260 @@
+/**
+ * Copyright © 2016 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.plugin;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.common.data.id.PluginId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
+import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.common.data.plugin.PluginMetaData;
+import org.thingsboard.server.dao.component.ComponentDescriptorService;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.exception.DatabaseException;
+import org.thingsboard.server.dao.exception.IncorrectParameterException;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.dao.model.PluginMetaDataEntity;
+import org.thingsboard.server.dao.model.RuleMetaDataEntity;
+import org.thingsboard.server.dao.rule.RuleDao;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.service.PaginatedRemover;
+import org.thingsboard.server.dao.service.Validator;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import static org.thingsboard.server.dao.DaoUtil.convertDataList;
+import static org.thingsboard.server.dao.DaoUtil.getData;
+
+@Service
+@Slf4j
+public class BasePluginService implements PluginService {
+
+    //TODO: move to a better place.
+    public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID);
+
+    @Autowired
+    private PluginDao pluginDao;
+
+    @Autowired
+    private RuleDao ruleDao;
+
+    @Autowired
+    private ComponentDescriptorService componentDescriptorService;
+
+    @Override
+    public PluginMetaData savePlugin(PluginMetaData plugin) {
+        pluginValidator.validate(plugin);
+        if (plugin.getTenantId() == null) {
+            log.trace("Save system plugin metadata with predefined id {}", SYSTEM_TENANT);
+            plugin.setTenantId(SYSTEM_TENANT);
+        }
+        if (plugin.getId() != null) {
+            PluginMetaData oldVersion = getData(pluginDao.findById(plugin.getId()));
+            if (plugin.getState() == null) {
+                plugin.setState(oldVersion.getState());
+            } else if (plugin.getState() != oldVersion.getState()) {
+                throw new IncorrectParameterException("Use Activate/Suspend method to control state of the plugin!");
+            }
+        } else {
+            if (plugin.getState() == null) {
+                plugin.setState(ComponentLifecycleState.SUSPENDED);
+            } else if (plugin.getState() != ComponentLifecycleState.SUSPENDED) {
+                throw new IncorrectParameterException("Use Activate/Suspend method to control state of the plugin!");
+            }
+        }
+        ComponentDescriptor descriptor = componentDescriptorService.findByClazz(plugin.getClazz());
+        if (descriptor == null) {
+            throw new IncorrectParameterException("Plugin descriptor not found!");
+        } else if (!ComponentType.PLUGIN.equals(descriptor.getType())) {
+            throw new IncorrectParameterException("Plugin class is actually " + descriptor.getType() + "!");
+        }
+        PluginMetaDataEntity entity = pluginDao.findByApiToken(plugin.getApiToken());
+        if (entity != null && (plugin.getId() == null || !entity.getId().equals(plugin.getId().getId()))) {
+            throw new IncorrectParameterException("API token is already reserved!");
+        }
+        if (!componentDescriptorService.validate(descriptor, plugin.getConfiguration())) {
+            throw new IncorrectParameterException("Filters configuration is not valid!");
+        }
+        return getData(pluginDao.save(plugin));
+    }
+
+    @Override
+    public PluginMetaData findPluginById(PluginId pluginId) {
+        Validator.validateId(pluginId, "Incorrect plugin id for search request.");
+        return getData(pluginDao.findById(pluginId));
+    }
+
+    @Override
+    public PluginMetaData findPluginByApiToken(String apiToken) {
+        Validator.validateString(apiToken, "Incorrect plugin apiToken for search request.");
+        return getData(pluginDao.findByApiToken(apiToken));
+    }
+
+    @Override
+    public TextPageData<PluginMetaData> findSystemPlugins(TextPageLink pageLink) {
+        Validator.validatePageLink(pageLink, "Incorrect PageLink object for search system plugin request.");
+        List<PluginMetaDataEntity> pluginEntities = pluginDao.findByTenantIdAndPageLink(SYSTEM_TENANT, pageLink);
+        List<PluginMetaData> plugins = convertDataList(pluginEntities);
+        return new TextPageData<>(plugins, pageLink);
+    }
+
+    @Override
+    public TextPageData<PluginMetaData> findTenantPlugins(TenantId tenantId, TextPageLink pageLink) {
+        Validator.validateId(tenantId, "Incorrect tenant id for search plugins request.");
+        Validator.validatePageLink(pageLink, "Incorrect PageLink object for search plugin request.");
+        List<PluginMetaDataEntity> pluginEntities = pluginDao.findByTenantIdAndPageLink(tenantId, pageLink);
+        List<PluginMetaData> plugins = convertDataList(pluginEntities);
+        return new TextPageData<>(plugins, pageLink);
+    }
+
+    @Override
+    public List<PluginMetaData> findSystemPlugins() {
+        log.trace("Executing findSystemPlugins");
+        List<PluginMetaData> plugins = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(300);
+        TextPageData<PluginMetaData> pageData = null;
+        do {
+            pageData = findSystemPlugins(pageLink);
+            plugins.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+        return plugins;
+    }
+
+    @Override
+    public TextPageData<PluginMetaData> findAllTenantPluginsByTenantIdAndPageLink(TenantId tenantId, TextPageLink pageLink) {
+        log.trace("Executing findAllTenantPluginsByTenantIdAndPageLink, tenantId [{}], pageLink [{}]", tenantId, pageLink);
+        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
+        Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
+        List<PluginMetaDataEntity> pluginsEntities = pluginDao.findAllTenantPluginsByTenantId(tenantId.getId(), pageLink);
+        List<PluginMetaData> plugins = convertDataList(pluginsEntities);
+        return new TextPageData<>(plugins, pageLink);
+    }
+
+    @Override
+    public List<PluginMetaData> findAllTenantPluginsByTenantId(TenantId tenantId) {
+        log.trace("Executing findAllTenantPluginsByTenantId, tenantId [{}]", tenantId);
+        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
+        List<PluginMetaData> plugins = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(300);
+        TextPageData<PluginMetaData> pageData = null;
+        do {
+            pageData = findAllTenantPluginsByTenantIdAndPageLink(tenantId, pageLink);
+            plugins.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+        return plugins;
+    }
+
+    @Override
+    public void activatePluginById(PluginId pluginId) {
+        updateLifeCycleState(pluginId, ComponentLifecycleState.ACTIVE);
+    }
+
+    @Override
+    public void suspendPluginById(PluginId pluginId) {
+        PluginMetaDataEntity plugin = pluginDao.findById(pluginId);
+        List<RuleMetaDataEntity> affectedRules = ruleDao.findRulesByPlugin(plugin.getApiToken())
+                .stream().filter(rule -> rule.getState() == ComponentLifecycleState.ACTIVE).collect(Collectors.toList());
+        if (affectedRules.isEmpty()) {
+            updateLifeCycleState(pluginId, ComponentLifecycleState.SUSPENDED);
+        } else {
+            throw new DataValidationException("Can't suspend plugin that has active rules!");
+        }
+    }
+
+    private void updateLifeCycleState(PluginId pluginId, ComponentLifecycleState state) {
+        Validator.validateId(pluginId, "Incorrect plugin id for state change request.");
+        PluginMetaDataEntity plugin = pluginDao.findById(pluginId);
+        if (plugin != null) {
+            plugin.setState(state);
+            pluginDao.save(plugin);
+        } else {
+            throw new DatabaseException("Plugin not found!");
+        }
+    }
+
+    @Override
+    public void deletePluginById(PluginId pluginId) {
+        Validator.validateId(pluginId, "Incorrect plugin id for delete request.");
+        checkRulesAndDelete(pluginId.getId());
+    }
+
+    private void checkRulesAndDelete(UUID pluginId) {
+        PluginMetaDataEntity plugin = pluginDao.findById(pluginId);
+        List<RuleMetaDataEntity> affectedRules = ruleDao.findRulesByPlugin(plugin.getApiToken());
+        if (affectedRules.isEmpty()) {
+            pluginDao.deleteById(pluginId);
+        } else {
+            throw new DataValidationException("Plugin deletion will affect existing rules!");
+        }
+    }
+
+    @Override
+    public void deletePluginsByTenantId(TenantId tenantId) {
+        Validator.validateId(tenantId, "Incorrect tenant id for delete plugins request.");
+        tenantPluginRemover.removeEntitites(tenantId);
+    }
+
+
+    private DataValidator<PluginMetaData> pluginValidator =
+            new DataValidator<PluginMetaData>() {
+                @Override
+                protected void validateDataImpl(PluginMetaData plugin) {
+                    if (StringUtils.isEmpty(plugin.getName())) {
+                        throw new DataValidationException("Plugin name should be specified!.");
+                    }
+                    if (StringUtils.isEmpty(plugin.getClazz())) {
+                        throw new DataValidationException("Plugin clazz should be specified!.");
+                    }
+                    if (StringUtils.isEmpty(plugin.getApiToken())) {
+                        throw new DataValidationException("Plugin api token is not set!");
+                    }
+                    if (plugin.getConfiguration() == null) {
+                        throw new DataValidationException("Plugin configuration is not set!");
+                    }
+                }
+            };
+
+    private PaginatedRemover<TenantId, PluginMetaDataEntity> tenantPluginRemover =
+            new PaginatedRemover<TenantId, PluginMetaDataEntity>() {
+
+                @Override
+                protected List<PluginMetaDataEntity> findEntities(TenantId id, TextPageLink pageLink) {
+                    return pluginDao.findByTenantIdAndPageLink(id, pageLink);
+                }
+
+                @Override
+                protected void removeEntity(PluginMetaDataEntity entity) {
+                    checkRulesAndDelete(entity.getId());
+                }
+            };
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/plugin/PluginDao.java b/dao/src/main/java/org/thingsboard/server/dao/plugin/PluginDao.java
new file mode 100644
index 0000000..d6b7434
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/plugin/PluginDao.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright © 2016 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.plugin;
+
+import org.thingsboard.server.common.data.id.PluginId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.plugin.PluginMetaData;
+import org.thingsboard.server.dao.Dao;
+import org.thingsboard.server.dao.model.PluginMetaDataEntity;
+
+import java.util.List;
+import java.util.UUID;
+
+public interface PluginDao extends Dao<PluginMetaDataEntity> {
+
+    PluginMetaDataEntity save(PluginMetaData plugin);
+
+    PluginMetaDataEntity findById(PluginId pluginId);
+
+    PluginMetaDataEntity findByApiToken(String apiToken);
+
+    void deleteById(UUID id);
+
+    void deleteById(PluginId pluginId);
+
+    List<PluginMetaDataEntity> findByTenantIdAndPageLink(TenantId tenantId, TextPageLink pageLink);
+
+    /**
+     * Find all tenant plugins (including system) by tenantId and page link.
+     *
+     * @param tenantId the tenantId
+     * @param pageLink the page link
+     * @return the list of plugins objects
+     */
+    List<PluginMetaDataEntity> findAllTenantPluginsByTenantId(UUID tenantId, TextPageLink pageLink);
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/plugin/PluginService.java b/dao/src/main/java/org/thingsboard/server/dao/plugin/PluginService.java
new file mode 100644
index 0000000..7c16bd4
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/plugin/PluginService.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright © 2016 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.plugin;
+
+import org.thingsboard.server.common.data.id.PluginId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.plugin.PluginMetaData;
+
+import java.util.List;
+
+public interface PluginService {
+
+    PluginMetaData savePlugin(PluginMetaData plugin);
+
+    PluginMetaData findPluginById(PluginId pluginId);
+
+    PluginMetaData findPluginByApiToken(String apiToken);
+
+    TextPageData<PluginMetaData> findSystemPlugins(TextPageLink pageLink);
+
+    TextPageData<PluginMetaData> findTenantPlugins(TenantId tenantId, TextPageLink pageLink);
+
+    List<PluginMetaData> findSystemPlugins();
+
+    TextPageData<PluginMetaData> findAllTenantPluginsByTenantIdAndPageLink(TenantId tenantId, TextPageLink pageLink);
+
+    List<PluginMetaData> findAllTenantPluginsByTenantId(TenantId tenantId);
+
+    void activatePluginById(PluginId pluginId);
+
+    void suspendPluginById(PluginId pluginId);
+
+    void deletePluginById(PluginId pluginId);
+
+    void deletePluginsByTenantId(TenantId tenantId);
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleDao.java
new file mode 100644
index 0000000..0070b79
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleDao.java
@@ -0,0 +1,108 @@
+/**
+ * Copyright © 2016 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.rule;
+
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.querybuilder.Select;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.id.RuleId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.rule.RuleMetaData;
+import org.thingsboard.server.dao.AbstractSearchTextDao;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.dao.model.RuleMetaDataEntity;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+import static com.datastax.driver.core.querybuilder.QueryBuilder.*;
+import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
+
+@Component
+@Slf4j
+public class BaseRuleDao extends AbstractSearchTextDao<RuleMetaDataEntity> implements RuleDao {
+
+    @Override
+    protected Class<RuleMetaDataEntity> getColumnFamilyClass() {
+        return RuleMetaDataEntity.class;
+    }
+
+    @Override
+    protected String getColumnFamilyName() {
+        return ModelConstants.RULE_COLUMN_FAMILY_NAME;
+    }
+
+    @Override
+    public RuleMetaDataEntity findById(RuleId ruleId) {
+        return findById(ruleId.getId());
+    }
+
+    @Override
+    public RuleMetaDataEntity save(RuleMetaData rule) {
+        return save(new RuleMetaDataEntity(rule));
+    }
+
+    @Override
+    public List<RuleMetaDataEntity> findByTenantIdAndPageLink(TenantId tenantId, TextPageLink pageLink) {
+        log.debug("Try to find rules by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
+        List<RuleMetaDataEntity> entities = findPageWithTextSearch(ModelConstants.RULE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
+                Arrays.asList(eq(ModelConstants.RULE_TENANT_ID_PROPERTY, tenantId.getId())), pageLink);
+        if (log.isTraceEnabled()) {
+            log.trace("Search result: [{}]", Arrays.toString(entities.toArray()));
+        } else {
+            log.debug("Search result: [{}]", entities.size());
+        }
+        return entities;
+    }
+
+    @Override
+    public List<RuleMetaDataEntity> findAllTenantRulesByTenantId(UUID tenantId, TextPageLink pageLink) {
+        log.debug("Try to find all tenant rules by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
+        List<RuleMetaDataEntity> entities = findPageWithTextSearch(ModelConstants.RULE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
+                Arrays.asList(in(ModelConstants.RULE_TENANT_ID_PROPERTY, Arrays.asList(NULL_UUID, tenantId))),
+                pageLink);
+        if (log.isTraceEnabled()) {
+            log.trace("Search result: [{}]", Arrays.toString(entities.toArray()));
+        } else {
+            log.debug("Search result: [{}]", entities.size());
+        }
+        return entities;
+    }
+
+    @Override
+    public List<RuleMetaDataEntity> findRulesByPlugin(String pluginToken) {
+        log.debug("Search rules by api token [{}]", pluginToken);
+        Select select = select().from(ModelConstants.RULE_BY_PLUGIN_TOKEN);
+        Select.Where query = select.where();
+        query.and(eq(ModelConstants.RULE_PLUGIN_TOKEN_PROPERTY, pluginToken));
+        return findListByStatement(query);
+    }
+
+    @Override
+    public void deleteById(UUID id) {
+        log.debug("Delete rule meta-data entity by id [{}]", id);
+        ResultSet resultSet = removeById(id);
+        log.debug("Delete result: [{}]", resultSet.wasApplied());
+    }
+
+    @Override
+    public void deleteById(RuleId ruleId) {
+        deleteById(ruleId.getId());
+    }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleService.java
new file mode 100644
index 0000000..b0755e7
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleService.java
@@ -0,0 +1,294 @@
+/**
+ * Copyright © 2016 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.rule;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.common.data.id.RuleId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
+import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.common.data.plugin.PluginMetaData;
+import org.thingsboard.server.common.data.rule.RuleMetaData;
+import org.thingsboard.server.dao.component.ComponentDescriptorService;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.exception.DatabaseException;
+import org.thingsboard.server.dao.exception.IncorrectParameterException;
+import org.thingsboard.server.dao.model.RuleMetaDataEntity;
+import org.thingsboard.server.dao.plugin.PluginService;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.service.PaginatedRemover;
+import org.thingsboard.server.dao.service.Validator;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+
+import static org.thingsboard.server.dao.DaoUtil.convertDataList;
+import static org.thingsboard.server.dao.DaoUtil.getData;
+import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
+import static org.thingsboard.server.dao.service.Validator.validateId;
+import static org.thingsboard.server.dao.service.Validator.validatePageLink;
+
+@Service
+@Slf4j
+public class BaseRuleService implements RuleService {
+
+    private final TenantId systemTenantId = new TenantId(NULL_UUID);
+
+    @Autowired
+    public RuleDao ruleDao;
+
+    @Autowired
+    public PluginService pluginService;
+
+    @Autowired
+    private ComponentDescriptorService componentDescriptorService;
+
+    @Override
+    public RuleMetaData saveRule(RuleMetaData rule) {
+        ruleValidator.validate(rule);
+        if (rule.getTenantId() == null) {
+            log.trace("Save system rule metadata with predefined id {}", systemTenantId);
+            rule.setTenantId(systemTenantId);
+        }
+        if (rule.getId() != null) {
+            RuleMetaData oldVersion = getData(ruleDao.findById(rule.getId()));
+            if (rule.getState() == null) {
+                rule.setState(oldVersion.getState());
+            } else if (rule.getState() != oldVersion.getState()) {
+                throw new IncorrectParameterException("Use Activate/Suspend method to control state of the rule!");
+            }
+        } else {
+            if (rule.getState() == null) {
+                rule.setState(ComponentLifecycleState.SUSPENDED);
+            } else if (rule.getState() != ComponentLifecycleState.SUSPENDED) {
+                throw new IncorrectParameterException("Use Activate/Suspend method to control state of the rule!");
+            }
+        }
+
+        validateFilters(rule.getFilters());
+        if (rule.getProcessor() != null && !rule.getProcessor().isNull()) {
+            validateComponentJson(rule.getProcessor(), ComponentType.PROCESSOR);
+        }
+        validateComponentJson(rule.getAction(), ComponentType.ACTION);
+        validateRuleAndPluginState(rule);
+        return getData(ruleDao.save(rule));
+    }
+
+    private void validateFilters(JsonNode filtersJson) {
+        if (filtersJson == null || filtersJson.isNull()) {
+            throw new IncorrectParameterException("Rule filters are required!");
+        }
+        if (!filtersJson.isArray()) {
+            throw new IncorrectParameterException("Filters json is not an array!");
+        }
+        ArrayNode filtersArray = (ArrayNode) filtersJson;
+        for (int i = 0; i < filtersArray.size(); i++) {
+            validateComponentJson(filtersArray.get(i), ComponentType.FILTER);
+        }
+    }
+
+    private void validateComponentJson(JsonNode json, ComponentType type) {
+        if (json == null || json.isNull()) {
+            throw new IncorrectParameterException(type.name() + " is required!");
+        }
+        String clazz = getIfValid(type.name(), json, "clazz", JsonNode::isTextual, JsonNode::asText);
+        String name = getIfValid(type.name(), json, "name", JsonNode::isTextual, JsonNode::asText);
+        JsonNode configuration = getIfValid(type.name(), json, "configuration", JsonNode::isObject, node -> node);
+        ComponentDescriptor descriptor = componentDescriptorService.findByClazz(clazz);
+        if (descriptor == null) {
+            throw new IncorrectParameterException(type.name() + " clazz " + clazz + " is not a valid component!");
+        }
+        if (descriptor.getType() != type) {
+            throw new IncorrectParameterException("Clazz " + clazz + " is not a valid " + type.name() + " component!");
+        }
+        if (!componentDescriptorService.validate(descriptor, configuration)) {
+            throw new IncorrectParameterException(type.name() + " configuration is not valid!");
+        }
+    }
+
+    private void validateRuleAndPluginState(RuleMetaData rule) {
+        PluginMetaData pluginMd = pluginService.findPluginByApiToken(rule.getPluginToken());
+        if (pluginMd == null) {
+            throw new IncorrectParameterException("Rule points to non-existent plugin!");
+        }
+        if (!pluginMd.getTenantId().equals(systemTenantId) && !pluginMd.getTenantId().equals(rule.getTenantId())) {
+            throw new IncorrectParameterException("Rule access plugin that belongs to different tenant!");
+        }
+        if (rule.getState() == ComponentLifecycleState.ACTIVE && pluginMd.getState() != ComponentLifecycleState.ACTIVE) {
+            throw new IncorrectParameterException("Can't save active rule that points to inactive plugin!");
+        }
+        ComponentDescriptor pluginDescriptor = componentDescriptorService.findByClazz(pluginMd.getClazz());
+        String actionClazz = getIfValid(ComponentType.ACTION.name(), rule.getAction(), "clazz", JsonNode::isTextual, JsonNode::asText);
+        if (!Arrays.asList(pluginDescriptor.getActions().split(",")).contains(actionClazz)) {
+            throw new IncorrectParameterException("Rule's action is not supported by plugin with token " + rule.getPluginToken() + "!");
+        }
+    }
+
+    private static <T> T getIfValid(String parentName, JsonNode node, String name, Function<JsonNode, Boolean> validator, Function<JsonNode, T> extractor) {
+        if (!node.has(name)) {
+            throw new IncorrectParameterException(parentName + "'s " + name + " is not set!");
+        } else {
+            JsonNode value = node.get(name);
+            if (validator.apply(value)) {
+                return extractor.apply(value);
+            } else {
+                throw new IncorrectParameterException(parentName + "'s " + name + " is not valid!");
+            }
+        }
+    }
+
+    @Override
+    public RuleMetaData findRuleById(RuleId ruleId) {
+        validateId(ruleId, "Incorrect rule id for search rule request.");
+        return getData(ruleDao.findById(ruleId.getId()));
+    }
+
+    @Override
+    public List<RuleMetaData> findPluginRules(String pluginToken) {
+        List<RuleMetaDataEntity> ruleEntities = ruleDao.findRulesByPlugin(pluginToken);
+        return convertDataList(ruleEntities);
+    }
+
+    @Override
+    public TextPageData<RuleMetaData> findSystemRules(TextPageLink pageLink) {
+        validatePageLink(pageLink, "Incorrect PageLink object for search rule request.");
+        List<RuleMetaDataEntity> ruleEntities = ruleDao.findByTenantIdAndPageLink(systemTenantId, pageLink);
+        List<RuleMetaData> plugins = convertDataList(ruleEntities);
+        return new TextPageData<>(plugins, pageLink);
+    }
+
+    @Override
+    public TextPageData<RuleMetaData> findTenantRules(TenantId tenantId, TextPageLink pageLink) {
+        validateId(tenantId, "Incorrect tenant id for search rule request.");
+        validatePageLink(pageLink, "Incorrect PageLink object for search rule request.");
+        List<RuleMetaDataEntity> ruleEntities = ruleDao.findByTenantIdAndPageLink(tenantId, pageLink);
+        List<RuleMetaData> plugins = convertDataList(ruleEntities);
+        return new TextPageData<>(plugins, pageLink);
+    }
+
+    @Override
+    public List<RuleMetaData> findSystemRules() {
+        log.trace("Executing findSystemRules");
+        List<RuleMetaData> rules = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(300);
+        TextPageData<RuleMetaData> pageData = null;
+        do {
+            pageData = findSystemRules(pageLink);
+            rules.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+        return rules;
+    }
+
+    @Override
+    public TextPageData<RuleMetaData> findAllTenantRulesByTenantIdAndPageLink(TenantId tenantId, TextPageLink pageLink) {
+        log.trace("Executing findAllTenantRulesByTenantIdAndPageLink, tenantId [{}], pageLink [{}]", tenantId, pageLink);
+        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
+        Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
+        List<RuleMetaDataEntity> rulesEntities = ruleDao.findAllTenantRulesByTenantId(tenantId.getId(), pageLink);
+        List<RuleMetaData> rules = convertDataList(rulesEntities);
+        return new TextPageData<>(rules, pageLink);
+    }
+
+    @Override
+    public List<RuleMetaData> findAllTenantRulesByTenantId(TenantId tenantId) {
+        log.trace("Executing findAllTenantRulesByTenantId, tenantId [{}]", tenantId);
+        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
+        List<RuleMetaData> rules = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(300);
+        TextPageData<RuleMetaData> pageData = null;
+        do {
+            pageData = findAllTenantRulesByTenantIdAndPageLink(tenantId, pageLink);
+            rules.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+        return rules;
+    }
+
+    @Override
+    public void deleteRuleById(RuleId ruleId) {
+        validateId(ruleId, "Incorrect rule id for delete rule request.");
+        ruleDao.deleteById(ruleId);
+    }
+
+    @Override
+    public void activateRuleById(RuleId ruleId) {
+        updateLifeCycleState(ruleId, ComponentLifecycleState.ACTIVE);
+
+    }
+
+    @Override
+    public void suspendRuleById(RuleId ruleId) {
+        updateLifeCycleState(ruleId, ComponentLifecycleState.SUSPENDED);
+    }
+
+    private void updateLifeCycleState(RuleId ruleId, ComponentLifecycleState state) {
+        Validator.validateId(ruleId, "Incorrect rule id for state change request.");
+        RuleMetaDataEntity rule = ruleDao.findById(ruleId);
+        if (rule != null) {
+            rule.setState(state);
+            validateRuleAndPluginState(getData(rule));
+            ruleDao.save(rule);
+        } else {
+            throw new DatabaseException("Plugin not found!");
+        }
+    }
+
+    @Override
+    public void deleteRulesByTenantId(TenantId tenantId) {
+        validateId(tenantId, "Incorrect tenant id for delete rules request.");
+        tenantRulesRemover.removeEntitites(tenantId);
+    }
+
+    private DataValidator<RuleMetaData> ruleValidator =
+            new DataValidator<RuleMetaData>() {
+                @Override
+                protected void validateDataImpl(RuleMetaData rule) {
+                    if (StringUtils.isEmpty(rule.getName())) {
+                        throw new DataValidationException("Rule name should be specified!.");
+                    }
+                }
+            };
+
+    private PaginatedRemover<TenantId, RuleMetaDataEntity> tenantRulesRemover =
+            new PaginatedRemover<TenantId, RuleMetaDataEntity>() {
+
+                @Override
+                protected List<RuleMetaDataEntity> findEntities(TenantId id, TextPageLink pageLink) {
+                    return ruleDao.findByTenantIdAndPageLink(id, pageLink);
+                }
+
+                @Override
+                protected void removeEntity(RuleMetaDataEntity entity) {
+                    ruleDao.deleteById(entity.getId());
+                }
+            };
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleDao.java
new file mode 100644
index 0000000..cdca0ed
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleDao.java
@@ -0,0 +1,50 @@
+/**
+ * Copyright © 2016 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.rule;
+
+import org.thingsboard.server.common.data.id.RuleId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.rule.RuleMetaData;
+import org.thingsboard.server.dao.Dao;
+import org.thingsboard.server.dao.model.RuleMetaDataEntity;
+
+import java.util.List;
+import java.util.UUID;
+
+public interface RuleDao extends Dao<RuleMetaDataEntity> {
+
+    RuleMetaDataEntity save(RuleMetaData rule);
+
+    RuleMetaDataEntity findById(RuleId ruleId);
+
+    List<RuleMetaDataEntity> findRulesByPlugin(String pluginToken);
+
+    List<RuleMetaDataEntity> findByTenantIdAndPageLink(TenantId tenantId, TextPageLink pageLink);
+
+    /**
+     * Find all tenant rules (including system) by tenantId and page link.
+     *
+     * @param tenantId the tenantId
+     * @param pageLink the page link
+     * @return the list of rules objects
+     */
+    List<RuleMetaDataEntity> findAllTenantRulesByTenantId(UUID tenantId, TextPageLink pageLink);
+
+    void deleteById(UUID id);
+
+    void deleteById(RuleId ruleId);
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleService.java
new file mode 100644
index 0000000..e11fa93
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleService.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright © 2016 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.rule;
+
+import org.thingsboard.server.common.data.id.RuleId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.rule.RuleMetaData;
+
+import java.util.List;
+
+public interface RuleService {
+
+    RuleMetaData saveRule(RuleMetaData device);
+
+    RuleMetaData findRuleById(RuleId ruleId);
+
+    List<RuleMetaData> findPluginRules(String pluginToken);
+
+    TextPageData<RuleMetaData> findSystemRules(TextPageLink pageLink);
+
+    TextPageData<RuleMetaData> findTenantRules(TenantId tenantId, TextPageLink pageLink);
+
+    List<RuleMetaData> findSystemRules();
+
+    TextPageData<RuleMetaData> findAllTenantRulesByTenantIdAndPageLink(TenantId tenantId, TextPageLink pageLink);
+
+    List<RuleMetaData> findAllTenantRulesByTenantId(TenantId tenantId);
+
+    void activateRuleById(RuleId ruleId);
+
+    void suspendRuleById(RuleId ruleId);
+
+    void deleteRuleById(RuleId ruleId);
+
+    void deleteRulesByTenantId(TenantId tenantId);
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java
new file mode 100644
index 0000000..87101c7
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java
@@ -0,0 +1,100 @@
+/**
+ * Copyright © 2016 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 java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.validator.routines.EmailValidator;
+import org.thingsboard.server.common.data.BaseData;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+@Slf4j
+public abstract class DataValidator<D extends BaseData<?>> {
+
+    private static EmailValidator emailValidator = EmailValidator.getInstance();
+    
+    public void validate(D data) {
+        try {
+            if (data == null) {
+                throw new DataValidationException("Data object can't be null!");
+            }
+            validateDataImpl(data);
+            if (data.getId() == null) {
+                validateCreate(data);
+            } else {
+                validateUpdate(data);
+            }
+        } catch (DataValidationException e) {
+            log.error("Data object is invalid: [{}]", e.getMessage());
+            throw e;
+        }
+    }
+    
+    protected void validateDataImpl(D data) {
+    }
+    
+    protected void validateCreate(D data) {
+    }
+
+    protected void validateUpdate(D data) {
+    }
+    
+    protected boolean isSameData(D existentData, D actualData) {
+        if (actualData.getId() == null) {
+            return false;
+        } else if (!existentData.getId().equals(actualData.getId())) {
+            return false;
+        }
+        return true;
+    }
+    
+    protected static void validateEmail(String email) {
+        if (!emailValidator.isValid(email)) {
+            throw new DataValidationException("Invalid email address format '" + email + "'!");
+        }
+    }
+    
+    protected static void validateJsonStructure(JsonNode expectedNode, JsonNode actualNode) {
+        Set<String> expectedFields = new HashSet<>();        
+        Iterator<String> fieldsIterator = expectedNode.fieldNames();
+        while (fieldsIterator.hasNext()) {
+            expectedFields.add(fieldsIterator.next());
+        }
+        
+        Set<String> actualFields = new HashSet<>();        
+        fieldsIterator = actualNode.fieldNames();
+        while (fieldsIterator.hasNext()) {
+            actualFields.add(fieldsIterator.next());
+        }
+        
+        if (!expectedFields.containsAll(actualFields) || !actualFields.containsAll(expectedFields)) {
+            throw new DataValidationException("Provided json structure is different from stored one '" + actualNode + "'!");
+        }
+        
+        for (String field : actualFields) {
+            if (!actualNode.get(field).isTextual()) {
+                throw new DataValidationException("Provided json structure can't contain non-text values '" + actualNode + "'!");
+            }
+        }
+    }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/PaginatedRemover.java b/dao/src/main/java/org/thingsboard/server/dao/service/PaginatedRemover.java
new file mode 100644
index 0000000..66914d5
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/PaginatedRemover.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright © 2016 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 java.util.List;
+import java.util.UUID;
+
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.model.BaseEntity;
+
+public abstract class PaginatedRemover<I, E extends BaseEntity<?>> {
+
+    public void removeEntitites(I id) {
+        TextPageLink pageLink = new TextPageLink(100);
+        boolean hasNext = true;
+        while (hasNext) {
+            List<E> entities = findEntities(id, pageLink);
+            for (E entity : entities) {
+                removeEntity(entity);
+            }
+            hasNext = entities.size() == pageLink.getLimit();
+            if (hasNext) {
+                int index = entities.size()-1;
+                UUID idOffset = entities.get(index).getId();
+                pageLink.setIdOffset(idOffset);
+            }
+        } 
+    }
+    
+    protected abstract List<E> findEntities(I id, TextPageLink pageLink);
+    
+    protected abstract void removeEntity(E entity);
+    
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java b/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java
new file mode 100644
index 0000000..7b6fbbd
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java
@@ -0,0 +1,97 @@
+/**
+ * Copyright © 2016 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 org.thingsboard.server.common.data.id.UUIDBased;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.exception.IncorrectParameterException;
+
+import java.util.UUID;
+
+public class Validator {
+
+    /**
+     * This method validate <code>String</code> string. If string is invalid than throw
+     * <code>IncorrectParameterException</code> exception
+     *
+     * @param val          the val
+     * @param errorMessage the error message for exception
+     */
+    public static void validateString(String val, String errorMessage) {
+        if (val == null || val.isEmpty()) {
+            throw new IncorrectParameterException(errorMessage);
+        }
+    }
+
+
+    /**
+     * This method validate <code>String</code> string. If string is invalid than throw
+     * <code>IncorrectParameterException</code> exception
+     *
+     * @param val          the val
+     * @param errorMessage the error message for exception
+     */
+    public static void validatePositiveNumber(long val, String errorMessage) {
+        if (val <= 0) {
+            throw new IncorrectParameterException(errorMessage);
+        }
+    }
+
+    /**
+     * This method validate <code>UUID</code> id. If id is null than throw
+     * <code>IncorrectParameterException</code> exception
+     *
+     * @param id           the id
+     * @param errorMessage the error message for exception
+     */
+    public static void validateId(UUID id, String errorMessage) {
+        if (id == null) {
+            throw new IncorrectParameterException(errorMessage);
+        }
+    }
+
+
+    /**
+     * This method validate <code>UUIDBased</code> id. If id is null than throw
+     * <code>IncorrectParameterException</code> exception
+     *
+     * @param id           the id
+     * @param errorMessage the error message for exception
+     */
+    public static void validateId(UUIDBased id, String errorMessage) {
+        if (id == null || id.getId() == null) {
+            throw new IncorrectParameterException(errorMessage);
+        }
+    }
+
+    /**
+     * This method validate <code>PageLink</code> page link. If pageLink is invalid than throw
+     * <code>IncorrectParameterException</code> exception
+     *
+     * @param pageLink     the page link
+     * @param errorMessage the error message for exception
+     */
+    public static void validatePageLink(TextPageLink pageLink, String errorMessage) {
+        if (pageLink == null) {
+            throw new IncorrectParameterException(errorMessage);
+        } else if (pageLink.getLimit() < 1) {
+            throw new IncorrectParameterException(errorMessage);
+        } else if (pageLink.getIdOffset() != null && pageLink.getIdOffset().version() != 1) {
+            throw new IncorrectParameterException(errorMessage);
+        }
+    }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsDao.java b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsDao.java
new file mode 100644
index 0000000..c8a6e90
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsDao.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright © 2016 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.settings;
+
+import org.thingsboard.server.common.data.AdminSettings;
+import org.thingsboard.server.dao.Dao;
+import org.thingsboard.server.dao.model.AdminSettingsEntity;
+
+public interface AdminSettingsDao extends Dao<AdminSettingsEntity> {
+
+    /**
+     * Save or update admin settings object
+     *
+     * @param adminSettings the admin settings object
+     * @return saved admin settings object
+     */
+    AdminSettingsEntity save(AdminSettings adminSettings);
+    
+    /**
+     * Find admin settings by key.
+     *
+     * @param key the key
+     * @return the admin settings object
+     */
+    AdminSettingsEntity findByKey(String key);
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsDaoImpl.java b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsDaoImpl.java
new file mode 100644
index 0000000..0a99b02
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsDaoImpl.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright © 2016 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.settings;
+
+import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
+import static org.thingsboard.server.dao.model.ModelConstants.ADMIN_SETTINGS_BY_KEY_COLUMN_FAMILY_NAME;
+import static org.thingsboard.server.dao.model.ModelConstants.ADMIN_SETTINGS_COLUMN_FAMILY_NAME;
+import static org.thingsboard.server.dao.model.ModelConstants.ADMIN_SETTINGS_KEY_PROPERTY;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.AdminSettings;
+import org.thingsboard.server.dao.AbstractModelDao;
+import org.thingsboard.server.dao.model.AdminSettingsEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Repository;
+
+import com.datastax.driver.core.querybuilder.Select.Where;
+
+@Component
+@Slf4j
+public class AdminSettingsDaoImpl extends AbstractModelDao<AdminSettingsEntity> implements AdminSettingsDao {
+
+    @Override
+    protected Class<AdminSettingsEntity> getColumnFamilyClass() {
+        return AdminSettingsEntity.class;
+    }
+
+    @Override
+    protected String getColumnFamilyName() {
+        return ADMIN_SETTINGS_COLUMN_FAMILY_NAME;
+    }
+    
+    @Override
+    public AdminSettingsEntity save(AdminSettings adminSettings) {
+        log.debug("Save admin settings [{}] ", adminSettings);
+        return save(new AdminSettingsEntity(adminSettings));
+    }
+
+    @Override
+    public AdminSettingsEntity findByKey(String key) {
+        log.debug("Try to find admin settings by key [{}] ", key);
+        Where query = select().from(ADMIN_SETTINGS_BY_KEY_COLUMN_FAMILY_NAME).where(eq(ADMIN_SETTINGS_KEY_PROPERTY, key));
+        log.trace("Execute query {}", query);
+        AdminSettingsEntity adminSettingsEntity = findOneByStatement(query);
+        log.trace("Found admin settings [{}] by key [{}]", adminSettingsEntity, key);
+        return adminSettingsEntity;
+    }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java
new file mode 100644
index 0000000..72492a8
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright © 2016 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.settings;
+
+import org.thingsboard.server.common.data.AdminSettings;
+import org.thingsboard.server.common.data.id.AdminSettingsId;
+
+public interface AdminSettingsService {
+
+    public AdminSettings findAdminSettingsById(AdminSettingsId adminSettingsId);
+
+    public AdminSettings findAdminSettingsByKey(String key);
+    
+    public AdminSettings saveAdminSettings(AdminSettings adminSettings);
+    
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java
new file mode 100644
index 0000000..cb4ed01
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java
@@ -0,0 +1,88 @@
+/**
+ * Copyright © 2016 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.settings;
+
+import static org.thingsboard.server.dao.DaoUtil.getData;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.thingsboard.server.common.data.AdminSettings;
+import org.thingsboard.server.common.data.id.AdminSettingsId;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.model.AdminSettingsEntity;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.dao.service.Validator;
+
+@Service
+@Slf4j
+public class AdminSettingsServiceImpl implements AdminSettingsService {
+    
+    @Autowired
+    private AdminSettingsDao adminSettingsDao;
+
+    @Override
+    public AdminSettings findAdminSettingsById(AdminSettingsId adminSettingsId) {
+        log.trace("Executing findAdminSettingsById [{}]", adminSettingsId);
+        Validator.validateId(adminSettingsId, "Incorrect adminSettingsId " + adminSettingsId);
+        AdminSettingsEntity adminSettingsEntity = adminSettingsDao.findById(adminSettingsId.getId());
+        return getData(adminSettingsEntity);
+    }
+
+    @Override
+    public AdminSettings findAdminSettingsByKey(String key) {
+        log.trace("Executing findAdminSettingsByKey [{}]", key);
+        Validator.validateString(key, "Incorrect key " + key);
+        AdminSettingsEntity adminSettingsEntity = adminSettingsDao.findByKey(key);
+        return getData(adminSettingsEntity);
+    }
+
+    @Override
+    public AdminSettings saveAdminSettings(AdminSettings adminSettings) {
+        log.trace("Executing saveAdminSettings [{}]", adminSettings);
+        adminSettingsValidator.validate(adminSettings);
+        AdminSettingsEntity adminSettingsEntity = adminSettingsDao.save(adminSettings);
+        return getData(adminSettingsEntity);
+    }
+    
+    private DataValidator<AdminSettings> adminSettingsValidator =
+            new DataValidator<AdminSettings>() {
+        
+                @Override
+                protected void validateCreate(AdminSettings adminSettings) {
+                    throw new DataValidationException("Creation of new admin settings entry is prohibited!");
+                }
+        
+                @Override
+                protected void validateDataImpl(AdminSettings adminSettings) {
+                    if (StringUtils.isEmpty(adminSettings.getKey())) {
+                        throw new DataValidationException("Key should be specified!");
+                    }
+                    if (adminSettings.getJsonValue() == null) {
+                        throw new DataValidationException("Json value should be specified!");
+                    }
+                    AdminSettings existentAdminSettingsWithKey = findAdminSettingsByKey(adminSettings.getKey());
+                    if (existentAdminSettingsWithKey == null || !isSameData(existentAdminSettingsWithKey, adminSettings)) {
+                        throw new DataValidationException("Changing key of admin settings entry is prohibited!");
+                    }
+                    validateJsonStructure(existentAdminSettingsWithKey.getJsonValue(), adminSettings.getJsonValue());
+                }
+    };
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantDao.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantDao.java
new file mode 100644
index 0000000..98894d2
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantDao.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright © 2016 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.tenant;
+
+import java.util.List;
+
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.Dao;
+import org.thingsboard.server.dao.model.TenantEntity;
+
+public interface TenantDao extends Dao<TenantEntity> {
+
+    /**
+     * Save or update tenant object
+     *
+     * @param tenant the tenant object
+     * @return saved tenant object
+     */
+    TenantEntity save(Tenant tenant);
+    
+    /**
+     * Find tenants by region and page link.
+     * 
+     * @param region the region
+     * @param pageLink the page link
+     * @return the list of tenant objects
+     */
+    List<TenantEntity> findTenantsByRegion(String region, TextPageLink pageLink);
+    
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantDaoImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantDaoImpl.java
new file mode 100644
index 0000000..18af130
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantDaoImpl.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright © 2016 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.tenant;
+
+import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
+import static org.thingsboard.server.dao.model.ModelConstants.TENANT_BY_REGION_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME;
+import static org.thingsboard.server.dao.model.ModelConstants.TENANT_COLUMN_FAMILY_NAME;
+import static org.thingsboard.server.dao.model.ModelConstants.TENANT_REGION_PROPERTY;
+
+import java.util.Arrays;
+import java.util.List;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.AbstractSearchTextDao;
+import org.thingsboard.server.dao.model.TenantEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component
+@Slf4j
+public class TenantDaoImpl extends AbstractSearchTextDao<TenantEntity> implements TenantDao {
+
+    @Override
+    protected Class<TenantEntity> getColumnFamilyClass() {
+        return TenantEntity.class;
+    }
+
+    @Override
+    protected String getColumnFamilyName() {
+        return TENANT_COLUMN_FAMILY_NAME;
+    }
+    
+    @Override
+    public TenantEntity save(Tenant tenant) {
+        log.debug("Save tenant [{}] ", tenant);
+        return save(new TenantEntity(tenant));
+    }
+
+    @Override
+    public List<TenantEntity> findTenantsByRegion(String region, TextPageLink pageLink) {
+        log.debug("Try to find tenants by region [{}] and pageLink [{}]", region, pageLink);
+        List<TenantEntity> tenantEntities = findPageWithTextSearch(TENANT_BY_REGION_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, 
+                Arrays.asList(eq(TENANT_REGION_PROPERTY, region)), 
+                pageLink); 
+        log.trace("Found tenants [{}] by region [{}] and pageLink [{}]", tenantEntities, region, pageLink);
+        return tenantEntities;
+    }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java
new file mode 100644
index 0000000..2778415
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright © 2016 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.tenant;
+
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+
+public interface TenantService {
+
+    public Tenant findTenantById(TenantId tenantId);
+    
+    public Tenant saveTenant(Tenant tenant);
+    
+    public void deleteTenant(TenantId tenantId);
+    
+    public TextPageData<Tenant> findTenants(TextPageLink pageLink);
+    
+    //public TextPageData<Tenant> findTenantsByTitle(String title, PageLink pageLink);
+    
+    public void deleteTenants();
+    
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
new file mode 100644
index 0000000..622adf7
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
@@ -0,0 +1,148 @@
+/**
+ * Copyright © 2016 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.tenant;
+
+import static org.thingsboard.server.dao.DaoUtil.convertDataList;
+import static org.thingsboard.server.dao.DaoUtil.getData;
+
+import java.util.List;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.customer.CustomerService;
+import org.thingsboard.server.dao.dashboard.DashboardService;
+import org.thingsboard.server.dao.device.DeviceService;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.model.TenantEntity;
+import org.thingsboard.server.dao.plugin.PluginService;
+import org.thingsboard.server.dao.rule.RuleService;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.service.PaginatedRemover;
+import org.thingsboard.server.dao.user.UserService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.dao.service.Validator;
+import org.thingsboard.server.dao.widget.WidgetsBundleService;
+
+@Service
+@Slf4j
+public class TenantServiceImpl implements TenantService {
+    
+    private static final String DEFAULT_TENANT_REGION = "Global";
+
+    @Autowired
+    private TenantDao tenantDao;
+    
+    @Autowired
+    private UserService userService;
+    
+    @Autowired
+    private CustomerService customerService;
+    
+    @Autowired
+    private DeviceService deviceService;
+
+    @Autowired
+    private WidgetsBundleService widgetsBundleService;
+
+    @Autowired
+    private DashboardService dashboardService;
+
+    @Autowired
+    private RuleService ruleService;
+
+    @Autowired
+    private PluginService pluginService;
+    
+    @Override
+    public Tenant findTenantById(TenantId tenantId) {
+        log.trace("Executing findTenantById [{}]", tenantId);
+        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
+        TenantEntity tenantEntity = tenantDao.findById(tenantId.getId());
+        return getData(tenantEntity);
+    }
+
+    @Override
+    public Tenant saveTenant(Tenant tenant) {
+        log.trace("Executing saveTenant [{}]", tenant);
+        tenant.setRegion(DEFAULT_TENANT_REGION);
+        tenantValidator.validate(tenant);
+        TenantEntity tenantEntity = tenantDao.save(tenant);
+        return getData(tenantEntity);
+    }
+
+    @Override
+    public void deleteTenant(TenantId tenantId) {
+        log.trace("Executing deleteTenant [{}]", tenantId);
+        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
+        customerService.deleteCustomersByTenantId(tenantId);
+        widgetsBundleService.deleteWidgetsBundlesByTenantId(tenantId);
+        dashboardService.deleteDashboardsByTenantId(tenantId);
+        deviceService.deleteDevicesByTenantId(tenantId);
+        userService.deleteTenantAdmins(tenantId);
+        ruleService.deleteRulesByTenantId(tenantId);
+        pluginService.deletePluginsByTenantId(tenantId);
+        tenantDao.removeById(tenantId.getId());
+    }
+
+    @Override
+    public TextPageData<Tenant> findTenants(TextPageLink pageLink) {
+        log.trace("Executing findTenants pageLink [{}]", pageLink);
+        Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
+        List<TenantEntity> tenantEntities = tenantDao.findTenantsByRegion(DEFAULT_TENANT_REGION, pageLink);
+        List<Tenant> tenants = convertDataList(tenantEntities);
+        return new TextPageData<Tenant>(tenants, pageLink);
+    }
+
+    @Override
+    public void deleteTenants() {
+        log.trace("Executing deleteTenants");
+        tenantsRemover.removeEntitites(DEFAULT_TENANT_REGION);
+    }
+
+    private DataValidator<Tenant> tenantValidator =
+            new DataValidator<Tenant>() {
+                @Override
+                protected void validateDataImpl(Tenant tenant) {
+                    if (StringUtils.isEmpty(tenant.getTitle())) {
+                        throw new DataValidationException("Tenant title should be specified!");
+                    }
+                    if (!StringUtils.isEmpty(tenant.getEmail())) {
+                        validateEmail(tenant.getEmail());
+                    }
+                }
+    };
+    
+    private PaginatedRemover<String, TenantEntity> tenantsRemover =
+            new PaginatedRemover<String, TenantEntity>() {
+        
+        @Override
+        protected List<TenantEntity> findEntities(String region, TextPageLink pageLink) {
+            return tenantDao.findTenantsByRegion(region, pageLink);
+        }
+
+        @Override
+        protected void removeEntity(TenantEntity entity) {
+            deleteTenant(new TenantId(entity.getId()));
+        }
+    };
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserCredentialsDao.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserCredentialsDao.java
new file mode 100644
index 0000000..9913df8
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserCredentialsDao.java
@@ -0,0 +1,63 @@
+/**
+ * Copyright © 2016 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.user;
+
+import java.util.UUID;
+
+import org.thingsboard.server.common.data.security.UserCredentials;
+import org.thingsboard.server.dao.Dao;
+import org.thingsboard.server.dao.model.UserCredentialsEntity;
+
+/**
+ * The Interface UserCredentialsDao.
+ *
+ * @param <T> the generic type
+ */
+public interface UserCredentialsDao extends Dao<UserCredentialsEntity> {
+
+    /**
+     * Save or update user credentials object
+     *
+     * @param userCredentials the user credentials object
+     * @return saved user credentials object
+     */
+    UserCredentialsEntity save(UserCredentials userCredentials);
+
+    /**
+     * Find user credentials by user id.
+     *
+     * @param userId the user id
+     * @return the user credentials object
+     */
+    UserCredentialsEntity findByUserId(UUID userId);
+    
+    /**
+     * Find user credentials by activate token.
+     *
+     * @param activateToken the activate token
+     * @return the user credentials object
+     */
+    UserCredentialsEntity findByActivateToken(String activateToken);
+    
+    /**
+     * Find user credentials by reset token.
+     *
+     * @param resetToken the reset token
+     * @return the user credentials object
+     */
+    UserCredentialsEntity findByResetToken(String resetToken);
+    
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserCredentialsDaoImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserCredentialsDaoImpl.java
new file mode 100644
index 0000000..65efdf4
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserCredentialsDaoImpl.java
@@ -0,0 +1,87 @@
+/**
+ * Copyright © 2016 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.user;
+
+import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
+
+import java.util.UUID;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.security.UserCredentials;
+import org.thingsboard.server.dao.AbstractModelDao;
+import org.thingsboard.server.dao.model.UserCredentialsEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Repository;
+
+import com.datastax.driver.core.querybuilder.Select.Where;
+import org.thingsboard.server.dao.model.ModelConstants;
+
+@Component
+@Slf4j
+public class UserCredentialsDaoImpl extends AbstractModelDao<UserCredentialsEntity> implements UserCredentialsDao {
+
+    @Override
+    protected Class<UserCredentialsEntity> getColumnFamilyClass() {
+        return UserCredentialsEntity.class;
+    }
+
+    @Override
+    protected String getColumnFamilyName() {
+        return ModelConstants.USER_CREDENTIALS_COLUMN_FAMILY_NAME;
+    }
+
+    @Override
+    public UserCredentialsEntity save(UserCredentials userCredentials) {
+        log.debug("Save user credentials [{}] ", userCredentials);
+        return save(new UserCredentialsEntity(userCredentials));
+    }
+
+    @Override
+    public UserCredentialsEntity findByUserId(UUID userId) {
+        log.debug("Try to find user credentials by userId [{}] ", userId);
+        Where query = select().from(ModelConstants.USER_CREDENTIALS_BY_USER_COLUMN_FAMILY_NAME).where(eq(ModelConstants.USER_CREDENTIALS_USER_ID_PROPERTY, userId));
+        log.trace("Execute query {}", query);
+        UserCredentialsEntity userCredentialsEntity = findOneByStatement(query);
+        log.trace("Found user credentials [{}] by userId [{}]", userCredentialsEntity, userId);
+        return userCredentialsEntity;
+    }
+
+    @Override
+    public UserCredentialsEntity findByActivateToken(String activateToken) {
+        log.debug("Try to find user credentials by activateToken [{}] ", activateToken);
+        Where query = select().from(ModelConstants.USER_CREDENTIALS_BY_ACTIVATE_TOKEN_COLUMN_FAMILY_NAME)
+                .where(eq(ModelConstants.USER_CREDENTIALS_ACTIVATE_TOKEN_PROPERTY, activateToken));
+        log.trace("Execute query {}", query);
+        UserCredentialsEntity userCredentialsEntity = findOneByStatement(query);
+        log.trace("Found user credentials [{}] by activateToken [{}]", userCredentialsEntity, activateToken);
+        return userCredentialsEntity;
+    }
+
+    @Override
+    public UserCredentialsEntity findByResetToken(String resetToken) {
+        log.debug("Try to find user credentials by resetToken [{}] ", resetToken);
+        Where query = select().from(ModelConstants.USER_CREDENTIALS_BY_RESET_TOKEN_COLUMN_FAMILY_NAME)
+                .where(eq(ModelConstants.USER_CREDENTIALS_RESET_TOKEN_PROPERTY, resetToken));
+        log.trace("Execute query {}", query);
+        UserCredentialsEntity userCredentialsEntity = findOneByStatement(query);
+        log.trace("Found user credentials [{}] by resetToken [{}]", userCredentialsEntity, resetToken);
+        return userCredentialsEntity;
+    }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java
new file mode 100644
index 0000000..45e2f51
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java
@@ -0,0 +1,63 @@
+/**
+ * Copyright © 2016 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.user;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.thingsboard.server.common.data.User;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.Dao;
+import org.thingsboard.server.dao.model.UserEntity;
+
+public interface UserDao extends Dao<UserEntity> {
+
+    /**
+     * Save or update user object
+     *
+     * @param user the user object
+     * @return saved user entity
+     */
+    UserEntity save(User user);
+
+    /**
+     * Find user by email.
+     *
+     * @param email the email
+     * @return the user entity
+     */
+    UserEntity findByEmail(String email);
+    
+    /**
+     * Find tenant admin users by tenantId and page link.
+     *
+     * @param tenantId the tenantId
+     * @param pageLink the page link
+     * @return the list of user entities
+     */
+    List<UserEntity> findTenantAdmins(UUID tenantId, TextPageLink pageLink);
+    
+    /**
+     * Find customer users by tenantId, customerId and page link.
+     *
+     * @param tenantId the tenantId
+     * @param customerId the customerId
+     * @param pageLink the page link
+     * @return the list of user entities
+     */
+    List<UserEntity> findCustomerUsers(UUID tenantId, UUID customerId, TextPageLink pageLink);
+    
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserDaoImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserDaoImpl.java
new file mode 100644
index 0000000..16f73fe
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserDaoImpl.java
@@ -0,0 +1,92 @@
+/**
+ * Copyright © 2016 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.user;
+
+import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.User;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.security.Authority;
+import org.thingsboard.server.dao.AbstractSearchTextDao;
+import org.thingsboard.server.dao.model.UserEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.datastax.driver.core.querybuilder.Select.Where;
+import org.thingsboard.server.dao.model.ModelConstants;
+
+@Component
+@Slf4j
+public class UserDaoImpl extends AbstractSearchTextDao<UserEntity> implements UserDao {
+
+    @Override
+    protected Class<UserEntity> getColumnFamilyClass() {
+        return UserEntity.class;
+    }
+
+    @Override
+    protected String getColumnFamilyName() {
+        return ModelConstants.USER_COLUMN_FAMILY_NAME;
+    }
+
+    @Override
+    public UserEntity findByEmail(String email) {
+        log.debug("Try to find user by email [{}] ", email);
+        Where query = select().from(ModelConstants.USER_BY_EMAIL_COLUMN_FAMILY_NAME).where(eq(ModelConstants.USER_EMAIL_PROPERTY, email));
+        log.trace("Execute query {}", query);
+        UserEntity userEntity = findOneByStatement(query);
+        log.trace("Found user [{}] by email [{}]", userEntity, email);
+        return userEntity;
+    }
+
+    @Override
+    public UserEntity save(User user) {
+        log.debug("Save user [{}] ", user);
+        return save(new UserEntity(user));
+    }
+
+    @Override
+    public List<UserEntity> findTenantAdmins(UUID tenantId, TextPageLink pageLink) {
+        log.debug("Try to find tenant admin users by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
+        List<UserEntity> userEntities = findPageWithTextSearch(ModelConstants.USER_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
+                Arrays.asList(eq(ModelConstants.USER_TENANT_ID_PROPERTY, tenantId),
+                              eq(ModelConstants.USER_CUSTOMER_ID_PROPERTY, ModelConstants.NULL_UUID),
+                              eq(ModelConstants.USER_AUTHORITY_PROPERTY, Authority.TENANT_ADMIN.name())),
+                pageLink); 
+        log.trace("Found tenant admin users [{}] by tenantId [{}] and pageLink [{}]", userEntities, tenantId, pageLink);
+        return userEntities;
+    }
+
+    @Override
+    public List<UserEntity> findCustomerUsers(UUID tenantId, UUID customerId, TextPageLink pageLink) {
+        log.debug("Try to find customer users by tenantId [{}], customerId [{}] and pageLink [{}]", tenantId, customerId, pageLink);
+        List<UserEntity> userEntities = findPageWithTextSearch(ModelConstants.USER_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
+                Arrays.asList(eq(ModelConstants.USER_TENANT_ID_PROPERTY, tenantId),
+                              eq(ModelConstants.USER_CUSTOMER_ID_PROPERTY, customerId),
+                              eq(ModelConstants.USER_AUTHORITY_PROPERTY, Authority.CUSTOMER_USER.name())),
+                pageLink); 
+        log.trace("Found customer users [{}] by tenantId [{}], customerId [{}] and pageLink [{}]", userEntities, tenantId, customerId, pageLink);
+        return userEntities;
+    }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserService.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserService.java
new file mode 100644
index 0000000..1ad77c9
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserService.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright © 2016 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.user;
+
+import org.thingsboard.server.common.data.User;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.id.UserId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.security.UserCredentials;
+
+public interface UserService {
+	
+	public User findUserById(UserId userId);
+
+	public User findUserByEmail(String email);
+	
+	public User saveUser(User user);
+
+	public UserCredentials findUserCredentialsByUserId(UserId userId);	
+	
+	public UserCredentials findUserCredentialsByActivateToken(String activateToken);
+
+	public UserCredentials findUserCredentialsByResetToken(String resetToken);
+
+	public UserCredentials saveUserCredentials(UserCredentials userCredentials);
+	
+	public UserCredentials activateUserCredentials(String activateToken, String password);
+	
+	public UserCredentials requestPasswordReset(String email);
+
+	public void deleteUser(UserId userId);
+	
+	public TextPageData<User> findTenantAdmins(TenantId tenantId, TextPageLink pageLink);
+	
+	public void deleteTenantAdmins(TenantId tenantId);
+	
+	public TextPageData<User> findCustomerUsers(TenantId tenantId, CustomerId customerId, TextPageLink pageLink);
+	    
+	public void deleteCustomerUsers(TenantId tenantId, CustomerId customerId);
+	
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
new file mode 100644
index 0000000..17784d1
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
@@ -0,0 +1,352 @@
+/**
+ * Copyright © 2016 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.user;
+
+import static org.thingsboard.server.dao.DaoUtil.convertDataList;
+import static org.thingsboard.server.dao.DaoUtil.getData;
+import static org.thingsboard.server.dao.service.Validator.validateId;
+import static org.thingsboard.server.dao.service.Validator.validatePageLink;
+import static org.thingsboard.server.dao.service.Validator.validateString;
+
+import java.util.List;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.thingsboard.server.common.data.User;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.id.UserId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.security.Authority;
+import org.thingsboard.server.common.data.security.UserCredentials;
+import org.thingsboard.server.dao.customer.CustomerDao;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.exception.IncorrectParameterException;
+import org.thingsboard.server.dao.model.*;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.service.PaginatedRemover;
+import org.thingsboard.server.dao.tenant.TenantDao;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@Slf4j
+public class UserServiceImpl implements UserService {
+
+    @Autowired
+    private UserDao userDao;
+    
+    @Autowired
+    private UserCredentialsDao userCredentialsDao;
+    
+    @Autowired
+    private TenantDao tenantDao;
+    
+    @Autowired
+    private CustomerDao customerDao;
+    
+	@Override
+	public User findUserByEmail(String email) {
+	    log.trace("Executing findUserByEmail [{}]", email);
+		validateString(email, "Incorrect email " + email);
+		UserEntity userEntity = userDao.findByEmail(email);
+		return getData(userEntity);
+	}
+
+	@Override
+	public User findUserById(UserId userId) {
+	    log.trace("Executing findUserById [{}]", userId);
+		validateId(userId, "Incorrect userId " + userId);
+		UserEntity userEntity = userDao.findById(userId.getId());
+		return getData(userEntity);
+	}
+
+    @Override
+    public User saveUser(User user) {
+        log.trace("Executing saveUser [{}]", user);
+        userValidator.validate(user);
+        UserEntity userEntity = userDao.save(user);
+        if (user.getId() == null) {
+            UserCredentials userCredentials = new UserCredentials();
+            userCredentials.setEnabled(false);
+            userCredentials.setActivateToken(RandomStringUtils.randomAlphanumeric(30));
+            userCredentials.setUserId(new UserId(userEntity.getId()));
+            userCredentialsDao.save(userCredentials);
+        }        
+        return getData(userEntity);
+    }
+    
+    @Override
+    public UserCredentials findUserCredentialsByUserId(UserId userId) {
+        log.trace("Executing findUserCredentialsByUserId [{}]", userId);
+        validateId(userId, "Incorrect userId " + userId);
+        UserCredentialsEntity userCredentialsEntity = userCredentialsDao.findByUserId(userId.getId());
+        return getData(userCredentialsEntity);
+    }
+
+    @Override
+    public UserCredentials findUserCredentialsByActivateToken(String activateToken) {
+        log.trace("Executing findUserCredentialsByActivateToken [{}]", activateToken);
+        validateString(activateToken, "Incorrect activateToken " + activateToken);
+        UserCredentialsEntity userCredentialsEntity = userCredentialsDao.findByActivateToken(activateToken);
+        return getData(userCredentialsEntity);
+    }
+
+    @Override
+    public UserCredentials findUserCredentialsByResetToken(String resetToken) {
+        log.trace("Executing findUserCredentialsByResetToken [{}]", resetToken);
+        validateString(resetToken, "Incorrect resetToken " + resetToken);
+        UserCredentialsEntity userCredentialsEntity = userCredentialsDao.findByResetToken(resetToken);
+        return getData(userCredentialsEntity);
+    }
+
+    @Override
+    public UserCredentials saveUserCredentials(UserCredentials userCredentials) {
+        log.trace("Executing saveUserCredentials [{}]", userCredentials);
+        userCredentialsValidator.validate(userCredentials);
+        UserCredentialsEntity userCredentialsEntity = userCredentialsDao.save(userCredentials);
+        return getData(userCredentialsEntity);
+    }
+    
+    @Override
+    public UserCredentials activateUserCredentials(String activateToken, String password) {
+        log.trace("Executing activateUserCredentials activateToken [{}], password [{}]", activateToken, password);
+        validateString(activateToken, "Incorrect activateToken " + activateToken);
+        validateString(password, "Incorrect password " + password);
+        UserCredentialsEntity userCredentialsEntity = userCredentialsDao.findByActivateToken(activateToken);
+        if (userCredentialsEntity == null) {
+            throw new IncorrectParameterException(String.format("Unable to find user credentials by activateToken [%s]", activateToken));
+        }
+        UserCredentials userCredentials = getData(userCredentialsEntity);
+        if (userCredentials.isEnabled()) {
+            throw new IncorrectParameterException("User credentials already activated");
+        }
+        userCredentials.setEnabled(true);
+        userCredentials.setActivateToken(null);
+        userCredentials.setPassword(password);
+        
+        return saveUserCredentials(userCredentials);
+    }
+
+    @Override
+    public UserCredentials requestPasswordReset(String email) {
+        log.trace("Executing requestPasswordReset email [{}]", email);
+        validateString(email, "Incorrect email " + email);
+        UserEntity userEntity = userDao.findByEmail(email);
+        if (userEntity == null) {
+            throw new IncorrectParameterException(String.format("Unable to find user by email [%s]", email));
+        }
+        UserCredentialsEntity userCredentialsEntity = userCredentialsDao.findByUserId(userEntity.getId());
+        UserCredentials userCredentials = getData(userCredentialsEntity);
+        if (!userCredentials.isEnabled()) {
+            throw new IncorrectParameterException("Unable to reset password for unactive user");
+        }
+        userCredentials.setResetToken(RandomStringUtils.randomAlphanumeric(30));
+        return saveUserCredentials(userCredentials);
+    }
+
+
+    @Override
+    public void deleteUser(UserId userId) {
+        log.trace("Executing deleteUser [{}]", userId);
+        validateId(userId, "Incorrect userId " + userId);
+        UserCredentialsEntity userCredentialsEntity = userCredentialsDao.findByUserId(userId.getId());
+        userCredentialsDao.removeById(userCredentialsEntity.getId());
+        userDao.removeById(userId.getId());
+    }
+
+    @Override
+    public TextPageData<User> findTenantAdmins(TenantId tenantId, TextPageLink pageLink) {
+        log.trace("Executing findTenantAdmins, tenantId [{}], pageLink [{}]", tenantId, pageLink);
+        validateId(tenantId, "Incorrect tenantId " + tenantId);
+        validatePageLink(pageLink, "Incorrect page link " + pageLink);
+        List<UserEntity> userEntities = userDao.findTenantAdmins(tenantId.getId(), pageLink);
+        List<User> users = convertDataList(userEntities);
+        return new TextPageData<User>(users, pageLink);
+    }
+
+    @Override
+    public void deleteTenantAdmins(TenantId tenantId) {
+        log.trace("Executing deleteTenantAdmins, tenantId [{}]", tenantId);
+        validateId(tenantId, "Incorrect tenantId " + tenantId);
+        tenantAdminsRemover.removeEntitites(tenantId);
+    }
+
+    @Override
+    public TextPageData<User> findCustomerUsers(TenantId tenantId, CustomerId customerId, TextPageLink pageLink) {
+        log.trace("Executing findCustomerUsers, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink);
+        validateId(tenantId, "Incorrect tenantId " + tenantId);
+        validateId(customerId, "Incorrect customerId " + customerId);
+        validatePageLink(pageLink, "Incorrect page link " + pageLink);
+        List<UserEntity> userEntities = userDao.findCustomerUsers(tenantId.getId(), customerId.getId(), pageLink);
+        List<User> users = convertDataList(userEntities);
+        return new TextPageData<User>(users, pageLink);
+    }
+
+    @Override
+    public void deleteCustomerUsers(TenantId tenantId, CustomerId customerId) {
+        log.trace("Executing deleteCustomerUsers, customerId [{}]", customerId);
+        validateId(tenantId, "Incorrect tenantId " + tenantId);
+        validateId(customerId, "Incorrect customerId " + customerId);
+        new CustomerUsersRemover(tenantId).removeEntitites(customerId);
+    }
+
+    private DataValidator<User> userValidator =
+            new DataValidator<User>() {
+                @Override
+                protected void validateDataImpl(User user) {
+                    if (StringUtils.isEmpty(user.getEmail())) {
+                        throw new DataValidationException("User email should be specified!");
+                    }
+                    
+                    validateEmail(user.getEmail());
+                    
+                    Authority authority = user.getAuthority();
+                    if (authority == null) {
+                        throw new DataValidationException("User authority isn't defined!");
+                    }
+                    TenantId tenantId = user.getTenantId();
+                    if (tenantId == null) {
+                        tenantId = new TenantId(ModelConstants.NULL_UUID);
+                        user.setTenantId(tenantId);
+                    }
+                    CustomerId customerId = user.getCustomerId();
+                    if (customerId == null) {
+                        customerId = new CustomerId(ModelConstants.NULL_UUID);
+                        user.setCustomerId(customerId);
+                    }
+                    
+                    switch (authority) {
+                        case SYS_ADMIN:
+                            if (user.getId() == null) {
+                                throw new DataValidationException("Creation of system administrator is prohibited!");
+                            }
+                            if (!tenantId.getId().equals(ModelConstants.NULL_UUID)
+                                    || !customerId.getId().equals(ModelConstants.NULL_UUID)) {
+                                    throw new DataValidationException("System administrator can't be assigned neither to tenant nor to customer!");
+                            }
+                            break;
+                        case TENANT_ADMIN:
+                            if (tenantId.getId().equals(ModelConstants.NULL_UUID)) {
+                                throw new DataValidationException("Tenant administrator should be assigned to tenant!");
+                            } else if (!customerId.getId().equals(ModelConstants.NULL_UUID)) {
+                                throw new DataValidationException("Tenant administrator can't be assigned to customer!");
+                            }
+                            break;
+                        case CUSTOMER_USER:
+                            if (tenantId.getId().equals(ModelConstants.NULL_UUID)
+                                || customerId.getId().equals(ModelConstants.NULL_UUID) ) {
+                                throw new DataValidationException("Customer user should be assigned to customer!");
+                            }
+                            break;
+                        default:
+                            break;
+                    }
+                    
+                    User existentUserWithEmail = findUserByEmail(user.getEmail());
+                    if (existentUserWithEmail != null && !isSameData(existentUserWithEmail, user)) {
+                        throw new DataValidationException("User with email '" + user.getEmail() + "' "
+                                + " already present in database!");
+                    }
+                    if (!tenantId.getId().equals(ModelConstants.NULL_UUID)) {
+                        TenantEntity tenant = tenantDao.findById(user.getTenantId().getId());
+                        if (tenant == null) {
+                            throw new DataValidationException("User is referencing to non-existent tenant!");
+                        }
+                    }
+                    if (!customerId.getId().equals(ModelConstants.NULL_UUID)) {
+                        CustomerEntity customer = customerDao.findById(user.getCustomerId().getId());
+                        if (customer == null) {
+                            throw new DataValidationException("User is referencing to non-existent customer!");
+                        } else if (!customer.getTenantId().equals(tenantId.getId())) {
+                            throw new DataValidationException("User can't be assigned to customer from different tenant!");
+                        }
+                    }
+                 }
+    };
+    
+    private DataValidator<UserCredentials> userCredentialsValidator = 
+            new DataValidator<UserCredentials>() {
+        
+                @Override
+                protected void validateCreate(UserCredentials userCredentials) {
+                    throw new IncorrectParameterException("Creation of new user credentials is prohibited.");
+                }
+                
+                @Override
+                protected void validateDataImpl(UserCredentials userCredentials) {
+                    if (userCredentials.getUserId() == null) {
+                        throw new DataValidationException("User credentials should be assigned to user!");
+                    }
+                    if (userCredentials.isEnabled()) {
+                        if (StringUtils.isEmpty(userCredentials.getPassword())) {
+                            throw new DataValidationException("Enabled user credentials should have password!");
+                        }
+                        if (StringUtils.isNotEmpty(userCredentials.getActivateToken())) {
+                            throw new DataValidationException("Enabled user credentials can't have activate token!");
+                        }
+                    }
+                    UserCredentialsEntity existingUserCredentialsEntity = userCredentialsDao.findById(userCredentials.getId().getId());
+                    if (existingUserCredentialsEntity == null) {
+                        throw new DataValidationException("Unable to update non-existent user credentials!");
+                    }
+                    User user = findUserById(userCredentials.getUserId());
+                    if (user == null) {
+                        throw new DataValidationException("Can't assign user credentials to non-existent user!");
+                    }
+                }
+    };
+    
+    private PaginatedRemover<TenantId, UserEntity> tenantAdminsRemover =
+            new PaginatedRemover<TenantId, UserEntity>() {
+        
+        @Override
+        protected List<UserEntity> findEntities(TenantId id, TextPageLink pageLink) {
+            return userDao.findTenantAdmins(id.getId(), pageLink);
+        }
+
+        @Override
+        protected void removeEntity(UserEntity entity) {
+            deleteUser(new UserId(entity.getId()));
+        }
+    };
+    
+    class CustomerUsersRemover extends PaginatedRemover<CustomerId, UserEntity> {
+        
+        private TenantId tenantId;
+        
+        CustomerUsersRemover(TenantId tenantId) {
+            this.tenantId = tenantId;
+        }
+
+        @Override
+        protected List<UserEntity> findEntities(CustomerId id, TextPageLink pageLink) {
+            return userDao.findCustomerUsers(tenantId.getId(), id.getId(), pageLink);
+ 
+        }
+
+        @Override
+        protected void removeEntity(UserEntity entity) {
+            deleteUser(new UserId(entity.getId()));
+        }
+        
+    }
+    
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleDao.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleDao.java
new file mode 100644
index 0000000..d047d7d
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleDao.java
@@ -0,0 +1,77 @@
+/**
+ * Copyright © 2016 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.widget;
+
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.widget.WidgetsBundle;
+import org.thingsboard.server.dao.Dao;
+import org.thingsboard.server.dao.model.WidgetsBundleEntity;
+
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * The Interface WidgetsBundleDao.
+ *
+ * @param <T> the generic type
+ */
+public interface WidgetsBundleDao extends Dao<WidgetsBundleEntity> {
+
+    /**
+     * Save or update widgets bundle object
+     *
+     * @param widgetsBundle the widgets bundle object
+     * @return saved widgets bundle object
+     */
+    WidgetsBundleEntity save(WidgetsBundle widgetsBundle);
+
+    /**
+     * Find widgets bundle by tenantId and alias.
+     *
+     * @param tenantId the tenantId
+     * @param alias the alias
+     * @return the widgets bundle object
+     */
+    WidgetsBundleEntity findWidgetsBundleByTenantIdAndAlias(UUID tenantId, String alias);
+
+    /**
+     * Find system widgets bundles by page link.
+     *
+     * @param pageLink the page link
+     * @return the list of widgets bundles objects
+     */
+    List<WidgetsBundleEntity> findSystemWidgetsBundles(TextPageLink pageLink);
+
+    /**
+     * Find tenant widgets bundles by tenantId and page link.
+     *
+     * @param tenantId the tenantId
+     * @param pageLink the page link
+     * @return the list of widgets bundles objects
+     */
+    List<WidgetsBundleEntity> findTenantWidgetsBundlesByTenantId(UUID tenantId, TextPageLink pageLink);
+
+    /**
+     * Find all tenant widgets bundles (including system) by tenantId and page link.
+     *
+     * @param tenantId the tenantId
+     * @param pageLink the page link
+     * @return the list of widgets bundles objects
+     */
+    List<WidgetsBundleEntity> findAllTenantWidgetsBundlesByTenantId(UUID tenantId, TextPageLink pageLink);
+
+}
+
diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleDaoImpl.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleDaoImpl.java
new file mode 100644
index 0000000..3de8bd3
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleDaoImpl.java
@@ -0,0 +1,101 @@
+/**
+ * Copyright © 2016 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.widget;
+
+import com.datastax.driver.core.querybuilder.Select;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.widget.WidgetsBundle;
+import org.thingsboard.server.dao.AbstractSearchTextDao;
+import org.thingsboard.server.dao.model.WidgetsBundleEntity;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.in;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
+import static org.thingsboard.server.dao.model.ModelConstants.*;
+
+@Component
+@Slf4j
+public class WidgetsBundleDaoImpl extends AbstractSearchTextDao<WidgetsBundleEntity> implements WidgetsBundleDao {
+
+    @Override
+    protected Class<WidgetsBundleEntity> getColumnFamilyClass() {
+        return WidgetsBundleEntity.class;
+    }
+
+    @Override
+    protected String getColumnFamilyName() {
+        return WIDGETS_BUNDLE_COLUMN_FAMILY_NAME;
+    }
+
+    @Override
+    public WidgetsBundleEntity save(WidgetsBundle widgetsBundle) {
+        log.debug("Save widgets bundle [{}] ", widgetsBundle);
+        return save(new WidgetsBundleEntity(widgetsBundle));
+    }
+
+    @Override
+    public WidgetsBundleEntity findWidgetsBundleByTenantIdAndAlias(UUID tenantId, String alias) {
+        log.debug("Try to find widgets bundle by tenantId [{}] and alias [{}]", tenantId, alias);
+        Select.Where query = select().from(WIDGETS_BUNDLE_BY_TENANT_AND_ALIAS_COLUMN_FAMILY_NAME)
+                .where()
+                .and(eq(WIDGETS_BUNDLE_TENANT_ID_PROPERTY, tenantId))
+                .and(eq(WIDGETS_BUNDLE_ALIAS_PROPERTY, alias));
+        log.trace("Execute query {}", query);
+        WidgetsBundleEntity widgetsBundleEntity = findOneByStatement(query);
+        log.trace("Found widgets bundle [{}] by tenantId [{}] and alias [{}]",
+                widgetsBundleEntity, tenantId, alias);
+        return widgetsBundleEntity;
+    }
+
+    @Override
+    public List<WidgetsBundleEntity> findSystemWidgetsBundles(TextPageLink pageLink) {
+        log.debug("Try to find system widgets bundles by pageLink [{}]", pageLink);
+        List<WidgetsBundleEntity> widgetsBundlesEntities = findPageWithTextSearch(WIDGETS_BUNDLE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
+                Arrays.asList(eq(WIDGETS_BUNDLE_TENANT_ID_PROPERTY, NULL_UUID)),
+                pageLink);
+        log.trace("Found system widgets bundles [{}] by pageLink [{}]", widgetsBundlesEntities, pageLink);
+        return widgetsBundlesEntities;
+    }
+
+    @Override
+    public List<WidgetsBundleEntity> findTenantWidgetsBundlesByTenantId(UUID tenantId, TextPageLink pageLink) {
+        log.debug("Try to find tenant widgets bundles by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
+        List<WidgetsBundleEntity> widgetsBundlesEntities = findPageWithTextSearch(WIDGETS_BUNDLE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
+                Arrays.asList(eq(WIDGETS_BUNDLE_TENANT_ID_PROPERTY, tenantId)),
+                pageLink);
+        log.trace("Found tenant widgets bundles [{}] by tenantId [{}] and pageLink [{}]", widgetsBundlesEntities, tenantId, pageLink);
+        return widgetsBundlesEntities;
+    }
+
+    @Override
+    public List<WidgetsBundleEntity> findAllTenantWidgetsBundlesByTenantId(UUID tenantId, TextPageLink pageLink) {
+        log.debug("Try to find all tenant widgets bundles by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
+        List<WidgetsBundleEntity> widgetsBundlesEntities = findPageWithTextSearch(WIDGETS_BUNDLE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
+                Arrays.asList(in(WIDGETS_BUNDLE_TENANT_ID_PROPERTY, Arrays.asList(NULL_UUID, tenantId))),
+                pageLink);
+        log.trace("Found all tenant widgets bundles [{}] by tenantId [{}] and pageLink [{}]", widgetsBundlesEntities, tenantId, pageLink);
+        return widgetsBundlesEntities;
+    }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleService.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleService.java
new file mode 100644
index 0000000..ac316e9
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleService.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright © 2016 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.widget;
+
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.id.WidgetsBundleId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.widget.WidgetsBundle;
+
+import java.util.List;
+
+public interface WidgetsBundleService {
+
+    public WidgetsBundle findWidgetsBundleById(WidgetsBundleId widgetsBundleId);
+
+    public WidgetsBundle saveWidgetsBundle(WidgetsBundle widgetsBundle);
+
+    public void deleteWidgetsBundle(WidgetsBundleId widgetsBundleId);
+
+    public WidgetsBundle findWidgetsBundleByTenantIdAndAlias(TenantId tenantId, String alias);
+
+    public TextPageData<WidgetsBundle> findSystemWidgetsBundlesByPageLink(TextPageLink pageLink);
+
+    public List<WidgetsBundle> findSystemWidgetsBundles();
+
+    public TextPageData<WidgetsBundle> findTenantWidgetsBundlesByTenantId(TenantId tenantId, TextPageLink pageLink);
+
+    public TextPageData<WidgetsBundle> findAllTenantWidgetsBundlesByTenantIdAndPageLink(TenantId tenantId, TextPageLink pageLink);
+
+    public List<WidgetsBundle> findAllTenantWidgetsBundlesByTenantId(TenantId tenantId);
+
+    public void deleteWidgetsBundlesByTenantId(TenantId tenantId);
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java
new file mode 100644
index 0000000..8f1d0ed
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java
@@ -0,0 +1,225 @@
+/**
+ * Copyright © 2016 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.widget;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.id.WidgetsBundleId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.widget.WidgetsBundle;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.exception.IncorrectParameterException;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.dao.model.TenantEntity;
+import org.thingsboard.server.dao.model.WidgetsBundleEntity;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.service.PaginatedRemover;
+import org.thingsboard.server.dao.service.Validator;
+import org.thingsboard.server.dao.tenant.TenantDao;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.thingsboard.server.dao.DaoUtil.convertDataList;
+import static org.thingsboard.server.dao.DaoUtil.getData;
+
+@Service
+@Slf4j
+public class WidgetsBundleServiceImpl implements WidgetsBundleService {
+
+    @Autowired
+    private WidgetsBundleDao widgetsBundleDao;
+
+    @Autowired
+    private TenantDao tenantDao;
+
+    @Autowired
+    private WidgetTypeService widgetTypeService;
+
+    @Override
+    public WidgetsBundle findWidgetsBundleById(WidgetsBundleId widgetsBundleId) {
+        log.trace("Executing findWidgetsBundleById [{}]", widgetsBundleId);
+        Validator.validateId(widgetsBundleId, "Incorrect widgetsBundleId " + widgetsBundleId);
+        WidgetsBundleEntity widgetsBundleEntity = widgetsBundleDao.findById(widgetsBundleId.getId());
+        return getData(widgetsBundleEntity);
+    }
+
+    @Override
+    public WidgetsBundle saveWidgetsBundle(WidgetsBundle widgetsBundle) {
+        log.trace("Executing saveWidgetsBundle [{}]", widgetsBundle);
+        widgetsBundleValidator.validate(widgetsBundle);
+        WidgetsBundleEntity widgetsBundleEntity = widgetsBundleDao.save(widgetsBundle);
+        return getData(widgetsBundleEntity);
+    }
+
+    @Override
+    public void deleteWidgetsBundle(WidgetsBundleId widgetsBundleId) {
+        log.trace("Executing deleteWidgetsBundle [{}]", widgetsBundleId);
+        Validator.validateId(widgetsBundleId, "Incorrect widgetsBundleId " + widgetsBundleId);
+        WidgetsBundle widgetsBundle = findWidgetsBundleById(widgetsBundleId);
+        if (widgetsBundle == null) {
+            throw new IncorrectParameterException("Unable to delete non-existent widgets bundle.");
+        }
+        widgetTypeService.deleteWidgetTypesByTenantIdAndBundleAlias(widgetsBundle.getTenantId(), widgetsBundle.getAlias());
+        widgetsBundleDao.removeById(widgetsBundleId.getId());
+    }
+
+    @Override
+    public WidgetsBundle findWidgetsBundleByTenantIdAndAlias(TenantId tenantId, String alias) {
+        log.trace("Executing findWidgetsBundleByTenantIdAndAlias, tenantId [{}], alias [{}]", tenantId, alias);
+        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
+        Validator.validateString(alias, "Incorrect alias " + alias);
+        WidgetsBundleEntity widgetsBundleEntity = widgetsBundleDao.findWidgetsBundleByTenantIdAndAlias(tenantId.getId(), alias);
+        return getData(widgetsBundleEntity);
+    }
+
+    @Override
+    public TextPageData<WidgetsBundle> findSystemWidgetsBundlesByPageLink(TextPageLink pageLink) {
+        log.trace("Executing findSystemWidgetsBundles, pageLink [{}]", pageLink);
+        Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
+        List<WidgetsBundleEntity> widgetsBundlesEntities = widgetsBundleDao.findSystemWidgetsBundles(pageLink);
+        List<WidgetsBundle> widgetsBundles = convertDataList(widgetsBundlesEntities);
+        return new TextPageData<>(widgetsBundles, pageLink);
+    }
+
+    @Override
+    public List<WidgetsBundle> findSystemWidgetsBundles() {
+        log.trace("Executing findSystemWidgetsBundles");
+        List<WidgetsBundle> widgetsBundles = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(300);
+        TextPageData<WidgetsBundle> pageData = null;
+        do {
+            pageData = findSystemWidgetsBundlesByPageLink(pageLink);
+            widgetsBundles.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+        return widgetsBundles;
+    }
+
+    @Override
+    public TextPageData<WidgetsBundle> findTenantWidgetsBundlesByTenantId(TenantId tenantId, TextPageLink pageLink) {
+        log.trace("Executing findTenantWidgetsBundlesByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
+        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
+        Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
+        List<WidgetsBundleEntity> widgetsBundlesEntities = widgetsBundleDao.findTenantWidgetsBundlesByTenantId(tenantId.getId(), pageLink);
+        List<WidgetsBundle> widgetsBundles = convertDataList(widgetsBundlesEntities);
+        return new TextPageData<>(widgetsBundles, pageLink);
+    }
+
+    @Override
+    public TextPageData<WidgetsBundle> findAllTenantWidgetsBundlesByTenantIdAndPageLink(TenantId tenantId, TextPageLink pageLink) {
+        log.trace("Executing findAllTenantWidgetsBundlesByTenantIdAndPageLink, tenantId [{}], pageLink [{}]", tenantId, pageLink);
+        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
+        Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
+        List<WidgetsBundleEntity> widgetsBundlesEntities = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId.getId(), pageLink);
+        List<WidgetsBundle> widgetsBundles = convertDataList(widgetsBundlesEntities);
+        return new TextPageData<>(widgetsBundles, pageLink);
+    }
+
+    @Override
+    public List<WidgetsBundle> findAllTenantWidgetsBundlesByTenantId(TenantId tenantId) {
+        log.trace("Executing findAllTenantWidgetsBundlesByTenantId, tenantId [{}]", tenantId);
+        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
+        List<WidgetsBundle> widgetsBundles = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(300);
+        TextPageData<WidgetsBundle> pageData = null;
+        do {
+            pageData = findAllTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, pageLink);
+            widgetsBundles.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+        return widgetsBundles;
+    }
+
+    @Override
+    public void deleteWidgetsBundlesByTenantId(TenantId tenantId) {
+        log.trace("Executing deleteWidgetsBundlesByTenantId, tenantId [{}]", tenantId);
+        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
+        tenantWidgetsBundleRemover.removeEntitites(tenantId);
+    }
+
+    private DataValidator<WidgetsBundle> widgetsBundleValidator =
+            new DataValidator<WidgetsBundle>() {
+
+                @Override
+                protected void validateDataImpl(WidgetsBundle widgetsBundle) {
+                    if (StringUtils.isEmpty(widgetsBundle.getTitle())) {
+                        throw new DataValidationException("Widgets bundle title should be specified!");
+                    }
+                    if (widgetsBundle.getTenantId() == null) {
+                        widgetsBundle.setTenantId(new TenantId(ModelConstants.NULL_UUID));
+                    }
+                    if (!widgetsBundle.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
+                        TenantEntity tenant = tenantDao.findById(widgetsBundle.getTenantId().getId());
+                        if (tenant == null) {
+                            throw new DataValidationException("Widgets bundle is referencing to non-existent tenant!");
+                        }
+                    }
+                }
+
+                @Override
+                protected void validateCreate(WidgetsBundle widgetsBundle) {
+                    String alias = widgetsBundle.getTitle().toLowerCase().replaceAll("\\W+", "_");
+                    String originalAlias = alias;
+                    int c = 1;
+                    WidgetsBundleEntity withSameAlias;
+                    do {
+                        withSameAlias = widgetsBundleDao.findWidgetsBundleByTenantIdAndAlias(widgetsBundle.getTenantId().getId(), alias);
+                        if (withSameAlias != null) {
+                            alias = originalAlias + (++c);
+                        }
+                    } while(withSameAlias != null);
+                    widgetsBundle.setAlias(alias);
+                }
+
+                @Override
+                protected void validateUpdate(WidgetsBundle widgetsBundle) {
+                    WidgetsBundleEntity storedWidgetsBundle = widgetsBundleDao.findById(widgetsBundle.getId().getId());
+                    if (!storedWidgetsBundle.getTenantId().equals(widgetsBundle.getTenantId().getId())) {
+                        throw new DataValidationException("Can't move existing widgets bundle to different tenant!");
+                    }
+                    if (!storedWidgetsBundle.getAlias().equals(widgetsBundle.getAlias())) {
+                        throw new DataValidationException("Update of widgets bundle alias is prohibited!");
+                    }
+                }
+
+            };
+
+    private PaginatedRemover<TenantId, WidgetsBundleEntity> tenantWidgetsBundleRemover =
+            new PaginatedRemover<TenantId, WidgetsBundleEntity>() {
+
+                @Override
+                protected List<WidgetsBundleEntity> findEntities(TenantId id, TextPageLink pageLink) {
+                    return widgetsBundleDao.findTenantWidgetsBundlesByTenantId(id.getId(), pageLink);
+                }
+
+                @Override
+                protected void removeEntity(WidgetsBundleEntity entity) {
+                    deleteWidgetsBundle(new WidgetsBundleId(entity.getId()));
+                }
+            };
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDao.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDao.java
new file mode 100644
index 0000000..8f98a64
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDao.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright © 2016 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.widget;
+
+import org.thingsboard.server.common.data.widget.WidgetType;
+import org.thingsboard.server.dao.Dao;
+import org.thingsboard.server.dao.model.WidgetTypeEntity;
+
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * The Interface WidgetTypeDao.
+ *
+ * @param <T> the generic type
+ */
+public interface WidgetTypeDao extends Dao<WidgetTypeEntity> {
+
+    /**
+     * Save or update widget type object
+     *
+     * @param widgetType the widget type object
+     * @return saved widget type object
+     */
+    WidgetTypeEntity save(WidgetType widgetType);
+
+    /**
+     * Find widget types by tenantId and bundleAlias.
+     *
+     * @param tenantId the tenantId
+     * @param bundleAlias the bundle alias
+     * @return the list of widget types objects
+     */
+    List<WidgetTypeEntity> findWidgetTypesByTenantIdAndBundleAlias(UUID tenantId, String bundleAlias);
+
+    /**
+     * Find widget type by tenantId, bundleAlias and alias.
+     *
+     * @param tenantId the tenantId
+     * @param bundleAlias the bundle alias
+     * @param alias the alias
+     * @return the widget type object
+     */
+    WidgetTypeEntity findByTenantIdBundleAliasAndAlias(UUID tenantId, String bundleAlias, String alias);
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDaoImpl.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDaoImpl.java
new file mode 100644
index 0000000..ced1830
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDaoImpl.java
@@ -0,0 +1,82 @@
+/**
+ * Copyright © 2016 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.widget;
+
+import com.datastax.driver.core.querybuilder.Select.Where;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Repository;
+import org.thingsboard.server.common.data.widget.WidgetType;
+import org.thingsboard.server.dao.AbstractModelDao;
+import org.thingsboard.server.dao.model.WidgetTypeEntity;
+
+import java.util.List;
+import java.util.UUID;
+
+import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
+import static org.thingsboard.server.dao.model.ModelConstants.*;
+
+@Component
+@Slf4j
+public class WidgetTypeDaoImpl extends AbstractModelDao<WidgetTypeEntity> implements WidgetTypeDao {
+
+    @Override
+    protected Class<WidgetTypeEntity> getColumnFamilyClass() {
+        return WidgetTypeEntity.class;
+    }
+
+    @Override
+    protected String getColumnFamilyName() {
+        return WIDGET_TYPE_COLUMN_FAMILY_NAME;
+    }
+
+    @Override
+    public WidgetTypeEntity save(WidgetType widgetType) {
+        log.debug("Save widget type [{}] ", widgetType);
+        return save(new WidgetTypeEntity(widgetType));
+    }
+
+    @Override
+    public List<WidgetTypeEntity> findWidgetTypesByTenantIdAndBundleAlias(UUID tenantId, String bundleAlias) {
+        log.debug("Try to find widget types by tenantId [{}] and bundleAlias [{}]", tenantId, bundleAlias);
+        Where query = select().from(WIDGET_TYPE_BY_TENANT_AND_ALIASES_COLUMN_FAMILY_NAME)
+                .where()
+                .and(eq(WIDGET_TYPE_TENANT_ID_PROPERTY, tenantId))
+                .and(eq(WIDGET_TYPE_BUNDLE_ALIAS_PROPERTY, bundleAlias));
+        List<WidgetTypeEntity> widgetTypesEntities = findListByStatement(query);
+        log.trace("Found widget types [{}] by tenantId [{}] and bundleAlias [{}]", widgetTypesEntities, tenantId, bundleAlias);
+        return widgetTypesEntities;
+    }
+
+    @Override
+    public WidgetTypeEntity findByTenantIdBundleAliasAndAlias(UUID tenantId, String bundleAlias, String alias) {
+        log.debug("Try to find widget type by tenantId [{}], bundleAlias [{}] and alias [{}]", tenantId, bundleAlias, alias);
+        Where query = select().from(WIDGET_TYPE_BY_TENANT_AND_ALIASES_COLUMN_FAMILY_NAME)
+                .where()
+                .and(eq(WIDGET_TYPE_TENANT_ID_PROPERTY, tenantId))
+                .and(eq(WIDGET_TYPE_BUNDLE_ALIAS_PROPERTY, bundleAlias))
+                .and(eq(WIDGET_TYPE_ALIAS_PROPERTY, alias));
+        log.trace("Execute query {}", query);
+        WidgetTypeEntity widgetTypeEntity = findOneByStatement(query);
+        log.trace("Found widget type [{}] by tenantId [{}], bundleAlias [{}] and alias [{}]",
+                widgetTypeEntity, tenantId, bundleAlias, alias);
+        return widgetTypeEntity;
+    }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeService.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeService.java
new file mode 100644
index 0000000..a3c8967
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeService.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright © 2016 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.widget;
+
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.id.WidgetTypeId;
+import org.thingsboard.server.common.data.widget.WidgetType;
+
+import java.util.List;
+
+public interface WidgetTypeService {
+
+    public WidgetType findWidgetTypeById(WidgetTypeId widgetTypeId);
+
+    public WidgetType saveWidgetType(WidgetType widgetType);
+
+    public void deleteWidgetType(WidgetTypeId widgetTypeId);
+
+    public List<WidgetType> findWidgetTypesByTenantIdAndBundleAlias(TenantId tenantId, String bundleAlias);
+
+    public WidgetType findWidgetTypeByTenantIdBundleAliasAndAlias(TenantId tenantId, String bundleAlias, String alias);
+
+    public void deleteWidgetTypesByTenantIdAndBundleAlias(TenantId tenantId, String bundleAlias);
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java
new file mode 100644
index 0000000..97f5048
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java
@@ -0,0 +1,169 @@
+/**
+ * Copyright © 2016 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.widget;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.id.WidgetTypeId;
+import org.thingsboard.server.common.data.widget.WidgetType;
+import org.thingsboard.server.common.data.widget.WidgetsBundle;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.dao.model.TenantEntity;
+import org.thingsboard.server.dao.model.WidgetTypeEntity;
+import org.thingsboard.server.dao.model.WidgetsBundleEntity;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.service.Validator;
+import org.thingsboard.server.dao.tenant.TenantDao;
+import org.thingsboard.server.dao.tenant.TenantService;
+
+import java.util.List;
+
+import static org.thingsboard.server.dao.DaoUtil.convertDataList;
+import static org.thingsboard.server.dao.DaoUtil.getData;
+
+@Service
+@Slf4j
+public class WidgetTypeServiceImpl implements WidgetTypeService {
+
+    @Autowired
+    private WidgetTypeDao widgetTypeDao;
+
+    @Autowired
+    private TenantDao tenantDao;
+
+    @Autowired
+    private WidgetsBundleDao widgetsBundleService;
+
+    @Override
+    public WidgetType findWidgetTypeById(WidgetTypeId widgetTypeId) {
+        log.trace("Executing findWidgetTypeById [{}]", widgetTypeId);
+        Validator.validateId(widgetTypeId, "Incorrect widgetTypeId " + widgetTypeId);
+        WidgetTypeEntity widgetTypeEntity = widgetTypeDao.findById(widgetTypeId.getId());
+        return getData(widgetTypeEntity);
+    }
+
+    @Override
+    public WidgetType saveWidgetType(WidgetType widgetType) {
+        log.trace("Executing saveWidgetType [{}]", widgetType);
+        widgetTypeValidator.validate(widgetType);
+        WidgetTypeEntity widgetTypeEntity = widgetTypeDao.save(widgetType);
+        return getData(widgetTypeEntity);
+    }
+
+    @Override
+    public void deleteWidgetType(WidgetTypeId widgetTypeId) {
+        log.trace("Executing deleteWidgetType [{}]", widgetTypeId);
+        Validator.validateId(widgetTypeId, "Incorrect widgetTypeId " + widgetTypeId);
+        widgetTypeDao.removeById(widgetTypeId.getId());
+    }
+
+    @Override
+    public List<WidgetType> findWidgetTypesByTenantIdAndBundleAlias(TenantId tenantId, String bundleAlias) {
+        log.trace("Executing findWidgetTypesByTenantIdAndBundleAlias, tenantId [{}], bundleAlias [{}]", tenantId, bundleAlias);
+        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
+        Validator.validateString(bundleAlias, "Incorrect bundleAlias " + bundleAlias);
+        List<WidgetTypeEntity> widgetTypesEntities = widgetTypeDao.findWidgetTypesByTenantIdAndBundleAlias(tenantId.getId(), bundleAlias);
+        return convertDataList(widgetTypesEntities);
+    }
+
+    @Override
+    public WidgetType findWidgetTypeByTenantIdBundleAliasAndAlias(TenantId tenantId, String bundleAlias, String alias) {
+        log.trace("Executing findWidgetTypeByTenantIdBundleAliasAndAlias, tenantId [{}], bundleAlias [{}], alias [{}]", tenantId, bundleAlias, alias);
+        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
+        Validator.validateString(bundleAlias, "Incorrect bundleAlias " + bundleAlias);
+        Validator.validateString(alias, "Incorrect alias " + alias);
+        WidgetTypeEntity widgetTypeEntity = widgetTypeDao.findByTenantIdBundleAliasAndAlias(tenantId.getId(), bundleAlias, alias);
+        return getData(widgetTypeEntity);
+    }
+
+    @Override
+    public void deleteWidgetTypesByTenantIdAndBundleAlias(TenantId tenantId, String bundleAlias) {
+        log.trace("Executing deleteWidgetTypesByTenantIdAndBundleAlias, tenantId [{}], bundleAlias [{}]", tenantId, bundleAlias);
+        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
+        Validator.validateString(bundleAlias, "Incorrect bundleAlias " + bundleAlias);
+        List<WidgetTypeEntity> widgetTypesEntities = widgetTypeDao.findWidgetTypesByTenantIdAndBundleAlias(tenantId.getId(), bundleAlias);
+        for (WidgetTypeEntity widgetTypeEntity : widgetTypesEntities) {
+            deleteWidgetType(new WidgetTypeId(widgetTypeEntity.getId()));
+        }
+    }
+
+    private DataValidator<WidgetType> widgetTypeValidator =
+            new DataValidator<WidgetType>() {
+                @Override
+                protected void validateDataImpl(WidgetType widgetType) {
+                    if (StringUtils.isEmpty(widgetType.getName())) {
+                        throw new DataValidationException("Widgets type name should be specified!");
+                    }
+                    if (StringUtils.isEmpty(widgetType.getBundleAlias())) {
+                        throw new DataValidationException("Widgets type bundle alias should be specified!");
+                    }
+                    if (widgetType.getDescriptor() == null || widgetType.getDescriptor().size() == 0) {
+                        throw new DataValidationException("Widgets type descriptor can't be empty!");
+                    }
+                    if (widgetType.getTenantId() == null) {
+                        widgetType.setTenantId(new TenantId(ModelConstants.NULL_UUID));
+                    }
+                    if (!widgetType.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
+                        TenantEntity tenant = tenantDao.findById(widgetType.getTenantId().getId());
+                        if (tenant == null) {
+                            throw new DataValidationException("Widget type is referencing to non-existent tenant!");
+                        }
+                    }
+                }
+
+                @Override
+                protected void validateCreate(WidgetType widgetType) {
+
+                    WidgetsBundleEntity widgetsBundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(widgetType.getTenantId().getId(), widgetType.getBundleAlias());
+                    if (widgetsBundle == null) {
+                        throw new DataValidationException("Widget type is referencing to non-existent widgets bundle!");
+                    }
+
+                    String alias = widgetType.getName().toLowerCase().replaceAll("\\W+", "_");
+                    String originalAlias = alias;
+                    int c = 1;
+                    WidgetTypeEntity withSameAlias;
+                    do {
+                        withSameAlias = widgetTypeDao.findByTenantIdBundleAliasAndAlias(widgetType.getTenantId().getId(), widgetType.getBundleAlias(), alias);
+                        if (withSameAlias != null) {
+                            alias = originalAlias + (++c);
+                        }
+                    } while(withSameAlias != null);
+                    widgetType.setAlias(alias);
+                }
+
+                @Override
+                protected void validateUpdate(WidgetType widgetType) {
+                    WidgetTypeEntity storedWidgetType = widgetTypeDao.findById(widgetType.getId().getId());
+                    if (!storedWidgetType.getTenantId().equals(widgetType.getTenantId().getId())) {
+                        throw new DataValidationException("Can't move existing widget type to different tenant!");
+                    }
+                    if (!storedWidgetType.getBundleAlias().equals(widgetType.getBundleAlias())) {
+                        throw new DataValidationException("Update of widget type bundle alias is prohibited!");
+                    }
+                    if (!storedWidgetType.getAlias().equals(widgetType.getAlias())) {
+                        throw new DataValidationException("Update of widget type alias is prohibited!");
+                    }
+                }
+            };
+}
diff --git a/dao/src/main/resources/demo-data.cql b/dao/src/main/resources/demo-data.cql
new file mode 100644
index 0000000..68154cc
--- /dev/null
+++ b/dao/src/main/resources/demo-data.cql
@@ -0,0 +1,402 @@
+--
+-- Copyright © 2016 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.
+--
+
+/** Demo data **/
+
+/** Demo tenant **/
+
+INSERT INTO thingsboard.tenant ( id, region, title, search_text )
+VALUES (
+	minTimeuuid ( '2016-11-01 01:02:01+0000' ),
+	'Global',
+	'Tenant',
+	'tenant'
+);
+
+/** Demo tenant admin **/
+
+INSERT INTO thingsboard.user ( id, tenant_id, customer_id, email, search_text, authority )
+VALUES (
+	minTimeuuid ( '2016-11-01 01:02:02+0000' ),
+	minTimeuuid ( '2016-11-01 01:02:01+0000' ),
+	minTimeuuid ( 0 ),
+	'tenant@thingsboard.org',
+	'tenant@thingsboard.org',
+	'TENANT_ADMIN'
+);
+
+INSERT INTO thingsboard.user_credentials ( id, user_id, enabled, password )
+VALUES (
+	now ( ),
+	minTimeuuid ( '2016-11-01 01:02:02+0000' ),
+	true,
+	'$2a$10$CUHks/PiEvxSGCKzrHCQGe/MoseIQw6qijIDjSa2sNoIyXkgJGyMO'
+);
+
+/** Demo customers **/
+
+INSERT INTO thingsboard.customer ( id, tenant_id, title, search_text )
+VALUES (
+	minTimeuuid ( '2016-11-01 01:02:03+0000' ),
+	minTimeuuid ( '2016-11-01 01:02:01+0000' ),
+	'Customer A',
+	'customer a'
+);
+
+INSERT INTO thingsboard.customer ( id, tenant_id, title, search_text )
+VALUES (
+	minTimeuuid ( '2016-11-01 01:02:03+0001' ),
+	minTimeuuid ( '2016-11-01 01:02:01+0000' ),
+	'Customer B',
+	'customer b'
+);
+
+INSERT INTO thingsboard.customer ( id, tenant_id, title, search_text )
+VALUES (
+	minTimeuuid ( '2016-11-01 01:02:03+0002' ),
+	minTimeuuid ( '2016-11-01 01:02:01+0000' ),
+	'Customer C',
+	'customer c'
+);
+
+/** Demo customer user **/
+
+INSERT INTO thingsboard.user ( id, tenant_id, customer_id, email, search_text, authority )
+VALUES (
+	minTimeuuid ( '2016-11-01 01:02:04+0000' ),
+	minTimeuuid ( '2016-11-01 01:02:01+0000' ),
+	minTimeuuid ( '2016-11-01 01:02:03+0000' ),
+	'customer@thingsboard.org',
+	'customer@thingsboard.org',
+	'CUSTOMER_USER'
+);
+
+INSERT INTO thingsboard.user ( id, tenant_id, customer_id, email, search_text, authority )
+VALUES (
+	minTimeuuid ( '2016-11-01 01:02:04+0001' ),
+	minTimeuuid ( '2016-11-01 01:02:01+0000' ),
+	minTimeuuid ( '2016-11-01 01:02:03+0000' ),
+	'customerA@thingsboard.org',
+	'customera@thingsboard.org',
+	'CUSTOMER_USER'
+);
+
+INSERT INTO thingsboard.user ( id, tenant_id, customer_id, email, search_text, authority )
+VALUES (
+	minTimeuuid ( '2016-11-01 01:02:04+0002' ),
+	minTimeuuid ( '2016-11-01 01:02:01+0000' ),
+	minTimeuuid ( '2016-11-01 01:02:03+0001' ),
+	'customerB@thingsboard.org',
+	'customerb@thingsboard.org',
+	'CUSTOMER_USER'
+);
+
+INSERT INTO thingsboard.user ( id, tenant_id, customer_id, email, search_text, authority )
+VALUES (
+	minTimeuuid ( '2016-11-01 01:02:04+0003' ),
+	minTimeuuid ( '2016-11-01 01:02:01+0000' ),
+	minTimeuuid ( '2016-11-01 01:02:03+0002' ),
+	'customerC@thingsboard.org',
+	'customerc@thingsboard.org',
+	'CUSTOMER_USER'
+);
+
+
+INSERT INTO thingsboard.user_credentials ( id, user_id, enabled, password )
+VALUES (
+	now ( ),
+	minTimeuuid ( '2016-11-01 01:02:04+0000' ),
+	true,
+	'$2a$10$1Ki3Nl10pagxZncDQZtU.uHttir3HGKzLeovxCNKdSSJa3PU49L1C'
+);
+
+INSERT INTO thingsboard.user_credentials ( id, user_id, enabled, password )
+VALUES (
+	now ( ),
+	minTimeuuid ( '2016-11-01 01:02:04+0001' ),
+	true,
+	'$2a$10$1Ki3Nl10pagxZncDQZtU.uHttir3HGKzLeovxCNKdSSJa3PU49L1C'
+);
+
+INSERT INTO thingsboard.user_credentials ( id, user_id, enabled, password )
+VALUES (
+	now ( ),
+	minTimeuuid ( '2016-11-01 01:02:04+0002' ),
+	true,
+	'$2a$10$1Ki3Nl10pagxZncDQZtU.uHttir3HGKzLeovxCNKdSSJa3PU49L1C'
+);
+
+INSERT INTO thingsboard.user_credentials ( id, user_id, enabled, password )
+VALUES (
+	now ( ),
+	minTimeuuid ( '2016-11-01 01:02:04+0003' ),
+	true,
+	'$2a$10$1Ki3Nl10pagxZncDQZtU.uHttir3HGKzLeovxCNKdSSJa3PU49L1C'
+);
+
+/** Demo device **/
+
+INSERT INTO thingsboard.device ( id, tenant_id, customer_id, name, search_text)
+VALUES (
+	minTimeuuid ( '2016-11-01 01:02:05+0000' ),
+	minTimeuuid ( '2016-11-01 01:02:01+0000' ),
+	minTimeuuid ( '2016-11-01 01:02:03+0000' ),
+	'Test Device A1',
+	'test device a1'
+);
+
+INSERT INTO thingsboard.device ( id, tenant_id, customer_id, name, search_text)
+VALUES (
+	minTimeuuid ( '2016-11-01 01:02:05+0001' ),
+	minTimeuuid ( '2016-11-01 01:02:01+0000' ),
+	minTimeuuid ( '2016-11-01 01:02:03+0000' ),
+	'Test Device A2',
+	'test device a2'
+);
+
+INSERT INTO thingsboard.device ( id, tenant_id, customer_id, name, search_text)
+VALUES (
+	minTimeuuid ( '2016-11-01 01:02:05+0002' ),
+	minTimeuuid ( '2016-11-01 01:02:01+0000' ),
+	minTimeuuid ( '2016-11-01 01:02:03+0000' ),
+	'Test Device A3',
+	'test device a3'
+);
+
+INSERT INTO thingsboard.device ( id, tenant_id, customer_id, name, search_text)
+VALUES (
+	minTimeuuid ( '2016-11-01 01:02:05+0003' ),
+	minTimeuuid ( '2016-11-01 01:02:01+0000' ),
+	minTimeuuid ( '2016-11-01 01:02:03+0001' ),
+	'Test Device B1',
+	'test device b1'
+);
+
+INSERT INTO thingsboard.device ( id, tenant_id, customer_id, name, search_text)
+VALUES (
+	minTimeuuid ( '2016-11-01 01:02:05+0004' ),
+	minTimeuuid ( '2016-11-01 01:02:01+0000' ),
+	minTimeuuid ( '2016-11-01 01:02:03+0002' ),
+	'Test Device C1',
+	'test device c1'
+);
+
+INSERT INTO thingsboard.device_credentials ( id, device_id, credentials_type, credentials_id)
+VALUES (
+	now(),
+	minTimeuuid ( '2016-11-01 01:02:05+0000' ),
+	'ACCESS_TOKEN',
+	'A1_TEST_TOKEN'
+);
+
+INSERT INTO thingsboard.device_credentials ( id, device_id, credentials_type, credentials_id)
+VALUES (
+	now(),
+	minTimeuuid ( '2016-11-01 01:02:05+0001' ),
+	'ACCESS_TOKEN',
+	'A2_TEST_TOKEN'
+);
+
+INSERT INTO thingsboard.device_credentials ( id, device_id, credentials_type, credentials_id)
+VALUES (
+	now(),
+	minTimeuuid ( '2016-11-01 01:02:05+0002' ),
+	'ACCESS_TOKEN',
+	'A3_TEST_TOKEN'
+);
+
+INSERT INTO thingsboard.device_credentials ( id, device_id, credentials_type, credentials_id)
+VALUES (
+	now(),
+	minTimeuuid ( '2016-11-01 01:02:05+0003' ),
+	'ACCESS_TOKEN',
+	'B1_TEST_TOKEN'
+);
+
+INSERT INTO thingsboard.device_credentials ( id, device_id, credentials_type, credentials_id)
+VALUES (
+	now(),
+	minTimeuuid ( '2016-11-01 01:02:05+0004' ),
+	'ACCESS_TOKEN',
+	'C1_TEST_TOKEN'
+);
+
+/** Demo data **/
+
+/** Demo plugins & rules **/
+
+/** Email plugin. Please change username and password here or via configuration **/
+
+INSERT INTO thingsboard.plugin ( id, tenant_id, name, state, search_text, api_token, plugin_class, public_access, configuration)
+VALUES (
+	minTimeuuid ( '2016-11-01 01:02:06+0000' ),
+	minTimeuuid ( '2016-11-01 01:02:01+0000' ),
+    'Demo Email Plugin',
+    'ACTIVE',
+	'mail sender plugin',
+	'mail',
+	'org.thingsboard.server.extensions.core.plugin.mail.MailPlugin',
+	true,
+	'{
+       "host": "smtp.gmail.com",
+       "port": 587,
+       "username": "username@gmail.com",
+       "password": "password",
+       "otherProperties": [
+         {
+           "key":"mail.smtp.auth",
+           "value":"true"
+         },
+         {
+           "key":"mail.smtp.timeout",
+           "value":"10000"
+         },
+         {
+           "key":"mail.smtp.starttls.enable",
+           "value":"true"
+         },
+         {
+           "key":"mail.smtp.host",
+           "value":"smtp.gmail.com"
+         },
+         {
+           "key":"mail.smtp.port",
+           "value":"587"
+         }
+       ]
+     }'
+);
+
+INSERT INTO thingsboard.plugin ( id, tenant_id, name, state, search_text, api_token, plugin_class, public_access, configuration)
+VALUES (
+	minTimeuuid ( '2016-11-01 01:02:07+0000' ),
+	minTimeuuid ( '2016-11-01 01:02:01+0000' ),
+    'Demo Time RPC Plugin',
+    'ACTIVE',
+	'demo time rpc plugin',
+	'time',
+	'org.thingsboard.server.extensions.core.plugin.time.TimePlugin',
+	false,
+	'{"timeFormat":"yyyy MM dd HH:mm:ss.SSS"}'
+);
+
+INSERT INTO thingsboard.plugin ( id, tenant_id, name, state, search_text, api_token, plugin_class, public_access, configuration)
+VALUES (
+	minTimeuuid ( '2016-11-01 01:02:08+0000' ),
+	minTimeuuid ( '2016-11-01 01:02:01+0000' ),
+    'Demo Device Messaging RPC Plugin',
+    'ACTIVE',
+	'demo device messaging rpc plugin',
+	'messaging',
+	'org.thingsboard.server.extensions.core.plugin.messaging.DeviceMessagingPlugin',
+	false,
+	'{"maxDeviceCountPerCustomer":1024,"defaultTimeout":20000,"maxTimeout":60000}'
+);
+
+
+INSERT INTO thingsboard.rule ( id, tenant_id, name, plugin_token, state, search_text, weight, filters, processor, action)
+VALUES (
+	minTimeuuid ( '2016-11-01 01:02:09+0000' ),
+	minTimeuuid ( '2016-11-01 01:02:01+0000' ),
+	'Demo Alarm Rule',
+	'mail',
+    'ACTIVE',
+	'demo alarm rule',
+	0,
+	'[{"clazz":"org.thingsboard.server.extensions.core.filter.MsgTypeFilter", "name":"MsgTypeFilter", "configuration": {"messageTypes":["POST_TELEMETRY","POST_ATTRIBUTES","GET_ATTRIBUTES"]}}
+	,
+	{"clazz":"org.thingsboard.server.extensions.core.filter.DeviceTelemetryFilter", "name":"Temperature filter", "configuration": {"filter":"typeof temperature !== ''undefined'' && temperature >= 100"}}
+	]',
+	'{"clazz":"org.thingsboard.server.extensions.core.processor.AlarmDeduplicationProcessor", "name": "AlarmDeduplicationProcessor", "configuration":{
+                                                                                                                                "alarmIdTemplate": "[$date.get(''yyyy-MM-dd HH:mm'')] Device $cs.get(''serialNumber'')($cs.get(''model'')) temperature is high!",
+                                                                                                                                "alarmBodyTemplate": "[$date.get(''yyyy-MM-dd HH:mm:ss'')] Device $cs.get(''serialNumber'')($cs.get(''model'')) temperature is $temp.valueAsString!"
+                                                                                                                              }}',
+	'{"clazz":"org.thingsboard.server.extensions.core.action.mail.SendMailAction", "name":"Send Mail Action", "configuration":{
+                                                                                                                                "sendFlag": "isNewAlarm",
+                                                                                                                                "fromTemplate": "thingsboard@gmail.com",
+                                                                                                                                "toTemplate": "thingsboard@gmail.com",
+                                                                                                                                "subjectTemplate": "$alarmId",
+                                                                                                                                "bodyTemplate": "$alarmBody"
+                                                                                                                              }}'
+);
+
+INSERT INTO thingsboard.rule ( id, tenant_id, name, plugin_token, state, search_text, weight, filters, processor, action)
+VALUES (
+	minTimeuuid ( '2016-11-01 01:02:10+0000' ),
+	minTimeuuid ( '2016-11-01 01:02:01+0000' ),
+	'Demo Alarm Rule',
+	'mail',
+    'ACTIVE',
+	'demo alarm rule',
+	0,
+	'[{"clazz":"org.thingsboard.server.extensions.core.filter.MsgTypeFilter", "name":"MsgTypeFilter", "configuration": {"messageTypes":["POST_TELEMETRY","POST_ATTRIBUTES","GET_ATTRIBUTES"]}}
+	,
+	{"clazz":"org.thingsboard.server.extensions.core.filter.DeviceTelemetryFilter", "name":"Temperature filter", "configuration": {"filter":"typeof temperature !== ''undefined'' && temperature >= 100"}}
+	]',
+	'{"clazz":"org.thingsboard.server.extensions.core.processor.AlarmDeduplicationProcessor", "name": "AlarmDeduplicationProcessor", "configuration":{
+                                                                                                                                "alarmIdTemplate": "[$date.get(''yyyy-MM-dd HH:mm'')] Device $cs.get(''serialNumber'')($cs.get(''model'')) temperature is high!",
+                                                                                                                                "alarmBodyTemplate": "[$date.get(''yyyy-MM-dd HH:mm:ss'')] Device $cs.get(''serialNumber'')($cs.get(''model'')) temperature is $temperature.valueAsString!"
+                                                                                                                              }}',
+	'{"clazz":"org.thingsboard.server.extensions.core.action.mail.SendMailAction", "name":"Send Mail Action", "configuration":{
+                                                                                                                                "sendFlag": "isNewAlarm",
+                                                                                                                                "fromTemplate": "thingsboard@gmail.com",
+                                                                                                                                "toTemplate": "thingsboard@gmail.com",
+                                                                                                                                "subjectTemplate": "$alarmId",
+                                                                                                                                "bodyTemplate": "$alarmBody"
+                                                                                                                              }}'
+);
+
+INSERT INTO thingsboard.rule ( id, tenant_id, name, plugin_token, state, search_text, weight, filters, processor, action)
+VALUES (
+	minTimeuuid ( '2016-11-01 01:02:10+0000' ),
+	minTimeuuid ( '2016-11-01 01:02:01+0000' ),
+	'Demo getTime RPC Rule',
+	'time',
+    'ACTIVE',
+	'demo alarm rule',
+	0,
+	'[{"configuration":{"messageTypes":["RPC_REQUEST"]},"name":"RPC Request Filter","clazz":"org.thingsboard.server.extensions.core.filter.MsgTypeFilter"},{"configuration":{"methodNames":[{"name":"getTime"}]},"name":"getTime method filter","clazz":"org.thingsboard.server.extensions.core.filter.MethodNameFilter"}]',
+	null,
+	'{"configuration":{},"clazz":"org.thingsboard.server.extensions.core.action.rpc.RpcPluginAction","name":"getTimeAction"}'
+);
+
+INSERT INTO thingsboard.rule ( id, tenant_id, name, plugin_token, state, search_text, weight, filters, processor, action)
+VALUES (
+	minTimeuuid ( '2016-11-01 01:02:11+0000' ),
+	minTimeuuid ( '2016-11-01 01:02:01+0000' ),
+	'Demo Time RPC Rule',
+	'time',
+    'ACTIVE',
+	'demo time rpc rule',
+	0,
+	'[{"configuration":{"messageTypes":["RPC_REQUEST"]},"name":"RPC Request Filter","clazz":"org.thingsboard.server.extensions.core.filter.MsgTypeFilter"},{"configuration":{"methodNames":[{"name":"getTime"}]},"name":"getTime method filter","clazz":"org.thingsboard.server.extensions.core.filter.MethodNameFilter"}]',
+	null,
+	'{"configuration":{},"clazz":"org.thingsboard.server.extensions.core.action.rpc.RpcPluginAction","name":"getTimeAction"}'
+);
+
+INSERT INTO thingsboard.rule ( id, tenant_id, name, plugin_token, state, search_text, weight, filters, processor, action)
+VALUES (
+	minTimeuuid ( '2016-11-01 01:02:12+0000' ),
+	minTimeuuid ( '2016-11-01 01:02:01+0000' ),
+	'Demo Messaging RPC Rule',
+	'messaging',
+    'ACTIVE',
+	'demo messaging rpc rule',
+	0,
+	'[{"configuration":{"messageTypes":["RPC_REQUEST"]},"name":"RPC Request Filter","clazz":"org.thingsboard.server.extensions.core.filter.MsgTypeFilter"},{"configuration":{"methodNames":[{"name":"getDevices"},{"name":"sendMsg"}]},"name":"Messaging methods filter","clazz":"org.thingsboard.server.extensions.core.filter.MethodNameFilter"}]',
+	null,
+	'{"configuration":{},"clazz":"org.thingsboard.server.extensions.core.action.rpc.RpcPluginAction","name":"Messaging RPC Action"}'
+);
\ No newline at end of file
diff --git a/dao/src/main/resources/schema.cql b/dao/src/main/resources/schema.cql
new file mode 100644
index 0000000..57bc650
--- /dev/null
+++ b/dao/src/main/resources/schema.cql
@@ -0,0 +1,425 @@
+--
+-- Copyright © 2016 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.
+--
+
+CREATE KEYSPACE IF NOT EXISTS thingsboard
+WITH replication = {
+	'class' : 'SimpleStrategy',
+	'replication_factor' : 1
+};
+
+CREATE TABLE IF NOT EXISTS thingsboard.user (
+	id timeuuid,
+	tenant_id timeuuid,
+	customer_id timeuuid,
+	email text,
+	search_text text,
+	authority text,
+	first_name text,
+	last_name text,
+	additional_info text,
+	PRIMARY KEY (id, tenant_id, customer_id, authority)
+);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.user_by_email AS
+	SELECT *
+	from thingsboard.user
+	WHERE email IS NOT NULL AND tenant_id IS NOT NULL AND customer_id IS NOT NULL AND id IS NOT NULL AND authority IS NOT
+	NULL
+	PRIMARY KEY ( email, tenant_id, customer_id, id, authority );
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.user_by_tenant_and_search_text AS
+	SELECT *
+	from thingsboard.user
+	WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND authority IS NOT NULL AND search_text IS NOT NULL AND id
+	IS NOT NULL
+	PRIMARY KEY ( tenant_id, customer_id, authority, search_text, id )
+	WITH CLUSTERING ORDER BY ( customer_id DESC, authority DESC, search_text ASC, id DESC );
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.user_by_customer_and_search_text AS
+	SELECT *
+	from thingsboard.user
+	WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND authority IS NOT NULL AND search_text IS NOT NULL AND id
+	IS NOT NULL
+	PRIMARY KEY ( customer_id, tenant_id, authority, search_text, id )
+	WITH CLUSTERING ORDER BY ( tenant_id DESC, authority DESC, search_text ASC, id DESC );
+
+CREATE TABLE IF NOT EXISTS thingsboard.user_credentials (
+	id timeuuid PRIMARY KEY,
+	user_id timeuuid,
+	enabled boolean,
+	password text,
+	activate_token text,
+	reset_token text
+);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.user_credentials_by_user AS
+	SELECT *
+	from thingsboard.user_credentials
+	WHERE user_id IS NOT NULL AND id IS NOT NULL
+	PRIMARY KEY ( user_id, id );
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.user_credentials_by_activate_token AS
+	SELECT *
+	from thingsboard.user_credentials
+	WHERE activate_token IS NOT NULL AND id IS NOT NULL
+	PRIMARY KEY ( activate_token, id );
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.user_credentials_by_reset_token AS
+	SELECT *
+	from thingsboard.user_credentials
+	WHERE reset_token IS NOT NULL AND id IS NOT NULL
+	PRIMARY KEY ( reset_token, id );
+
+CREATE TABLE IF NOT EXISTS thingsboard.admin_settings (
+	id timeuuid PRIMARY KEY,
+	key text,
+	json_value text
+);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.admin_settings_by_key AS
+	SELECT *
+	from thingsboard.admin_settings
+	WHERE key IS NOT NULL AND id IS NOT NULL
+	PRIMARY KEY ( key, id )
+	WITH CLUSTERING ORDER BY ( id DESC );
+
+CREATE TABLE IF NOT EXISTS thingsboard.tenant (
+	id timeuuid,
+	title text,
+	search_text text,
+	region text,
+	country text,
+	state text,
+	city text,
+	address text,
+	address2 text,
+	zip text,
+	phone text,
+	email text,
+	additional_info text,
+	PRIMARY KEY (id, region)
+);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.tenant_by_region_and_search_text AS
+	SELECT *
+	from thingsboard.tenant
+	WHERE region IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+	PRIMARY KEY ( region, search_text, id )
+	WITH CLUSTERING ORDER BY ( search_text ASC, id DESC );
+
+CREATE TABLE IF NOT EXISTS thingsboard.customer (
+	id timeuuid,
+	tenant_id timeuuid,
+	title text,
+	search_text text,
+	country text,
+	state text,
+	city text,
+	address text,
+	address2 text,
+	zip text,
+	phone text,
+	email text,
+	additional_info text,
+	PRIMARY KEY (id, tenant_id)
+);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.customer_by_tenant_and_search_text AS
+	SELECT *
+	from thingsboard.customer
+	WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+	PRIMARY KEY ( tenant_id, search_text, id )
+	WITH CLUSTERING ORDER BY ( search_text ASC, id DESC );
+
+CREATE TABLE IF NOT EXISTS thingsboard.device (
+	id timeuuid,
+	tenant_id timeuuid,
+	customer_id timeuuid,
+	name text,
+	search_text text,
+	additional_info text,
+	PRIMARY KEY (id, tenant_id, customer_id)
+);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_and_name AS
+	SELECT *
+	from thingsboard.device
+	WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND name IS NOT NULL AND id IS NOT NULL
+	PRIMARY KEY ( tenant_id, name, id, customer_id)
+	WITH CLUSTERING ORDER BY ( name ASC, id DESC, customer_id DESC);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_and_search_text AS
+	SELECT *
+	from thingsboard.device
+	WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+	PRIMARY KEY ( tenant_id, search_text, id, customer_id)
+	WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_id DESC);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_customer_and_search_text AS
+	SELECT *
+	from thingsboard.device
+	WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+	PRIMARY KEY ( customer_id, tenant_id, search_text, id )
+	WITH CLUSTERING ORDER BY ( tenant_id DESC, search_text ASC, id DESC );
+
+CREATE TABLE IF NOT EXISTS thingsboard.device_credentials (
+	id timeuuid PRIMARY KEY,
+	device_id timeuuid,
+	credentials_type text,
+	credentials_id text,
+	credentials_value text
+);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_credentials_by_device AS
+	SELECT *
+	from thingsboard.device_credentials
+	WHERE device_id IS NOT NULL AND id IS NOT NULL
+	PRIMARY KEY ( device_id, id );
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_credentials_by_credentials_id AS
+	SELECT *
+	from thingsboard.device_credentials
+	WHERE credentials_id IS NOT NULL AND id IS NOT NULL
+	PRIMARY KEY ( credentials_id, id );
+
+CREATE TABLE IF NOT EXISTS thingsboard.widgets_bundle (
+    id timeuuid,
+    tenant_id timeuuid,
+    alias text,
+    title text,
+    search_text text,
+    image blob,
+    PRIMARY KEY (id, tenant_id)
+);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.widgets_bundle_by_tenant_and_search_text AS
+    SELECT *
+    from thingsboard.widgets_bundle
+    WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+    PRIMARY KEY ( tenant_id, search_text, id )
+    WITH CLUSTERING ORDER BY ( search_text ASC, id DESC );
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.widgets_bundle_by_tenant_and_alias AS
+    SELECT *
+    from thingsboard.widgets_bundle
+    WHERE tenant_id IS NOT NULL AND alias IS NOT NULL AND id IS NOT NULL
+    PRIMARY KEY ( tenant_id, alias, id )
+    WITH CLUSTERING ORDER BY ( alias ASC, id DESC );
+
+CREATE TABLE IF NOT EXISTS thingsboard.widget_type (
+    id timeuuid,
+    tenant_id timeuuid,
+    bundle_alias text,
+    alias text,
+    name text,
+    descriptor text,
+    PRIMARY KEY (id, tenant_id, bundle_alias)
+);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.widget_type_by_tenant_and_aliases AS
+    SELECT *
+    from thingsboard.widget_type
+    WHERE tenant_id IS NOT NULL AND bundle_alias IS NOT NULL AND alias IS NOT NULL AND id IS NOT NULL
+    PRIMARY KEY ( tenant_id, bundle_alias, alias, id )
+    WITH CLUSTERING ORDER BY ( bundle_alias ASC, alias ASC, id DESC );
+
+CREATE TABLE IF NOT EXISTS thingsboard.dashboard (
+	id timeuuid,
+	tenant_id timeuuid,
+	customer_id timeuuid,
+	title text,
+	search_text text,
+	configuration text,
+	PRIMARY KEY (id, tenant_id, customer_id)
+);	
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_tenant_and_search_text AS
+	SELECT *
+	from thingsboard.dashboard
+	WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+	PRIMARY KEY ( tenant_id, customer_id, search_text, id )
+	WITH CLUSTERING ORDER BY ( customer_id DESC, search_text ASC, id DESC );
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_customer_and_search_text AS
+	SELECT *
+	from thingsboard.dashboard
+	WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+	PRIMARY KEY ( customer_id, tenant_id, search_text, id )
+	WITH CLUSTERING ORDER BY ( tenant_id DESC, search_text ASC, id DESC );
+
+CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_cf (
+    entity_type text, // (DEVICE, CUSTOMER, TENANT)
+    entity_id timeuuid,
+    key text,
+    partition bigint,
+    ts bigint,
+    bool_v boolean,
+    str_v text,
+    long_v bigint,
+    dbl_v double,
+    PRIMARY KEY (( entity_type, entity_id, key, partition ), ts)
+);
+
+CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_partitions_cf (
+    entity_type text, // (DEVICE, CUSTOMER, TENANT)
+    entity_id timeuuid,
+    key text,
+    partition bigint,
+    PRIMARY KEY (( entity_type, entity_id, key ), partition)
+) WITH CLUSTERING ORDER BY ( partition ASC )
+  AND compaction = { 'class' :  'LeveledCompactionStrategy'  };
+
+CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_latest_cf (
+    entity_type text, // (DEVICE, CUSTOMER, TENANT)
+    entity_id timeuuid,
+    key text,
+    ts bigint,
+    bool_v boolean,
+    str_v text,
+    long_v bigint,
+    dbl_v double,
+    PRIMARY KEY (( entity_type, entity_id ), key)
+) WITH compaction = { 'class' :  'LeveledCompactionStrategy'  };
+
+
+CREATE TABLE IF NOT EXISTS thingsboard.attributes_kv_cf (
+    entity_type text, // (DEVICE, CUSTOMER, TENANT)
+    entity_id timeuuid,
+    attribute_type text, // (CLIENT_SIDE, SHARED, SERVER_SIDE)
+    attribute_key text,
+    bool_v boolean,
+    str_v text,
+    long_v bigint,
+    dbl_v double,
+    last_update_ts bigint,
+    PRIMARY KEY ((entity_type, entity_id, attribute_type), attribute_key)
+) WITH compaction = { 'class' :  'LeveledCompactionStrategy'  };
+
+CREATE TABLE IF NOT EXISTS  thingsboard.component_descriptor (
+    id timeuuid,
+    type text, //("FILTER", "PROCESSOR", "ACTION", "PLUGIN")
+    scope text,
+    name text,
+    search_text text,
+    clazz text,
+    configuration_descriptor text,
+    actions text,
+    PRIMARY KEY (clazz, id, type, scope)
+);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.component_desc_by_type_search_text AS
+    SELECT *
+    from thingsboard.component_descriptor
+    WHERE type IS NOT NULL AND scope IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL AND clazz IS NOT NULL
+    PRIMARY KEY ( type, search_text, id, clazz, scope)
+    WITH CLUSTERING ORDER BY ( search_text DESC);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.component_desc_by_scope_type_search_text AS
+    SELECT *
+    from thingsboard.component_descriptor
+    WHERE type IS NOT NULL AND scope IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL AND clazz IS NOT NULL
+    PRIMARY KEY ( (scope, type), search_text, id, clazz)
+    WITH CLUSTERING ORDER BY ( search_text DESC);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.component_desc_by_id AS
+    SELECT *
+    from thingsboard.component_descriptor
+    WHERE type IS NOT NULL AND scope IS NOT NULL AND id IS NOT NULL AND clazz IS NOT NULL
+    PRIMARY KEY ( id, clazz, scope, type )
+    WITH CLUSTERING ORDER BY ( clazz ASC, scope ASC, type DESC);
+
+CREATE TABLE IF NOT EXISTS  thingsboard.rule (
+    id timeuuid,
+    tenant_id timeuuid,
+    name text,
+    state text,
+    search_text text,
+    weight int,
+    plugin_token text,
+    filters text, // Format: {"clazz":"A", "name": "Filter A", "configuration": {"types":["TELEMETRY"]}}
+    processor text, // Format: {"clazz":"A", "name": "Processor A", "configuration": null}
+    action text, // Format: {"clazz":"A", "name": "Action A", "configuration": null}
+    additional_info text,
+    PRIMARY KEY (id, tenant_id)
+);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.rule_by_plugin_token AS
+    SELECT *
+
+    FROM thingsboard.rule
+    WHERE tenant_id IS NOT NULL AND id IS NOT NULL AND plugin_token IS NOT NULL
+    PRIMARY KEY (plugin_token, tenant_id, id) WITH CLUSTERING ORDER BY (tenant_id DESC, id DESC);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.rule_by_tenant_and_search_text AS
+    SELECT *
+    FROM thingsboard.rule
+    WHERE tenant_id IS NOT NULL AND id IS NOT NULL AND search_text IS NOT NULL
+    PRIMARY KEY (tenant_id, search_text, id) WITH CLUSTERING ORDER BY (search_text ASC);
+
+CREATE TABLE IF NOT EXISTS  thingsboard.plugin (
+    id uuid,
+    tenant_id uuid,
+    name text,
+    state text,
+    search_text text,
+    api_token text,
+    plugin_class text,
+    public_access boolean,
+    configuration text,
+    additional_info text,
+    PRIMARY KEY (id, tenant_id)
+);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.plugin_by_api_token AS
+    SELECT *
+    FROM thingsboard.plugin
+    WHERE api_token IS NOT NULL AND id IS NOT NULL AND tenant_id IS NOT NULL
+    PRIMARY KEY (api_token, id, tenant_id) WITH CLUSTERING ORDER BY (id DESC);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.plugin_by_tenant_and_search_text AS
+    SELECT *
+    from thingsboard.plugin
+    WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+    PRIMARY KEY ( tenant_id, search_text, id )
+    WITH CLUSTERING ORDER BY ( search_text ASC, id DESC );
+
+CREATE TABLE IF NOT EXISTS thingsboard.event (
+	tenant_id timeuuid, // tenant or system
+	id timeuuid,
+	event_type text,
+	event_uid text,
+	entity_type text, // (device, customer, rule, plugin)
+	entity_id timeuuid,
+	body text,
+	PRIMARY KEY ((tenant_id, entity_type, entity_id), event_type, event_uid)
+);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.event_by_type_and_id AS
+    SELECT *
+    FROM thingsboard.event
+    WHERE tenant_id IS NOT NULL AND entity_type IS NOT NULL AND entity_id IS NOT NULL AND id IS NOT NULL
+    AND event_type IS NOT NULL AND event_uid IS NOT NULL
+    PRIMARY KEY ((tenant_id, entity_type, entity_id), event_type, id, event_uid)
+    WITH CLUSTERING ORDER BY (event_type ASC, id ASC, event_uid ASC);
+
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.event_by_id AS
+    SELECT *
+    FROM thingsboard.event
+    WHERE tenant_id IS NOT NULL AND entity_type IS NOT NULL AND entity_id IS NOT NULL AND id IS NOT NULL
+    AND event_type IS NOT NULL AND event_uid IS NOT NULL
+    PRIMARY KEY ((tenant_id, entity_type, entity_id), id, event_type, event_uid)
+    WITH CLUSTERING ORDER BY (id ASC, event_type ASC, event_uid ASC);
diff --git a/dao/src/main/resources/system-data.cql b/dao/src/main/resources/system-data.cql
new file mode 100644
index 0000000..1358e58
--- /dev/null
+++ b/dao/src/main/resources/system-data.cql
@@ -0,0 +1,258 @@
+--
+-- Copyright © 2016 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.
+--
+
+/** SYSTEM **/
+
+/** System admin **/
+INSERT INTO thingsboard.user ( id, tenant_id, customer_id, email, search_text, authority )
+VALUES ( minTimeuuid ( '2016-11-01 01:01:01+0000' ), minTimeuuid ( 0 ), minTimeuuid ( 0 ), 'sysadmin@thingsboard.org',
+'sysadmin@thingsboard.org', 'SYS_ADMIN' );
+
+INSERT INTO thingsboard.user_credentials ( id, user_id, enabled, password )
+VALUES ( now ( ), minTimeuuid ( '2016-11-01 01:01:01+0000' ), true,
+'$2a$10$5JTB8/hxWc9WAy62nCGSxeefl3KWmipA9nFpVdDa0/xfIseeBB4Bu' );
+
+/** System settings **/
+INSERT INTO thingsboard.admin_settings ( id, key, json_value )
+VALUES ( now ( ), 'general', '{
+	"baseUrl": "http://localhost:8080"
+}' );
+
+INSERT INTO thingsboard.admin_settings ( id, key, json_value )
+VALUES ( now ( ), 'mail', '{
+	"mailFrom": "Thingsboard <sysadmin@localhost.localdomain>",
+	"smtpProtocol": "smtp",
+	"smtpHost": "localhost",
+	"smtpPort": "25",
+	"timeout": "10000",
+	"enableTls": "false",
+	"username": "",
+	"password": ""
+}' );
+
+/** System widgets library **/
+INSERT INTO "thingsboard"."widgets_bundle" ( "id", "tenant_id", "alias", "search_text", "title" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'gpio_widgets', 'gpio widgets', 'GPIO widgets' );
+
+INSERT INTO "thingsboard"."widgets_bundle" ( "id", "tenant_id", "alias", "search_text", "title" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'maps', 'maps', 'Maps' );
+
+INSERT INTO "thingsboard"."widgets_bundle" ( "id", "tenant_id", "alias", "search_text", "title" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'digital_gauges', 'digital gauges', 'Digital gauges' );
+
+INSERT INTO "thingsboard"."widgets_bundle" ( "id", "tenant_id", "alias", "search_text", "title" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'charts', 'charts', 'Charts' );
+
+INSERT INTO "thingsboard"."widgets_bundle" ( "id", "tenant_id", "alias", "search_text", "title" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'cards', 'cards', 'Cards' );
+
+INSERT INTO "thingsboard"."widgets_bundle" ( "id", "tenant_id", "alias", "search_text", "title" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'analogue_gauges', 'analogue gauges', 'Analogue gauges' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'cards', 'attributes_card',
+'{"type":"latest","sizeX":7.5,"sizeY":3,"resources":[],"templateHtml":"","templateCss":"#container {\n    overflow: auto;\n}\n\n.tbDatasource-container {\n    margin: 5px;\n    padding: 8px;\n}\n\n.tbDatasource-title {\n    font-size: 1.200rem;\n    font-weight: 500;\n    padding-bottom: 10px;\n}\n\n.tbDatasource-table {\n    width: 100%;\n    box-shadow: 0 0 10px #ccc;\n    border-collapse: collapse;\n    white-space: nowrap;\n    font-size: 1.000rem;\n    color: #757575;\n}\n\n.tbDatasource-table td {\n    position: relative;\n    border-top: 1px solid rgba(0, 0, 0, 0.12);\n    border-bottom: 1px solid rgba(0, 0, 0, 0.12);\n    padding: 0px 18px;\n    box-sizing: border-box;\n}","controllerScript":"var datasourceTitleCells = [];\nvar valueCells = [];\nvar labelCells = [];\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n\n    var container = $(containerElement);\n\n    for (var i in datasources) {\n        var tbDatasource = datasources[i];\n\n        var datasourceId = ''tbDatasource'' + i;\n        container.append(\n            \"<div id=''\" + datasourceId +\n            \"'' class=''tbDatasource-container''></div>\"\n        );\n\n        var datasourceContainer = $(''#'' + datasourceId,\n            container);\n\n        datasourceContainer.append(\n            \"<div class=''tbDatasource-title''>\" +\n            tbDatasource.name + \"</div>\"\n        );\n        \n        var datasourceTitleCell = $(''.tbDatasource-title'', datasourceContainer);\n        datasourceTitleCells.push(datasourceTitleCell);\n        \n        var tableId = ''table'' + i;\n        datasourceContainer.append(\n            \"<table id=''\" + tableId +\n            \"'' class=''tbDatasource-table''><col width=''30%''><col width=''70%''></table>\"\n        );\n        var table = $(''#'' + tableId, containerElement);\n\n        for (var a in tbDatasource.dataKeys) {\n            var dataKey = tbDatasource.dataKeys[a];\n            var labelCellId = ''labelCell'' + a;\n            var cellId = ''cell'' + a;\n            table.append(\"<tr><td id=''\" + labelCellId + \"''>\" + dataKey.label +\n                \"</td><td id=''\" + cellId +\n                \"''></td></tr>\");\n            var labelCell = $(''#'' + labelCellId, table);\n            labelCells.push(labelCell);\n            var valueCell = $(''#'' + cellId, table);\n            valueCells.push(valueCell);\n        }\n    }\n\n}\n\n\nfns.redraw = function(containerElement, width, height, data, timeWindow, sizeChanged) {\n    \n    if (sizeChanged) {\n        var datasoirceTitleFontSize = height/8;\n        if (width/height <= 1.5) {\n            datasoirceTitleFontSize = width/12;\n        }\n        datasoirceTitleFontSize = Math.min(datasoirceTitleFontSize, 20);\n        for (var i in datasourceTitleCells) {\n            datasourceTitleCells[i].css(''font-size'', datasoirceTitleFontSize+''px'');\n        }\n        var valueFontSize = height/9;\n        var labelFontSize = height/9;\n        if (width/height <= 1.5) {\n            valueFontSize = width/15;\n            labelFontSize = width/15;\n        }\n        valueFontSize = Math.min(valueFontSize, 18);\n        labelFontSize = Math.min(labelFontSize, 18);\n\n        for (i in valueCells) {\n            valueCells[i].css(''font-size'', valueFontSize+''px'');\n            valueCells[i].css(''height'', valueFontSize*2.5+''px'');\n            valueCells[i].css(''padding'', ''0px '' + valueFontSize + ''px'');\n            labelCells[i].css(''font-size'', labelFontSize+''px'');\n            labelCells[i].css(''height'', labelFontSize*2.5+''px'');\n            labelCells[i].css(''padding'', ''0px '' + labelFontSize + ''px'');\n        }\n    }\n    \n    for (i in valueCells) {\n        var cellData = data[i];\n        if (cellData.data.length > 0) {\n            var tvPair = cellData.data[cellData.data.length -\n                1];\n            var value = tvPair[1];\n            valueCells[i].html(value);\n        }\n    }\n\n};","settingsSchema":"{}","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Attributes card\"}"}',
+'Attributes card' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'cards', 'simple_card',
+'{"type":"latest","sizeX":5,"sizeY":3,"resources":[],"templateHtml":"","templateCss":"#container {\n    overflow: auto;\n}\n\n.tbDatasource-container {\n    width: 100%;\n    height: 100%;\n    overflow: hidden;\n}\n\n.tbDatasource-table {\n    width: 100%;\n    height: 100%;\n    border-collapse: collapse;\n    white-space: nowrap;\n    font-weight: 100;\n    text-align: right;\n}\n\n.tbDatasource-table td {\n    padding: 12px;\n    position: relative;\n    box-sizing: border-box;\n}\n\n.tbDatasource-data-key {\n    opacity: 0.7;\n    font-weight: 400;\n    font-size: 3.500rem;\n}\n\n.tbDatasource-value {\n    font-size: 5.000rem;\n}","controllerScript":"var labelCell;\nvar valueCell;\nvar valueFontSize;\nvar padding;\nvar datasourceContainer;\nvar units;\nvar valueDec;\nvar labelPosition;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n\n    var container = $(containerElement);\n    \n    units = settings.units || \"\";\n    valueDec = (typeof settings.valueDec !== ''undefined'' && settings.valueDec !== null)\n                ? settings.valueDec : 2;\n                \n    labelPosition = settings.labelPosition || ''left'';\n    \n    if (datasources.length > 0) {\n        var tbDatasource = datasources[0];\n        var datasourceId = ''tbDatasource'' + 0;\n        container.append(\n            \"<div id=''\" + datasourceId +\n            \"'' class=''tbDatasource-container''></div>\"\n        );\n        \n        datasourceContainer = $(''#'' + datasourceId,\n            container);\n        \n        var tableId = ''table'' + 0;\n        datasourceContainer.append(\n            \"<table id=''\" + tableId +\n            \"'' class=''tbDatasource-table''><col width=''30%''><col width=''70%''></table>\"\n        );\n        var table = $(''#'' + tableId, containerElement);\n        if (labelPosition === ''top'') {\n            table.css(''text-align'', ''left'');\n        }\n        \n        if (tbDatasource.dataKeys.length > 0) {\n            var dataKey = tbDatasource.dataKeys[0];\n            var labelCellId = ''labelCell'' + 0;\n            var cellId = ''cell'' + 0;\n            if (labelPosition === ''left'') {\n                table.append(\n                    \"<tr><td class=''tbDatasource-data-key'' id=''\" + labelCellId +\"''>\" +\n                    dataKey.label +\n                    \"</td><td class=''tbDatasource-value'' id=''\" +\n                    cellId +\n                    \"''></td></tr>\");\n            } else {\n                table.append(\n                    \"<tr style=''vertical-align: bottom;''><td class=''tbDatasource-data-key'' id=''\" + labelCellId +\"''>\" +\n                    dataKey.label +\n                    \"</td></tr><tr><td class=''tbDatasource-value'' id=''\" +\n                    cellId +\n                    \"''></td></tr>\");\n            }\n            labelCell = $(''#'' + labelCellId, table);\n            valueCell = $(''#'' + cellId, table);\n            valueCell.html(0 + '' '' + units);\n        }\n    }\n    \n    $.fn.textWidth = function(){\n        var html_org = $(this).html();\n        var html_calc = ''<span>'' + html_org + ''</span>'';\n        $(this).html(html_calc);\n        var width = $(this).find(''span:first'').width();\n        $(this).html(html_org);\n        return width;\n    };\n}\n\n\nfns.redraw = function(containerElement, width, height, data, timeWindow, sizeChanged) {\n    \n    function isNumber(n) {\n        return !isNaN(parseFloat(n)) && isFinite(n);\n    }\n    \n    function padValue(val, dec, int) {\n        var i = 0;\n        var s, strVal, n;\n    \n        val = parseFloat(val);\n        n = (val < 0);\n        val = Math.abs(val);\n    \n        if (dec > 0) {\n            strVal = val.toFixed(dec).toString().split(''.'');\n            s = int - strVal[0].length;\n    \n            for (; i < s; ++i) {\n                strVal[0] = ''0'' + strVal[0];\n            }\n    \n            strVal = (n ? ''-'' : '''') + strVal[0] + ''.'' + strVal[1];\n        }\n    \n        else {\n            strVal = Math.round(val).toString();\n            s = int - strVal.length;\n    \n            for (; i < s; ++i) {\n                strVal = ''0'' + strVal;\n            }\n    \n            strVal = (n ? ''-'' : '''') + strVal;\n        }\n    \n        return strVal;\n    }\n    \n    if (sizeChanged) {\n        var labelFontSize;\n        if (labelPosition === ''top'') {\n            padding = height/20;\n            labelFontSize = height/4;\n            valueFontSize = height/2;\n        } else {\n            padding = width/50;\n            labelFontSize = height/2.5;\n            valueFontSize = height/2;\n            if (width/height <= 2.7) {\n                labelFontSize = width/7;\n                valueFontSize = width/6;\n            }\n        }\n        padding = Math.min(12, padding);\n        \n        if (labelCell) {\n            labelCell.css(''font-size'', labelFontSize+''px'');\n            labelCell.css(''padding'', padding+''px'');\n        }\n        if (valueCell) {\n            valueCell.css(''font-size'', valueFontSize+''px'');\n            valueCell.css(''padding'', padding+''px'');\n        }\n    }\n\n    if (valueCell && data.length > 0) {\n        var cellData = data[0];\n        if (cellData.data.length > 0) {\n            var tvPair = cellData.data[cellData.data.length -\n                1];\n            var value = tvPair[1];\n            var txtValue;\n            if (isNumber(value)) {\n                txtValue = padValue(value, valueDec, 0) + '' '' + units;\n            } else {\n                txtValue = value;\n            }\n            valueCell.html(txtValue);\n            var targetWidth;\n            var minDelta;\n            if (labelPosition === ''left'') {\n                targetWidth = datasourceContainer.width() - labelCell.width();\n                minDelta = width/16 + padding;\n            } else {\n                targetWidth = datasourceContainer.width();\n                minDelta = padding;\n            }\n            var delta = targetWidth - valueCell.textWidth();\n            var fontSize = valueFontSize;\n            if (targetWidth > minDelta) {\n                while (delta < minDelta && fontSize > 6) {\n                    fontSize--;\n                    valueCell.css(''font-size'', fontSize+''px'');\n                    delta = targetWidth - valueCell.textWidth();\n                }\n            }\n        }\n    }\n\n};\n","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"units\": {\n                \"title\": \"Units\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"valueDec\": {\n                \"title\": \"Digits count for decimal part of value\",\n                \"type\": \"number\",\n                \"default\": 2\n            },\n            \"labelPosition\": {\n                \"title\": \"Label position\",\n                \"type\": \"string\",\n                \"default\": \"left\"\n            }\n        },\n        \"required\": []\n    },\n    \"form\": [\n        \"units\",\n        \"valueDec\",\n        {\n           \"key\": \"labelPosition\",\n           \"type\": \"rc-select\",\n           \"multiple\": false,\n           \"items\": [\n               {\n                   \"value\": \"left\",\n                   \"label\": \"Left\"\n               },\n               {\n                   \"value\": \"top\",\n                   \"label\": \"Top\"\n               }\n            ]\n        }\n    ]\n}","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ff5722\",\"color\":\"rgba(255, 255, 255, 0.87)\",\"padding\":\"16px\",\"settings\":{\"units\":\"°C\",\"valueDec\":1,\"labelPosition\":\"top\"},\"title\":\"Simple card\"}"}',
+'Simple card' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'analogue_gauges', 'speed_gauge_canvas_gauges',
+'{"type":"latest","sizeX":7,"sizeY":5,"resources":[],"templateHtml":"<canvas id=\"radialGauge\"></canvas>\n","templateCss":"","controllerScript":"var gauge;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n    gauge = new TbAnalogueRadialGauge(containerElement, settings, data, ''radialGauge'');    \n\n}\n\n\nfns.redraw = function(containerElement, width, height, data, timeWindow, sizeChanged) {\n    gauge.redraw(width, height, data, sizeChanged);\n};\n\nfns.destroy = function() {\n}\n","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"minValue\": {\n                \"title\": \"Minimum value\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"maxValue\": {\n                \"title\": \"Maximum value\",\n                \"type\": \"number\",\n                \"default\": 100\n            },\n            \"unitTitle\": {\n                \"title\": \"Unit title\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"showUnitTitle\": {\n                \"title\": \"Show unit title\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"units\": {\n                \"title\": \"Units\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"majorTicksCount\": {\n                \"title\": \"Major ticks count\",\n                \"type\": \"number\",\n                \"default\": null\n            },\n            \"minorTicks\": {\n                \"title\": \"Minor ticks count\",\n                \"type\": \"number\",\n                \"default\": 2\n            },\n            \"valueBox\": {\n                \"title\": \"Show value box\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"valueInt\": {\n                \"title\": \"Digits count for integer part of value\",\n                \"type\": \"number\",\n                \"default\": 3\n            },\n            \"valueDec\": {\n                \"title\": \"Digits count for decimal part of value\",\n                \"type\": \"number\",\n                \"default\": 2\n            },\n            \"defaultColor\": {\n                \"title\": \"Default color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"colorPlate\": {\n                \"title\": \"Plate color\",\n                \"type\": \"string\",\n                \"default\": \"#fff\"\n            },\n            \"colorMajorTicks\": {\n                \"title\": \"Major ticks color\",\n                \"type\": \"string\",\n                \"default\": \"#444\"\n            },\n            \"colorMinorTicks\": {\n                \"title\": \"Minor ticks color\",\n                \"type\": \"string\",\n                \"default\": \"#666\"\n            },\n            \"colorNeedle\": {\n                \"title\": \"Needle color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"colorNeedleEnd\": {\n                \"title\": \"Needle color - end gradient\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"colorNeedleShadowUp\": {\n                \"title\": \"Upper half of the needle shadow color\",\n                \"type\": \"string\",\n                \"default\": \"rgba(2,255,255,0.2)\"\n            },\n            \"colorNeedleShadowDown\": {\n                \"title\": \"Drop shadow needle color.\",\n                \"type\": \"string\",\n                \"default\": \"rgba(188,143,143,0.45)\"\n            },\n            \"colorValueBoxRect\": {\n                \"title\": \"Value box rectangle stroke color\",\n                \"type\": \"string\",\n                \"default\": \"#888\"\n            },\n            \"colorValueBoxRectEnd\": {\n                \"title\": \"Value box rectangle stroke color - end gradient\",\n                \"type\": \"string\",\n                \"default\": \"#666\"\n            },\n            \"colorValueBoxBackground\": {\n                \"title\": \"Value box background color\",\n                \"type\": \"string\",\n                \"default\": \"#babab2\"\n            },\n            \"colorValueBoxShadow\": {\n                \"title\": \"Value box shadow color\",\n                \"type\": \"string\",\n                \"default\": \"rgba(0,0,0,1)\"\n            },\n            \"highlights\": {\n                \"title\": \"Highlights\",\n                \"type\": \"array\",\n                \"items\": {\n                  \"title\": \"Highlight\",\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"from\": {\n                      \"title\": \"From\",\n                      \"type\": \"number\"\n                    },\n                    \"to\": {\n                      \"title\": \"To\",\n                      \"type\": \"number\"\n                    },\n                    \"color\": {\n                      \"title\": \"Color\",\n                      \"type\": \"string\"\n                    }\n                  }\n                }\n            },\n            \"highlightsWidth\": {\n                \"title\": \"Highlights width\",\n                \"type\": \"number\",\n                \"default\": 15\n            },\n            \"showBorder\": {\n                \"title\": \"Show border\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"numbersFont\": {\n                \"title\": \"Tick numbers font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 18\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"titleFont\": {\n                \"title\": \"Title text font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 24\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": \"#888\"\n                    }\n                }\n            },\n            \"unitsFont\": {\n                \"title\": \"Units text font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 22\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": \"#888\"\n                    }\n                }\n            },\n            \"valueFont\": {\n                \"title\": \"Value text font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 40\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": \"#444\"\n                    },\n                    \"shadowColor\": {\n                        \"title\": \"Shadow color\",\n                        \"type\": \"string\",\n                        \"default\": \"rgba(0,0,0,0.3)\"\n                    }\n                }\n            },\n            \"animation\": {\n                \"title\": \"Enable animation\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"animationDuration\": {\n                \"title\": \"Animation duration\",\n                \"type\": \"number\",\n                \"default\": 500\n            },\n            \"animationRule\": {\n                \"title\": \"Animation rule\",\n                \"type\": \"string\",\n                \"default\": \"cycle\"\n            },\n            \"startAngle\": {\n                \"title\": \"Start ticks angle\",\n                \"type\": \"number\",\n                \"default\": 45\n            },\n            \"ticksAngle\": {\n                \"title\": \"Ticks angle\",\n                \"type\": \"number\",\n                \"default\": 270\n            },\n            \"needleCircleSize\": {\n                \"title\": \"Needle circle size\",\n                \"type\": \"number\",\n                \"default\": 10\n            }\n        },\n        \"required\": []\n    },\n    \"form\": [\n        \"startAngle\",\n        \"ticksAngle\",\n        \"needleCircleSize\",\n        \"minValue\",\n        \"maxValue\",\n        \"unitTitle\",\n        \"showUnitTitle\",\n        \"units\",\n        \"majorTicksCount\",\n        \"minorTicks\",\n        \"valueBox\",\n        \"valueInt\",\n        \"valueDec\",\n        {\n            \"key\": \"defaultColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorPlate\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorMajorTicks\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorMinorTicks\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorNeedle\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorNeedleEnd\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorNeedleShadowUp\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorNeedleShadowDown\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorValueBoxRect\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorValueBoxRectEnd\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorValueBoxBackground\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorValueBoxShadow\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"highlights\",\n            \"items\": [\n                \"highlights[].from\",\n                \"highlights[].to\",\n                {\n                    \"key\": \"highlights[].color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        \"highlightsWidth\",\n        \"showBorder\",\n        {\n            \"key\": \"numbersFont\",\n            \"items\": [\n                \"numbersFont.family\",\n                \"numbersFont.size\",\n                {\n                   \"key\": \"numbersFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"numbersFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"numbersFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"titleFont\",\n            \"items\": [\n                \"titleFont.family\",\n                \"titleFont.size\",\n                {\n                   \"key\": \"titleFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"titleFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"titleFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"unitsFont\",\n            \"items\": [\n                \"unitsFont.family\",\n                \"unitsFont.size\",\n                {\n                   \"key\": \"unitsFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"unitsFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"unitsFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"valueFont\",\n            \"items\": [\n                \"valueFont.family\",\n                \"valueFont.size\",\n                {\n                   \"key\": \"valueFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"valueFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"valueFont.color\",\n                    \"type\": \"color\"\n                },\n                {\n                    \"key\": \"valueFont.shadowColor\",\n                    \"type\": \"color\"\n                }\n            ]\n        },        \n        \"animation\",\n        \"animationDuration\",\n        {\n            \"key\": \"animationRule\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \"quad\",\n                    \"label\": \"Quad\"\n                },\n                {\n                    \"value\": \"quint\",\n                    \"label\": \"Quint\"\n                },\n                {\n                    \"value\": \"cycle\",\n                    \"label\": \"Cycle\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                },\n                {\n                    \"value\": \"elastic\",\n                    \"label\": \"Elastic\"\n                },\n                {\n                    \"value\": \"dequad\",\n                    \"label\": \"Dequad\"\n                },\n                {\n                    \"value\": \"dequint\",\n                    \"label\": \"Dequint\"\n                },\n                {\n                    \"value\": \"decycle\",\n                    \"label\": \"Decycle\"\n                },\n                {\n                    \"value\": \"debounce\",\n                    \"label\": \"Debounce\"\n                },\n                {\n                    \"value\": \"delastic\",\n                    \"label\": \"Delastic\"\n                }\n            ]\n        }\n    ]\n}","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 50 - 25;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 220) {\\n\\tvalue = 220;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":180,\"startAngle\":45,\"ticksAngle\":270,\"showBorder\":false,\"defaultColor\":\"#e65100\",\"needleCircleSize\":7,\"highlights\":[{\"from\":80,\"to\":120,\"color\":\"#fdd835\"},{\"color\":\"#e57373\",\"from\":120,\"to\":180}],\"showUnitTitle\":false,\"colorPlate\":\"#fff\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"minorTicks\":2,\"valueInt\":3,\"minValue\":0,\"valueDec\":0,\"highlightsWidth\":15,\"valueBox\":true,\"animation\":true,\"animationDuration\":1500,\"animationRule\":\"linear\",\"colorNeedleShadowUp\":\"rgba(2, 255, 255, 0)\",\"colorNeedleShadowDown\":\"rgba(188, 143, 143, 0.78)\",\"units\":\"MPH\",\"majorTicksCount\":9,\"numbersFont\":{\"family\":\"RobotoDraft\",\"size\":22,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"titleFont\":{\"family\":\"RobotoDraft\",\"size\":24,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#888\"},\"unitsFont\":{\"family\":\"RobotoDraft\",\"size\":28,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"valueFont\":{\"size\":32,\"style\":\"normal\",\"weight\":\"normal\",\"shadowColor\":\"rgba(0, 0, 0, 0.49)\",\"color\":\"#444\",\"family\":\"Segment7Standard\"},\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\"},\"title\":\"Speed gauge - Canvas Gauges\"}"}',
+'Speed gauge - Canvas Gauges' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'analogue_gauges', 'temperature_gauge_canvas_gauges',
+'{"type":"latest","sizeX":7,"sizeY":3,"resources":[],"templateHtml":"<canvas id=\"linearGauge\"></canvas>\n","templateCss":"","controllerScript":"var gauge;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n    gauge = new TbAnalogueLinearGauge(containerElement, settings, data, ''linearGauge'');    \n\n}\n\n\nfns.redraw = function(containerElement, width, height, data, timeWindow, sizeChanged) {\n    gauge.redraw(width, height, data, sizeChanged);\n};\n\nfns.destroy = function() {\n}\n","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"minValue\": {\n                \"title\": \"Minimum value\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"maxValue\": {\n                \"title\": \"Maximum value\",\n                \"type\": \"number\",\n                \"default\": 100\n            },\n            \"unitTitle\": {\n                \"title\": \"Unit title\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"showUnitTitle\": {\n                \"title\": \"Show unit title\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"units\": {\n                \"title\": \"Units\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"majorTicksCount\": {\n                \"title\": \"Major ticks count\",\n                \"type\": \"number\",\n                \"default\": null\n            },\n            \"minorTicks\": {\n                \"title\": \"Minor ticks count\",\n                \"type\": \"number\",\n                \"default\": 2\n            },\n            \"valueBox\": {\n                \"title\": \"Show value box\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"valueInt\": {\n                \"title\": \"Digits count for integer part of value\",\n                \"type\": \"number\",\n                \"default\": 3\n            },\n            \"valueDec\": {\n                \"title\": \"Digits count for decimal part of value\",\n                \"type\": \"number\",\n                \"default\": 2\n            },\n            \"defaultColor\": {\n                \"title\": \"Default color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"colorPlate\": {\n                \"title\": \"Plate color\",\n                \"type\": \"string\",\n                \"default\": \"#fff\"\n            },\n            \"colorMajorTicks\": {\n                \"title\": \"Major ticks color\",\n                \"type\": \"string\",\n                \"default\": \"#444\"\n            },\n            \"colorMinorTicks\": {\n                \"title\": \"Minor ticks color\",\n                \"type\": \"string\",\n                \"default\": \"#666\"\n            },\n            \"colorNeedle\": {\n                \"title\": \"Needle color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"colorNeedleEnd\": {\n                \"title\": \"Needle color - end gradient\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"colorNeedleShadowUp\": {\n                \"title\": \"Upper half of the needle shadow color\",\n                \"type\": \"string\",\n                \"default\": \"rgba(2,255,255,0.2)\"\n            },\n            \"colorNeedleShadowDown\": {\n                \"title\": \"Drop shadow needle color.\",\n                \"type\": \"string\",\n                \"default\": \"rgba(188,143,143,0.45)\"\n            },\n            \"colorValueBoxRect\": {\n                \"title\": \"Value box rectangle stroke color\",\n                \"type\": \"string\",\n                \"default\": \"#888\"\n            },\n            \"colorValueBoxRectEnd\": {\n                \"title\": \"Value box rectangle stroke color - end gradient\",\n                \"type\": \"string\",\n                \"default\": \"#666\"\n            },\n            \"colorValueBoxBackground\": {\n                \"title\": \"Value box background color\",\n                \"type\": \"string\",\n                \"default\": \"#babab2\"\n            },\n            \"colorValueBoxShadow\": {\n                \"title\": \"Value box shadow color\",\n                \"type\": \"string\",\n                \"default\": \"rgba(0,0,0,1)\"\n            },\n            \"highlights\": {\n                \"title\": \"Highlights\",\n                \"type\": \"array\",\n                \"items\": {\n                  \"title\": \"Highlight\",\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"from\": {\n                      \"title\": \"From\",\n                      \"type\": \"number\"\n                    },\n                    \"to\": {\n                      \"title\": \"To\",\n                      \"type\": \"number\"\n                    },\n                    \"color\": {\n                      \"title\": \"Color\",\n                      \"type\": \"string\"\n                    }\n                  }\n                }\n            },\n            \"highlightsWidth\": {\n                \"title\": \"Highlights width\",\n                \"type\": \"number\",\n                \"default\": 15\n            },\n            \"showBorder\": {\n                \"title\": \"Show border\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"numbersFont\": {\n                \"title\": \"Tick numbers font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 18\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"titleFont\": {\n                \"title\": \"Title text font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 24\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": \"#888\"\n                    }\n                }\n            },\n            \"unitsFont\": {\n                \"title\": \"Units text font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 22\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": \"#888\"\n                    }\n                }\n            },\n            \"valueFont\": {\n                \"title\": \"Value text font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 40\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": \"#444\"\n                    },\n                    \"shadowColor\": {\n                        \"title\": \"Shadow color\",\n                        \"type\": \"string\",\n                        \"default\": \"rgba(0,0,0,0.3)\"\n                    }\n                }\n            },\n            \"animation\": {\n                \"title\": \"Enable animation\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"animationDuration\": {\n                \"title\": \"Animation duration\",\n                \"type\": \"number\",\n                \"default\": 500\n            },\n            \"animationRule\": {\n                \"title\": \"Animation rule\",\n                \"type\": \"string\",\n                \"default\": \"cycle\"\n            },\n            \"barStrokeWidth\": {\n                \"title\": \"Bar stroke width\",\n                \"type\": \"number\",\n                \"default\": 2.5\n            },\n            \"colorBarStroke\": {\n                \"title\": \"Bar stroke color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"colorBar\": {\n                \"title\": \"Bar background color\",\n                \"type\": \"string\",\n                \"default\": \"#fff\"\n            },\n            \"colorBarEnd\": {\n                \"title\": \"Bar background color - end gradient\",\n                \"type\": \"string\",\n                \"default\": \"#ddd\"\n            },\n            \"colorBarProgress\": {\n                \"title\": \"Progress bar color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"colorBarProgressEnd\": {\n                \"title\": \"Progress bar color - end gradient\",\n                \"type\": \"string\",\n                \"default\": null\n            }        \n            \n        },\n        \"required\": []\n    },\n    \"form\": [\n        \"barStrokeWidth\",\n        {\n            \"key\": \"colorBarStroke\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorBar\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorBarEnd\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorBarProgress\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorBarProgressEnd\",\n            \"type\": \"color\"\n        },\n        \"minValue\",\n        \"maxValue\",\n        \"unitTitle\",\n        \"showUnitTitle\",\n        \"units\",\n        \"majorTicksCount\",\n        \"minorTicks\",\n        \"valueBox\",\n        \"valueInt\",\n        \"valueDec\",\n        {\n            \"key\": \"defaultColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorPlate\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorMajorTicks\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorMinorTicks\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorNeedle\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorNeedleEnd\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorNeedleShadowUp\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorNeedleShadowDown\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorValueBoxRect\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorValueBoxRectEnd\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorValueBoxBackground\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorValueBoxShadow\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"highlights\",\n            \"items\": [\n                \"highlights[].from\",\n                \"highlights[].to\",\n                {\n                    \"key\": \"highlights[].color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        \"highlightsWidth\",\n        \"showBorder\",\n        {\n            \"key\": \"numbersFont\",\n            \"items\": [\n                \"numbersFont.family\",\n                \"numbersFont.size\",\n                {\n                   \"key\": \"numbersFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"numbersFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"numbersFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"titleFont\",\n            \"items\": [\n                \"titleFont.family\",\n                \"titleFont.size\",\n                {\n                   \"key\": \"titleFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"titleFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"titleFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"unitsFont\",\n            \"items\": [\n                \"unitsFont.family\",\n                \"unitsFont.size\",\n                {\n                   \"key\": \"unitsFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"unitsFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"unitsFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"valueFont\",\n            \"items\": [\n                \"valueFont.family\",\n                \"valueFont.size\",\n                {\n                   \"key\": \"valueFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"valueFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"valueFont.color\",\n                    \"type\": \"color\"\n                },\n                {\n                    \"key\": \"valueFont.shadowColor\",\n                    \"type\": \"color\"\n                }\n            ]\n        },        \n        \"animation\",\n        \"animationDuration\",\n        {\n            \"key\": \"animationRule\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \"quad\",\n                    \"label\": \"Quad\"\n                },\n                {\n                    \"value\": \"quint\",\n                    \"label\": \"Quint\"\n                },\n                {\n                    \"value\": \"cycle\",\n                    \"label\": \"Cycle\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                },\n                {\n                    \"value\": \"elastic\",\n                    \"label\": \"Elastic\"\n                },\n                {\n                    \"value\": \"dequad\",\n                    \"label\": \"Dequad\"\n                },\n                {\n                    \"value\": \"dequint\",\n                    \"label\": \"Dequint\"\n                },\n                {\n                    \"value\": \"decycle\",\n                    \"label\": \"Decycle\"\n                },\n                {\n                    \"value\": \"debounce\",\n                    \"label\": \"Debounce\"\n                },\n                {\n                    \"value\": \"delastic\",\n                    \"label\": \"Delastic\"\n                }\n            ]\n        }\n    ]\n}","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 30 - 15;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":100,\"defaultColor\":\"#e64a19\",\"barStrokeWidth\":2.5,\"colorBar\":\"rgba(255, 255, 255, 0.4)\",\"colorBarEnd\":\"rgba(221, 221, 221, 0.38)\",\"showUnitTitle\":true,\"minorTicks\":2,\"valueBox\":true,\"valueInt\":3,\"colorPlate\":\"#fff\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"colorNeedleShadowUp\":\"rgba(2,255,255,0.2)\",\"colorNeedleShadowDown\":\"rgba(188,143,143,0.45)\",\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\",\"highlightsWidth\":10,\"animation\":true,\"animationDuration\":1500,\"animationRule\":\"linear\",\"showBorder\":false,\"majorTicksCount\":8,\"numbersFont\":{\"family\":\"Arial\",\"size\":18,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#263238\"},\"titleFont\":{\"family\":\"RobotoDraft\",\"size\":24,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#78909c\"},\"unitsFont\":{\"family\":\"RobotoDraft\",\"size\":26,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#37474f\"},\"valueFont\":{\"family\":\"RobotoDraft\",\"size\":40,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#444\",\"shadowColor\":\"rgba(0,0,0,0.3)\"},\"minValue\":-60,\"highlights\":[{\"from\":0,\"to\":20,\"color\":\"#90caf9\"},{\"from\":20,\"to\":40,\"color\":\"rgba(144, 202, 249, 0.66)\"},{\"from\":40,\"to\":60,\"color\":\"rgba(144, 202, 249, 0.33)\"},{\"from\":60,\"to\":80,\"color\":\"rgba(244, 67, 54, 0.2)\"},{\"from\":80,\"to\":100,\"color\":\"rgba(244, 67, 54, 0.4)\"},{\"from\":100,\"to\":120,\"color\":\"rgba(244, 67, 54, 0.6)\"},{\"from\":120,\"to\":140,\"color\":\"rgba(244, 67, 54, 0.8)\"},{\"from\":140,\"to\":160,\"color\":\"#f44336\"}],\"unitTitle\":\"Temperature\",\"units\":\"°C\",\"colorBarProgress\":\"#90caf9\",\"colorBarProgressEnd\":\"#f44336\",\"colorBarStroke\":\"#b0bec5\",\"valueDec\":1},\"title\":\"Temperature gauge - Canvas Gauges\"}"}',
+'Temperature gauge - Canvas Gauges' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'gpio_widgets', 'gpio_panel',
+'{"type":"latest","sizeX":5,"sizeY":2,"resources":[],"templateHtml":"<div class=\"gpio-panel\" style=\"height: 100%;\">\n    <section layout=\"row\" ng-repeat=\"row in rows\">\n        <section flex layout=\"row\" ng-repeat=\"cell in row\">\n            <section layout=\"row\" flex ng-if=\"cell\" layout-align=\"{{$index===0 ? ''end center'' : ''start center''}}\">\n                <span class=\"gpio-left-label\" ng-show=\"$index===0\">{{ cell.label }}</span>\n                <section layout=\"row\" class=\"led-panel\" ng-class=\"$index===0 ? ''col-0'' : ''col-1''\"\n                      style=\"background-color: {{ ledPanelBackgroundColor }};\">\n                    <span class=\"pin\" ng-show=\"$index===0\">{{cell.pin}}</span>\n                    <span class=\"led-container\">\n                        <tb-led-light size=\"prefferedRowHeight\"\n                                      color-on=\"cell.colorOn\"\n                                      color-off=\"cell.colorOff\"\n                                      off-opacity=\"''0.9''\"\n                                      tb-enabled=\"cell.enabled\">\n                        </tb-led-light>\n                    </span>\n                    <span class=\"pin\" ng-show=\"$index===1\">{{cell.pin}}</span>\n                </section>\n                <span class=\"gpio-right-label\" ng-show=\"$index===1\">{{ cell.label }}</span>\n            </section>\n            <section layout=\"row\" flex ng-if=\"!cell\">\n                <span flex ng-show=\"$index===0\"></span>\n                <span class=\"led-panel\"\n                      style=\"background-color: {{ ledPanelBackgroundColor }};\"></span>\n                <span flex ng-show=\"$index===1\"></span>\n            </section>\n        </section>\n    </section>                            \n</div>","templateCss":".error {\n    font-size: 14px !important;\n    color: maroon;/*rgb(250,250,250);*/\n    background-color: transparent;\n    padding: 6px;\n}\n\n.error span {\n    margin: auto;\n}\n\n.gpio-panel {\n    padding-top: 10px;\n    white-space: nowrap;\n}\n\n.gpio-panel tb-led-light > div {\n    margin: auto;\n}\n\n.led-panel {\n    margin: 0;\n    width: 66px;\n    min-width: 66px;\n}\n\n.led-container {\n    width: 48px;\n    min-width: 48px;\n}\n\n.pin {\n    margin-top: auto;\n    margin-bottom: auto;\n    color: white;\n    font-size: 12px;\n    width: 16px;\n    min-width: 16px;\n}\n\n.led-panel.col-0 .pin {\n    margin-left: auto;\n    padding-left: 2px;\n    text-align: right;\n}\n\n.led-panel.col-1 .pin {\n    margin-right: auto;\n    \n    text-align: left;\n}\n\n.gpio-left-label {\n    margin-right: 8px;\n}\n\n.gpio-right-label {\n    margin-left: 8px;\n}","controllerScript":"\nfns.init = function(containerElement, settings, datasources, data, scope, controlApi) {\n    \n    var i, gpio;\n    \n    scope.gpioList = [];\n    scope.gpioByPin = {};\n    for (var g in settings.gpioList) {\n        gpio = settings.gpioList[g];\n        scope.gpioList.push(\n            {\n                row: gpio.row,\n                col: gpio.col,\n                pin: gpio.pin,\n                label: gpio.label,\n                enabled: false,\n                colorOn: tinycolor(gpio.color).lighten(20).toHexString(),\n                colorOff: tinycolor(gpio.color).darken().toHexString()\n            }\n        );\n        scope.gpioByPin[gpio.pin] = scope.gpioList[scope.gpioList.length-1];\n    }\n\n    scope.ledPanelBackgroundColor = settings.ledPanelBackgroundColor || tinycolor(''green'').lighten(2).toRgbString();\n\n    scope.gpioCells = {};\n    var rowCount = 0;\n    for (i = 0; i < scope.gpioList.length; i++) {\n        gpio = scope.gpioList[i];\n        scope.gpioCells[gpio.row+''_''+gpio.col] = gpio;\n        rowCount = Math.max(rowCount, gpio.row+1);\n    }\n    \n    scope.prefferedRowHeight = 32;\n    scope.rows = [];\n    for (i = 0; i < rowCount; i++) {\n        var row = [];\n        for (var c =0; c<2;c++) {\n            if (scope.gpioCells[i+''_''+c]) {\n                row[c] = scope.gpioCells[i+''_''+c];\n            } else {\n                row[c] = null;\n            }\n        }\n        scope.rows.push(row);\n    }\n\n};\n\nfns.redraw = function(containerElement, width, height, data, timeWindow, sizeChanged, scope) {\n    \n    if (sizeChanged) {\n        var rowCount = scope.rows.length;\n        var prefferedRowHeight = (height - 35)/rowCount;\n        prefferedRowHeight = Math.min(32, prefferedRowHeight);\n        prefferedRowHeight = Math.max(12, prefferedRowHeight);\n        scope.$apply(function(scope) {\n            scope.prefferedRowHeight = prefferedRowHeight;\n        });\n        var ratio = prefferedRowHeight/32;\n        \n        var leftLabels = $(''.gpio-left-label'', containerElement);\n        leftLabels.css(''font-size'', 16*ratio+''px'');\n        var rightLabels = $(''.gpio-right-label'', containerElement);\n        rightLabels.css(''font-size'', 16*ratio+''px'');\n        var pins = $(''.pin'', containerElement);\n        var pinsFontSize = Math.max(9, 12*ratio);\n        pins.css(''font-size'', pinsFontSize+''px'');\n    }\n    \n    var changed = false;\n    for (var d in data) {\n        var cellData = data[d];\n        var dataKey = cellData.dataKey;\n        var gpio = scope.gpioByPin[dataKey.label];\n        if (gpio) {\n            var enabled = false;\n            if (cellData.data.length > 0) {\n                var tvPair = cellData.data[cellData.data.length - 1];\n                enabled = (tvPair[1] === true || tvPair[1] === ''true'');\n            }\n            if (gpio.enabled != enabled) {\n                changed = true;\n                gpio.enabled = enabled;\n            }\n        }\n    }\n    if (changed) {\n        scope.$apply();\n    }\n};\n\nfns.destroy = function() {\n};","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"gpioList\": {\n                \"title\": \"Gpio leds\",\n                \"type\": \"array\",\n                \"minItems\" : 1,\n                \"items\": {\n                    \"title\": \"Gpio led\",\n                    \"type\": \"object\",\n                    \"properties\": {\n                        \"pin\": {\n                            \"title\": \"Pin\",\n                            \"type\": \"number\"\n                        },\n                        \"label\": {\n                            \"title\": \"Label\",\n                            \"type\": \"string\"\n                        },\n                        \"row\": {\n                            \"title\": \"Row\",\n                            \"type\": \"number\"\n                        },\n                        \"col\": {\n                            \"title\": \"Column\",\n                            \"type\": \"number\"\n                        },\n                        \"color\": {\n                            \"title\": \"Color\",\n                            \"type\": \"string\",\n                            \"default\": \"red\"\n                        }\n                    },\n                    \"required\": [\"pin\", \"label\", \"row\", \"col\", \"color\"]\n                }\n            },\n            \"ledPanelBackgroundColor\": {\n                \"title\": \"LED panel background color\",\n                \"type\": \"string\",\n                \"default\": \"#008a00\"\n            } \n        },\n        \"required\": [\"gpioList\", \n                     \"ledPanelBackgroundColor\"]\n    },\n    \"form\": [\n        {\n            \"key\": \"gpioList\",\n            \"items\": [\n                \"gpioList[].pin\",\n                \"gpioList[].label\",\n                \"gpioList[].row\",\n                \"gpioList[].col\",\n                {\n                    \"key\": \"gpioList[].color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"ledPanelBackgroundColor\",\n            \"type\": \"color\"\n        }\n    ]\n}","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"color\":\"#008000\",\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"color\":\"#ffff00\",\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"color\":\"#cf006f\",\"_uniqueKey\":2}],\"ledPanelBackgroundColor\":\"#b71c1c\"},\"title\":\"Basic GPIO Panel\",\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"1\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.22518255793320163,\"funcBody\":\"var period = time % 1500;\\nreturn period < 500;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"2\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.7008206860666621,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 500 && period < 1000;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"3\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.42600325102193426,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 1000;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}}}"}',
+'Basic GPIO Panel' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'gpio_widgets', 'basic_gpio_control',
+'{"type":"rpc","sizeX":4,"sizeY":2,"resources":[],"templateHtml":"<fieldset class=\"gpio-panel\" ng-disabled=\"!rpcEnabled || executingRpcRequest\" style=\"height: 100%;\">\n    <section class=\"gpio-row\" layout=\"row\" ng-repeat=\"row in rows track by $index\" \n            ng-style=\"{''height'': prefferedRowHeight+''px''}\">\n        <section flex layout=\"row\" ng-repeat=\"cell in row track by $index\">\n            <section layout=\"row\" flex ng-if=\"cell\" layout-align=\"{{$index===0 ? ''end center'' : ''start center''}}\">\n                <span class=\"gpio-left-label\" ng-show=\"$index===0\">{{ cell.label }}</span>\n                <section layout=\"row\" class=\"switch-panel\" layout-align=\"start center\" ng-class=\"$index===0 ? ''col-0'' : ''col-1''\"\n                      ng-style=\"{''height'': prefferedRowHeight+''px'', ''backgroundColor'': ''{{ switchPanelBackgroundColor }}''}\">\n                    <span class=\"pin\" ng-show=\"$index===0\">{{cell.pin}}</span>\n                    <span flex ng-show=\"$index===1\"></span>\n                    <md-switch\n                        aria-label=\"{{ cell.label }}\"\n                        ng-disabled=\"!rpcEnabled || executingRpcRequest\"\n                        ng-model=\"cell.enabled\" \n                        ng-change=\"cell.enabled = !cell.enabled\" \n                        ng-click=\"gpioClick($event, cell)\">\n                    </md-switch>\n                    <span flex ng-show=\"$index===0\"></span>\n                    <span class=\"pin\" ng-show=\"$index===1\">{{cell.pin}}</span>\n                </section>\n                <span class=\"gpio-right-label\" ng-show=\"$index===1\">{{ cell.label }}</span>\n            </section>\n            <section layout=\"row\" flex ng-if=\"!cell\">\n                <span flex ng-show=\"$index===0\"></span>\n                <span class=\"switch-panel\"\n                      ng-style=\"{''height'': prefferedRowHeight+''px'', ''backgroundColor'': ''{{ switchPanelBackgroundColor }}''}\"></span>\n                <span flex ng-show=\"$index===1\"></span>\n            </section>\n        </section>\n    </section>                            \n    <span class=\"error\" style=\"position: absolute; bottom: 5px;\" ng-show=\"rpcErrorText\">{{rpcErrorText}}</span>\n    <md-progress-linear ng-show=\"executingRpcRequest\" style=\"position: absolute; bottom: 0;\" md-mode=\"indeterminate\"></md-progress-linear>    \n</fieldset>","templateCss":".error {\n    font-size: 14px !important;\n    color: maroon;/*rgb(250,250,250);*/\n    background-color: transparent;\n    padding: 6px;\n}\n\n.error span {\n    margin: auto;\n}\n\n.gpio-panel {\n    padding-top: 10px;\n    white-space: nowrap;\n}\n\n.switch-panel {\n    margin: 0;\n    height: 32px;\n    width: 66px;\n    min-width: 66px;\n}\n\n.switch-panel md-switch {\n    margin: 0;\n    width: 36px;\n    min-width: 36px;\n}\n\n.switch-panel md-switch > div.md-container {\n    margin: 0;\n}\n\n.switch-panel.col-0 md-switch {\n    padding-left: 8px;\n    padding-right: 4px;\n}\n\n.switch-panel.col-1 md-switch {\n    padding-left: 4px;\n    padding-right: 8px;\n}\n\n.gpio-row {\n    height: 32px;\n}\n\n.pin {\n    margin-top: auto;\n    margin-bottom: auto;\n    color: white;\n    font-size: 12px;\n    width: 16px;\n    min-width: 16px;\n}\n\n.switch-panel.col-0 .pin {\n    margin-left: auto;\n    padding-left: 2px;\n    text-align: right;\n}\n\n.switch-panel.col-1 .pin {\n    margin-right: auto;\n    \n    text-align: left;\n}\n\n.gpio-left-label {\n    margin-right: 8px;\n}\n\n.gpio-right-label {\n    margin-left: 8px;\n}","controllerScript":"\nfns.init = function(containerElement, settings, datasources, data, scope, controlApi) {\n    \n    var i, gpio;\n    \n    scope.gpioList = [];\n    for (var g in settings.gpioList) {\n        gpio = settings.gpioList[g];\n        scope.gpioList.push(\n            {\n                row: gpio.row,\n                col: gpio.col,\n                pin: gpio.pin,\n                label: gpio.label,\n                enabled: false\n            }\n        );\n    }\n\n    scope.requestTimeout = settings.requestTimeout || 1000;\n\n    scope.switchPanelBackgroundColor = settings.switchPanelBackgroundColor || tinycolor(''green'').lighten(2).toRgbString();\n\n    scope.gpioStatusRequest = {\n        method: \"getGpioStatus\",\n        paramsBody: \"{}\"\n    };\n    \n    if (settings.gpioStatusRequest) {\n        scope.gpioStatusRequest.method = settings.gpioStatusRequest.method || scope.gpioStatusRequest.method;\n        scope.gpioStatusRequest.paramsBody = settings.gpioStatusRequest.paramsBody || scope.gpioStatusRequest.paramsBody;\n    }\n    \n    scope.gpioStatusChangeRequest = {\n        method: \"setGpioStatus\",\n        paramsBody: \"{\\n   \\\"pin\\\": \\\"{$pin}\\\",\\n   \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n    };\n    \n    if (settings.gpioStatusChangeRequest) {\n        scope.gpioStatusChangeRequest.method = settings.gpioStatusChangeRequest.method || scope.gpioStatusChangeRequest.method;\n        scope.gpioStatusChangeRequest.paramsBody = settings.gpioStatusChangeRequest.paramsBody || scope.gpioStatusChangeRequest.paramsBody;\n    }\n    \n    scope.parseGpioStatusFunction = \"return body[pin] === true;\";\n    \n    if (settings.parseGpioStatusFunction && settings.parseGpioStatusFunction.length > 0) {\n        scope.parseGpioStatusFunction = settings.parseGpioStatusFunction;\n    }\n    \n    scope.parseGpioStatusFunction = new Function(\"body, pin\", scope.parseGpioStatusFunction);\n    \n    function requestGpioStatus() {\n        controlApi.sendTwoWayCommand(scope.gpioStatusRequest.method, \n                            scope.gpioStatusRequest.paramsBody, \n                            scope.requestTimeout)\n            .then(\n                function success(responseBody) {\n                    for (var g in scope.gpioList) {\n                        var gpio = scope.gpioList[g];\n                        var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n                        gpio.enabled = enabled;                        \n                    }\n                }\n            );\n    }\n    \n    function changeGpioStatus(gpio) {\n        var pin = gpio.pin + '''';\n        var enabled = !gpio.enabled;\n        enabled = enabled === true ? ''true'' : ''false'';\n        var paramsBody = scope.gpioStatusChangeRequest.paramsBody;\n        var requestBody = JSON.parse(paramsBody.replace(\"\\\"{$pin}\\\"\", pin).replace(\"\\\"{$enabled}\\\"\", enabled));\n        controlApi.sendTwoWayCommand(scope.gpioStatusChangeRequest.method, \n                                    requestBody, scope.requestTimeout)\n                    .then(\n                        function success(responseBody) {\n                            var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n                            gpio.enabled = enabled;\n                        }\n                    );\n    }\n    \n    scope.gpioCells = {};\n    var rowCount = 0;\n    for (i = 0; i < scope.gpioList.length; i++) {\n        gpio = scope.gpioList[i];\n        scope.gpioCells[gpio.row+''_''+gpio.col] = gpio;\n        rowCount = Math.max(rowCount, gpio.row+1);\n    }\n    \n    scope.prefferedRowHeight = 32;\n    scope.rows = [];\n    for (i = 0; i < rowCount; i++) {\n        var row = [];\n        for (var c =0; c<2;c++) {\n            if (scope.gpioCells[i+''_''+c]) {\n                row[c] = scope.gpioCells[i+''_''+c];\n            } else {\n                row[c] = null;\n            }\n        }\n        scope.rows.push(row);\n    }\n\n    scope.gpioClick = function($event, gpio) {\n        changeGpioStatus(gpio);\n    };\n\n    requestGpioStatus();\n    \n};\n\nfns.redraw = function(containerElement, width, height, data, timeWindow, sizeChanged, scope) {\n    if (sizeChanged) {\n        var rowCount = scope.rows.length;\n        var prefferedRowHeight = (height - 35)/rowCount;\n        prefferedRowHeight = Math.min(32, prefferedRowHeight);\n        prefferedRowHeight = Math.max(12, prefferedRowHeight);\n        scope.$apply(function(scope) {\n            scope.prefferedRowHeight = prefferedRowHeight;\n        });\n        var ratio = prefferedRowHeight/32;\n        var switches = $(''md-switch'', containerElement);\n        switches.css(''height'', 30*ratio+''px'');\n        switches.css(''width'', 36*ratio+''px'');\n        switches.css(''min-width'', 36*ratio+''px'');\n        $(''.md-container'', switches).css(''height'', 24*ratio+''px'');\n        $(''.md-container'', switches).css(''width'', 36*ratio+''px'');\n        var bars = $(''.md-bar'', containerElement);\n        bars.css(''height'', 14*ratio+''px'');\n        bars.css(''width'', 34*ratio+''px'');\n        var thumbs = $(''.md-thumb'', containerElement);\n        thumbs.css(''height'', 20*ratio+''px'');\n        thumbs.css(''width'', 20*ratio+''px'');\n        \n        var leftLabels = $(''.gpio-left-label'', containerElement);\n        leftLabels.css(''font-size'', 16*ratio+''px'');\n        var rightLabels = $(''.gpio-right-label'', containerElement);\n        rightLabels.css(''font-size'', 16*ratio+''px'');\n        var pins = $(''.pin'', containerElement);\n        var pinsFontSize = Math.max(9, 12*ratio);\n        pins.css(''font-size'', pinsFontSize+''px'');\n    }\n};\n\nfns.destroy = function() {\n};","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"gpioList\": {\n                \"title\": \"Gpio switches\",\n                \"type\": \"array\",\n                \"minItems\" : 1,\n                \"items\": {\n                    \"title\": \"Gpio switch\",\n                    \"type\": \"object\",\n                    \"properties\": {\n                        \"pin\": {\n                            \"title\": \"Pin\",\n                            \"type\": \"number\"\n                        },\n                        \"label\": {\n                            \"title\": \"Label\",\n                            \"type\": \"string\"\n                        },\n                        \"row\": {\n                            \"title\": \"Row\",\n                            \"type\": \"number\"\n                        },\n                        \"col\": {\n                            \"title\": \"Column\",\n                            \"type\": \"number\"\n                        }\n                    },\n                    \"required\": [\"pin\", \"label\", \"row\", \"col\"]\n                }\n            },\n            \"requestTimeout\": {\n                \"title\": \"RPC request timeout\",\n                \"type\": \"number\",\n                \"default\": 500\n            },\n            \"switchPanelBackgroundColor\": {\n                \"title\": \"Switches panel background color\",\n                \"type\": \"string\",\n                \"default\": \"#008a00\"\n            },\n            \"gpioStatusRequest\": {\n                \"title\": \"GPIO status request\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"method\": {\n                        \"title\": \"Method name\",\n                        \"type\": \"string\",\n                        \"default\": \"getGpioStatus\"\n                    },\n                    \"paramsBody\": {\n                      \"title\": \"Method body\",\n                      \"type\": \"string\",\n                      \"default\": \"{}\"\n                    }\n                },\n                \"required\": [\"method\", \"paramsBody\"]\n            },\n            \"gpioStatusChangeRequest\": {\n                \"title\": \"GPIO status change request\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"method\": {\n                        \"title\": \"Method name\",\n                        \"type\": \"string\",\n                        \"default\": \"setGpioStatus\"\n                    },\n                    \"paramsBody\": {\n                      \"title\": \"Method body\",\n                      \"type\": \"string\",\n                      \"default\": \"{\\n   \\\"pin\\\": \\\"{$pin}\\\",\\n   \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n                    }\n                },\n                \"required\": [\"method\", \"paramsBody\"]\n            },\n            \"parseGpioStatusFunction\": {\n                \"title\": \"Parse gpio status function\",\n                \"type\": \"string\",\n                \"default\": \"return body[pin] === true;\"\n            } \n        },\n        \"required\": [\"gpioList\", \n                     \"requestTimeout\",\n                     \"switchPanelBackgroundColor\",\n                     \"gpioStatusRequest\",\n                     \"gpioStatusChangeRequest\",\n                     \"parseGpioStatusFunction\"]\n    },\n    \"form\": [\n        \"gpioList\",\n        \"requestTimeout\",\n        {\n            \"key\": \"switchPanelBackgroundColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"gpioStatusRequest\",\n            \"items\": [\n                \"gpioStatusRequest.method\",\n                {\n                    \"key\": \"gpioStatusRequest.paramsBody\",\n                    \"type\": \"json\"\n                }\n            ]\n        },\n        {\n            \"key\": \"gpioStatusChangeRequest\",\n            \"items\": [\n                \"gpioStatusChangeRequest.method\",\n                {\n                    \"key\": \"gpioStatusChangeRequest.paramsBody\",\n                    \"type\": \"json\"\n                }\n            ]\n        },\n        {\n            \"key\": \"parseGpioStatusFunction\",\n            \"type\": \"javascript\"\n        }\n    ]\n}","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n   \\\"pin\\\": \\\"{$pin}\\\",\\n   \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"Basic GPIO Control\"}"}',
+'Basic GPIO Control' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'gpio_widgets', 'raspberry_pi_gpio_panel',
+'{"type":"latest","sizeX":7,"sizeY":10.5,"resources":[],"templateHtml":"<div class=\"gpio-panel\" style=\"height: 100%;\">\n    <section layout=\"row\" ng-repeat=\"row in rows\">\n        <section flex layout=\"row\" ng-repeat=\"cell in row\">\n            <section layout=\"row\" flex ng-if=\"cell\" layout-align=\"{{$index===0 ? ''end center'' : ''start center''}}\">\n                <span class=\"gpio-left-label\" ng-show=\"$index===0\">{{ cell.label }}</span>\n                <section layout=\"row\" class=\"led-panel\" ng-class=\"$index===0 ? ''col-0'' : ''col-1''\"\n                      style=\"background-color: {{ ledPanelBackgroundColor }};\">\n                    <span class=\"pin\" ng-show=\"$index===0\">{{cell.pin}}</span>\n                    <span class=\"led-container\">\n                        <tb-led-light size=\"prefferedRowHeight\"\n                                      color-on=\"cell.colorOn\"\n                                      color-off=\"cell.colorOff\"\n                                      off-opacity=\"''0.9''\"\n                                      tb-enabled=\"cell.enabled\">\n                        </tb-led-light>\n                    </span>\n                    <span class=\"pin\" ng-show=\"$index===1\">{{cell.pin}}</span>\n                </section>\n                <span class=\"gpio-right-label\" ng-show=\"$index===1\">{{ cell.label }}</span>\n            </section>\n            <section layout=\"row\" flex ng-if=\"!cell\">\n                <span flex ng-show=\"$index===0\"></span>\n                <span class=\"led-panel\"\n                      style=\"background-color: {{ ledPanelBackgroundColor }};\"></span>\n                <span flex ng-show=\"$index===1\"></span>\n            </section>\n        </section>\n    </section>                            \n</div>","templateCss":".error {\n    font-size: 14px !important;\n    color: maroon;/*rgb(250,250,250);*/\n    background-color: transparent;\n    padding: 6px;\n}\n\n.error span {\n    margin: auto;\n}\n\n.gpio-panel {\n    padding-top: 10px;\n    white-space: nowrap;\n}\n\n.gpio-panel tb-led-light > div {\n    margin: auto;\n}\n\n.led-panel {\n    margin: 0;\n    width: 66px;\n    min-width: 66px;\n}\n\n.led-container {\n    width: 48px;\n    min-width: 48px;\n}\n\n.pin {\n    margin-top: auto;\n    margin-bottom: auto;\n    color: white;\n    font-size: 12px;\n    width: 16px;\n    min-width: 16px;\n}\n\n.led-panel.col-0 .pin {\n    margin-left: auto;\n    padding-left: 2px;\n    text-align: right;\n}\n\n.led-panel.col-1 .pin {\n    margin-right: auto;\n    \n    text-align: left;\n}\n\n.gpio-left-label {\n    margin-right: 8px;\n}\n\n.gpio-right-label {\n    margin-left: 8px;\n}","controllerScript":"\nfns.init = function(containerElement, settings, datasources, data, scope, controlApi) {\n    \n    var i, gpio;\n    \n    scope.gpioList = [];\n    scope.gpioByPin = {};\n    for (var g in settings.gpioList) {\n        gpio = settings.gpioList[g];\n        scope.gpioList.push(\n            {\n                row: gpio.row,\n                col: gpio.col,\n                pin: gpio.pin,\n                label: gpio.label,\n                enabled: false,\n                colorOn: tinycolor(gpio.color).lighten(20).toHexString(),\n                colorOff: tinycolor(gpio.color).darken().toHexString()\n            }\n        );\n        scope.gpioByPin[gpio.pin] = scope.gpioList[scope.gpioList.length-1];\n    }\n\n    scope.ledPanelBackgroundColor = settings.ledPanelBackgroundColor || tinycolor(''green'').lighten(2).toRgbString();\n\n    scope.gpioCells = {};\n    var rowCount = 0;\n    for (i = 0; i < scope.gpioList.length; i++) {\n        gpio = scope.gpioList[i];\n        scope.gpioCells[gpio.row+''_''+gpio.col] = gpio;\n        rowCount = Math.max(rowCount, gpio.row+1);\n    }\n    \n    scope.prefferedRowHeight = 32;\n    scope.rows = [];\n    for (i = 0; i < rowCount; i++) {\n        var row = [];\n        for (var c =0; c<2;c++) {\n            if (scope.gpioCells[i+''_''+c]) {\n                row[c] = scope.gpioCells[i+''_''+c];\n            } else {\n                row[c] = null;\n            }\n        }\n        scope.rows.push(row);\n    }\n\n};\n\nfns.redraw = function(containerElement, width, height, data, timeWindow, sizeChanged, scope) {\n    \n    if (sizeChanged) {\n        var rowCount = scope.rows.length;\n        var prefferedRowHeight = (height - 35)/rowCount;\n        prefferedRowHeight = Math.min(32, prefferedRowHeight);\n        prefferedRowHeight = Math.max(12, prefferedRowHeight);\n        scope.$apply(function(scope) {\n            scope.prefferedRowHeight = prefferedRowHeight;\n        });\n        var ratio = prefferedRowHeight/32;\n        \n        var leftLabels = $(''.gpio-left-label'', containerElement);\n        leftLabels.css(''font-size'', 16*ratio+''px'');\n        var rightLabels = $(''.gpio-right-label'', containerElement);\n        rightLabels.css(''font-size'', 16*ratio+''px'');\n        var pins = $(''.pin'', containerElement);\n        var pinsFontSize = Math.max(9, 12*ratio);\n        pins.css(''font-size'', pinsFontSize+''px'');\n    }\n    \n    var changed = false;\n    for (var d in data) {\n        var cellData = data[d];\n        var dataKey = cellData.dataKey;\n        var gpio = scope.gpioByPin[dataKey.label];\n        if (gpio) {\n            var enabled = false;\n            if (cellData.data.length > 0) {\n                var tvPair = cellData.data[cellData.data.length - 1];\n                enabled = (tvPair[1] === true || tvPair[1] === ''true'');\n            }\n            if (gpio.enabled != enabled) {\n                changed = true;\n                gpio.enabled = enabled;\n            }\n        }\n    }\n    if (changed) {\n        scope.$apply();\n    }\n};\n\nfns.destroy = function() {\n};","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"gpioList\": {\n                \"title\": \"Gpio leds\",\n                \"type\": \"array\",\n                \"minItems\" : 1,\n                \"items\": {\n                    \"title\": \"Gpio led\",\n                    \"type\": \"object\",\n                    \"properties\": {\n                        \"pin\": {\n                            \"title\": \"Pin\",\n                            \"type\": \"number\"\n                        },\n                        \"label\": {\n                            \"title\": \"Label\",\n                            \"type\": \"string\"\n                        },\n                        \"row\": {\n                            \"title\": \"Row\",\n                            \"type\": \"number\"\n                        },\n                        \"col\": {\n                            \"title\": \"Column\",\n                            \"type\": \"number\"\n                        },\n                        \"color\": {\n                            \"title\": \"Color\",\n                            \"type\": \"string\",\n                            \"default\": \"red\"\n                        }\n                    },\n                    \"required\": [\"pin\", \"label\", \"row\", \"col\", \"color\"]\n                }\n            },\n            \"ledPanelBackgroundColor\": {\n                \"title\": \"LED panel background color\",\n                \"type\": \"string\",\n                \"default\": \"#008a00\"\n            } \n        },\n        \"required\": [\"gpioList\", \n                     \"ledPanelBackgroundColor\"]\n    },\n    \"form\": [\n        {\n            \"key\": \"gpioList\",\n            \"items\": [\n                \"gpioList[].pin\",\n                \"gpioList[].label\",\n                \"gpioList[].row\",\n                \"gpioList[].col\",\n                {\n                    \"key\": \"gpioList[].color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"ledPanelBackgroundColor\",\n            \"type\": \"color\"\n        }\n    ]\n}","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"gpioList\":[{\"pin\":1,\"label\":\"3.3V\",\"row\":0,\"col\":0,\"color\":\"#fc9700\",\"_uniqueKey\":0},{\"pin\":2,\"label\":\"5V\",\"row\":0,\"col\":1,\"color\":\"#fb0000\",\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 2 (I2C1_SDA)\",\"row\":1,\"col\":0,\"color\":\"#02fefb\",\"_uniqueKey\":2},{\"color\":\"#fb0000\",\"pin\":4,\"label\":\"5V\",\"row\":1,\"col\":1},{\"color\":\"#02fefb\",\"pin\":5,\"label\":\"GPIO 3 (I2C1_SCL)\",\"row\":2,\"col\":0},{\"color\":\"#000000\",\"pin\":6,\"label\":\"GND\",\"row\":2,\"col\":1},{\"color\":\"#00fd00\",\"pin\":7,\"label\":\"GPIO 4 (GPCLK0)\",\"row\":3,\"col\":0},{\"color\":\"#fdfb00\",\"pin\":8,\"label\":\"GPIO 14 (UART_TXD)\",\"row\":3,\"col\":1},{\"color\":\"#000000\",\"pin\":9,\"label\":\"GND\",\"row\":4,\"col\":0},{\"color\":\"#fdfb00\",\"pin\":10,\"label\":\"GPIO 15 (UART_RXD)\",\"row\":4,\"col\":1},{\"color\":\"#00fd00\",\"pin\":11,\"label\":\"GPIO 17\",\"row\":5,\"col\":0},{\"color\":\"#00fd00\",\"pin\":12,\"label\":\"GPIO 18\",\"row\":5,\"col\":1},{\"color\":\"#00fd00\",\"pin\":13,\"label\":\"GPIO 27\",\"row\":6,\"col\":0},{\"color\":\"#000000\",\"pin\":14,\"label\":\"GND\",\"row\":6,\"col\":1},{\"color\":\"#00fd00\",\"pin\":15,\"label\":\"GPIO 22\",\"row\":7,\"col\":0},{\"color\":\"#00fd00\",\"pin\":16,\"label\":\"GPIO 23\",\"row\":7,\"col\":1},{\"color\":\"#fc9700\",\"pin\":17,\"label\":\"3.3V\",\"row\":8,\"col\":0},{\"color\":\"#00fd00\",\"pin\":18,\"label\":\"GPIO 24\",\"row\":8,\"col\":1},{\"color\":\"#fd01fd\",\"pin\":19,\"label\":\"GPIO 10 (SPI_MOSI)\",\"row\":9,\"col\":0},{\"color\":\"#000000\",\"pin\":20,\"label\":\"GND\",\"row\":9,\"col\":1},{\"color\":\"#fd01fd\",\"pin\":21,\"label\":\"GPIO 9 (SPI_MISO)\",\"row\":10,\"col\":0},{\"color\":\"#00fd00\",\"pin\":22,\"label\":\"GPIO 25\",\"row\":10,\"col\":1},{\"color\":\"#fd01fd\",\"pin\":23,\"label\":\"GPIO 11 (SPI_SCLK)\",\"row\":11,\"col\":0},{\"color\":\"#fd01fd\",\"pin\":24,\"label\":\"GPIO 8 (SPI_CE0)\",\"row\":11,\"col\":1},{\"color\":\"#000000\",\"pin\":25,\"label\":\"GND\",\"row\":12,\"col\":0},{\"color\":\"#fd01fd\",\"pin\":26,\"label\":\"GPIO 7 (SPI_CE1)\",\"row\":12,\"col\":1},{\"color\":\"#ffffff\",\"pin\":27,\"label\":\"ID_SD\",\"row\":13,\"col\":0},{\"color\":\"#ffffff\",\"pin\":28,\"label\":\"ID_SC\",\"row\":13,\"col\":1},{\"color\":\"#00fd00\",\"pin\":29,\"label\":\"GPIO 5\",\"row\":14,\"col\":0},{\"color\":\"#000000\",\"pin\":30,\"label\":\"GND\",\"row\":14,\"col\":1},{\"color\":\"#00fd00\",\"pin\":31,\"label\":\"GPIO 6\",\"row\":15,\"col\":0},{\"color\":\"#00fd00\",\"pin\":32,\"label\":\"GPIO 12\",\"row\":15,\"col\":1},{\"color\":\"#00fd00\",\"pin\":33,\"label\":\"GPIO 13\",\"row\":16,\"col\":0},{\"color\":\"#000000\",\"pin\":34,\"label\":\"GND\",\"row\":16,\"col\":1},{\"color\":\"#00fd00\",\"pin\":35,\"label\":\"GPIO 19\",\"row\":17,\"col\":0},{\"color\":\"#00fd00\",\"pin\":36,\"label\":\"GPIO 16\",\"row\":17,\"col\":1},{\"color\":\"#00fd00\",\"pin\":37,\"label\":\"GPIO 26\",\"row\":18,\"col\":0},{\"color\":\"#00fd00\",\"pin\":38,\"label\":\"GPIO 20\",\"row\":18,\"col\":1},{\"color\":\"#000000\",\"pin\":39,\"label\":\"GND\",\"row\":19,\"col\":0},{\"color\":\"#00fd00\",\"pin\":40,\"label\":\"GPIO 21\",\"row\":19,\"col\":1}],\"ledPanelBackgroundColor\":\"#008a00\"},\"title\":\"Raspberry Pi GPIO Panel\",\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"7\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.22518255793320163,\"funcBody\":\"var period = time % 1500;\\nreturn period < 500;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"11\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.7008206860666621,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 500 && period < 1000;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"12\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.42600325102193426,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 1000;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"13\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.48362241571415243,\"funcBody\":\"var period = time % 1500;\\nreturn period < 500;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"29\",\"color\":\"#607d8b\",\"settings\":{},\"_hash\":0.7217670147518815,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 500 && period < 1000;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}}}"}',
+'Raspberry Pi GPIO Panel' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'gpio_widgets', 'raspberry_pi_gpio_control',
+'{"type":"rpc","sizeX":6,"sizeY":10.5,"resources":[],"templateHtml":"<fieldset class=\"gpio-panel\" ng-disabled=\"!rpcEnabled || executingRpcRequest\" style=\"height: 100%;\">\n    <section class=\"gpio-row\" layout=\"row\" ng-repeat=\"row in rows track by $index\" \n            ng-style=\"{''height'': prefferedRowHeight+''px''}\">\n        <section flex layout=\"row\" ng-repeat=\"cell in row track by $index\">\n            <section layout=\"row\" flex ng-if=\"cell\" layout-align=\"{{$index===0 ? ''end center'' : ''start center''}}\">\n                <span class=\"gpio-left-label\" ng-show=\"$index===0\">{{ cell.label }}</span>\n                <section layout=\"row\" class=\"switch-panel\" layout-align=\"start center\" ng-class=\"$index===0 ? ''col-0'' : ''col-1''\"\n                      ng-style=\"{''height'': prefferedRowHeight+''px'', ''backgroundColor'': ''{{ switchPanelBackgroundColor }}''}\">\n                    <span class=\"pin\" ng-show=\"$index===0\">{{cell.pin}}</span>\n                    <span flex ng-show=\"$index===1\"></span>\n                    <md-switch\n                        aria-label=\"{{ cell.label }}\"\n                        ng-disabled=\"!rpcEnabled || executingRpcRequest\"\n                        ng-model=\"cell.enabled\" \n                        ng-change=\"cell.enabled = !cell.enabled\" \n                        ng-click=\"gpioClick($event, cell)\">\n                    </md-switch>\n                    <span flex ng-show=\"$index===0\"></span>\n                    <span class=\"pin\" ng-show=\"$index===1\">{{cell.pin}}</span>\n                </section>\n                <span class=\"gpio-right-label\" ng-show=\"$index===1\">{{ cell.label }}</span>\n            </section>\n            <section layout=\"row\" flex ng-if=\"!cell\">\n                <span flex ng-show=\"$index===0\"></span>\n                <span class=\"switch-panel\"\n                      ng-style=\"{''height'': prefferedRowHeight+''px'', ''backgroundColor'': ''{{ switchPanelBackgroundColor }}''}\"></span>\n                <span flex ng-show=\"$index===1\"></span>\n            </section>\n        </section>\n    </section>                            \n    <span class=\"error\" style=\"position: absolute; bottom: 5px;\" ng-show=\"rpcErrorText\">{{rpcErrorText}}</span>\n    <md-progress-linear ng-show=\"executingRpcRequest\" style=\"position: absolute; bottom: 0;\" md-mode=\"indeterminate\"></md-progress-linear>    \n</fieldset>","templateCss":".error {\n    font-size: 14px !important;\n    color: maroon;/*rgb(250,250,250);*/\n    background-color: transparent;\n    padding: 6px;\n}\n\n.error span {\n    margin: auto;\n}\n\n.gpio-panel {\n    padding-top: 10px;\n    white-space: nowrap;\n}\n\n.switch-panel {\n    margin: 0;\n    height: 32px;\n    width: 66px;\n    min-width: 66px;\n}\n\n.switch-panel md-switch {\n    margin: 0;\n    width: 36px;\n    min-width: 36px;\n}\n\n.switch-panel md-switch > div.md-container {\n    margin: 0;\n}\n\n.switch-panel.col-0 md-switch {\n    margin-left: 8px;\n    margin-right: 4px;\n}\n\n.switch-panel.col-1 md-switch {\n    margin-left: 4px;\n    margin-right: 8px;\n}\n\n.gpio-row {\n    height: 32px;\n}\n\n.pin {\n    margin-top: auto;\n    margin-bottom: auto;\n    color: white;\n    font-size: 12px;\n    width: 16px;\n    min-width: 16px;\n}\n\n.switch-panel.col-0 .pin {\n    margin-left: auto;\n    padding-left: 2px;\n    text-align: right;\n}\n\n.switch-panel.col-1 .pin {\n    margin-right: auto;\n    \n    text-align: left;\n}\n\n.gpio-left-label {\n    margin-right: 8px;\n}\n\n.gpio-right-label {\n    margin-left: 8px;\n}","controllerScript":"\nfns.init = function(containerElement, settings, datasources, data, scope, controlApi) {\n    \n    var i, gpio;\n    \n    scope.gpioList = [];\n    for (var g in settings.gpioList) {\n        gpio = settings.gpioList[g];\n        scope.gpioList.push(\n            {\n                row: gpio.row,\n                col: gpio.col,\n                pin: gpio.pin,\n                label: gpio.label,\n                enabled: false\n            }\n        );\n    }\n\n    scope.requestTimeout = settings.requestTimeout || 1000;\n\n    scope.switchPanelBackgroundColor = settings.switchPanelBackgroundColor || tinycolor(''green'').lighten(2).toRgbString();\n\n    scope.gpioStatusRequest = {\n        method: \"getGpioStatus\",\n        paramsBody: \"{}\"\n    };\n    \n    if (settings.gpioStatusRequest) {\n        scope.gpioStatusRequest.method = settings.gpioStatusRequest.method || scope.gpioStatusRequest.method;\n        scope.gpioStatusRequest.paramsBody = settings.gpioStatusRequest.paramsBody || scope.gpioStatusRequest.paramsBody;\n    }\n    \n    scope.gpioStatusChangeRequest = {\n        method: \"setGpioStatus\",\n        paramsBody: \"{\\n   \\\"pin\\\": \\\"{$pin}\\\",\\n   \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n    };\n    \n    if (settings.gpioStatusChangeRequest) {\n        scope.gpioStatusChangeRequest.method = settings.gpioStatusChangeRequest.method || scope.gpioStatusChangeRequest.method;\n        scope.gpioStatusChangeRequest.paramsBody = settings.gpioStatusChangeRequest.paramsBody || scope.gpioStatusChangeRequest.paramsBody;\n    }\n    \n    scope.parseGpioStatusFunction = \"return body[pin] === true;\";\n    \n    if (settings.parseGpioStatusFunction && settings.parseGpioStatusFunction.length > 0) {\n        scope.parseGpioStatusFunction = settings.parseGpioStatusFunction;\n    }\n    \n    scope.parseGpioStatusFunction = new Function(\"body, pin\", scope.parseGpioStatusFunction);\n    \n    function requestGpioStatus() {\n        controlApi.sendTwoWayCommand(scope.gpioStatusRequest.method, \n                            scope.gpioStatusRequest.paramsBody, \n                            scope.requestTimeout)\n            .then(\n                function success(responseBody) {\n                    for (var g in scope.gpioList) {\n                        var gpio = scope.gpioList[g];\n                        var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n                        gpio.enabled = enabled;                        \n                    }\n                }\n            );\n    }\n    \n    function changeGpioStatus(gpio) {\n        var pin = gpio.pin + '''';\n        var enabled = !gpio.enabled;\n        enabled = enabled === true ? ''true'' : ''false'';\n        var paramsBody = scope.gpioStatusChangeRequest.paramsBody;\n        var requestBody = JSON.parse(paramsBody.replace(\"\\\"{$pin}\\\"\", pin).replace(\"\\\"{$enabled}\\\"\", enabled));\n        controlApi.sendTwoWayCommand(scope.gpioStatusChangeRequest.method, \n                                    requestBody, scope.requestTimeout)\n                    .then(\n                        function success(responseBody) {\n                            var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n                            gpio.enabled = enabled;\n                        }\n                    );\n    }\n    \n    scope.gpioCells = {};\n    var rowCount = 0;\n    for (i = 0; i < scope.gpioList.length; i++) {\n        gpio = scope.gpioList[i];\n        scope.gpioCells[gpio.row+''_''+gpio.col] = gpio;\n        rowCount = Math.max(rowCount, gpio.row+1);\n    }\n    \n    scope.prefferedRowHeight = 32;\n    scope.rows = [];\n    for (i = 0; i < rowCount; i++) {\n        var row = [];\n        for (var c =0; c<2;c++) {\n            if (scope.gpioCells[i+''_''+c]) {\n                row[c] = scope.gpioCells[i+''_''+c];\n            } else {\n                row[c] = null;\n            }\n        }\n        scope.rows.push(row);\n    }\n\n    scope.gpioClick = function($event, gpio) {\n        changeGpioStatus(gpio);\n    };\n\n    requestGpioStatus();\n    \n};\n\nfns.redraw = function(containerElement, width, height, data, timeWindow, sizeChanged, scope) {\n    if (sizeChanged) {\n        var rowCount = scope.rows.length;\n        var prefferedRowHeight = (height - 35)/rowCount;\n        prefferedRowHeight = Math.min(32, prefferedRowHeight);\n        prefferedRowHeight = Math.max(12, prefferedRowHeight);\n        scope.$apply(function(scope) {\n            scope.prefferedRowHeight = prefferedRowHeight;\n        });\n        var ratio = prefferedRowHeight/32;\n        var switches = $(''md-switch'', containerElement);\n        switches.css(''height'', 30*ratio+''px'');\n        switches.css(''width'', 36*ratio+''px'');\n        switches.css(''min-width'', 36*ratio+''px'');\n        $(''.md-container'', switches).css(''height'', 24*ratio+''px'');\n        $(''.md-container'', switches).css(''width'', 36*ratio+''px'');\n        var bars = $(''.md-bar'', containerElement);\n        bars.css(''height'', 14*ratio+''px'');\n        bars.css(''width'', 34*ratio+''px'');\n        var thumbs = $(''.md-thumb'', containerElement);\n        thumbs.css(''height'', 20*ratio+''px'');\n        thumbs.css(''width'', 20*ratio+''px'');\n        \n        var leftLabels = $(''.gpio-left-label'', containerElement);\n        leftLabels.css(''font-size'', 16*ratio+''px'');\n        var rightLabels = $(''.gpio-right-label'', containerElement);\n        rightLabels.css(''font-size'', 16*ratio+''px'');\n        var pins = $(''.pin'', containerElement);\n        var pinsFontSize = Math.max(9, 12*ratio);\n        pins.css(''font-size'', pinsFontSize+''px'');\n    }\n};\n\nfns.destroy = function() {\n};","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"gpioList\": {\n                \"title\": \"Gpio switches\",\n                \"type\": \"array\",\n                \"minItems\" : 1,\n                \"items\": {\n                    \"title\": \"Gpio switch\",\n                    \"type\": \"object\",\n                    \"properties\": {\n                        \"pin\": {\n                            \"title\": \"Pin\",\n                            \"type\": \"number\"\n                        },\n                        \"label\": {\n                            \"title\": \"Label\",\n                            \"type\": \"string\"\n                        },\n                        \"row\": {\n                            \"title\": \"Row\",\n                            \"type\": \"number\"\n                        },\n                        \"col\": {\n                            \"title\": \"Column\",\n                            \"type\": \"number\"\n                        }\n                    },\n                    \"required\": [\"pin\", \"label\", \"row\", \"col\"]\n                }\n            },\n            \"requestTimeout\": {\n                \"title\": \"RPC request timeout\",\n                \"type\": \"number\",\n                \"default\": 500\n            },\n            \"switchPanelBackgroundColor\": {\n                \"title\": \"Switches panel background color\",\n                \"type\": \"string\",\n                \"default\": \"#008a00\"\n            },\n            \"gpioStatusRequest\": {\n                \"title\": \"GPIO status request\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"method\": {\n                        \"title\": \"Method name\",\n                        \"type\": \"string\",\n                        \"default\": \"getGpioStatus\"\n                    },\n                    \"paramsBody\": {\n                      \"title\": \"Method body\",\n                      \"type\": \"string\",\n                      \"default\": \"{}\"\n                    }\n                },\n                \"required\": [\"method\", \"paramsBody\"]\n            },\n            \"gpioStatusChangeRequest\": {\n                \"title\": \"GPIO status change request\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"method\": {\n                        \"title\": \"Method name\",\n                        \"type\": \"string\",\n                        \"default\": \"setGpioStatus\"\n                    },\n                    \"paramsBody\": {\n                      \"title\": \"Method body\",\n                      \"type\": \"string\",\n                      \"default\": \"{\\n   \\\"pin\\\": \\\"{$pin}\\\",\\n   \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n                    }\n                },\n                \"required\": [\"method\", \"paramsBody\"]\n            },\n            \"parseGpioStatusFunction\": {\n                \"title\": \"Parse gpio status function\",\n                \"type\": \"string\",\n                \"default\": \"return body[pin] === true;\"\n            } \n        },\n        \"required\": [\"gpioList\", \n                     \"requestTimeout\",\n                     \"switchPanelBackgroundColor\",\n                     \"gpioStatusRequest\",\n                     \"gpioStatusChangeRequest\",\n                     \"parseGpioStatusFunction\"]\n    },\n    \"form\": [\n        \"gpioList\",\n        \"requestTimeout\",\n        {\n            \"key\": \"switchPanelBackgroundColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"gpioStatusRequest\",\n            \"items\": [\n                \"gpioStatusRequest.method\",\n                {\n                    \"key\": \"gpioStatusRequest.paramsBody\",\n                    \"type\": \"json\"\n                }\n            ]\n        },\n        {\n            \"key\": \"gpioStatusChangeRequest\",\n            \"items\": [\n                \"gpioStatusChangeRequest.method\",\n                {\n                    \"key\": \"gpioStatusChangeRequest.paramsBody\",\n                    \"type\": \"json\"\n                }\n            ]\n        },\n        {\n            \"key\": \"parseGpioStatusFunction\",\n            \"type\": \"javascript\"\n        }\n    ]\n}","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n   \\\"pin\\\": \\\"{$pin}\\\",\\n   \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#008a00\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":7,\"label\":\"GPIO 4 (GPCLK0)\",\"row\":3,\"col\":0,\"_uniqueKey\":0},{\"pin\":11,\"label\":\"GPIO 17\",\"row\":5,\"col\":0,\"_uniqueKey\":1},{\"pin\":12,\"label\":\"GPIO 18\",\"row\":5,\"col\":1,\"_uniqueKey\":2},{\"_uniqueKey\":3,\"pin\":13,\"label\":\"GPIO 27\",\"row\":6,\"col\":0},{\"_uniqueKey\":4,\"pin\":15,\"label\":\"GPIO 22\",\"row\":7,\"col\":0},{\"_uniqueKey\":5,\"pin\":16,\"label\":\"GPIO 23\",\"row\":7,\"col\":1},{\"_uniqueKey\":6,\"pin\":18,\"label\":\"GPIO 24\",\"row\":8,\"col\":1},{\"_uniqueKey\":7,\"pin\":22,\"label\":\"GPIO 25\",\"row\":10,\"col\":1},{\"_uniqueKey\":8,\"pin\":29,\"label\":\"GPIO 5\",\"row\":14,\"col\":0},{\"_uniqueKey\":9,\"pin\":31,\"label\":\"GPIO 6\",\"row\":15,\"col\":0},{\"_uniqueKey\":10,\"pin\":32,\"label\":\"GPIO 12\",\"row\":15,\"col\":1},{\"_uniqueKey\":11,\"pin\":33,\"label\":\"GPIO 13\",\"row\":16,\"col\":0},{\"_uniqueKey\":12,\"pin\":35,\"label\":\"GPIO 19\",\"row\":17,\"col\":0},{\"_uniqueKey\":13,\"pin\":36,\"label\":\"GPIO 16\",\"row\":17,\"col\":1},{\"_uniqueKey\":14,\"pin\":37,\"label\":\"GPIO 26\",\"row\":18,\"col\":0},{\"_uniqueKey\":15,\"pin\":38,\"label\":\"GPIO 20\",\"row\":18,\"col\":1},{\"_uniqueKey\":16,\"pin\":40,\"label\":\"GPIO 21\",\"row\":19,\"col\":1}]},\"title\":\"Raspberry Pi GPIO Control\"}"}',
+'Raspberry Pi GPIO Control' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'digital_gauges', 'lcd_bar_gauge',
+'{"type":"latest","sizeX":2,"sizeY":3.5,"resources":[],"templateHtml":"","templateCss":"#gauge {\n    text-align: center;\n   /* margin-left: -100px;\n    margin-right: -100px;*/\n    /*margin-top: -50px;*/\n    \n}\n","controllerScript":"var gauge;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n    gauge = new TbDigitalGauge(containerElement, settings, data);    \n\n}\n\n\nfns.redraw = function(containerElement, width, height, data) {\n    gauge.redraw(data);\n};\n\nfns.destroy = function() {\n}\n","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"minValue\": {\n                \"title\": \"Minimum value\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"maxValue\": {\n                \"title\": \"Maximum value\",\n                \"type\": \"number\",\n                \"default\": 100\n            },            \n            \"gaugeType\": {\n                \"title\": \"Gauge type\",\n                \"type\": \"string\",\n                \"default\": \"arc\"\n            },         \n            \"donutStartAngle\": {\n                \"title\": \"Angle to start from when in donut mode\",\n                \"type\": \"number\",\n                \"default\": 90\n            },            \n            \"neonGlowBrightness\": {\n                \"title\": \"Neon glow effect brightness, (0-100), 0 - disable effect\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"dashThickness\": {\n                \"title\": \"Thickness of the stripes, 0 - no stripes\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"roundedLineCap\": {\n                \"title\": \"Display rounded line cap\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"title\": {\n                \"title\": \"Gauge title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showTitle\": {\n                \"title\": \"Show gauge title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"unitTitle\": {\n                \"title\": \"Unit title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showUnitTitle\": {\n                \"title\": \"Show unit title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"showValue\": {\n                \"title\": \"Show value text\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"showMinMax\": {\n                \"title\": \"Show min and max values\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"gaugeWidthScale\": {\n                \"title\": \"Width of the gauge element\",\n                \"type\": \"number\",\n                \"default\": 0.75\n            },\n            \"defaultColor\": {\n                \"title\": \"Default color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"gaugeColor\": {\n                \"title\": \"Background color of the gauge element\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"levelColors\": {\n                \"title\": \"Colors of indicator, from lower to upper\",\n                \"type\": \"array\",\n                \"items\": {\n                  \"title\": \"Color\",\n                  \"type\": \"string\"\n                }\n            },\n            \"refreshAnimationType\": {\n                \"title\": \"Type of refresh animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"refreshAnimationTime\": {\n                \"title\": \"Duration of refresh animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"startAnimationType\": {\n                \"title\": \"Type of start animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"startAnimationTime\": {\n                \"title\": \"Duration of start animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"decimals\": {\n                \"title\": \"Number of digits after floating point\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"units\": {\n                \"title\": \"Special symbol to show next to value\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"titleFont\": {\n                \"title\": \"Gauge title font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 12\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"labelFont\": {\n                \"title\": \"Font of label showing under value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 8\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"valueFont\": {\n                \"title\": \"Font of label showing current value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 18\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"minMaxFont\": {\n                \"title\": \"Font of minimum and maximum labels\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 10\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            }\n        }\n    },\n    \"form\": [\n        \"minValue\",\n        \"maxValue\",\n        {\n           \"key\": \"gaugeType\",\n           \"type\": \"rc-select\",\n           \"multiple\": false,\n           \"items\": [\n               {\n                   \"value\": \"arc\",\n                   \"label\": \"Arc\"\n               },\n               {\n                   \"value\": \"donut\",\n                   \"label\": \"Donut\"\n               },\n               {\n                   \"value\": \"horizontalBar\",\n                   \"label\": \"Horizontal bar\"\n               },\n               {\n                   \"value\": \"verticalBar\",\n                   \"label\": \"Vertical bar\"\n               }\n            ]\n        },\n        \"donutStartAngle\",\n        \"neonGlowBrightness\",\n        \"dashThickness\",\n        \"roundedLineCap\",\n        \"title\",\n        \"showTitle\",\n        \"unitTitle\",\n        \"showUnitTitle\",\n        \"showValue\",\n        \"showMinMax\",\n        \"gaugeWidthScale\",\n        {\n            \"key\": \"defaultColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"gaugeColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"levelColors\",\n            \"items\": [\n                {\n                    \"key\": \"levelColors[]\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"refreshAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"refreshAnimationTime\",\n        {\n            \"key\": \"startAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"startAnimationTime\",\n        \"decimals\",\n        \"units\",\n        {\n            \"key\": \"titleFont\",\n            \"items\": [\n                \"titleFont.family\",\n                \"titleFont.size\",\n                {\n                   \"key\": \"titleFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"titleFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"titleFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"labelFont\",\n            \"items\": [\n                \"labelFont.family\",\n                \"labelFont.size\",\n                {\n                   \"key\": \"labelFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"labelFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"labelFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"valueFont\",\n            \"items\": [\n                \"valueFont.family\",\n                \"valueFont.size\",\n                {\n                   \"key\": \"valueFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"valueFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"valueFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"minMaxFont\",\n            \"items\": [\n                \"minMaxFont.family\",\n                \"minMaxFont.size\",\n                {\n                   \"key\": \"minMaxFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"minMaxFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"minMaxFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        }\n    ]\n}\n    ","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#babab2\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\"linear\",\"refreshAnimationTime\":700,\"startAnimationType\":\"linear\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"RobotoDraft\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"RobotoDraft\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"RobotoDraft\",\"style\":\"normal\",\"weight\":\"400\",\"size\":16},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":1.5,\"decimals\":0,\"showUnitTitle\":true,\"defaultColor\":\"#444444\",\"gaugeType\":\"verticalBar\",\"units\":\"%\"},\"title\":\"LCD bar gauge\"}"}',
+'LCD bar gauge' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'charts', 'pie',
+'{"type":"latest","sizeX":8,"sizeY":6,"resources":[{"url":"https://rawgithub.com/HumbleSoftware/Flotr2/master/flotr2.min.js"}],"templateHtml":"","templateCss":"","controllerScript":"var options, graph, pieData = [];\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n\n    var colors = [];\n    for (var i in data) {\n        colors.push(data[i].dataKey.color);\n        var pieCell = {\n             data: [[0,0]],\n             label: data[i].dataKey.label\n        }\n        pieData.push(pieCell);\n    }\n\n    options = {\n         colors: colors,\n         HtmlText : false,\n        grid : {\n            verticalLines : false,\n            horizontalLines : false\n        },\n        xaxis : { showLabels : false },\n        yaxis : { showLabels : false },\n        pie : {\n             show : true, \n             explode : 6\n        }\n    };\n}\n\nfns.redraw = function(containerElement, width, height, data,\n    timeWindow, sizeChanged) {\n\n    for (var i in pieData) {\n         cellData = data[i];\n         if (cellData.data.length > 0) {\n             var tvPair = cellData.data[cellData.data.length -\n                1];\n             var value = tvPair[1];\n             pieData[i].data[0][1] = parseFloat(value);\n         }\n    }\n    graph = Flotr.draw(containerElement, pieData, options);\n};\n","settingsSchema":"{}","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#0097a7\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#f57f17\",\"settings\":{},\"_hash\":0.6114638304362894,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#e91e63\",\"settings\":{},\"_hash\":0.9955906536344441,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#66bb6a\",\"settings\":{},\"_hash\":0.9430835931647599,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Pie - Flotr2\"}"}',
+'Pie - Flotr2' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'digital_gauges', 'mini_gauge_justgage',
+'{"type":"latest","sizeX":2,"sizeY":2,"resources":[],"templateHtml":"","templateCss":"#gauge {\n    text-align: center;\n   /* margin-left: -100px;\n    margin-right: -100px;*/\n    /*margin-top: -50px;*/\n    \n}\n","controllerScript":"var gauge;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n    gauge = new TbDigitalGauge(containerElement, settings, data);    \n\n}\n\n\nfns.redraw = function(containerElement, width, height, data) {\n    gauge.redraw(data);\n};\n\nfns.destroy = function() {\n}\n","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"minValue\": {\n                \"title\": \"Minimum value\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"maxValue\": {\n                \"title\": \"Maximum value\",\n                \"type\": \"number\",\n                \"default\": 100\n            },            \n            \"gaugeType\": {\n                \"title\": \"Gauge type\",\n                \"type\": \"string\",\n                \"default\": \"arc\"\n            },         \n            \"donutStartAngle\": {\n                \"title\": \"Angle to start from when in donut mode\",\n                \"type\": \"number\",\n                \"default\": 90\n            },            \n            \"neonGlowBrightness\": {\n                \"title\": \"Neon glow effect brightness, (0-100), 0 - disable effect\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"dashThickness\": {\n                \"title\": \"Thickness of the stripes, 0 - no stripes\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"roundedLineCap\": {\n                \"title\": \"Display rounded line cap\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"title\": {\n                \"title\": \"Gauge title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showTitle\": {\n                \"title\": \"Show gauge title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"unitTitle\": {\n                \"title\": \"Unit title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showUnitTitle\": {\n                \"title\": \"Show unit title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"showValue\": {\n                \"title\": \"Show value text\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"showMinMax\": {\n                \"title\": \"Show min and max values\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"gaugeWidthScale\": {\n                \"title\": \"Width of the gauge element\",\n                \"type\": \"number\",\n                \"default\": 0.75\n            },\n            \"defaultColor\": {\n                \"title\": \"Default color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"gaugeColor\": {\n                \"title\": \"Background color of the gauge element\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"levelColors\": {\n                \"title\": \"Colors of indicator, from lower to upper\",\n                \"type\": \"array\",\n                \"items\": {\n                  \"title\": \"Color\",\n                  \"type\": \"string\"\n                }\n            },\n            \"refreshAnimationType\": {\n                \"title\": \"Type of refresh animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"refreshAnimationTime\": {\n                \"title\": \"Duration of refresh animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"startAnimationType\": {\n                \"title\": \"Type of start animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"startAnimationTime\": {\n                \"title\": \"Duration of start animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"decimals\": {\n                \"title\": \"Number of digits after floating point\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"units\": {\n                \"title\": \"Special symbol to show next to value\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"titleFont\": {\n                \"title\": \"Gauge title font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 12\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"labelFont\": {\n                \"title\": \"Font of label showing under value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 8\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"valueFont\": {\n                \"title\": \"Font of label showing current value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 18\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"minMaxFont\": {\n                \"title\": \"Font of minimum and maximum labels\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 10\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            }\n        }\n    },\n    \"form\": [\n        \"minValue\",\n        \"maxValue\",\n        {\n           \"key\": \"gaugeType\",\n           \"type\": \"rc-select\",\n           \"multiple\": false,\n           \"items\": [\n               {\n                   \"value\": \"arc\",\n                   \"label\": \"Arc\"\n               },\n               {\n                   \"value\": \"donut\",\n                   \"label\": \"Donut\"\n               },\n               {\n                   \"value\": \"horizontalBar\",\n                   \"label\": \"Horizontal bar\"\n               },\n               {\n                   \"value\": \"verticalBar\",\n                   \"label\": \"Vertical bar\"\n               }\n            ]\n        },\n        \"donutStartAngle\",\n        \"neonGlowBrightness\",\n        \"dashThickness\",\n        \"roundedLineCap\",\n        \"title\",\n        \"showTitle\",\n        \"unitTitle\",\n        \"showUnitTitle\",\n        \"showValue\",\n        \"showMinMax\",\n        \"gaugeWidthScale\",\n        {\n            \"key\": \"defaultColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"gaugeColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"levelColors\",\n            \"items\": [\n                {\n                    \"key\": \"levelColors[]\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"refreshAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"refreshAnimationTime\",\n        {\n            \"key\": \"startAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"startAnimationTime\",\n        \"decimals\",\n        \"units\",\n        {\n            \"key\": \"titleFont\",\n            \"items\": [\n                \"titleFont.family\",\n                \"titleFont.size\",\n                {\n                   \"key\": \"titleFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"titleFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"titleFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"labelFont\",\n            \"items\": [\n                \"labelFont.family\",\n                \"labelFont.size\",\n                {\n                   \"key\": \"labelFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"labelFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"labelFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"valueFont\",\n            \"items\": [\n                \"valueFont.family\",\n                \"valueFont.size\",\n                {\n                   \"key\": \"valueFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"valueFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"valueFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"minMaxFont\",\n            \"items\": [\n                \"minMaxFont.family\",\n                \"minMaxFont.size\",\n                {\n                   \"key\": \"minMaxFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"minMaxFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"minMaxFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        }\n    ]\n}\n    ","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#7cb342\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"RobotoDraft\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"RobotoDraft\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"RobotoDraft\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":0,\"decimals\":0,\"roundedLineCap\":true,\"gaugeType\":\"donut\"},\"title\":\"Mini gauge - justGage\"}"}',
+'Mini gauge - justGage' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'analogue_gauges', 'radial_gauge_canvas_gauges',
+'{"type":"latest","sizeX":6,"sizeY":5,"resources":[],"templateHtml":"<canvas id=\"radialGauge\"></canvas>\n","templateCss":"","controllerScript":"var gauge;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n    gauge = new TbAnalogueRadialGauge(containerElement, settings, data, ''radialGauge'');    \n\n}\n\n\nfns.redraw = function(containerElement, width, height, data, timeWindow, sizeChanged) {\n    gauge.redraw(width, height, data, sizeChanged);\n};\n\nfns.destroy = function() {\n}\n","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"minValue\": {\n                \"title\": \"Minimum value\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"maxValue\": {\n                \"title\": \"Maximum value\",\n                \"type\": \"number\",\n                \"default\": 100\n            },\n            \"unitTitle\": {\n                \"title\": \"Unit title\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"showUnitTitle\": {\n                \"title\": \"Show unit title\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"units\": {\n                \"title\": \"Units\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"majorTicksCount\": {\n                \"title\": \"Major ticks count\",\n                \"type\": \"number\",\n                \"default\": null\n            },\n            \"minorTicks\": {\n                \"title\": \"Minor ticks count\",\n                \"type\": \"number\",\n                \"default\": 2\n            },\n            \"valueBox\": {\n                \"title\": \"Show value box\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"valueInt\": {\n                \"title\": \"Digits count for integer part of value\",\n                \"type\": \"number\",\n                \"default\": 3\n            },\n            \"valueDec\": {\n                \"title\": \"Digits count for decimal part of value\",\n                \"type\": \"number\",\n                \"default\": 2\n            },\n            \"defaultColor\": {\n                \"title\": \"Default color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"colorPlate\": {\n                \"title\": \"Plate color\",\n                \"type\": \"string\",\n                \"default\": \"#fff\"\n            },\n            \"colorMajorTicks\": {\n                \"title\": \"Major ticks color\",\n                \"type\": \"string\",\n                \"default\": \"#444\"\n            },\n            \"colorMinorTicks\": {\n                \"title\": \"Minor ticks color\",\n                \"type\": \"string\",\n                \"default\": \"#666\"\n            },\n            \"colorNeedle\": {\n                \"title\": \"Needle color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"colorNeedleEnd\": {\n                \"title\": \"Needle color - end gradient\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"colorNeedleShadowUp\": {\n                \"title\": \"Upper half of the needle shadow color\",\n                \"type\": \"string\",\n                \"default\": \"rgba(2,255,255,0.2)\"\n            },\n            \"colorNeedleShadowDown\": {\n                \"title\": \"Drop shadow needle color.\",\n                \"type\": \"string\",\n                \"default\": \"rgba(188,143,143,0.45)\"\n            },\n            \"colorValueBoxRect\": {\n                \"title\": \"Value box rectangle stroke color\",\n                \"type\": \"string\",\n                \"default\": \"#888\"\n            },\n            \"colorValueBoxRectEnd\": {\n                \"title\": \"Value box rectangle stroke color - end gradient\",\n                \"type\": \"string\",\n                \"default\": \"#666\"\n            },\n            \"colorValueBoxBackground\": {\n                \"title\": \"Value box background color\",\n                \"type\": \"string\",\n                \"default\": \"#babab2\"\n            },\n            \"colorValueBoxShadow\": {\n                \"title\": \"Value box shadow color\",\n                \"type\": \"string\",\n                \"default\": \"rgba(0,0,0,1)\"\n            },\n            \"highlights\": {\n                \"title\": \"Highlights\",\n                \"type\": \"array\",\n                \"items\": {\n                  \"title\": \"Highlight\",\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"from\": {\n                      \"title\": \"From\",\n                      \"type\": \"number\"\n                    },\n                    \"to\": {\n                      \"title\": \"To\",\n                      \"type\": \"number\"\n                    },\n                    \"color\": {\n                      \"title\": \"Color\",\n                      \"type\": \"string\"\n                    }\n                  }\n                }\n            },\n            \"highlightsWidth\": {\n                \"title\": \"Highlights width\",\n                \"type\": \"number\",\n                \"default\": 15\n            },\n            \"showBorder\": {\n                \"title\": \"Show border\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"numbersFont\": {\n                \"title\": \"Tick numbers font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 18\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"titleFont\": {\n                \"title\": \"Title text font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 24\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": \"#888\"\n                    }\n                }\n            },\n            \"unitsFont\": {\n                \"title\": \"Units text font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 22\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": \"#888\"\n                    }\n                }\n            },\n            \"valueFont\": {\n                \"title\": \"Value text font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 40\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": \"#444\"\n                    },\n                    \"shadowColor\": {\n                        \"title\": \"Shadow color\",\n                        \"type\": \"string\",\n                        \"default\": \"rgba(0,0,0,0.3)\"\n                    }\n                }\n            },\n            \"animation\": {\n                \"title\": \"Enable animation\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"animationDuration\": {\n                \"title\": \"Animation duration\",\n                \"type\": \"number\",\n                \"default\": 500\n            },\n            \"animationRule\": {\n                \"title\": \"Animation rule\",\n                \"type\": \"string\",\n                \"default\": \"cycle\"\n            },\n            \"startAngle\": {\n                \"title\": \"Start ticks angle\",\n                \"type\": \"number\",\n                \"default\": 45\n            },\n            \"ticksAngle\": {\n                \"title\": \"Ticks angle\",\n                \"type\": \"number\",\n                \"default\": 270\n            },\n            \"needleCircleSize\": {\n                \"title\": \"Needle circle size\",\n                \"type\": \"number\",\n                \"default\": 10\n            }\n        },\n        \"required\": []\n    },\n    \"form\": [\n        \"startAngle\",\n        \"ticksAngle\",\n        \"needleCircleSize\",\n        \"minValue\",\n        \"maxValue\",\n        \"unitTitle\",\n        \"showUnitTitle\",\n        \"units\",\n        \"majorTicksCount\",\n        \"minorTicks\",\n        \"valueBox\",\n        \"valueInt\",\n        \"valueDec\",\n        {\n            \"key\": \"defaultColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorPlate\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorMajorTicks\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorMinorTicks\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorNeedle\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorNeedleEnd\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorNeedleShadowUp\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorNeedleShadowDown\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorValueBoxRect\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorValueBoxRectEnd\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorValueBoxBackground\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorValueBoxShadow\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"highlights\",\n            \"items\": [\n                \"highlights[].from\",\n                \"highlights[].to\",\n                {\n                    \"key\": \"highlights[].color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        \"highlightsWidth\",\n        \"showBorder\",\n        {\n            \"key\": \"numbersFont\",\n            \"items\": [\n                \"numbersFont.family\",\n                \"numbersFont.size\",\n                {\n                   \"key\": \"numbersFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"numbersFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"numbersFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"titleFont\",\n            \"items\": [\n                \"titleFont.family\",\n                \"titleFont.size\",\n                {\n                   \"key\": \"titleFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"titleFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"titleFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"unitsFont\",\n            \"items\": [\n                \"unitsFont.family\",\n                \"unitsFont.size\",\n                {\n                   \"key\": \"unitsFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"unitsFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"unitsFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"valueFont\",\n            \"items\": [\n                \"valueFont.family\",\n                \"valueFont.size\",\n                {\n                   \"key\": \"valueFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"valueFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"valueFont.color\",\n                    \"type\": \"color\"\n                },\n                {\n                    \"key\": \"valueFont.shadowColor\",\n                    \"type\": \"color\"\n                }\n            ]\n        },        \n        \"animation\",\n        \"animationDuration\",\n        {\n            \"key\": \"animationRule\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \"quad\",\n                    \"label\": \"Quad\"\n                },\n                {\n                    \"value\": \"quint\",\n                    \"label\": \"Quint\"\n                },\n                {\n                    \"value\": \"cycle\",\n                    \"label\": \"Cycle\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                },\n                {\n                    \"value\": \"elastic\",\n                    \"label\": \"Elastic\"\n                },\n                {\n                    \"value\": \"dequad\",\n                    \"label\": \"Dequad\"\n                },\n                {\n                    \"value\": \"dequint\",\n                    \"label\": \"Dequint\"\n                },\n                {\n                    \"value\": \"decycle\",\n                    \"label\": \"Decycle\"\n                },\n                {\n                    \"value\": \"debounce\",\n                    \"label\": \"Debounce\"\n                },\n                {\n                    \"value\": \"delastic\",\n                    \"label\": \"Delastic\"\n                }\n            ]\n        }\n    ]\n}","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 50 - 25;\\nif (value < -100) {\\n\\tvalue = -100;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":100,\"startAngle\":45,\"ticksAngle\":270,\"showBorder\":true,\"defaultColor\":\"#e65100\",\"needleCircleSize\":10,\"highlights\":[],\"showUnitTitle\":true,\"colorPlate\":\"#fff\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"minorTicks\":10,\"valueInt\":3,\"valueDec\":0,\"highlightsWidth\":15,\"valueBox\":true,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"cycle\",\"colorNeedleShadowUp\":\"rgba(2, 255, 255, 0)\",\"numbersFont\":{\"family\":\"RobotoDraft\",\"size\":18,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"titleFont\":{\"family\":\"RobotoDraft\",\"size\":24,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#888\"},\"unitsFont\":{\"family\":\"RobotoDraft\",\"size\":22,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"size\":36,\"style\":\"normal\",\"weight\":\"normal\",\"shadowColor\":\"rgba(0, 0, 0, 0.49)\",\"color\":\"#444\"},\"minValue\":-100,\"colorNeedleShadowDown\":\"rgba(188,143,143,0.45)\",\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\"},\"title\":\"Radial gauge - Canvas Gauges\"}"}',
+'Radial gauge - Canvas Gauges' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'maps', 'google_maps',
+'{"type":"latest","sizeX":8.5,"sizeY":6,"resources":[],"templateHtml":"","templateCss":".error {\n    color: red;\n}\n.tb-labels {\n  color: #222;\n  font: 12px/1.5 \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n  text-align: center;\n  width: 100px;\n  white-space: nowrap;\n}","controllerScript":"var map;\nvar positions;\nvar markers = [];\nvar markersSettings = [];\nvar defaultZoomLevel;\nvar dontFitMapBounds;\n\nvar markerCluster;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n    \n    if (settings.defaultZoomLevel) {\n        if (settings.defaultZoomLevel > 0 && settings.defaultZoomLevel < 21) {\n            defaultZoomLevel = Math.floor(settings.defaultZoomLevel);\n        }\n    }\n    \n    dontFitMapBounds = settings.fitMapBounds === false;\n    \n    var configuredMarkersSettings = settings.markersSettings;\n    if (!configuredMarkersSettings) {\n        configuredMarkersSettings = [];\n    }\n    \n    for (var i=0;i<datasources.length;i++) {\n        markersSettings[i] = {\n            latKeyName: \"lat\",\n            lngKeyName: \"lng\",\n            showLabel: true,\n            label: datasources[i].name,\n            color: \"FE7569\"\n        };\n        if (configuredMarkersSettings[i]) {\n            markersSettings[i].latKeyName = configuredMarkersSettings[i].latKeyName || markersSettings[i].latKeyName;\n            markersSettings[i].lngKeyName = configuredMarkersSettings[i].lngKeyName || markersSettings[i].lngKeyName;\n            markersSettings[i].showLabel = configuredMarkersSettings[i].showLabel !== false;\n            markersSettings[i].label = configuredMarkersSettings[i].label || markersSettings[i].label;\n            markersSettings[i].color = configuredMarkersSettings[i].color ? tinycolor(configuredMarkersSettings[i].color).toHex() : markersSettings[i].color;\n        }\n    }\n\n    var mapId = '''' + Math.random().toString(36).substr(2, 9);\n    \n    function clearGlobalId() {\n        if ($window.loadingGmId && $window.loadingGmId === mapId) {\n            $window.loadingGmId = null;\n        }\n    }\n    \n    $window.gm_authFailure = function() {\n        if ($window.loadingGmId && $window.loadingGmId === mapId) {\n            $window.loadingGmId = null;\n            $window.gmApiKeys[apiKey].error = ''Unable to authentificate for Google Map API.</br>Please check your API key.'';\n            displayError($window.gmApiKeys[apiKey].error);\n        }\n    };\n    \n    function displayError(message) {\n        $(containerElement).html(\n            \"<div class=''error''>\"+ message + \"</div>\"\n        );\n    }\n\n    var initMapFunctionName = ''initGoogleMap_'' + mapId;\n    $window[initMapFunctionName] = function() {\n        lazyLoad.load({ type: ''js'', path: ''https://cdn.rawgit.com/googlemaps/v3-utility-library/master/markerwithlabel/src/markerwithlabel.js'' }).then(\n            function success() {\n                initMap();\n            },\n            function fail() {\n                clearGloabalId();\n                $window.gmApiKeys[apiKey].error = ''Google map api load failed!</br>''+e;\n                displayError($window.gmApiKeys[apiKey].error);\n            }\n        );\n        \n    };   \n    \n    var apiKey = settings.gmApiKey || '''';\n\n    if (apiKey && apiKey.length > 0) {\n        if (!$window.gmApiKeys) {\n            $window.gmApiKeys = {};\n        }\n        if ($window.gmApiKeys[apiKey]) {\n            if ($window.gmApiKeys[apiKey].error) {\n                displayError($window.gmApiKeys[apiKey].error);\n            } else {\n                initMap();\n            }\n        } else {\n            $window.gmApiKeys[apiKey] = {};\n            var googleMapScriptRes = ''https://maps.googleapis.com/maps/api/js?key=''+apiKey+''&callback=''+initMapFunctionName;\n        \n            $window.loadingGmId = mapId;\n            lazyLoad.load({ type: ''js'', path: googleMapScriptRes }).then(\n                function success() {\n                    setTimeout(clearGlobalId, 2000);\n                },\n                function fail(e) {\n                    clearGloabalId();\n                    $window.gmApiKeys[apiKey].error = ''Google map api load failed!</br>''+e;\n                    displayError($window.gmApiKeys[apiKey].error);\n                }\n            );\n        }\n    } else {\n        displayError(''No Google Map Api Key provided!'');\n    }\n\n    function initMap() {\n        \n        map = new google.maps.Map(containerElement, {\n          scrollwheel: false,\n          zoom: defaultZoomLevel || 8\n        });\n\n    };\n\n}\n\n\nfns.redraw = function(containerElement, width, height, data,\n    timeWindow, sizeChanged) {\n    \n    function createMarker(location, settings) {\n        var pinColor = settings.color;\n        var pinImage = new google.maps.MarkerImage(\"http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|\" + pinColor,\n            new google.maps.Size(21, 34),\n            new google.maps.Point(0,0),\n            new google.maps.Point(10, 34));\n        var pinShadow = new google.maps.MarkerImage(\"http://chart.apis.google.com/chart?chst=d_map_pin_shadow\",\n            new google.maps.Size(40, 37),\n            new google.maps.Point(0, 0),\n            new google.maps.Point(12, 35));        \n        var marker;\n        if (settings.showLabel) {    \n                marker = new MarkerWithLabel({\n                    position: location, \n                    map: map,\n                    icon: pinImage,\n                    shadow: pinShadow,\n                    labelContent: ''<b>''+settings.label+''</b>'',\n                    labelClass: \"tb-labels\",\n                    labelAnchor: new google.maps.Point(50, 55)\n                });            \n        } else {\n                marker = new google.maps.Marker({\n                    position: location, \n                    map: map,\n                    icon: pinImage,\n                    shadow: pinShadow\n                });            \n        }\n            \n        return marker;    \n    }\n    \n    function updatePosition(position, data) {\n        if (position.latIndex > -1 && position.lngIndex > -1) {\n            var latData = data[position.latIndex].data;\n            var lngData = data[position.lngIndex].data;\n            if (latData.length > 0 && lngData.length > 0) {\n                var lat = latData[latData.length-1][1];\n                var lng = lngData[lngData.length-1][1];\n                var location = new google.maps.LatLng(lat, lng);\n                if (!position.marker) {\n                    position.marker = createMarker(location, position.settings);\n                    markers.push(position.marker);\n                    return true;\n                } else {\n                    var prevPosition = position.marker.getPosition();\n                    if (!prevPosition.equals(location)) {\n                        position.marker.setPosition(location);\n                        return true;\n                    }\n                }\n            }\n        }\n        return false;\n    }\n        \n    function loadPositions(data) {\n        var bounds = new google.maps.LatLngBounds();\n        positions = [];\n        var datasourceIndex = -1;\n        var markerSettings;\n        var datasource;\n        for (var i = 0; i < data.length; i++) {\n            var datasourceData = data[i];\n            if (!datasource || datasource != datasourceData.datasource) {\n                datasourceIndex++;\n                datasource = datasourceData.datasource;\n                markerSettings = markersSettings[datasourceIndex];\n            }\n            var dataKey = datasourceData.dataKey;\n            if (dataKey.label === markerSettings.latKeyName ||\n                dataKey.label === markerSettings.lngKeyName) {\n                var position = positions[datasourceIndex];\n                if (!position) {\n                    position = {\n                        latIndex: -1,\n                        lngIndex: -1,\n                        settings: markerSettings\n                    };\n                    positions[datasourceIndex] = position;\n                } else if (position.marker) {\n                    continue;\n                }\n                if (dataKey.label === markerSettings.latKeyName) {\n                    position.latIndex = i;\n                } else {\n                    position.lngIndex = i;\n                }\n                if (position.latIndex > -1 && position.lngIndex > -1) {\n                    updatePosition(position, data);\n                    if (position.marker) {\n                        bounds.extend(position.marker.getPosition());\n                    }\n                }\n            }\n        }\n        fitMapBounds(bounds);\n    }\n    \n    function updatePositions(data) {\n        var positionsChanged = false;\n        var bounds = new google.maps.LatLngBounds();\n        for (var p in positions) {\n            var position = positions[p];\n            positionsChanged |= updatePosition(position, data);\n            if (position.marker) {\n                bounds.extend(position.marker.getPosition());\n            }\n        }\n        if (!dontFitMapBounds && positionsChanged) {\n            fitMapBounds(bounds);\n        }\n    }\n    \n    function fitMapBounds(bounds) {\n        google.maps.event.addListenerOnce(map, ''bounds_changed'', function(event) {\n            var zoomLevel = defaultZoomLevel || map.getZoom();\n            this.setZoom(zoomLevel);\n            if (!defaultZoomLevel && this.getZoom() > 15) {\n                this.setZoom(15);\n            }\n        });\n        map.fitBounds(bounds);\n    }\n\n    if (map) {\n        if (data) {\n            if (!positions) {\n                loadPositions(data);\n            } else {\n                updatePositions(data);\n            }\n        }\n        if (sizeChanged) {\n            google.maps.event.trigger(map, \"resize\");\n            var bounds = new google.maps.LatLngBounds();\n            for (var m in markers) {\n                bounds.extend(markers[m].getPosition());\n            }\n            fitMapBounds(bounds);\n        }\n    }\n\n};","settingsSchema":"{\n  \"schema\": {\n    \"title\": \"Google Map Configuration\",\n    \"type\": \"object\",\n    \"properties\": {\n      \"gmApiKey\": {\n        \"title\": \"Google Maps API Key\",\n        \"type\": \"string\"\n      },\n      \"defaultZoomLevel\": {\n         \"title\": \"Default map zoom level (1 - 20)\",\n         \"type\": \"number\"\n      },\n      \"fitMapBounds\": {\n          \"title\": \"Fit map bounds to cover all markers\",\n          \"type\": \"boolean\",\n          \"default\": true\n      },\n      \"markersSettings\": {\n            \"title\": \"Markers settings, same order as datasources\",\n            \"type\": \"array\",\n            \"items\": {\n              \"title\": \"Marker settings\",\n              \"type\": \"object\",\n              \"properties\": {\n                  \"latKeyName\": {\n                    \"title\": \"Latitude key name\",\n                    \"type\": \"string\",\n                    \"default\": \"lat\"\n                  },\n                  \"lngKeyName\": {\n                    \"title\": \"Longitude key name\",\n                    \"type\": \"string\",\n                    \"default\": \"lng\"\n                  },                  \n                  \"showLabel\": {\n                    \"title\": \"Show label\",\n                    \"type\": \"boolean\",\n                    \"default\": true\n                  },                  \n                  \"label\": {\n                    \"title\": \"Label\",\n                    \"type\": \"string\"\n                  },\n                  \"color\": {\n                    \"title\": \"Color\",\n                    \"type\": \"string\"\n                  }\n              }\n            }\n      }\n    },\n    \"required\": [\n      \"gmApiKey\"\n    ]\n  },\n  \"form\": [\n    \"gmApiKey\",\n    \"defaultZoomLevel\",\n    \"fitMapBounds\",\n    {\n        \"key\": \"markersSettings\",\n        \"items\": [\n            \"markersSettings[].latKeyName\",\n            \"markersSettings[].lngKeyName\",\n            \"markersSettings[].showLabel\",\n            \"markersSettings[].label\",\n            {\n                \"key\": \"markersSettings[].color\",\n                \"type\": \"color\"\n            }\n        ]\n    }\n  ]\n}","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.05427416942713381,\"funcBody\":\"var value = prevValue || 15.833293;\\nif (time % 5000 < 500) {\\n    value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.680594833308841,\"funcBody\":\"var value = prevValue || -90.454350;\\nif (time % 5000 < 500) {\\n    value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"}]},{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"lat\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.05012157428742059,\"funcBody\":\"var value = prevValue || 14.450463;\\nif (time % 4000 < 500) {\\n    value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"lng\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.6742359401617628,\"funcBody\":\"var value = prevValue || -84.845334;\\nif (time % 4000 < 500) {\\n    value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"gmApiKey\":\"AIzaSyDoEx2kaGz3PxwbI9T7ccTSg5xjdw8Nw8Q\",\"markersSettings\":[{\"label\":\"First point\",\"color\":\"#1e88e5\",\"latKeyName\":\"latitude\",\"lngKeyName\":\"longitude\",\"showLabel\":true},{\"label\":\"Second point\",\"color\":\"#fdd835\",\"latKeyName\":\"lat\",\"lngKeyName\":\"lng\",\"showLabel\":true}],\"fitMapBounds\":true},\"title\":\"Google Maps\"}"}',
+'Google Maps' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'charts', 'basic_timeseries',
+'{"type":"timeseries","sizeX":8,"sizeY":6,"resources":[{"url":"https://rawgithub.com/HumbleSoftware/Flotr2/master/flotr2.min.js"}],"templateHtml":"","templateCss":"","controllerScript":"var graph, options;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n\n    var colors = [];\n    for (var i in data) {\n        data[i].label = data[i].dataKey.label;\n        colors.push(data[i].dataKey.color);\n        var keySettings = data[i].dataKey.settings;\n\n        data[i].lines = {\n            fill: keySettings.fillLines || false,\n            show: keySettings.showLines || true\n        };\n\n        data[i].points = {\n            show: keySettings.showPoints || false\n        };\n    }\n    options = {\n        colors: colors,\n        title: null,\n        subtitle: null,\n        shadowSize: settings.shadowSize || 4,\n        fontColor: settings.fontColor || \"#545454\",\n        fontSize: settings.fontSize || 7.5,\n        xaxis: {\n            mode: ''time'',\n            timeMode: ''local''\n        },\n        yaxis: {\n        },\n        HtmlText: false,\n        grid: {\n            verticalLines: true,\n            horizontalLines: true\n        }\n    };\n    if (settings.grid) {\n        options.grid.color = settings.grid.color || \"#545454\";\n        options.grid.backgroundColor = settings.grid.backgroundColor || null;\n        options.grid.tickColor = settings.grid.tickColor || \"#DDDDDD\";\n        options.grid.verticalLines = settings.grid.verticalLines !== false;\n        options.grid.horizontalLines = settings.grid.horizontalLines !== false;\n    }\n    if (settings.xaxis) {\n        options.xaxis.showLabels = settings.xaxis.showLabels !== false;\n        options.xaxis.color = settings.xaxis.color || null;\n        options.xaxis.title = settings.xaxis.title || null;\n        options.xaxis.titleAngle = settings.xaxis.titleAngle || 0;\n    }\n    if (settings.yaxis) {\n        options.yaxis.showLabels = settings.yaxis.showLabels !== false;\n        options.yaxis.color = settings.yaxis.color || null;\n        options.yaxis.title = settings.yaxis.title || null;\n        options.yaxis.titleAngle = settings.yaxis.titleAngle || 0;\n    }\n}\n\nfns.redraw = function(containerElement, width, height, data,\n    timeWindow, sizeChanged) {\n    options.xaxis.min = timeWindow.minTime;\n    options.xaxis.max = timeWindow.maxTime;\n    graph = Flotr.draw(containerElement, data, options);\n};\n\nfns.destroy = function() {\n    //console.log(''destroy!'');\n};","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"shadowSize\": {\n                \"title\": \"Shadow size\",\n                \"type\": \"number\",\n                \"default\": 4\n            },\n            \"fontColor\": {\n                \"title\": \"Font color\",\n                \"type\": \"string\",\n                \"default\": \"#545454\"\n            },\n            \"fontSize\": {\n                \"title\": \"Font size\",\n                \"type\": \"number\",\n                \"default\": 7.5\n            },\n            \"grid\": {\n                \"title\": \"Grid settings\",\n                \"type\": \"object\",\n                \"properties\": {\n                    \"color\": {\n                        \"title\": \"Primary color\",\n                        \"type\": \"string\",\n                        \"default\": \"#545454\"\n                    },\n                    \"backgroundColor\": {\n                        \"title\": \"Background color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    },\n                    \"tickColor\": {\n                        \"title\": \"Ticks color\",\n                        \"type\": \"string\",\n                        \"default\": \"#DDDDDD\"\n                    },\n                    \"verticalLines\": {\n                        \"title\": \"Show vertical lines\",\n                        \"type\": \"boolean\",\n                        \"default\": true\n                    },\n                    \"horizontalLines\": {\n                        \"title\": \"Show horizontal lines\",\n                        \"type\": \"boolean\",\n                        \"default\": true\n                    }\n                }\n            },\n            \"xaxis\": {\n                \"title\": \"X axis settings\",\n                \"type\": \"object\",\n                \"properties\": {\n                    \"showLabels\": {\n                        \"title\": \"Show labels\",\n                        \"type\": \"boolean\",\n                        \"default\": true\n                    },\n                    \"title\": {\n                        \"title\": \"Axis title\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    },\n                    \"titleAngle\": {\n                        \"title\": \"Axis title''s angle in degrees\",\n                        \"type\": \"number\",\n                        \"default\": 0\n                    },\n                    \"color\": {\n                        \"title\": \"Ticks color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"yaxis\": {\n                \"title\": \"Y axis settings\",\n                \"type\": \"object\",\n                \"properties\": {\n                    \"showLabels\": {\n                        \"title\": \"Show labels\",\n                        \"type\": \"boolean\",\n                        \"default\": true\n                    },\n                    \"title\": {\n                        \"title\": \"Axis title\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    },\n                    \"titleAngle\": {\n                        \"title\": \"Axis title''s angle in degrees\",\n                        \"type\": \"number\",\n                        \"default\": 0\n                    },\n                    \"color\": {\n                        \"title\": \"Ticks color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            }\n        },\n        \"required\": []\n    },\n    \"form\": [\n        \"shadowSize\", \n        {\n            \"key\": \"fontColor\",\n            \"type\": \"color\"\n        },\n        \"fontSize\", \n        {\n            \"key\": \"grid\",\n            \"items\": [\n                {\n                    \"key\": \"grid.color\",\n                    \"type\": \"color\"\n                },\n                {\n                    \"key\": \"grid.backgroundColor\",\n                    \"type\": \"color\"\n                },\n                {\n                    \"key\": \"grid.tickColor\",\n                    \"type\": \"color\"\n                },\n                \"grid.verticalLines\",\n                \"grid.horizontalLines\"\n            ]\n        },\n        {\n            \"key\": \"xaxis\",\n            \"items\": [\n                \"xaxis.showLabels\",\n                \"xaxis.title\",\n                \"xaxis.titleAngle\",\n                {\n                    \"key\": \"xaxis.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"yaxis\",\n            \"items\": [\n                \"yaxis.showLabels\",\n                \"yaxis.title\",\n                \"yaxis.titleAngle\",\n                {\n                    \"key\": \"yaxis.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        }\n\n    ]\n}","dataKeySettingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"DataKeySettings\",\n        \"properties\": {\n            \"showLines\": {\n                \"title\": \"Show lines\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"fillLines\": {\n                \"title\": \"Fill lines\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },\n            \"showPoints\": {\n                \"title\": \"Show points\",\n                \"type\": \"boolean\",\n                \"default\": false\n            }\n        },\n        \"required\": [\"showLines\", \"fillLines\", \"showPoints\"]\n    },\n    \"form\": [\n        \"showLines\",\n        \"fillLines\",\n        \"showPoints\"\n    ]\n}","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":7.5,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"backgroundColor\":\"#ffffff\"}},\"title\":\"Timeseries - Flotr2\"}"}',
+'Timeseries - Flotr2' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'digital_gauges', 'horizontal_bar_justgage',
+'{"type":"latest","sizeX":7,"sizeY":3,"resources":[],"templateHtml":"","templateCss":"#gauge {\n    text-align: center;\n   /* margin-left: -100px;\n    margin-right: -100px;*/\n    /*margin-top: -50px;*/\n    \n}\n","controllerScript":"var gauge;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n    gauge = new TbDigitalGauge(containerElement, settings, data);    \n\n}\n\n\nfns.redraw = function(containerElement, width, height, data) {\n    gauge.redraw(data);\n};\n\nfns.destroy = function() {\n}\n","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"minValue\": {\n                \"title\": \"Minimum value\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"maxValue\": {\n                \"title\": \"Maximum value\",\n                \"type\": \"number\",\n                \"default\": 100\n            },            \n            \"gaugeType\": {\n                \"title\": \"Gauge type\",\n                \"type\": \"string\",\n                \"default\": \"arc\"\n            },         \n            \"donutStartAngle\": {\n                \"title\": \"Angle to start from when in donut mode\",\n                \"type\": \"number\",\n                \"default\": 90\n            },            \n            \"neonGlowBrightness\": {\n                \"title\": \"Neon glow effect brightness, (0-100), 0 - disable effect\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"dashThickness\": {\n                \"title\": \"Thickness of the stripes, 0 - no stripes\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"roundedLineCap\": {\n                \"title\": \"Display rounded line cap\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"title\": {\n                \"title\": \"Gauge title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showTitle\": {\n                \"title\": \"Show gauge title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"unitTitle\": {\n                \"title\": \"Unit title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showUnitTitle\": {\n                \"title\": \"Show unit title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"showValue\": {\n                \"title\": \"Show value text\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"showMinMax\": {\n                \"title\": \"Show min and max values\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"gaugeWidthScale\": {\n                \"title\": \"Width of the gauge element\",\n                \"type\": \"number\",\n                \"default\": 0.75\n            },\n            \"defaultColor\": {\n                \"title\": \"Default color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"gaugeColor\": {\n                \"title\": \"Background color of the gauge element\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"levelColors\": {\n                \"title\": \"Colors of indicator, from lower to upper\",\n                \"type\": \"array\",\n                \"items\": {\n                  \"title\": \"Color\",\n                  \"type\": \"string\"\n                }\n            },\n            \"refreshAnimationType\": {\n                \"title\": \"Type of refresh animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"refreshAnimationTime\": {\n                \"title\": \"Duration of refresh animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"startAnimationType\": {\n                \"title\": \"Type of start animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"startAnimationTime\": {\n                \"title\": \"Duration of start animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"decimals\": {\n                \"title\": \"Number of digits after floating point\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"units\": {\n                \"title\": \"Special symbol to show next to value\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"titleFont\": {\n                \"title\": \"Gauge title font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 12\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"labelFont\": {\n                \"title\": \"Font of label showing under value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 8\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"valueFont\": {\n                \"title\": \"Font of label showing current value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 18\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"minMaxFont\": {\n                \"title\": \"Font of minimum and maximum labels\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 10\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            }\n        }\n    },\n    \"form\": [\n        \"minValue\",\n        \"maxValue\",\n        {\n           \"key\": \"gaugeType\",\n           \"type\": \"rc-select\",\n           \"multiple\": false,\n           \"items\": [\n               {\n                   \"value\": \"arc\",\n                   \"label\": \"Arc\"\n               },\n               {\n                   \"value\": \"donut\",\n                   \"label\": \"Donut\"\n               },\n               {\n                   \"value\": \"horizontalBar\",\n                   \"label\": \"Horizontal bar\"\n               },\n               {\n                   \"value\": \"verticalBar\",\n                   \"label\": \"Vertical bar\"\n               }\n            ]\n        },\n        \"donutStartAngle\",\n        \"neonGlowBrightness\",\n        \"dashThickness\",\n        \"roundedLineCap\",\n        \"title\",\n        \"showTitle\",\n        \"unitTitle\",\n        \"showUnitTitle\",\n        \"showValue\",\n        \"showMinMax\",\n        \"gaugeWidthScale\",\n        {\n            \"key\": \"defaultColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"gaugeColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"levelColors\",\n            \"items\": [\n                {\n                    \"key\": \"levelColors[]\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"refreshAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"refreshAnimationTime\",\n        {\n            \"key\": \"startAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"startAnimationTime\",\n        \"decimals\",\n        \"units\",\n        {\n            \"key\": \"titleFont\",\n            \"items\": [\n                \"titleFont.family\",\n                \"titleFont.size\",\n                {\n                   \"key\": \"titleFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"titleFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"titleFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"labelFont\",\n            \"items\": [\n                \"labelFont.family\",\n                \"labelFont.size\",\n                {\n                   \"key\": \"labelFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"labelFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"labelFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"valueFont\",\n            \"items\": [\n                \"valueFont.family\",\n                \"valueFont.size\",\n                {\n                   \"key\": \"valueFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"valueFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"valueFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"minMaxFont\",\n            \"items\": [\n                \"minMaxFont.family\",\n                \"minMaxFont.size\",\n                {\n                   \"key\": \"minMaxFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"minMaxFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"minMaxFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        }\n    ]\n}\n    ","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"RobotoDraft\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#999999\"},\"labelFont\":{\"family\":\"RobotoDraft\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"RobotoDraft\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"RobotoDraft\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#666666\"},\"neonGlowBrightness\":0,\"decimals\":0,\"dashThickness\":0,\"gaugeColor\":\"#eeeeee\",\"showTitle\":true,\"gaugeType\":\"horizontalBar\"},\"title\":\"Horizontal bar - justGage\"}"}',
+'Horizontal bar - justGage' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'digital_gauges', 'lcd_gauge',
+'{"type":"latest","sizeX":5,"sizeY":3,"resources":[],"templateHtml":"","templateCss":"#gauge {\n    text-align: center;\n   /* margin-left: -100px;\n    margin-right: -100px;*/\n    /*margin-top: -50px;*/\n    \n}\n","controllerScript":"var gauge;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n    gauge = new TbDigitalGauge(containerElement, settings, data);    \n\n}\n\n\nfns.redraw = function(containerElement, width, height, data) {\n    gauge.redraw(data);\n};\n\nfns.destroy = function() {\n}\n","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"minValue\": {\n                \"title\": \"Minimum value\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"maxValue\": {\n                \"title\": \"Maximum value\",\n                \"type\": \"number\",\n                \"default\": 100\n            },            \n            \"gaugeType\": {\n                \"title\": \"Gauge type\",\n                \"type\": \"string\",\n                \"default\": \"arc\"\n            },         \n            \"donutStartAngle\": {\n                \"title\": \"Angle to start from when in donut mode\",\n                \"type\": \"number\",\n                \"default\": 90\n            },            \n            \"neonGlowBrightness\": {\n                \"title\": \"Neon glow effect brightness, (0-100), 0 - disable effect\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"dashThickness\": {\n                \"title\": \"Thickness of the stripes, 0 - no stripes\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"roundedLineCap\": {\n                \"title\": \"Display rounded line cap\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"title\": {\n                \"title\": \"Gauge title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showTitle\": {\n                \"title\": \"Show gauge title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"unitTitle\": {\n                \"title\": \"Unit title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showUnitTitle\": {\n                \"title\": \"Show unit title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"showValue\": {\n                \"title\": \"Show value text\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"showMinMax\": {\n                \"title\": \"Show min and max values\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"gaugeWidthScale\": {\n                \"title\": \"Width of the gauge element\",\n                \"type\": \"number\",\n                \"default\": 0.75\n            },\n            \"defaultColor\": {\n                \"title\": \"Default color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"gaugeColor\": {\n                \"title\": \"Background color of the gauge element\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"levelColors\": {\n                \"title\": \"Colors of indicator, from lower to upper\",\n                \"type\": \"array\",\n                \"items\": {\n                  \"title\": \"Color\",\n                  \"type\": \"string\"\n                }\n            },\n            \"refreshAnimationType\": {\n                \"title\": \"Type of refresh animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"refreshAnimationTime\": {\n                \"title\": \"Duration of refresh animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"startAnimationType\": {\n                \"title\": \"Type of start animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"startAnimationTime\": {\n                \"title\": \"Duration of start animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"decimals\": {\n                \"title\": \"Number of digits after floating point\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"units\": {\n                \"title\": \"Special symbol to show next to value\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"titleFont\": {\n                \"title\": \"Gauge title font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 12\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"labelFont\": {\n                \"title\": \"Font of label showing under value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 8\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"valueFont\": {\n                \"title\": \"Font of label showing current value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 18\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"minMaxFont\": {\n                \"title\": \"Font of minimum and maximum labels\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 10\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            }\n        }\n    },\n    \"form\": [\n        \"minValue\",\n        \"maxValue\",\n        {\n           \"key\": \"gaugeType\",\n           \"type\": \"rc-select\",\n           \"multiple\": false,\n           \"items\": [\n               {\n                   \"value\": \"arc\",\n                   \"label\": \"Arc\"\n               },\n               {\n                   \"value\": \"donut\",\n                   \"label\": \"Donut\"\n               },\n               {\n                   \"value\": \"horizontalBar\",\n                   \"label\": \"Horizontal bar\"\n               },\n               {\n                   \"value\": \"verticalBar\",\n                   \"label\": \"Vertical bar\"\n               }\n            ]\n        },\n        \"donutStartAngle\",\n        \"neonGlowBrightness\",\n        \"dashThickness\",\n        \"roundedLineCap\",\n        \"title\",\n        \"showTitle\",\n        \"unitTitle\",\n        \"showUnitTitle\",\n        \"showValue\",\n        \"showMinMax\",\n        \"gaugeWidthScale\",\n        {\n            \"key\": \"defaultColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"gaugeColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"levelColors\",\n            \"items\": [\n                {\n                    \"key\": \"levelColors[]\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"refreshAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"refreshAnimationTime\",\n        {\n            \"key\": \"startAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"startAnimationTime\",\n        \"decimals\",\n        \"units\",\n        {\n            \"key\": \"titleFont\",\n            \"items\": [\n                \"titleFont.family\",\n                \"titleFont.size\",\n                {\n                   \"key\": \"titleFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"titleFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"titleFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"labelFont\",\n            \"items\": [\n                \"labelFont.family\",\n                \"labelFont.size\",\n                {\n                   \"key\": \"labelFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"labelFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"labelFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"valueFont\",\n            \"items\": [\n                \"valueFont.family\",\n                \"valueFont.size\",\n                {\n                   \"key\": \"valueFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"valueFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"valueFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"minMaxFont\",\n            \"items\": [\n                \"minMaxFont.family\",\n                \"minMaxFont.size\",\n                {\n                   \"key\": \"minMaxFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"minMaxFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"minMaxFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        }\n    ]\n}\n    ","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 180) {\\n\\tvalue = 180;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#babab2\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\"linear\",\"refreshAnimationTime\":700,\"startAnimationType\":\"linear\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"RobotoDraft\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"RobotoDraft\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":1.5,\"decimals\":0,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"defaultColor\":\"#444444\",\"gaugeType\":\"arc\"},\"title\":\"LCD gauge\"}"}',
+'LCD gauge' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'digital_gauges', 'digital_bar',
+'{"type":"latest","sizeX":6,"sizeY":2.5,"resources":[],"templateHtml":"","templateCss":"#gauge {\n    text-align: center;\n   /* margin-left: -100px;\n    margin-right: -100px;*/\n    /*margin-top: -50px;*/\n    \n}\n","controllerScript":"var gauge;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n    gauge = new TbDigitalGauge(containerElement, settings, data);    \n\n}\n\n\nfns.redraw = function(containerElement, width, height, data) {\n    gauge.redraw(data);\n};\n\nfns.destroy = function() {\n}\n","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"minValue\": {\n                \"title\": \"Minimum value\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"maxValue\": {\n                \"title\": \"Maximum value\",\n                \"type\": \"number\",\n                \"default\": 100\n            },            \n            \"gaugeType\": {\n                \"title\": \"Gauge type\",\n                \"type\": \"string\",\n                \"default\": \"arc\"\n            },         \n            \"donutStartAngle\": {\n                \"title\": \"Angle to start from when in donut mode\",\n                \"type\": \"number\",\n                \"default\": 90\n            },            \n            \"neonGlowBrightness\": {\n                \"title\": \"Neon glow effect brightness, (0-100), 0 - disable effect\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"dashThickness\": {\n                \"title\": \"Thickness of the stripes, 0 - no stripes\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"roundedLineCap\": {\n                \"title\": \"Display rounded line cap\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"title\": {\n                \"title\": \"Gauge title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showTitle\": {\n                \"title\": \"Show gauge title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"unitTitle\": {\n                \"title\": \"Unit title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showUnitTitle\": {\n                \"title\": \"Show unit title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"showValue\": {\n                \"title\": \"Show value text\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"showMinMax\": {\n                \"title\": \"Show min and max values\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"gaugeWidthScale\": {\n                \"title\": \"Width of the gauge element\",\n                \"type\": \"number\",\n                \"default\": 0.75\n            },\n            \"defaultColor\": {\n                \"title\": \"Default color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"gaugeColor\": {\n                \"title\": \"Background color of the gauge element\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"levelColors\": {\n                \"title\": \"Colors of indicator, from lower to upper\",\n                \"type\": \"array\",\n                \"items\": {\n                  \"title\": \"Color\",\n                  \"type\": \"string\"\n                }\n            },\n            \"refreshAnimationType\": {\n                \"title\": \"Type of refresh animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"refreshAnimationTime\": {\n                \"title\": \"Duration of refresh animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"startAnimationType\": {\n                \"title\": \"Type of start animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"startAnimationTime\": {\n                \"title\": \"Duration of start animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"decimals\": {\n                \"title\": \"Number of digits after floating point\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"units\": {\n                \"title\": \"Special symbol to show next to value\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"titleFont\": {\n                \"title\": \"Gauge title font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 12\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"labelFont\": {\n                \"title\": \"Font of label showing under value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 8\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"valueFont\": {\n                \"title\": \"Font of label showing current value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 18\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"minMaxFont\": {\n                \"title\": \"Font of minimum and maximum labels\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 10\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            }\n        }\n    },\n    \"form\": [\n        \"minValue\",\n        \"maxValue\",\n        {\n           \"key\": \"gaugeType\",\n           \"type\": \"rc-select\",\n           \"multiple\": false,\n           \"items\": [\n               {\n                   \"value\": \"arc\",\n                   \"label\": \"Arc\"\n               },\n               {\n                   \"value\": \"donut\",\n                   \"label\": \"Donut\"\n               },\n               {\n                   \"value\": \"horizontalBar\",\n                   \"label\": \"Horizontal bar\"\n               },\n               {\n                   \"value\": \"verticalBar\",\n                   \"label\": \"Vertical bar\"\n               }\n            ]\n        },\n        \"donutStartAngle\",\n        \"neonGlowBrightness\",\n        \"dashThickness\",\n        \"roundedLineCap\",\n        \"title\",\n        \"showTitle\",\n        \"unitTitle\",\n        \"showUnitTitle\",\n        \"showValue\",\n        \"showMinMax\",\n        \"gaugeWidthScale\",\n        {\n            \"key\": \"defaultColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"gaugeColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"levelColors\",\n            \"items\": [\n                {\n                    \"key\": \"levelColors[]\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"refreshAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"refreshAnimationTime\",\n        {\n            \"key\": \"startAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"startAnimationTime\",\n        \"decimals\",\n        \"units\",\n        {\n            \"key\": \"titleFont\",\n            \"items\": [\n                \"titleFont.family\",\n                \"titleFont.size\",\n                {\n                   \"key\": \"titleFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"titleFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"titleFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"labelFont\",\n            \"items\": [\n                \"labelFont.family\",\n                \"labelFont.size\",\n                {\n                   \"key\": \"labelFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"labelFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"labelFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"valueFont\",\n            \"items\": [\n                \"valueFont.family\",\n                \"valueFont.size\",\n                {\n                   \"key\": \"valueFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"valueFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"valueFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"minMaxFont\",\n            \"items\": [\n                \"minMaxFont.family\",\n                \"minMaxFont.size\",\n                {\n                   \"key\": \"minMaxFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"minMaxFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"minMaxFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        }\n    ]\n}\n    ","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 80) {\\n\\tvalue = 80;\\n} else if (value > 160) {\\n\\tvalue = 160;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#008000\",\"#fbc02d\",\"#f44336\"],\"refreshAnimationType\":\"linear\",\"refreshAnimationTime\":700,\"startAnimationType\":\"linear\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"RobotoDraft\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"RobotoDraft\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#ffffff\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"decimals\":0,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"horizontalBar\",\"showTitle\":false},\"title\":\"Digital horizontal bar\"}"}',
+'Digital horizontal bar' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'digital_gauges', 'simple_gauge_justgage',
+'{"type":"latest","sizeX":2,"sizeY":2,"resources":[],"templateHtml":"","templateCss":"#gauge {\n    text-align: center;\n   /* margin-left: -100px;\n    margin-right: -100px;*/\n    /*margin-top: -50px;*/\n    \n}\n","controllerScript":"var gauge;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n    gauge = new TbDigitalGauge(containerElement, settings, data);    \n\n}\n\n\nfns.redraw = function(containerElement, width, height, data) {\n    gauge.redraw(data);\n};\n\nfns.destroy = function() {\n}\n","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"minValue\": {\n                \"title\": \"Minimum value\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"maxValue\": {\n                \"title\": \"Maximum value\",\n                \"type\": \"number\",\n                \"default\": 100\n            },            \n            \"gaugeType\": {\n                \"title\": \"Gauge type\",\n                \"type\": \"string\",\n                \"default\": \"arc\"\n            },         \n            \"donutStartAngle\": {\n                \"title\": \"Angle to start from when in donut mode\",\n                \"type\": \"number\",\n                \"default\": 90\n            },            \n            \"neonGlowBrightness\": {\n                \"title\": \"Neon glow effect brightness, (0-100), 0 - disable effect\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"dashThickness\": {\n                \"title\": \"Thickness of the stripes, 0 - no stripes\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"roundedLineCap\": {\n                \"title\": \"Display rounded line cap\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"title\": {\n                \"title\": \"Gauge title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showTitle\": {\n                \"title\": \"Show gauge title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"unitTitle\": {\n                \"title\": \"Unit title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showUnitTitle\": {\n                \"title\": \"Show unit title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"showValue\": {\n                \"title\": \"Show value text\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"showMinMax\": {\n                \"title\": \"Show min and max values\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"gaugeWidthScale\": {\n                \"title\": \"Width of the gauge element\",\n                \"type\": \"number\",\n                \"default\": 0.75\n            },\n            \"defaultColor\": {\n                \"title\": \"Default color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"gaugeColor\": {\n                \"title\": \"Background color of the gauge element\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"levelColors\": {\n                \"title\": \"Colors of indicator, from lower to upper\",\n                \"type\": \"array\",\n                \"items\": {\n                  \"title\": \"Color\",\n                  \"type\": \"string\"\n                }\n            },\n            \"refreshAnimationType\": {\n                \"title\": \"Type of refresh animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"refreshAnimationTime\": {\n                \"title\": \"Duration of refresh animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"startAnimationType\": {\n                \"title\": \"Type of start animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"startAnimationTime\": {\n                \"title\": \"Duration of start animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"decimals\": {\n                \"title\": \"Number of digits after floating point\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"units\": {\n                \"title\": \"Special symbol to show next to value\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"titleFont\": {\n                \"title\": \"Gauge title font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 12\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"labelFont\": {\n                \"title\": \"Font of label showing under value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 8\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"valueFont\": {\n                \"title\": \"Font of label showing current value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 18\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"minMaxFont\": {\n                \"title\": \"Font of minimum and maximum labels\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 10\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            }\n        }\n    },\n    \"form\": [\n        \"minValue\",\n        \"maxValue\",\n        {\n           \"key\": \"gaugeType\",\n           \"type\": \"rc-select\",\n           \"multiple\": false,\n           \"items\": [\n               {\n                   \"value\": \"arc\",\n                   \"label\": \"Arc\"\n               },\n               {\n                   \"value\": \"donut\",\n                   \"label\": \"Donut\"\n               },\n               {\n                   \"value\": \"horizontalBar\",\n                   \"label\": \"Horizontal bar\"\n               },\n               {\n                   \"value\": \"verticalBar\",\n                   \"label\": \"Vertical bar\"\n               }\n            ]\n        },\n        \"donutStartAngle\",\n        \"neonGlowBrightness\",\n        \"dashThickness\",\n        \"roundedLineCap\",\n        \"title\",\n        \"showTitle\",\n        \"unitTitle\",\n        \"showUnitTitle\",\n        \"showValue\",\n        \"showMinMax\",\n        \"gaugeWidthScale\",\n        {\n            \"key\": \"defaultColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"gaugeColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"levelColors\",\n            \"items\": [\n                {\n                    \"key\": \"levelColors[]\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"refreshAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"refreshAnimationTime\",\n        {\n            \"key\": \"startAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"startAnimationTime\",\n        \"decimals\",\n        \"units\",\n        {\n            \"key\": \"titleFont\",\n            \"items\": [\n                \"titleFont.family\",\n                \"titleFont.size\",\n                {\n                   \"key\": \"titleFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"titleFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"titleFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"labelFont\",\n            \"items\": [\n                \"labelFont.family\",\n                \"labelFont.size\",\n                {\n                   \"key\": \"labelFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"labelFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"labelFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"valueFont\",\n            \"items\": [\n                \"valueFont.family\",\n                \"valueFont.size\",\n                {\n                   \"key\": \"valueFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"valueFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"valueFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"minMaxFont\",\n            \"items\": [\n                \"minMaxFont.family\",\n                \"minMaxFont.size\",\n                {\n                   \"key\": \"minMaxFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"minMaxFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"minMaxFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        }\n    ]\n}\n    ","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#ef6c00\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"RobotoDraft\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"RobotoDraft\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"RobotoDraft\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":0,\"decimals\":0,\"gaugeColor\":\"#eeeeee\",\"gaugeType\":\"donut\"},\"title\":\"Simple gauge - justGage\"}"}',
+'Simple gauge - justGage' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'charts', 'radar_chart_js',
+'{"type":"latest","sizeX":7,"sizeY":6,"resources":[{"url":"https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.min.js"}],"templateHtml":"<canvas id=\"radarChart\"></canvas>\n","templateCss":"","controllerScript":"var chart;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n\n    var barData = {\n        labels: [],\n        datasets: []\n    };\n\n    var backgroundColor = tinycolor(data[0].dataKey.color);\n    backgroundColor.setAlpha(0.2);\n    var borderColor = tinycolor(data[0].dataKey.color);\n    borderColor.setAlpha(1);\n    var dataset = {\n        label: datasources[0].name,\n        data: [],\n        backgroundColor: backgroundColor.toRgbString(),\n        borderColor: borderColor.toRgbString(),\n        pointBackgroundColor: borderColor.toRgbString(),\n        pointBorderColor: borderColor.darken().toRgbString(),\n        borderWidth: 1\n    }\n    \n    barData.datasets.push(dataset);\n    \n    for (var i in data) {\n        var dataKey = data[i].dataKey;\n        barData.labels.push(dataKey.label);\n        dataset.data.push(0);\n    }\n\n    var ctx = $(''#radarChart'', containerElement);\n    chart = new Chart(ctx, {\n        type: ''radar'',\n        data: barData,\n        options: {\n            maintainAspectRatio: false\n        }\n    });\n    \n}\n\nfns.redraw = function(containerElement, width, height, data,\n    timeWindow, sizeChanged) {\n\n    for (var i = 0; i < data.length; i++) {\n        var cellData = data[i];\n        if (cellData.data.length > 0) {\n             var tvPair = cellData.data[cellData.data.length - 1];\n             var value = tvPair[1];\n             chart.data.datasets[0].data[i] = parseFloat(value);\n        }\n    }\n  \n    chart.update();\n    if (sizeChanged) {\n        chart.resize();\n    }\n    \n};\n","settingsSchema":"{}","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Radar - Chart.js\"}"}',
+'Radar - Chart.js' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'digital_gauges', 'digital_speedometer',
+'{"type":"latest","sizeX":5,"sizeY":3,"resources":[],"templateHtml":"","templateCss":"#gauge {\n    text-align: center;\n   /* margin-left: -100px;\n    margin-right: -100px;*/\n    /*margin-top: -50px;*/\n    \n}\n","controllerScript":"var gauge;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n    gauge = new TbDigitalGauge(containerElement, settings, data);    \n\n}\n\n\nfns.redraw = function(containerElement, width, height, data) {\n    gauge.redraw(data);\n};\n\nfns.destroy = function() {\n}\n","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"minValue\": {\n                \"title\": \"Minimum value\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"maxValue\": {\n                \"title\": \"Maximum value\",\n                \"type\": \"number\",\n                \"default\": 100\n            },            \n            \"gaugeType\": {\n                \"title\": \"Gauge type\",\n                \"type\": \"string\",\n                \"default\": \"arc\"\n            },         \n            \"donutStartAngle\": {\n                \"title\": \"Angle to start from when in donut mode\",\n                \"type\": \"number\",\n                \"default\": 90\n            },            \n            \"neonGlowBrightness\": {\n                \"title\": \"Neon glow effect brightness, (0-100), 0 - disable effect\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"dashThickness\": {\n                \"title\": \"Thickness of the stripes, 0 - no stripes\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"roundedLineCap\": {\n                \"title\": \"Display rounded line cap\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"title\": {\n                \"title\": \"Gauge title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showTitle\": {\n                \"title\": \"Show gauge title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"unitTitle\": {\n                \"title\": \"Unit title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showUnitTitle\": {\n                \"title\": \"Show unit title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"showValue\": {\n                \"title\": \"Show value text\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"showMinMax\": {\n                \"title\": \"Show min and max values\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"gaugeWidthScale\": {\n                \"title\": \"Width of the gauge element\",\n                \"type\": \"number\",\n                \"default\": 0.75\n            },\n            \"defaultColor\": {\n                \"title\": \"Default color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"gaugeColor\": {\n                \"title\": \"Background color of the gauge element\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"levelColors\": {\n                \"title\": \"Colors of indicator, from lower to upper\",\n                \"type\": \"array\",\n                \"items\": {\n                  \"title\": \"Color\",\n                  \"type\": \"string\"\n                }\n            },\n            \"refreshAnimationType\": {\n                \"title\": \"Type of refresh animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"refreshAnimationTime\": {\n                \"title\": \"Duration of refresh animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"startAnimationType\": {\n                \"title\": \"Type of start animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"startAnimationTime\": {\n                \"title\": \"Duration of start animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"decimals\": {\n                \"title\": \"Number of digits after floating point\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"units\": {\n                \"title\": \"Special symbol to show next to value\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"titleFont\": {\n                \"title\": \"Gauge title font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 12\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"labelFont\": {\n                \"title\": \"Font of label showing under value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 8\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"valueFont\": {\n                \"title\": \"Font of label showing current value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 18\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"minMaxFont\": {\n                \"title\": \"Font of minimum and maximum labels\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 10\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            }\n        }\n    },\n    \"form\": [\n        \"minValue\",\n        \"maxValue\",\n        {\n           \"key\": \"gaugeType\",\n           \"type\": \"rc-select\",\n           \"multiple\": false,\n           \"items\": [\n               {\n                   \"value\": \"arc\",\n                   \"label\": \"Arc\"\n               },\n               {\n                   \"value\": \"donut\",\n                   \"label\": \"Donut\"\n               },\n               {\n                   \"value\": \"horizontalBar\",\n                   \"label\": \"Horizontal bar\"\n               },\n               {\n                   \"value\": \"verticalBar\",\n                   \"label\": \"Vertical bar\"\n               }\n            ]\n        },\n        \"donutStartAngle\",\n        \"neonGlowBrightness\",\n        \"dashThickness\",\n        \"roundedLineCap\",\n        \"title\",\n        \"showTitle\",\n        \"unitTitle\",\n        \"showUnitTitle\",\n        \"showValue\",\n        \"showMinMax\",\n        \"gaugeWidthScale\",\n        {\n            \"key\": \"defaultColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"gaugeColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"levelColors\",\n            \"items\": [\n                {\n                    \"key\": \"levelColors[]\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"refreshAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"refreshAnimationTime\",\n        {\n            \"key\": \"startAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"startAnimationTime\",\n        \"decimals\",\n        \"units\",\n        {\n            \"key\": \"titleFont\",\n            \"items\": [\n                \"titleFont.family\",\n                \"titleFont.size\",\n                {\n                   \"key\": \"titleFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"titleFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"titleFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"labelFont\",\n            \"items\": [\n                \"labelFont.family\",\n                \"labelFont.size\",\n                {\n                   \"key\": \"labelFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"labelFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"labelFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"valueFont\",\n            \"items\": [\n                \"valueFont.family\",\n                \"valueFont.size\",\n                {\n                   \"key\": \"valueFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"valueFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"valueFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"minMaxFont\",\n            \"items\": [\n                \"minMaxFont.family\",\n                \"minMaxFont.size\",\n                {\n                   \"key\": \"minMaxFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"minMaxFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"minMaxFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        }\n    ]\n}\n    ","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 45) {\\n\\tvalue = 45;\\n} else if (value > 130) {\\n\\tvalue = 130;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#008000\",\"#fbc02d\",\"#f44336\"],\"refreshAnimationType\":\"linear\",\"refreshAnimationTime\":700,\"startAnimationType\":\"linear\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"RobotoDraft\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"RobotoDraft\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#ffffff\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"decimals\":0,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"arc\"},\"title\":\"Digital speedometer\"}"}',
+'Digital speedometer' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'charts', 'timeseries',
+'{"type":"timeseries","sizeX":8,"sizeY":6,"resources":[{"url":"https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.min.js"}],"templateHtml":"<canvas id=\"lineChart\"></canvas>\n","templateCss":"","controllerScript":"var chart;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n\n      var lineData = {\n        labels: [],\n        datasets: []\n    };\n    \n    for (var i in data) {\n        var dataKey = data[i].dataKey;\n        var keySettings = dataKey.settings;\n        var backgroundColor = tinycolor(dataKey.color);\n        backgroundColor.setAlpha(0.4);\n        var dataset = {\n            label: dataKey.label,\n            data: [],\n            borderColor: dataKey.color,\n            borderWidth: 2,\n            backgroundColor: backgroundColor.toRgbString(),\n            pointRadius: keySettings.showPoints ? 1 : 0,\n            fill: keySettings.fillLines || false,\n            showLine: keySettings.showLines || true,\n            spanGaps: false\n        }\n        lineData.datasets.push(dataset);\n    }\n\n    var ctx = $(''#lineChart'', containerElement);\n    chart = new Chart(ctx, {\n        type: ''line'',\n        data: lineData,\n        options: {\n            maintainAspectRatio: false,\n            /*animation: {\n              duration: 200,\n              easing: ''linear''\n            },*/\n            elements: {\n              line: {\n                  tension: 0.2\n              }  \n            },\n            scales: {\n                xAxes: [{\n                    type: ''time'',\n                    ticks: {\n                        maxRotation: 20,\n                        autoSkip: true\n                    },\n                    time: {\n                        displayFormats: {\n                            second: ''hh:mm:ss'',\n                            minute: ''hh:mm:ss''\n                        }\n                    }\n                }]\n            }\n        }\n    });\n\n}\n\nfns.redraw = function(containerElement, width, height, data,\n    timeWindow, sizeChanged) {\n\n    for (var i = 0; i < data.length; i++) {\n        var dataSetData = [];\n        var dataKeyData = data[i].data;\n        for (var i2 = 0; i2 < dataKeyData.length; i2 ++) {\n            dataSetData.push({x: moment(dataKeyData[i2][0]), y: dataKeyData[i2][1]});\n            \n        }\n        chart.data.datasets[i].data = dataSetData; \n    }\n\n    chart.options.scales.xAxes[0].time.min = moment(timeWindow.minTime);\n    chart.options.scales.xAxes[0].time.max = moment(timeWindow.maxTime);\n\n    if (sizeChanged) {\n        chart.resize();\n    }\n    \n    chart.update(0, true);\n\n};\n\nfns.destroy = function() {\n};","settingsSchema":"{}","dataKeySettingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"DataKeySettings\",\n        \"properties\": {\n            \"showLines\": {\n                \"title\": \"Show lines\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"fillLines\": {\n                \"title\": \"Fill lines\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },\n            \"showPoints\": {\n                \"title\": \"Show points\",\n                \"type\": \"boolean\",\n                \"default\": false\n            }\n        },\n        \"required\": [\"showLines\", \"fillLines\", \"showPoints\"]\n    },\n    \"form\": [\n        \"showLines\",\n        \"fillLines\",\n        \"showPoints\"\n    ]\n}","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.5644745944820795,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.18379294198604845,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Timeseries - Chart.js\"}"}',
+'Timeseries - Chart.js' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'charts', 'bars',
+'{"type":"latest","sizeX":7,"sizeY":6,"resources":[{"url":"https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.min.js"}],"templateHtml":"<canvas id=\"barChart\"></canvas>\n","templateCss":"","controllerScript":"var chart;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n        \n    var barData = {\n        labels: [],\n        datasets: []\n    };\n    \n    for (var i in datasources) {\n        var datasource = datasources[i];\n        for (i in datasource.dataKeys) {\n            var dataset = {\n                label: datasource.dataKeys[i].label,\n                data: [0],\n                backgroundColor: [datasource.dataKeys[i].color],\n                borderColor: [datasource.dataKeys[i].color],\n                borderWidth: 1\n            }\n            barData.datasets.push(dataset);\n        }\n    }\n\n    var ctx = $(''#barChart'', containerElement);\n    chart = new Chart(ctx, {\n        type: ''bar'',\n        data: barData,\n        options: {\n            maintainAspectRatio: false,\n            scales: {\n                yAxes: [{\n                    ticks: {\n                        beginAtZero:true\n                    }\n                }]\n            }\n        }\n    });\n    \n}\n\nfns.redraw = function(containerElement, width, height, data,\n    timeWindow, sizeChanged) {\n    var c = 0;\n    for (var i = 0; i < chart.data.datasets.length; i++) {\n        var dataset = chart.data.datasets[i];\n        var cellData = data[i]; \n        if (cellData.data.length > 0) {\n             var tvPair = cellData.data[cellData.data.length - 1];\n             var value = tvPair[1];\n             dataset.data[0] = parseFloat(value);\n        }\n    }\n    chart.update();\n    if (sizeChanged) {\n        chart.resize();\n    }\n    \n};\n","settingsSchema":"{}","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Bars - Chart.js\"}"}',
+'Bars - Chart.js' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'charts', 'polar_area_chart_js',
+'{"type":"latest","sizeX":7,"sizeY":6,"resources":[{"url":"https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.min.js"}],"templateHtml":"<canvas id=\"pieChart\"></canvas>\n","templateCss":"","controllerScript":"var chart;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n\n    var pieData = {\n        labels: [],\n        datasets: []\n    };\n\n    var dataset = {\n        data: [],\n        backgroundColor: [],\n        borderColor: [],\n        borderWidth: [],\n        hoverBackgroundColor: []\n    }\n    \n    pieData.datasets.push(dataset);\n    \n    for (var i in data) {\n        var dataKey = data[i].dataKey;\n        pieData.labels.push(dataKey.label);\n        dataset.data.push(0);\n        var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n        var borderColor = tinycolor(dataKey.color).darken();\n        dataset.backgroundColor.push(dataKey.color);\n        dataset.borderColor.push(''#fff'');\n        dataset.borderWidth.push(5);\n        dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n    }\n\n    var ctx = $(''#pieChart'', containerElement);\n    chart = new Chart(ctx, {\n        type: ''polarArea'',\n        data: pieData,\n        options: {\n            maintainAspectRatio: false\n        }\n    });\n    \n}\n\nfns.redraw = function(containerElement, width, height, data,\n    timeWindow, sizeChanged) {\n\n    for (var i = 0; i < data.length; i++) {\n        var cellData = data[i];\n        if (cellData.data.length > 0) {\n             var tvPair = cellData.data[cellData.data.length - 1];\n             var value = tvPair[1];\n             chart.data.datasets[0].data[i] = parseFloat(value);\n        }\n    }\n  \n    chart.update();\n    if (sizeChanged) {\n        chart.resize();\n    }\n    \n};\n","settingsSchema":"{}","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fifth\",\"color\":\"#607d8b\",\"settings\":{},\"_hash\":0.2074391823443591,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Polar Area - Chart.js\"}"}',
+'Polar Area - Chart.js' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'digital_gauges', 'neon_gauge_justgage',
+'{"type":"latest","sizeX":5,"sizeY":3,"resources":[],"templateHtml":"","templateCss":"#gauge {\n    text-align: center;\n   /* margin-left: -100px;\n    margin-right: -100px;*/\n    /*margin-top: -50px;*/\n    \n}\n","controllerScript":"var gauge;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n    gauge = new TbDigitalGauge(containerElement, settings, data);    \n\n}\n\n\nfns.redraw = function(containerElement, width, height, data) {\n    gauge.redraw(data);\n};\n\nfns.destroy = function() {\n}\n","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"minValue\": {\n                \"title\": \"Minimum value\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"maxValue\": {\n                \"title\": \"Maximum value\",\n                \"type\": \"number\",\n                \"default\": 100\n            },            \n            \"gaugeType\": {\n                \"title\": \"Gauge type\",\n                \"type\": \"string\",\n                \"default\": \"arc\"\n            },         \n            \"donutStartAngle\": {\n                \"title\": \"Angle to start from when in donut mode\",\n                \"type\": \"number\",\n                \"default\": 90\n            },            \n            \"neonGlowBrightness\": {\n                \"title\": \"Neon glow effect brightness, (0-100), 0 - disable effect\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"dashThickness\": {\n                \"title\": \"Thickness of the stripes, 0 - no stripes\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"roundedLineCap\": {\n                \"title\": \"Display rounded line cap\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"title\": {\n                \"title\": \"Gauge title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showTitle\": {\n                \"title\": \"Show gauge title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"unitTitle\": {\n                \"title\": \"Unit title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showUnitTitle\": {\n                \"title\": \"Show unit title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"showValue\": {\n                \"title\": \"Show value text\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"showMinMax\": {\n                \"title\": \"Show min and max values\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"gaugeWidthScale\": {\n                \"title\": \"Width of the gauge element\",\n                \"type\": \"number\",\n                \"default\": 0.75\n            },\n            \"defaultColor\": {\n                \"title\": \"Default color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"gaugeColor\": {\n                \"title\": \"Background color of the gauge element\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"levelColors\": {\n                \"title\": \"Colors of indicator, from lower to upper\",\n                \"type\": \"array\",\n                \"items\": {\n                  \"title\": \"Color\",\n                  \"type\": \"string\"\n                }\n            },\n            \"refreshAnimationType\": {\n                \"title\": \"Type of refresh animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"refreshAnimationTime\": {\n                \"title\": \"Duration of refresh animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"startAnimationType\": {\n                \"title\": \"Type of start animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"startAnimationTime\": {\n                \"title\": \"Duration of start animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"decimals\": {\n                \"title\": \"Number of digits after floating point\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"units\": {\n                \"title\": \"Special symbol to show next to value\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"titleFont\": {\n                \"title\": \"Gauge title font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 12\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"labelFont\": {\n                \"title\": \"Font of label showing under value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 8\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"valueFont\": {\n                \"title\": \"Font of label showing current value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 18\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"minMaxFont\": {\n                \"title\": \"Font of minimum and maximum labels\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 10\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            }\n        }\n    },\n    \"form\": [\n        \"minValue\",\n        \"maxValue\",\n        {\n           \"key\": \"gaugeType\",\n           \"type\": \"rc-select\",\n           \"multiple\": false,\n           \"items\": [\n               {\n                   \"value\": \"arc\",\n                   \"label\": \"Arc\"\n               },\n               {\n                   \"value\": \"donut\",\n                   \"label\": \"Donut\"\n               },\n               {\n                   \"value\": \"horizontalBar\",\n                   \"label\": \"Horizontal bar\"\n               },\n               {\n                   \"value\": \"verticalBar\",\n                   \"label\": \"Vertical bar\"\n               }\n            ]\n        },\n        \"donutStartAngle\",\n        \"neonGlowBrightness\",\n        \"dashThickness\",\n        \"roundedLineCap\",\n        \"title\",\n        \"showTitle\",\n        \"unitTitle\",\n        \"showUnitTitle\",\n        \"showValue\",\n        \"showMinMax\",\n        \"gaugeWidthScale\",\n        {\n            \"key\": \"defaultColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"gaugeColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"levelColors\",\n            \"items\": [\n                {\n                    \"key\": \"levelColors[]\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"refreshAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"refreshAnimationTime\",\n        {\n            \"key\": \"startAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"startAnimationTime\",\n        \"decimals\",\n        \"units\",\n        {\n            \"key\": \"titleFont\",\n            \"items\": [\n                \"titleFont.family\",\n                \"titleFont.size\",\n                {\n                   \"key\": \"titleFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"titleFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"titleFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"labelFont\",\n            \"items\": [\n                \"labelFont.family\",\n                \"labelFont.size\",\n                {\n                   \"key\": \"labelFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"labelFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"labelFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"valueFont\",\n            \"items\": [\n                \"valueFont.family\",\n                \"valueFont.size\",\n                {\n                   \"key\": \"valueFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"valueFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"valueFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"minMaxFont\",\n            \"items\": [\n                \"minMaxFont.family\",\n                \"minMaxFont.size\",\n                {\n                   \"key\": \"minMaxFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"minMaxFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"minMaxFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        }\n    ]\n}\n    ","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"RobotoDraft\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"RobotoDraft\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":70,\"dashThickness\":1,\"decimals\":1,\"gaugeType\":\"arc\"},\"title\":\"Neon gauge - justGage\"}"}',
+'Neon gauge - justGage' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'charts', 'doughnut_chart_js',
+'{"type":"latest","sizeX":7,"sizeY":6,"resources":[{"url":"https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.min.js"}],"templateHtml":"<canvas id=\"pieChart\"></canvas>\n","templateCss":"","controllerScript":"var chart;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n\n    var pieData = {\n        labels: [],\n        datasets: []\n    };\n\n    var dataset = {\n        data: [],\n        backgroundColor: [],\n        borderColor: [],\n        borderWidth: [],\n        hoverBackgroundColor: []\n    }\n    \n    pieData.datasets.push(dataset);\n    \n    for (var i in data) {\n        var dataKey = data[i].dataKey;\n        pieData.labels.push(dataKey.label);\n        dataset.data.push(0);\n        var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n        var borderColor = tinycolor(dataKey.color).darken();\n        dataset.backgroundColor.push(dataKey.color);\n        dataset.borderColor.push(''#fff'');\n        dataset.borderWidth.push(5);\n        dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n    }\n\n    var ctx = $(''#pieChart'', containerElement);\n    chart = new Chart(ctx, {\n        type: ''doughnut'',\n        data: pieData,\n        options: {\n            maintainAspectRatio: false\n        }\n    });\n    \n}\n\nfns.redraw = function(containerElement, width, height, data,\n    timeWindow, sizeChanged) {\n\n    for (var i = 0; i < data.length; i++) {\n        var cellData = data[i];\n        if (cellData.data.length > 0) {\n             var tvPair = cellData.data[cellData.data.length - 1];\n             var value = tvPair[1];\n             chart.data.datasets[0].data[i] = parseFloat(value);\n        }\n    }\n  \n    chart.update();\n    if (sizeChanged) {\n        chart.resize();\n    }\n    \n};\n","settingsSchema":"{}","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#26a69a\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#f57c00\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#afb42b\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#673ab7\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Doughnut - Chart.js\"}"}',
+'Doughnut - Chart.js' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'digital_gauges', 'digital_vertical_bar',
+'{"type":"latest","sizeX":2.5,"sizeY":4.5,"resources":[],"templateHtml":"","templateCss":"#gauge {\n    text-align: center;\n   /* margin-left: -100px;\n    margin-right: -100px;*/\n    /*margin-top: -50px;*/\n    \n}\n","controllerScript":"var gauge;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n    gauge = new TbDigitalGauge(containerElement, settings, data);    \n\n}\n\n\nfns.redraw = function(containerElement, width, height, data) {\n    gauge.redraw(data);\n};\n\nfns.destroy = function() {\n}\n","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"minValue\": {\n                \"title\": \"Minimum value\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"maxValue\": {\n                \"title\": \"Maximum value\",\n                \"type\": \"number\",\n                \"default\": 100\n            },            \n            \"gaugeType\": {\n                \"title\": \"Gauge type\",\n                \"type\": \"string\",\n                \"default\": \"arc\"\n            },         \n            \"donutStartAngle\": {\n                \"title\": \"Angle to start from when in donut mode\",\n                \"type\": \"number\",\n                \"default\": 90\n            },            \n            \"neonGlowBrightness\": {\n                \"title\": \"Neon glow effect brightness, (0-100), 0 - disable effect\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"dashThickness\": {\n                \"title\": \"Thickness of the stripes, 0 - no stripes\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"roundedLineCap\": {\n                \"title\": \"Display rounded line cap\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"title\": {\n                \"title\": \"Gauge title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showTitle\": {\n                \"title\": \"Show gauge title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"unitTitle\": {\n                \"title\": \"Unit title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showUnitTitle\": {\n                \"title\": \"Show unit title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"showValue\": {\n                \"title\": \"Show value text\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"showMinMax\": {\n                \"title\": \"Show min and max values\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"gaugeWidthScale\": {\n                \"title\": \"Width of the gauge element\",\n                \"type\": \"number\",\n                \"default\": 0.75\n            },\n            \"defaultColor\": {\n                \"title\": \"Default color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"gaugeColor\": {\n                \"title\": \"Background color of the gauge element\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"levelColors\": {\n                \"title\": \"Colors of indicator, from lower to upper\",\n                \"type\": \"array\",\n                \"items\": {\n                  \"title\": \"Color\",\n                  \"type\": \"string\"\n                }\n            },\n            \"refreshAnimationType\": {\n                \"title\": \"Type of refresh animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"refreshAnimationTime\": {\n                \"title\": \"Duration of refresh animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"startAnimationType\": {\n                \"title\": \"Type of start animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"startAnimationTime\": {\n                \"title\": \"Duration of start animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"decimals\": {\n                \"title\": \"Number of digits after floating point\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"units\": {\n                \"title\": \"Special symbol to show next to value\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"titleFont\": {\n                \"title\": \"Gauge title font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 12\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"labelFont\": {\n                \"title\": \"Font of label showing under value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 8\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"valueFont\": {\n                \"title\": \"Font of label showing current value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 18\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"minMaxFont\": {\n                \"title\": \"Font of minimum and maximum labels\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 10\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            }\n        }\n    },\n    \"form\": [\n        \"minValue\",\n        \"maxValue\",\n        {\n           \"key\": \"gaugeType\",\n           \"type\": \"rc-select\",\n           \"multiple\": false,\n           \"items\": [\n               {\n                   \"value\": \"arc\",\n                   \"label\": \"Arc\"\n               },\n               {\n                   \"value\": \"donut\",\n                   \"label\": \"Donut\"\n               },\n               {\n                   \"value\": \"horizontalBar\",\n                   \"label\": \"Horizontal bar\"\n               },\n               {\n                   \"value\": \"verticalBar\",\n                   \"label\": \"Vertical bar\"\n               }\n            ]\n        },\n        \"donutStartAngle\",\n        \"neonGlowBrightness\",\n        \"dashThickness\",\n        \"roundedLineCap\",\n        \"title\",\n        \"showTitle\",\n        \"unitTitle\",\n        \"showUnitTitle\",\n        \"showValue\",\n        \"showMinMax\",\n        \"gaugeWidthScale\",\n        {\n            \"key\": \"defaultColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"gaugeColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"levelColors\",\n            \"items\": [\n                {\n                    \"key\": \"levelColors[]\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"refreshAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"refreshAnimationTime\",\n        {\n            \"key\": \"startAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"startAnimationTime\",\n        \"decimals\",\n        \"units\",\n        {\n            \"key\": \"titleFont\",\n            \"items\": [\n                \"titleFont.family\",\n                \"titleFont.size\",\n                {\n                   \"key\": \"titleFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"titleFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"titleFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"labelFont\",\n            \"items\": [\n                \"labelFont.family\",\n                \"labelFont.size\",\n                {\n                   \"key\": \"labelFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"labelFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"labelFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"valueFont\",\n            \"items\": [\n                \"valueFont.family\",\n                \"valueFont.size\",\n                {\n                   \"key\": \"valueFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"valueFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"valueFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"minMaxFont\",\n            \"items\": [\n                \"minMaxFont.family\",\n                \"minMaxFont.size\",\n                {\n                   \"key\": \"minMaxFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"minMaxFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"minMaxFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        }\n    ]\n}\n    ","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":60,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#3d5afe\",\"#f44336\"],\"refreshAnimationType\":\"<>\",\"refreshAnimationTime\":700,\"startAnimationType\":\"<>\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"RobotoDraft\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"RobotoDraft\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":14},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":8,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#cccccc\"},\"neonGlowBrightness\":20,\"decimals\":0,\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"verticalBar\",\"showTitle\":false,\"units\":\"°C\",\"minValue\":-60,\"dashThickness\":1.2},\"title\":\"Digital vertical bar\"}"}',
+'Digital vertical bar' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'maps', 'openstreetmap',
+'{"type":"latest","sizeX":8.5,"sizeY":6,"resources":[{"url":"https://unpkg.com/leaflet@1.0.1/dist/leaflet.css"},{"url":"https://unpkg.com/leaflet@1.0.1/dist/leaflet.js"}],"templateHtml":"","templateCss":".tb-marker-label {\n    border: none;\n    background: none;\n    box-shadow: none;\n}\n\n.tb-marker-label:before {\n    border: none;\n    background: none;\n}\n","controllerScript":"var map;\nvar positions;\nvar markers = [];\nvar markersSettings = [];\nvar defaultZoomLevel;\nvar dontFitMapBounds;\n\nvar markerCluster;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n    \n    if (settings.defaultZoomLevel) {\n        if (settings.defaultZoomLevel > 0 && settings.defaultZoomLevel < 21) {\n            defaultZoomLevel = Math.floor(settings.defaultZoomLevel);\n        }\n    }\n    \n    dontFitMapBounds = settings.fitMapBounds === false;\n    \n    var configuredMarkersSettings = settings.markersSettings;\n    if (!configuredMarkersSettings) {\n        configuredMarkersSettings = [];\n    }\n    \n    for (var i=0;i<datasources.length;i++) {\n        markersSettings[i] = {\n            latKeyName: \"lat\",\n            lngKeyName: \"lng\",\n            showLabel: true,\n            label: datasources[i].name,\n            color: \"FE7569\"\n        };\n        if (configuredMarkersSettings[i]) {\n            markersSettings[i].latKeyName = configuredMarkersSettings[i].latKeyName || markersSettings[i].latKeyName;\n            markersSettings[i].lngKeyName = configuredMarkersSettings[i].lngKeyName || markersSettings[i].lngKeyName;\n            markersSettings[i].showLabel = configuredMarkersSettings[i].showLabel !== false;\n            markersSettings[i].label = configuredMarkersSettings[i].label || markersSettings[i].label;\n            markersSettings[i].color = configuredMarkersSettings[i].color ? tinycolor(configuredMarkersSettings[i].color).toHex() : markersSettings[i].color;\n        }\n    }\n    \n    map = L.map(containerElement).setView([0, 0], defaultZoomLevel || 8);\n\n    L.tileLayer(''http://{s}.tile.osm.org/{z}/{x}/{y}.png'', {\n        attribution: ''&copy; <a href=\"http://osm.org/copyright\">OpenStreetMap</a> contributors''\n    }).addTo(map);\n\n\n}\n\n\nfns.redraw = function(containerElement, width, height, data,\n    timeWindow, sizeChanged) {\n    \n    function createMarker(location, settings) {\n        var pinColor = settings.color;\n\n        var icon = L.icon({\n            iconUrl: ''http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|'' + pinColor,\n            iconSize: [21, 34],\n            iconAnchor: [10, 34],\n            popupAnchor: [0, -34],\n            shadowUrl: ''http://chart.apis.google.com/chart?chst=d_map_pin_shadow'',\n            shadowSize: [40, 37],\n            shadowAnchor: [12, 35]\n        });\n        \n        var marker = L.marker(location, {icon: icon}).addTo(map);\n        marker.bindPopup(''<b>'' + settings.label + ''</b>'');\n        if (settings.showLabel) {\n            marker.bindTooltip(''<b>'' + settings.label + ''</b>'', { className: ''tb-marker-label'', permanent: true, direction: ''top'', offset: [0, -24] });\n        }\n        return marker;\n    }\n    \n    function updatePosition(position, data) {\n        if (position.latIndex > -1 && position.lngIndex > -1) {\n            var latData = data[position.latIndex].data;\n            var lngData = data[position.lngIndex].data;\n            if (latData.length > 0 && lngData.length > 0) {\n                var lat = latData[latData.length-1][1];\n                var lng = lngData[lngData.length-1][1];\n                var location = L.latLng(lat, lng);\n                if (!position.marker) {\n                    position.marker = createMarker(location, position.settings);\n                    markers.push(position.marker);\n                    return true;\n                } else {\n                    var prevPosition = position.marker.getLatLng();\n                    if (!prevPosition.equals(location)) {\n                        position.marker.setLatLng(location);\n                        return true;\n                    }\n                }\n            }\n        }\n        return false;\n    }\n    \n    function loadPositions(data) {\n        var bounds = L.latLngBounds();\n        positions = [];\n        var datasourceIndex = -1;\n        var markerSettings;\n        var datasource;\n        for (var i = 0; i < data.length; i++) {\n            var datasourceData = data[i];\n            if (!datasource || datasource != datasourceData.datasource) {\n                datasourceIndex++;\n                datasource = datasourceData.datasource;\n                markerSettings = markersSettings[datasourceIndex];\n            }\n            var dataKey = datasourceData.dataKey;\n            if (dataKey.label === markerSettings.latKeyName ||\n                dataKey.label === markerSettings.lngKeyName) {\n                var position = positions[datasourceIndex];\n                if (!position) {\n                    position = {\n                        latIndex: -1,\n                        lngIndex: -1,\n                        settings: markerSettings\n                    };\n                    positions[datasourceIndex] = position;\n                } else if (position.marker) {\n                    continue;\n                }\n                if (dataKey.label === markerSettings.latKeyName) {\n                    position.latIndex = i;\n                } else {\n                    position.lngIndex = i;\n                }\n                if (position.latIndex > -1 && position.lngIndex > -1) {\n                    updatePosition(position, data);\n                    if (position.marker) {\n                        bounds.extend(position.marker.getLatLng());\n                    }\n                }\n            }\n        }\n        fitMapBounds(bounds);\n    }\n    \n    function updatePositions(data) {\n        var positionsChanged = false;\n        var bounds = L.latLngBounds();\n        for (var p in positions) {\n            var position = positions[p];\n            positionsChanged |= updatePosition(position, data);\n            if (position.marker) {\n                bounds.extend(position.marker.getLatLng());\n            }\n        }\n        if (!dontFitMapBounds && positionsChanged) {\n            fitMapBounds(bounds);\n        }\n    }\n    \n    function fitMapBounds(bounds) {\n        map.once(''zoomend'', function(event) {\n            var zoomLevel = defaultZoomLevel || map.getZoom();\n            map.setZoom(zoomLevel, {animate: false});\n            if (!defaultZoomLevel && this.getZoom() > 15) {\n                map.setZoom(15, {animate: false});\n            }\n        });\n        map.fitBounds(bounds, {padding: [50, 50], animate: false});\n    }\n    \n    if (map) {\n        if (data) {\n            if (!positions) {\n                loadPositions(data);\n            } else {\n                updatePositions(data);\n            }\n        }\n        if (sizeChanged) {\n            map.invalidateSize(true);\n            var bounds = L.latLngBounds();\n            for (var m in markers) {\n                bounds.extend(markers[m].getLatLng());\n            }\n            fitMapBounds(bounds);\n        }\n    }\n\n};","settingsSchema":"{\n  \"schema\": {\n    \"title\": \"Google Map Configuration\",\n    \"type\": \"object\",\n    \"properties\": {\n      \"defaultZoomLevel\": {\n         \"title\": \"Default map zoom level (1 - 20)\",\n         \"type\": \"number\"\n      },\n      \"fitMapBounds\": {\n          \"title\": \"Fit map bounds to cover all markers\",\n          \"type\": \"boolean\",\n          \"default\": true\n      },\n      \"markersSettings\": {\n            \"title\": \"Markers settings, same order as datasources\",\n            \"type\": \"array\",\n            \"items\": {\n              \"title\": \"Marker settings\",\n              \"type\": \"object\",\n              \"properties\": {\n                  \"latKeyName\": {\n                    \"title\": \"Latitude key name\",\n                    \"type\": \"string\",\n                    \"default\": \"lat\"\n                  },\n                  \"lngKeyName\": {\n                    \"title\": \"Longitude key name\",\n                    \"type\": \"string\",\n                    \"default\": \"lng\"\n                  },                  \n                  \"showLabel\": {\n                    \"title\": \"Show label\",\n                    \"type\": \"boolean\",\n                    \"default\": true\n                  },                  \n                  \"label\": {\n                    \"title\": \"Label\",\n                    \"type\": \"string\"\n                  },\n                  \"color\": {\n                    \"title\": \"Color\",\n                    \"type\": \"string\"\n                  }\n              }\n            }\n      }\n    },\n    \"required\": [\n    ]\n  },\n  \"form\": [\n    \"defaultZoomLevel\",\n    \"fitMapBounds\",\n    {\n        \"key\": \"markersSettings\",\n        \"items\": [\n            \"markersSettings[].latKeyName\",\n            \"markersSettings[].lngKeyName\",\n            \"markersSettings[].showLabel\",\n            \"markersSettings[].label\",\n            {\n                \"key\": \"markersSettings[].color\",\n                \"type\": \"color\"\n            }\n        ]\n    }\n  ]\n}","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.05427416942713381,\"funcBody\":\"var value = prevValue || 15.833293;\\nif (time % 5000 < 500) {\\n    value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.680594833308841,\"funcBody\":\"var value = prevValue || -90.454350;\\nif (time % 5000 < 500) {\\n    value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"}]},{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"lat\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.05012157428742059,\"funcBody\":\"var value = prevValue || 14.450463;\\nif (time % 4000 < 500) {\\n    value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"lng\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.6742359401617628,\"funcBody\":\"var value = prevValue || -84.845334;\\nif (time % 4000 < 500) {\\n    value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"markersSettings\":[{\"label\":\"First point\",\"color\":\"#1e88e5\",\"latKeyName\":\"latitude\",\"lngKeyName\":\"longitude\",\"showLabel\":true},{\"label\":\"Second point\",\"color\":\"#fdd835\",\"latKeyName\":\"lat\",\"lngKeyName\":\"lng\",\"showLabel\":true}],\"fitMapBounds\":true},\"title\":\"OpenStreetMap\"}"}',
+'OpenStreetMap' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'analogue_gauges', 'temperature_radial_gauge_canvas_gauges',
+'{"type":"latest","sizeX":6,"sizeY":5,"resources":[],"templateHtml":"<canvas id=\"radialGauge\"></canvas>\n","templateCss":"","controllerScript":"var gauge;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n    gauge = new TbAnalogueRadialGauge(containerElement, settings, data, ''radialGauge'');    \n\n}\n\n\nfns.redraw = function(containerElement, width, height, data, timeWindow, sizeChanged) {\n    gauge.redraw(width, height, data, sizeChanged);\n};\n\nfns.destroy = function() {\n}\n","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"minValue\": {\n                \"title\": \"Minimum value\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"maxValue\": {\n                \"title\": \"Maximum value\",\n                \"type\": \"number\",\n                \"default\": 100\n            },\n            \"unitTitle\": {\n                \"title\": \"Unit title\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"showUnitTitle\": {\n                \"title\": \"Show unit title\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"units\": {\n                \"title\": \"Units\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"majorTicksCount\": {\n                \"title\": \"Major ticks count\",\n                \"type\": \"number\",\n                \"default\": null\n            },\n            \"minorTicks\": {\n                \"title\": \"Minor ticks count\",\n                \"type\": \"number\",\n                \"default\": 2\n            },\n            \"valueBox\": {\n                \"title\": \"Show value box\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"valueInt\": {\n                \"title\": \"Digits count for integer part of value\",\n                \"type\": \"number\",\n                \"default\": 3\n            },\n            \"valueDec\": {\n                \"title\": \"Digits count for decimal part of value\",\n                \"type\": \"number\",\n                \"default\": 2\n            },\n            \"defaultColor\": {\n                \"title\": \"Default color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"colorPlate\": {\n                \"title\": \"Plate color\",\n                \"type\": \"string\",\n                \"default\": \"#fff\"\n            },\n            \"colorMajorTicks\": {\n                \"title\": \"Major ticks color\",\n                \"type\": \"string\",\n                \"default\": \"#444\"\n            },\n            \"colorMinorTicks\": {\n                \"title\": \"Minor ticks color\",\n                \"type\": \"string\",\n                \"default\": \"#666\"\n            },\n            \"colorNeedle\": {\n                \"title\": \"Needle color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"colorNeedleEnd\": {\n                \"title\": \"Needle color - end gradient\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"colorNeedleShadowUp\": {\n                \"title\": \"Upper half of the needle shadow color\",\n                \"type\": \"string\",\n                \"default\": \"rgba(2,255,255,0.2)\"\n            },\n            \"colorNeedleShadowDown\": {\n                \"title\": \"Drop shadow needle color.\",\n                \"type\": \"string\",\n                \"default\": \"rgba(188,143,143,0.45)\"\n            },\n            \"colorValueBoxRect\": {\n                \"title\": \"Value box rectangle stroke color\",\n                \"type\": \"string\",\n                \"default\": \"#888\"\n            },\n            \"colorValueBoxRectEnd\": {\n                \"title\": \"Value box rectangle stroke color - end gradient\",\n                \"type\": \"string\",\n                \"default\": \"#666\"\n            },\n            \"colorValueBoxBackground\": {\n                \"title\": \"Value box background color\",\n                \"type\": \"string\",\n                \"default\": \"#babab2\"\n            },\n            \"colorValueBoxShadow\": {\n                \"title\": \"Value box shadow color\",\n                \"type\": \"string\",\n                \"default\": \"rgba(0,0,0,1)\"\n            },\n            \"highlights\": {\n                \"title\": \"Highlights\",\n                \"type\": \"array\",\n                \"items\": {\n                  \"title\": \"Highlight\",\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"from\": {\n                      \"title\": \"From\",\n                      \"type\": \"number\"\n                    },\n                    \"to\": {\n                      \"title\": \"To\",\n                      \"type\": \"number\"\n                    },\n                    \"color\": {\n                      \"title\": \"Color\",\n                      \"type\": \"string\"\n                    }\n                  }\n                }\n            },\n            \"highlightsWidth\": {\n                \"title\": \"Highlights width\",\n                \"type\": \"number\",\n                \"default\": 15\n            },\n            \"showBorder\": {\n                \"title\": \"Show border\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"numbersFont\": {\n                \"title\": \"Tick numbers font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 18\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"titleFont\": {\n                \"title\": \"Title text font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 24\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": \"#888\"\n                    }\n                }\n            },\n            \"unitsFont\": {\n                \"title\": \"Units text font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 22\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": \"#888\"\n                    }\n                }\n            },\n            \"valueFont\": {\n                \"title\": \"Value text font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 40\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": \"#444\"\n                    },\n                    \"shadowColor\": {\n                        \"title\": \"Shadow color\",\n                        \"type\": \"string\",\n                        \"default\": \"rgba(0,0,0,0.3)\"\n                    }\n                }\n            },\n            \"animation\": {\n                \"title\": \"Enable animation\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"animationDuration\": {\n                \"title\": \"Animation duration\",\n                \"type\": \"number\",\n                \"default\": 500\n            },\n            \"animationRule\": {\n                \"title\": \"Animation rule\",\n                \"type\": \"string\",\n                \"default\": \"cycle\"\n            },\n            \"startAngle\": {\n                \"title\": \"Start ticks angle\",\n                \"type\": \"number\",\n                \"default\": 45\n            },\n            \"ticksAngle\": {\n                \"title\": \"Ticks angle\",\n                \"type\": \"number\",\n                \"default\": 270\n            },\n            \"needleCircleSize\": {\n                \"title\": \"Needle circle size\",\n                \"type\": \"number\",\n                \"default\": 10\n            }\n        },\n        \"required\": []\n    },\n    \"form\": [\n        \"startAngle\",\n        \"ticksAngle\",\n        \"needleCircleSize\",\n        \"minValue\",\n        \"maxValue\",\n        \"unitTitle\",\n        \"showUnitTitle\",\n        \"units\",\n        \"majorTicksCount\",\n        \"minorTicks\",\n        \"valueBox\",\n        \"valueInt\",\n        \"valueDec\",\n        {\n            \"key\": \"defaultColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorPlate\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorMajorTicks\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorMinorTicks\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorNeedle\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorNeedleEnd\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorNeedleShadowUp\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorNeedleShadowDown\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorValueBoxRect\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorValueBoxRectEnd\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorValueBoxBackground\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorValueBoxShadow\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"highlights\",\n            \"items\": [\n                \"highlights[].from\",\n                \"highlights[].to\",\n                {\n                    \"key\": \"highlights[].color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        \"highlightsWidth\",\n        \"showBorder\",\n        {\n            \"key\": \"numbersFont\",\n            \"items\": [\n                \"numbersFont.family\",\n                \"numbersFont.size\",\n                {\n                   \"key\": \"numbersFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"numbersFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"numbersFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"titleFont\",\n            \"items\": [\n                \"titleFont.family\",\n                \"titleFont.size\",\n                {\n                   \"key\": \"titleFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"titleFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"titleFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"unitsFont\",\n            \"items\": [\n                \"unitsFont.family\",\n                \"unitsFont.size\",\n                {\n                   \"key\": \"unitsFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"unitsFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"unitsFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"valueFont\",\n            \"items\": [\n                \"valueFont.family\",\n                \"valueFont.size\",\n                {\n                   \"key\": \"valueFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"valueFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"valueFont.color\",\n                    \"type\": \"color\"\n                },\n                {\n                    \"key\": \"valueFont.shadowColor\",\n                    \"type\": \"color\"\n                }\n            ]\n        },        \n        \"animation\",\n        \"animationDuration\",\n        {\n            \"key\": \"animationRule\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \"quad\",\n                    \"label\": \"Quad\"\n                },\n                {\n                    \"value\": \"quint\",\n                    \"label\": \"Quint\"\n                },\n                {\n                    \"value\": \"cycle\",\n                    \"label\": \"Cycle\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                },\n                {\n                    \"value\": \"elastic\",\n                    \"label\": \"Elastic\"\n                },\n                {\n                    \"value\": \"dequad\",\n                    \"label\": \"Dequad\"\n                },\n                {\n                    \"value\": \"dequint\",\n                    \"label\": \"Dequint\"\n                },\n                {\n                    \"value\": \"decycle\",\n                    \"label\": \"Decycle\"\n                },\n                {\n                    \"value\": \"debounce\",\n                    \"label\": \"Debounce\"\n                },\n                {\n                    \"value\": \"delastic\",\n                    \"label\": \"Delastic\"\n                }\n            ]\n        }\n    ]\n}","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":60,\"startAngle\":67.5,\"ticksAngle\":225,\"showBorder\":true,\"defaultColor\":\"#e65100\",\"needleCircleSize\":7,\"highlights\":[{\"from\":-60,\"to\":-50,\"color\":\"#42a5f5\"},{\"from\":-50,\"to\":-40,\"color\":\"rgba(66, 165, 245, 0.83)\"},{\"from\":-40,\"to\":-30,\"color\":\"rgba(66, 165, 245, 0.66)\"},{\"from\":-30,\"to\":-20,\"color\":\"rgba(66, 165, 245, 0.5)\"},{\"from\":-20,\"to\":-10,\"color\":\"rgba(66, 165, 245, 0.33)\"},{\"from\":-10,\"to\":0,\"color\":\"rgba(66, 165, 245, 0.16)\"},{\"from\":0,\"to\":10,\"color\":\"rgba(229, 115, 115, 0.16)\"},{\"from\":10,\"to\":20,\"color\":\"rgba(229, 115, 115, 0.33)\"},{\"from\":20,\"to\":30,\"color\":\"rgba(229, 115, 115, 0.5)\"},{\"from\":30,\"to\":40,\"color\":\"rgba(229, 115, 115, 0.66)\"},{\"from\":40,\"to\":50,\"color\":\"rgba(229, 115, 115, 0.83)\"},{\"from\":50,\"to\":60,\"color\":\"#e57373\"}],\"showUnitTitle\":true,\"colorPlate\":\"#cfd8dc\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"minorTicks\":2,\"valueInt\":3,\"valueDec\":1,\"highlightsWidth\":15,\"valueBox\":true,\"animation\":true,\"animationDuration\":1000,\"animationRule\":\"bounce\",\"colorNeedleShadowUp\":\"rgba(2, 255, 255, 0)\",\"colorNeedleShadowDown\":\"rgba(188, 143, 143, 0.78)\",\"units\":\"°C\",\"majorTicksCount\":12,\"numbersFont\":{\"family\":\"RobotoDraft\",\"size\":20,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#263238\"},\"titleFont\":{\"family\":\"RobotoDraft\",\"size\":24,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#263238\"},\"unitsFont\":{\"family\":\"RobotoDraft\",\"size\":28,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"size\":30,\"style\":\"normal\",\"weight\":\"normal\",\"shadowColor\":\"rgba(0, 0, 0, 0.49)\",\"color\":\"#444\"},\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\",\"unitTitle\":\"Temperature\",\"minValue\":-60},\"title\":\"Temperature radial gauge - Canvas Gauges\"}"}',
+'Temperature radial gauge - Canvas Gauges' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'digital_gauges', 'vertical_bar_justgage',
+'{"type":"latest","sizeX":2,"sizeY":3.5,"resources":[],"templateHtml":"","templateCss":"#gauge {\n    text-align: center;\n   /* margin-left: -100px;\n    margin-right: -100px;*/\n    /*margin-top: -50px;*/\n    \n}\n","controllerScript":"var gauge;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n    gauge = new TbDigitalGauge(containerElement, settings, data);    \n\n}\n\n\nfns.redraw = function(containerElement, width, height, data) {\n    gauge.redraw(data);\n};\n\nfns.destroy = function() {\n}\n","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"minValue\": {\n                \"title\": \"Minimum value\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"maxValue\": {\n                \"title\": \"Maximum value\",\n                \"type\": \"number\",\n                \"default\": 100\n            },            \n            \"gaugeType\": {\n                \"title\": \"Gauge type\",\n                \"type\": \"string\",\n                \"default\": \"arc\"\n            },         \n            \"donutStartAngle\": {\n                \"title\": \"Angle to start from when in donut mode\",\n                \"type\": \"number\",\n                \"default\": 90\n            },            \n            \"neonGlowBrightness\": {\n                \"title\": \"Neon glow effect brightness, (0-100), 0 - disable effect\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"dashThickness\": {\n                \"title\": \"Thickness of the stripes, 0 - no stripes\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"roundedLineCap\": {\n                \"title\": \"Display rounded line cap\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"title\": {\n                \"title\": \"Gauge title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showTitle\": {\n                \"title\": \"Show gauge title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"unitTitle\": {\n                \"title\": \"Unit title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showUnitTitle\": {\n                \"title\": \"Show unit title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"showValue\": {\n                \"title\": \"Show value text\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"showMinMax\": {\n                \"title\": \"Show min and max values\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"gaugeWidthScale\": {\n                \"title\": \"Width of the gauge element\",\n                \"type\": \"number\",\n                \"default\": 0.75\n            },\n            \"defaultColor\": {\n                \"title\": \"Default color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"gaugeColor\": {\n                \"title\": \"Background color of the gauge element\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"levelColors\": {\n                \"title\": \"Colors of indicator, from lower to upper\",\n                \"type\": \"array\",\n                \"items\": {\n                  \"title\": \"Color\",\n                  \"type\": \"string\"\n                }\n            },\n            \"refreshAnimationType\": {\n                \"title\": \"Type of refresh animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"refreshAnimationTime\": {\n                \"title\": \"Duration of refresh animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"startAnimationType\": {\n                \"title\": \"Type of start animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"startAnimationTime\": {\n                \"title\": \"Duration of start animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"decimals\": {\n                \"title\": \"Number of digits after floating point\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"units\": {\n                \"title\": \"Special symbol to show next to value\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"titleFont\": {\n                \"title\": \"Gauge title font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 12\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"labelFont\": {\n                \"title\": \"Font of label showing under value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 8\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"valueFont\": {\n                \"title\": \"Font of label showing current value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 18\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"minMaxFont\": {\n                \"title\": \"Font of minimum and maximum labels\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 10\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            }\n        }\n    },\n    \"form\": [\n        \"minValue\",\n        \"maxValue\",\n        {\n           \"key\": \"gaugeType\",\n           \"type\": \"rc-select\",\n           \"multiple\": false,\n           \"items\": [\n               {\n                   \"value\": \"arc\",\n                   \"label\": \"Arc\"\n               },\n               {\n                   \"value\": \"donut\",\n                   \"label\": \"Donut\"\n               },\n               {\n                   \"value\": \"horizontalBar\",\n                   \"label\": \"Horizontal bar\"\n               },\n               {\n                   \"value\": \"verticalBar\",\n                   \"label\": \"Vertical bar\"\n               }\n            ]\n        },\n        \"donutStartAngle\",\n        \"neonGlowBrightness\",\n        \"dashThickness\",\n        \"roundedLineCap\",\n        \"title\",\n        \"showTitle\",\n        \"unitTitle\",\n        \"showUnitTitle\",\n        \"showValue\",\n        \"showMinMax\",\n        \"gaugeWidthScale\",\n        {\n            \"key\": \"defaultColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"gaugeColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"levelColors\",\n            \"items\": [\n                {\n                    \"key\": \"levelColors[]\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"refreshAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"refreshAnimationTime\",\n        {\n            \"key\": \"startAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"startAnimationTime\",\n        \"decimals\",\n        \"units\",\n        {\n            \"key\": \"titleFont\",\n            \"items\": [\n                \"titleFont.family\",\n                \"titleFont.size\",\n                {\n                   \"key\": \"titleFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"titleFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"titleFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"labelFont\",\n            \"items\": [\n                \"labelFont.family\",\n                \"labelFont.size\",\n                {\n                   \"key\": \"labelFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"labelFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"labelFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"valueFont\",\n            \"items\": [\n                \"valueFont.family\",\n                \"valueFont.size\",\n                {\n                   \"key\": \"valueFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"valueFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"valueFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"minMaxFont\",\n            \"items\": [\n                \"minMaxFont.family\",\n                \"minMaxFont.size\",\n                {\n                   \"key\": \"minMaxFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"minMaxFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"minMaxFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        }\n    ]\n}\n    ","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#f57c00\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"RobotoDraft\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#999999\"},\"labelFont\":{\"family\":\"RobotoDraft\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"RobotoDraft\",\"style\":\"normal\",\"weight\":\"500\",\"size\":12,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"RobotoDraft\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#666666\"},\"neonGlowBrightness\":0,\"decimals\":0,\"dashThickness\":1.5,\"gaugeColor\":\"#eeeeee\",\"showTitle\":false,\"gaugeType\":\"verticalBar\"},\"title\":\"Vertical bar - justGage\"}"}',
+'Vertical bar - justGage' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'digital_gauges', 'gauge_justgage',
+'{"type":"latest","sizeX":4,"sizeY":3,"resources":[],"templateHtml":"","templateCss":"#gauge {\n    text-align: center;\n   /* margin-left: -100px;\n    margin-right: -100px;*/\n    /*margin-top: -50px;*/\n    \n}\n","controllerScript":"var gauge;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n    gauge = new TbDigitalGauge(containerElement, settings, data);    \n\n}\n\n\nfns.redraw = function(containerElement, width, height, data) {\n    gauge.redraw(data);\n};\n\nfns.destroy = function() {\n}\n","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"minValue\": {\n                \"title\": \"Minimum value\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"maxValue\": {\n                \"title\": \"Maximum value\",\n                \"type\": \"number\",\n                \"default\": 100\n            },            \n            \"gaugeType\": {\n                \"title\": \"Gauge type\",\n                \"type\": \"string\",\n                \"default\": \"arc\"\n            },         \n            \"donutStartAngle\": {\n                \"title\": \"Angle to start from when in donut mode\",\n                \"type\": \"number\",\n                \"default\": 90\n            },            \n            \"neonGlowBrightness\": {\n                \"title\": \"Neon glow effect brightness, (0-100), 0 - disable effect\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"dashThickness\": {\n                \"title\": \"Thickness of the stripes, 0 - no stripes\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"roundedLineCap\": {\n                \"title\": \"Display rounded line cap\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"title\": {\n                \"title\": \"Gauge title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showTitle\": {\n                \"title\": \"Show gauge title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"unitTitle\": {\n                \"title\": \"Unit title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showUnitTitle\": {\n                \"title\": \"Show unit title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"showValue\": {\n                \"title\": \"Show value text\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"showMinMax\": {\n                \"title\": \"Show min and max values\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"gaugeWidthScale\": {\n                \"title\": \"Width of the gauge element\",\n                \"type\": \"number\",\n                \"default\": 0.75\n            },\n            \"defaultColor\": {\n                \"title\": \"Default color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"gaugeColor\": {\n                \"title\": \"Background color of the gauge element\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"levelColors\": {\n                \"title\": \"Colors of indicator, from lower to upper\",\n                \"type\": \"array\",\n                \"items\": {\n                  \"title\": \"Color\",\n                  \"type\": \"string\"\n                }\n            },\n            \"refreshAnimationType\": {\n                \"title\": \"Type of refresh animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"refreshAnimationTime\": {\n                \"title\": \"Duration of refresh animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"startAnimationType\": {\n                \"title\": \"Type of start animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"startAnimationTime\": {\n                \"title\": \"Duration of start animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"decimals\": {\n                \"title\": \"Number of digits after floating point\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"units\": {\n                \"title\": \"Special symbol to show next to value\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"titleFont\": {\n                \"title\": \"Gauge title font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 12\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"labelFont\": {\n                \"title\": \"Font of label showing under value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 8\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"valueFont\": {\n                \"title\": \"Font of label showing current value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 18\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"minMaxFont\": {\n                \"title\": \"Font of minimum and maximum labels\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 10\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            }\n        }\n    },\n    \"form\": [\n        \"minValue\",\n        \"maxValue\",\n        {\n           \"key\": \"gaugeType\",\n           \"type\": \"rc-select\",\n           \"multiple\": false,\n           \"items\": [\n               {\n                   \"value\": \"arc\",\n                   \"label\": \"Arc\"\n               },\n               {\n                   \"value\": \"donut\",\n                   \"label\": \"Donut\"\n               },\n               {\n                   \"value\": \"horizontalBar\",\n                   \"label\": \"Horizontal bar\"\n               },\n               {\n                   \"value\": \"verticalBar\",\n                   \"label\": \"Vertical bar\"\n               }\n            ]\n        },\n        \"donutStartAngle\",\n        \"neonGlowBrightness\",\n        \"dashThickness\",\n        \"roundedLineCap\",\n        \"title\",\n        \"showTitle\",\n        \"unitTitle\",\n        \"showUnitTitle\",\n        \"showValue\",\n        \"showMinMax\",\n        \"gaugeWidthScale\",\n        {\n            \"key\": \"defaultColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"gaugeColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"levelColors\",\n            \"items\": [\n                {\n                    \"key\": \"levelColors[]\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"refreshAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"refreshAnimationTime\",\n        {\n            \"key\": \"startAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"startAnimationTime\",\n        \"decimals\",\n        \"units\",\n        {\n            \"key\": \"titleFont\",\n            \"items\": [\n                \"titleFont.family\",\n                \"titleFont.size\",\n                {\n                   \"key\": \"titleFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"titleFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"titleFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"labelFont\",\n            \"items\": [\n                \"labelFont.family\",\n                \"labelFont.size\",\n                {\n                   \"key\": \"labelFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"labelFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"labelFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"valueFont\",\n            \"items\": [\n                \"valueFont.family\",\n                \"valueFont.size\",\n                {\n                   \"key\": \"valueFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"valueFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"valueFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"minMaxFont\",\n            \"items\": [\n                \"minMaxFont.family\",\n                \"minMaxFont.size\",\n                {\n                   \"key\": \"minMaxFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"minMaxFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"minMaxFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        }\n    ]\n}\n    ","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"RobotoDraft\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#999999\"},\"labelFont\":{\"family\":\"RobotoDraft\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"RobotoDraft\",\"style\":\"normal\",\"weight\":\"500\",\"size\":36,\"color\":\"#666666\"},\"minMaxFont\":{\"family\":\"RobotoDraft\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#666666\"},\"neonGlowBrightness\":0,\"decimals\":0,\"dashThickness\":0,\"gaugeColor\":\"#eeeeee\",\"showTitle\":true,\"gaugeType\":\"arc\"},\"title\":\"Gauge - justGage\"}"}',
+'Gauge - justGage' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'digital_gauges', 'digital_thermometer',
+'{"type":"latest","sizeX":3,"sizeY":3,"resources":[],"templateHtml":"","templateCss":"#gauge {\n    text-align: center;\n   /* margin-left: -100px;\n    margin-right: -100px;*/\n    /*margin-top: -50px;*/\n    \n}\n","controllerScript":"var gauge;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n    gauge = new TbDigitalGauge(containerElement, settings, data);    \n\n}\n\n\nfns.redraw = function(containerElement, width, height, data) {\n    gauge.redraw(data);\n};\n\nfns.destroy = function() {\n}\n","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"minValue\": {\n                \"title\": \"Minimum value\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"maxValue\": {\n                \"title\": \"Maximum value\",\n                \"type\": \"number\",\n                \"default\": 100\n            },            \n            \"gaugeType\": {\n                \"title\": \"Gauge type\",\n                \"type\": \"string\",\n                \"default\": \"arc\"\n            },         \n            \"donutStartAngle\": {\n                \"title\": \"Angle to start from when in donut mode\",\n                \"type\": \"number\",\n                \"default\": 90\n            },            \n            \"neonGlowBrightness\": {\n                \"title\": \"Neon glow effect brightness, (0-100), 0 - disable effect\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"dashThickness\": {\n                \"title\": \"Thickness of the stripes, 0 - no stripes\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"roundedLineCap\": {\n                \"title\": \"Display rounded line cap\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"title\": {\n                \"title\": \"Gauge title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showTitle\": {\n                \"title\": \"Show gauge title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"unitTitle\": {\n                \"title\": \"Unit title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showUnitTitle\": {\n                \"title\": \"Show unit title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"showValue\": {\n                \"title\": \"Show value text\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"showMinMax\": {\n                \"title\": \"Show min and max values\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"gaugeWidthScale\": {\n                \"title\": \"Width of the gauge element\",\n                \"type\": \"number\",\n                \"default\": 0.75\n            },\n            \"defaultColor\": {\n                \"title\": \"Default color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"gaugeColor\": {\n                \"title\": \"Background color of the gauge element\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"levelColors\": {\n                \"title\": \"Colors of indicator, from lower to upper\",\n                \"type\": \"array\",\n                \"items\": {\n                  \"title\": \"Color\",\n                  \"type\": \"string\"\n                }\n            },\n            \"refreshAnimationType\": {\n                \"title\": \"Type of refresh animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"refreshAnimationTime\": {\n                \"title\": \"Duration of refresh animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"startAnimationType\": {\n                \"title\": \"Type of start animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"startAnimationTime\": {\n                \"title\": \"Duration of start animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"decimals\": {\n                \"title\": \"Number of digits after floating point\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"units\": {\n                \"title\": \"Special symbol to show next to value\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"titleFont\": {\n                \"title\": \"Gauge title font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 12\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"labelFont\": {\n                \"title\": \"Font of label showing under value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 8\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"valueFont\": {\n                \"title\": \"Font of label showing current value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 18\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"minMaxFont\": {\n                \"title\": \"Font of minimum and maximum labels\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 10\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            }\n        }\n    },\n    \"form\": [\n        \"minValue\",\n        \"maxValue\",\n        {\n           \"key\": \"gaugeType\",\n           \"type\": \"rc-select\",\n           \"multiple\": false,\n           \"items\": [\n               {\n                   \"value\": \"arc\",\n                   \"label\": \"Arc\"\n               },\n               {\n                   \"value\": \"donut\",\n                   \"label\": \"Donut\"\n               },\n               {\n                   \"value\": \"horizontalBar\",\n                   \"label\": \"Horizontal bar\"\n               },\n               {\n                   \"value\": \"verticalBar\",\n                   \"label\": \"Vertical bar\"\n               }\n            ]\n        },\n        \"donutStartAngle\",\n        \"neonGlowBrightness\",\n        \"dashThickness\",\n        \"roundedLineCap\",\n        \"title\",\n        \"showTitle\",\n        \"unitTitle\",\n        \"showUnitTitle\",\n        \"showValue\",\n        \"showMinMax\",\n        \"gaugeWidthScale\",\n        {\n            \"key\": \"defaultColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"gaugeColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"levelColors\",\n            \"items\": [\n                {\n                    \"key\": \"levelColors[]\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"refreshAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"refreshAnimationTime\",\n        {\n            \"key\": \"startAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"startAnimationTime\",\n        \"decimals\",\n        \"units\",\n        {\n            \"key\": \"titleFont\",\n            \"items\": [\n                \"titleFont.family\",\n                \"titleFont.size\",\n                {\n                   \"key\": \"titleFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"titleFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"titleFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"labelFont\",\n            \"items\": [\n                \"labelFont.family\",\n                \"labelFont.size\",\n                {\n                   \"key\": \"labelFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"labelFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"labelFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"valueFont\",\n            \"items\": [\n                \"valueFont.family\",\n                \"valueFont.size\",\n                {\n                   \"key\": \"valueFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"valueFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"valueFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"minMaxFont\",\n            \"items\": [\n                \"minMaxFont.family\",\n                \"minMaxFont.size\",\n                {\n                   \"key\": \"minMaxFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"minMaxFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"minMaxFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        }\n    ]\n}\n    ","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < -60) {\\n\\tvalue = 60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":60,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":1,\"levelColors\":[\"#304ffe\",\"#7e57c2\",\"#ff4081\",\"#d32f2f\"],\"refreshAnimationType\":\"<>\",\"refreshAnimationTime\":700,\"startAnimationType\":\"<>\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"RobotoDraft\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"RobotoDraft\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"dashThickness\":1.5,\"decimals\":0,\"minValue\":-60,\"units\":\"°C\",\"gaugeColor\":\"#333333\",\"neonGlowBrightness\":35,\"gaugeType\":\"donut\"},\"title\":\"Digital thermometer\"}"}',
+'Digital thermometer' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'analogue_gauges', 'linear_gauge_canvas_gauges',
+'{"type":"latest","sizeX":7,"sizeY":3,"resources":[],"templateHtml":"<canvas id=\"linearGauge\"></canvas>\n","templateCss":"","controllerScript":"var gauge;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n    gauge = new TbAnalogueLinearGauge(containerElement, settings, data, ''linearGauge'');    \n\n}\n\n\nfns.redraw = function(containerElement, width, height, data, timeWindow, sizeChanged) {\n    gauge.redraw(width, height, data, sizeChanged);\n};\n\nfns.destroy = function() {\n}\n","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"minValue\": {\n                \"title\": \"Minimum value\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"maxValue\": {\n                \"title\": \"Maximum value\",\n                \"type\": \"number\",\n                \"default\": 100\n            },\n            \"unitTitle\": {\n                \"title\": \"Unit title\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"showUnitTitle\": {\n                \"title\": \"Show unit title\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"units\": {\n                \"title\": \"Units\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"majorTicksCount\": {\n                \"title\": \"Major ticks count\",\n                \"type\": \"number\",\n                \"default\": null\n            },\n            \"minorTicks\": {\n                \"title\": \"Minor ticks count\",\n                \"type\": \"number\",\n                \"default\": 2\n            },\n            \"valueBox\": {\n                \"title\": \"Show value box\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"valueInt\": {\n                \"title\": \"Digits count for integer part of value\",\n                \"type\": \"number\",\n                \"default\": 3\n            },\n            \"valueDec\": {\n                \"title\": \"Digits count for decimal part of value\",\n                \"type\": \"number\",\n                \"default\": 2\n            },\n            \"defaultColor\": {\n                \"title\": \"Default color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"colorPlate\": {\n                \"title\": \"Plate color\",\n                \"type\": \"string\",\n                \"default\": \"#fff\"\n            },\n            \"colorMajorTicks\": {\n                \"title\": \"Major ticks color\",\n                \"type\": \"string\",\n                \"default\": \"#444\"\n            },\n            \"colorMinorTicks\": {\n                \"title\": \"Minor ticks color\",\n                \"type\": \"string\",\n                \"default\": \"#666\"\n            },\n            \"colorNeedle\": {\n                \"title\": \"Needle color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"colorNeedleEnd\": {\n                \"title\": \"Needle color - end gradient\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"colorNeedleShadowUp\": {\n                \"title\": \"Upper half of the needle shadow color\",\n                \"type\": \"string\",\n                \"default\": \"rgba(2,255,255,0.2)\"\n            },\n            \"colorNeedleShadowDown\": {\n                \"title\": \"Drop shadow needle color.\",\n                \"type\": \"string\",\n                \"default\": \"rgba(188,143,143,0.45)\"\n            },\n            \"colorValueBoxRect\": {\n                \"title\": \"Value box rectangle stroke color\",\n                \"type\": \"string\",\n                \"default\": \"#888\"\n            },\n            \"colorValueBoxRectEnd\": {\n                \"title\": \"Value box rectangle stroke color - end gradient\",\n                \"type\": \"string\",\n                \"default\": \"#666\"\n            },\n            \"colorValueBoxBackground\": {\n                \"title\": \"Value box background color\",\n                \"type\": \"string\",\n                \"default\": \"#babab2\"\n            },\n            \"colorValueBoxShadow\": {\n                \"title\": \"Value box shadow color\",\n                \"type\": \"string\",\n                \"default\": \"rgba(0,0,0,1)\"\n            },\n            \"highlights\": {\n                \"title\": \"Highlights\",\n                \"type\": \"array\",\n                \"items\": {\n                  \"title\": \"Highlight\",\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"from\": {\n                      \"title\": \"From\",\n                      \"type\": \"number\"\n                    },\n                    \"to\": {\n                      \"title\": \"To\",\n                      \"type\": \"number\"\n                    },\n                    \"color\": {\n                      \"title\": \"Color\",\n                      \"type\": \"string\"\n                    }\n                  }\n                }\n            },\n            \"highlightsWidth\": {\n                \"title\": \"Highlights width\",\n                \"type\": \"number\",\n                \"default\": 15\n            },\n            \"showBorder\": {\n                \"title\": \"Show border\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"numbersFont\": {\n                \"title\": \"Tick numbers font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 18\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"titleFont\": {\n                \"title\": \"Title text font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 24\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": \"#888\"\n                    }\n                }\n            },\n            \"unitsFont\": {\n                \"title\": \"Units text font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 22\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": \"#888\"\n                    }\n                }\n            },\n            \"valueFont\": {\n                \"title\": \"Value text font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 40\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": \"#444\"\n                    },\n                    \"shadowColor\": {\n                        \"title\": \"Shadow color\",\n                        \"type\": \"string\",\n                        \"default\": \"rgba(0,0,0,0.3)\"\n                    }\n                }\n            },\n            \"animation\": {\n                \"title\": \"Enable animation\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"animationDuration\": {\n                \"title\": \"Animation duration\",\n                \"type\": \"number\",\n                \"default\": 500\n            },\n            \"animationRule\": {\n                \"title\": \"Animation rule\",\n                \"type\": \"string\",\n                \"default\": \"cycle\"\n            },\n            \"barStrokeWidth\": {\n                \"title\": \"Bar stroke width\",\n                \"type\": \"number\",\n                \"default\": 2.5\n            },\n            \"colorBarStroke\": {\n                \"title\": \"Bar stroke color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"colorBar\": {\n                \"title\": \"Bar background color\",\n                \"type\": \"string\",\n                \"default\": \"#fff\"\n            },\n            \"colorBarEnd\": {\n                \"title\": \"Bar background color - end gradient\",\n                \"type\": \"string\",\n                \"default\": \"#ddd\"\n            },\n            \"colorBarProgress\": {\n                \"title\": \"Progress bar color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"colorBarProgressEnd\": {\n                \"title\": \"Progress bar color - end gradient\",\n                \"type\": \"string\",\n                \"default\": null\n            }        \n            \n        },\n        \"required\": []\n    },\n    \"form\": [\n        \"barStrokeWidth\",\n        {\n            \"key\": \"colorBarStroke\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorBar\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorBarEnd\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorBarProgress\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorBarProgressEnd\",\n            \"type\": \"color\"\n        },\n        \"minValue\",\n        \"maxValue\",\n        \"unitTitle\",\n        \"showUnitTitle\",\n        \"units\",\n        \"majorTicksCount\",\n        \"minorTicks\",\n        \"valueBox\",\n        \"valueInt\",\n        \"valueDec\",\n        {\n            \"key\": \"defaultColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorPlate\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorMajorTicks\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorMinorTicks\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorNeedle\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorNeedleEnd\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorNeedleShadowUp\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorNeedleShadowDown\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorValueBoxRect\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorValueBoxRectEnd\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorValueBoxBackground\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorValueBoxShadow\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"highlights\",\n            \"items\": [\n                \"highlights[].from\",\n                \"highlights[].to\",\n                {\n                    \"key\": \"highlights[].color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        \"highlightsWidth\",\n        \"showBorder\",\n        {\n            \"key\": \"numbersFont\",\n            \"items\": [\n                \"numbersFont.family\",\n                \"numbersFont.size\",\n                {\n                   \"key\": \"numbersFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"numbersFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"numbersFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"titleFont\",\n            \"items\": [\n                \"titleFont.family\",\n                \"titleFont.size\",\n                {\n                   \"key\": \"titleFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"titleFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"titleFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"unitsFont\",\n            \"items\": [\n                \"unitsFont.family\",\n                \"unitsFont.size\",\n                {\n                   \"key\": \"unitsFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"unitsFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"unitsFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"valueFont\",\n            \"items\": [\n                \"valueFont.family\",\n                \"valueFont.size\",\n                {\n                   \"key\": \"valueFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"valueFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"valueFont.color\",\n                    \"type\": \"color\"\n                },\n                {\n                    \"key\": \"valueFont.shadowColor\",\n                    \"type\": \"color\"\n                }\n            ]\n        },        \n        \"animation\",\n        \"animationDuration\",\n        {\n            \"key\": \"animationRule\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \"quad\",\n                    \"label\": \"Quad\"\n                },\n                {\n                    \"value\": \"quint\",\n                    \"label\": \"Quint\"\n                },\n                {\n                    \"value\": \"cycle\",\n                    \"label\": \"Cycle\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                },\n                {\n                    \"value\": \"elastic\",\n                    \"label\": \"Elastic\"\n                },\n                {\n                    \"value\": \"dequad\",\n                    \"label\": \"Dequad\"\n                },\n                {\n                    \"value\": \"dequint\",\n                    \"label\": \"Dequint\"\n                },\n                {\n                    \"value\": \"decycle\",\n                    \"label\": \"Decycle\"\n                },\n                {\n                    \"value\": \"debounce\",\n                    \"label\": \"Debounce\"\n                },\n                {\n                    \"value\": \"delastic\",\n                    \"label\": \"Delastic\"\n                }\n            ]\n        }\n    ]\n}","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 10 - 5;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":100,\"defaultColor\":\"#e64a19\",\"barStrokeWidth\":2.5,\"colorBar\":\"#fff\",\"colorBarEnd\":\"#ddd\",\"showUnitTitle\":true,\"minorTicks\":2,\"valueBox\":true,\"valueInt\":3,\"valueDec\":0,\"colorPlate\":\"#fff\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"colorNeedleShadowUp\":\"rgba(2,255,255,0.2)\",\"colorNeedleShadowDown\":\"rgba(188,143,143,0.45)\",\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\",\"highlightsWidth\":10,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"cycle\",\"showBorder\":false,\"majorTicksCount\":10,\"numbersFont\":{\"family\":\"RobotoDraft\",\"size\":18,\"style\":\"normal\",\"weight\":\"500\"},\"titleFont\":{\"family\":\"RobotoDraft\",\"size\":24,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#888\"},\"unitsFont\":{\"family\":\"RobotoDraft\",\"size\":22,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#888\"},\"valueFont\":{\"family\":\"RobotoDraft\",\"size\":40,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#444\",\"shadowColor\":\"rgba(0,0,0,0.3)\"},\"minValue\":-100,\"highlights\":[]},\"title\":\"Linear gauge - Canvas Gauges\"}"}',
+'Linear gauge - Canvas Gauges' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'charts', 'pie_chart_js',
+'{"type":"latest","sizeX":8,"sizeY":6,"resources":[{"url":"https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.min.js"}],"templateHtml":"<canvas id=\"pieChart\"></canvas>\n","templateCss":"","controllerScript":"var chart;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n\n    var pieData = {\n        labels: [],\n        datasets: []\n    };\n\n    var dataset = {\n        data: [],\n        backgroundColor: [],\n        borderColor: [],\n        borderWidth: [],\n        hoverBackgroundColor: []\n    }\n    \n    pieData.datasets.push(dataset);\n    \n    for (var i in data) {\n        var dataKey = data[i].dataKey;\n        pieData.labels.push(dataKey.label);\n        dataset.data.push(0);\n        var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n        var borderColor = tinycolor(dataKey.color).darken();\n        dataset.backgroundColor.push(dataKey.color);\n        dataset.borderColor.push(''#fff'');\n        dataset.borderWidth.push(5);\n        dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n    }\n\n    var ctx = $(''#pieChart'', containerElement);\n    chart = new Chart(ctx, {\n        type: ''pie'',\n        data: pieData,\n        options: {\n            maintainAspectRatio: false\n        }\n    });\n    \n}\n\nfns.redraw = function(containerElement, width, height, data,\n    timeWindow, sizeChanged) {\n\n    for (var i = 0; i < data.length; i++) {\n        var cellData = data[i];\n        if (cellData.data.length > 0) {\n             var tvPair = cellData.data[cellData.data.length - 1];\n             var value = tvPair[1];\n             chart.data.datasets[0].data[i] = parseFloat(value);\n        }\n    }\n  \n    chart.update();\n    if (sizeChanged) {\n        chart.resize();\n    }\n    \n};\n","settingsSchema":"{}","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Pie - Chart.js\"}"}',
+'Pie - Chart.js' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'digital_gauges', 'simple_neon_gauge_justgage',
+'{"type":"latest","sizeX":3,"sizeY":3,"resources":[],"templateHtml":"","templateCss":"#gauge {\n    text-align: center;\n   /* margin-left: -100px;\n    margin-right: -100px;*/\n    /*margin-top: -50px;*/\n    \n}\n","controllerScript":"var gauge;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n    gauge = new TbDigitalGauge(containerElement, settings, data);    \n\n}\n\n\nfns.redraw = function(containerElement, width, height, data) {\n    gauge.redraw(data);\n};\n\nfns.destroy = function() {\n}\n","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"minValue\": {\n                \"title\": \"Minimum value\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"maxValue\": {\n                \"title\": \"Maximum value\",\n                \"type\": \"number\",\n                \"default\": 100\n            },            \n            \"gaugeType\": {\n                \"title\": \"Gauge type\",\n                \"type\": \"string\",\n                \"default\": \"arc\"\n            },         \n            \"donutStartAngle\": {\n                \"title\": \"Angle to start from when in donut mode\",\n                \"type\": \"number\",\n                \"default\": 90\n            },            \n            \"neonGlowBrightness\": {\n                \"title\": \"Neon glow effect brightness, (0-100), 0 - disable effect\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"dashThickness\": {\n                \"title\": \"Thickness of the stripes, 0 - no stripes\",\n                \"type\": \"number\",\n                \"default\": 0\n            },            \n            \"roundedLineCap\": {\n                \"title\": \"Display rounded line cap\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"title\": {\n                \"title\": \"Gauge title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showTitle\": {\n                \"title\": \"Show gauge title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"unitTitle\": {\n                \"title\": \"Unit title\",\n                \"type\": \"string\",\n                \"default\": null\n            },            \n            \"showUnitTitle\": {\n                \"title\": \"Show unit title\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },            \n            \"showValue\": {\n                \"title\": \"Show value text\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"showMinMax\": {\n                \"title\": \"Show min and max values\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"gaugeWidthScale\": {\n                \"title\": \"Width of the gauge element\",\n                \"type\": \"number\",\n                \"default\": 0.75\n            },\n            \"defaultColor\": {\n                \"title\": \"Default color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"gaugeColor\": {\n                \"title\": \"Background color of the gauge element\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"levelColors\": {\n                \"title\": \"Colors of indicator, from lower to upper\",\n                \"type\": \"array\",\n                \"items\": {\n                  \"title\": \"Color\",\n                  \"type\": \"string\"\n                }\n            },\n            \"refreshAnimationType\": {\n                \"title\": \"Type of refresh animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"refreshAnimationTime\": {\n                \"title\": \"Duration of refresh animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"startAnimationType\": {\n                \"title\": \"Type of start animation\",\n                \"type\": \"string\",\n                \"default\": \">\"\n            },\n            \"startAnimationTime\": {\n                \"title\": \"Duration of start animation (ms)\",\n                \"type\": \"number\",\n                \"default\": 700\n            },\n            \"decimals\": {\n                \"title\": \"Number of digits after floating point\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"units\": {\n                \"title\": \"Special symbol to show next to value\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"titleFont\": {\n                \"title\": \"Gauge title font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 12\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"labelFont\": {\n                \"title\": \"Font of label showing under value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 8\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"valueFont\": {\n                \"title\": \"Font of label showing current value\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 18\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"minMaxFont\": {\n                \"title\": \"Font of minimum and maximum labels\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 10\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            }\n        }\n    },\n    \"form\": [\n        \"minValue\",\n        \"maxValue\",\n        {\n           \"key\": \"gaugeType\",\n           \"type\": \"rc-select\",\n           \"multiple\": false,\n           \"items\": [\n               {\n                   \"value\": \"arc\",\n                   \"label\": \"Arc\"\n               },\n               {\n                   \"value\": \"donut\",\n                   \"label\": \"Donut\"\n               },\n               {\n                   \"value\": \"horizontalBar\",\n                   \"label\": \"Horizontal bar\"\n               },\n               {\n                   \"value\": \"verticalBar\",\n                   \"label\": \"Vertical bar\"\n               }\n            ]\n        },\n        \"donutStartAngle\",\n        \"neonGlowBrightness\",\n        \"dashThickness\",\n        \"roundedLineCap\",\n        \"title\",\n        \"showTitle\",\n        \"unitTitle\",\n        \"showUnitTitle\",\n        \"showValue\",\n        \"showMinMax\",\n        \"gaugeWidthScale\",\n        {\n            \"key\": \"defaultColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"gaugeColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"levelColors\",\n            \"items\": [\n                {\n                    \"key\": \"levelColors[]\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"refreshAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"refreshAnimationTime\",\n        {\n            \"key\": \"startAnimationType\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \">\",\n                    \"label\": \">\"\n                },\n                {\n                    \"value\": \"<\",\n                    \"label\": \"<\"\n                },\n                {\n                    \"value\": \"<>\",\n                    \"label\": \"<>\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                }\n            ]\n        },\n        \"startAnimationTime\",\n        \"decimals\",\n        \"units\",\n        {\n            \"key\": \"titleFont\",\n            \"items\": [\n                \"titleFont.family\",\n                \"titleFont.size\",\n                {\n                   \"key\": \"titleFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"titleFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"titleFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"labelFont\",\n            \"items\": [\n                \"labelFont.family\",\n                \"labelFont.size\",\n                {\n                   \"key\": \"labelFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"labelFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"labelFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"valueFont\",\n            \"items\": [\n                \"valueFont.family\",\n                \"valueFont.size\",\n                {\n                   \"key\": \"valueFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"valueFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"valueFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"minMaxFont\",\n            \"items\": [\n                \"minMaxFont.family\",\n                \"minMaxFont.size\",\n                {\n                   \"key\": \"minMaxFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"minMaxFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"minMaxFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        }\n    ]\n}\n    ","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#388e3c\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":1,\"levelColors\":[],\"refreshAnimationType\":\">\",\"refreshAnimationTime\":700,\"startAnimationType\":\">\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"RobotoDraft\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"RobotoDraft\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"decimals\":0,\"gaugeType\":\"donut\"},\"title\":\"Simple neon gauge - justGage\"}"}',
+'Simple neon gauge - justGage' );
+
+/** System plugins and rules **/
+INSERT INTO thingsboard.plugin ( id, tenant_id, name, state, search_text, api_token, plugin_class, public_access,
+configuration )
+VALUES ( minTimeuuid ( '2016-11-01 01:01:01+0000' ), minTimeuuid ( 0 ), 'System Telemetry Plugin', 'ACTIVE',
+'system telemetry plugin', 'telemetry',
+'org.thingsboard.server.extensions.core.plugin.telemetry.TelemetryStoragePlugin', true, '{}' );
+
+INSERT INTO thingsboard.rule ( id, tenant_id, name, plugin_token, state, search_text, weight, filters, processor,
+action )
+VALUES ( minTimeuuid ( '2016-11-01 01:01:02+0000' ), minTimeuuid ( 0 ), 'System Telemetry Rule', 'telemetry', 'ACTIVE',
+'system telemetry rule', 0,
+'[{"clazz":"org.thingsboard.server.extensions.core.filter.MsgTypeFilter", "name":"TelemetryFilter", "configuration": {"messageTypes":["POST_TELEMETRY","POST_ATTRIBUTES","GET_ATTRIBUTES"]}}]',
+null,
+'{"clazz":"org.thingsboard.server.extensions.core.action.telemetry.TelemetryPluginAction", "name":"TelemetryMsgConverterAction", "configuration":{}}'
+);
+
+INSERT INTO thingsboard.plugin ( id, tenant_id, name, state, search_text, api_token, plugin_class, public_access,
+configuration )
+VALUES ( minTimeuuid ( '2016-11-01 01:01:03+0000' ), minTimeuuid ( 0 ), 'System RPC Plugin', 'ACTIVE',
+'system rpc plugin', 'rpc', 'org.thingsboard.server.extensions.core.plugin.rpc.RpcPlugin', true, '{
+       "defaultTimeout": 20000
+     }' );
+
+/** SYSTEM **/
diff --git a/dao/src/test/java/org/thingsboard/server/dao/attributes/BaseAttributesServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/attributes/BaseAttributesServiceTest.java
new file mode 100644
index 0000000..e2f5acb
--- /dev/null
+++ b/dao/src/test/java/org/thingsboard/server/dao/attributes/BaseAttributesServiceTest.java
@@ -0,0 +1,103 @@
+/**
+ * Copyright © 2016 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.attributes;
+
+import com.datastax.driver.core.utils.UUIDs;
+import org.thingsboard.server.common.data.DataConstants;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.kv.AttributeKvEntry;
+import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
+import org.thingsboard.server.common.data.kv.KvEntry;
+import org.thingsboard.server.common.data.kv.StringDataEntry;
+import org.thingsboard.server.dao.service.AbstractServiceTest;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.thingsboard.server.common.data.DataConstants.CLIENT_SCOPE;
+import static org.thingsboard.server.common.data.DataConstants.DEVICE;
+
+/**
+ * @author Andrew Shvayka
+ */
+public class BaseAttributesServiceTest extends AbstractServiceTest {
+
+    @Autowired
+    private AttributesService attributesService;
+
+    @Before
+    public void before() {
+    }
+
+    @Test
+    public void saveAndFetch() throws Exception {
+        DeviceId deviceId = new DeviceId(UUIDs.timeBased());
+        KvEntry attrValue = new StringDataEntry("attribute1", "value1");
+        AttributeKvEntry attr = new BaseAttributeKvEntry(attrValue, 42L);
+        attributesService.save(deviceId, DataConstants.CLIENT_SCOPE, Collections.singletonList(attr)).get();
+        AttributeKvEntry saved = attributesService.find(deviceId, DataConstants.CLIENT_SCOPE, attr.getKey());
+        Assert.assertEquals(attr, saved);
+    }
+
+    @Test
+    public void saveMultipleTypeAndFetch() throws Exception {
+        DeviceId deviceId = new DeviceId(UUIDs.timeBased());
+        KvEntry attrOldValue = new StringDataEntry("attribute1", "value1");
+        AttributeKvEntry attrOld = new BaseAttributeKvEntry(attrOldValue, 42L);
+
+        attributesService.save(deviceId, DataConstants.CLIENT_SCOPE, Collections.singletonList(attrOld)).get();
+        AttributeKvEntry saved = attributesService.find(deviceId, DataConstants.CLIENT_SCOPE, attrOld.getKey());
+        Assert.assertEquals(attrOld, saved);
+
+        KvEntry attrNewValue = new StringDataEntry("attribute1", "value2");
+        AttributeKvEntry attrNew = new BaseAttributeKvEntry(attrNewValue, 73L);
+        attributesService.save(deviceId, DataConstants.CLIENT_SCOPE, Collections.singletonList(attrNew)).get();
+
+        saved = attributesService.find(deviceId, DataConstants.CLIENT_SCOPE, attrOld.getKey());
+        Assert.assertEquals(attrNew, saved);
+    }
+
+    @Test
+    public void findAll() throws Exception {
+        DeviceId deviceId = new DeviceId(UUIDs.timeBased());
+
+        KvEntry attrAOldValue = new StringDataEntry("A", "value1");
+        AttributeKvEntry attrAOld = new BaseAttributeKvEntry(attrAOldValue, 42L);
+        KvEntry attrANewValue = new StringDataEntry("A", "value2");
+        AttributeKvEntry attrANew = new BaseAttributeKvEntry(attrANewValue, 73L);
+        KvEntry attrBNewValue = new StringDataEntry("B", "value3");
+        AttributeKvEntry attrBNew = new BaseAttributeKvEntry(attrBNewValue, 73L);
+
+        attributesService.save(deviceId, DataConstants.CLIENT_SCOPE, Collections.singletonList(attrAOld)).get();
+        attributesService.save(deviceId, DataConstants.CLIENT_SCOPE, Collections.singletonList(attrANew)).get();
+        attributesService.save(deviceId, DataConstants.CLIENT_SCOPE, Collections.singletonList(attrBNew)).get();
+
+        List<AttributeKvEntry> saved = attributesService.findAll(deviceId, DataConstants.CLIENT_SCOPE);
+
+        Assert.assertNotNull(saved);
+        Assert.assertEquals(2, saved.size());
+
+        Assert.assertEquals(attrANew, saved.get(0));
+        Assert.assertEquals(attrBNew, saved.get(1));
+    }
+
+}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/CustomCassandraCQLUnit.java b/dao/src/test/java/org/thingsboard/server/dao/CustomCassandraCQLUnit.java
new file mode 100644
index 0000000..503fd6c
--- /dev/null
+++ b/dao/src/test/java/org/thingsboard/server/dao/CustomCassandraCQLUnit.java
@@ -0,0 +1,97 @@
+/**
+ * Copyright © 2016 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;
+
+import org.cassandraunit.BaseCassandraUnit;
+import org.cassandraunit.CQLDataLoader;
+import org.cassandraunit.dataset.CQLDataSet;
+import org.cassandraunit.utils.EmbeddedCassandraServerHelper;
+
+import com.datastax.driver.core.Cluster;
+import com.datastax.driver.core.Session;
+
+import java.util.List;
+
+public class CustomCassandraCQLUnit extends BaseCassandraUnit {
+    private List<CQLDataSet> dataSets;
+
+    public Session session;
+    public Cluster cluster;
+
+    public CustomCassandraCQLUnit(List<CQLDataSet> dataSets) {
+        this.dataSets = dataSets;
+    }
+
+    public CustomCassandraCQLUnit(List<CQLDataSet> dataSets, int readTimeoutMillis) {
+        this.dataSets = dataSets;
+        this.readTimeoutMillis = readTimeoutMillis;
+    }
+
+    public CustomCassandraCQLUnit(List<CQLDataSet> dataSets, String configurationFileName) {
+        this(dataSets);
+        this.configurationFileName = configurationFileName;
+    }
+
+    public CustomCassandraCQLUnit(List<CQLDataSet> dataSets, String configurationFileName, int readTimeoutMillis) {
+        this(dataSets);
+        this.configurationFileName = configurationFileName;
+        this.readTimeoutMillis = readTimeoutMillis;
+    }
+
+    public CustomCassandraCQLUnit(List<CQLDataSet> dataSets, String configurationFileName, long startUpTimeoutMillis) {
+        super(startUpTimeoutMillis);
+        this.dataSets = dataSets;
+        this.configurationFileName = configurationFileName;
+    }
+
+    public CustomCassandraCQLUnit(List<CQLDataSet> dataSets, String configurationFileName, long startUpTimeoutMillis, int readTimeoutMillis) {
+        super(startUpTimeoutMillis);
+        this.dataSets = dataSets;
+        this.configurationFileName = configurationFileName;
+        this.readTimeoutMillis = readTimeoutMillis;
+    }
+
+    @Override
+    protected void load() {
+        String hostIp = EmbeddedCassandraServerHelper.getHost();
+        int port = EmbeddedCassandraServerHelper.getNativeTransportPort();
+        cluster = new Cluster.Builder().addContactPoints(hostIp).withPort(port).withSocketOptions(getSocketOptions())
+                .build();
+        session = cluster.connect();
+        CQLDataLoader dataLoader = new CQLDataLoader(session);
+        dataSets.forEach(dataLoader::load);
+        session = dataLoader.getSession();
+    }
+
+    @Override
+    protected void after() {
+        super.after();
+        try (Cluster c = cluster; Session s = session) {
+            session = null;
+            cluster = null;
+        }
+    }
+
+    // Getters for those who do not like to directly access fields
+
+    public Session getSession() {
+        return session;
+    }
+
+    public Cluster getCluster() {
+        return cluster;
+    }
+}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/DaoTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/DaoTestSuite.java
new file mode 100644
index 0000000..21b27ad
--- /dev/null
+++ b/dao/src/test/java/org/thingsboard/server/dao/DaoTestSuite.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright © 2016 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;
+
+import org.cassandraunit.dataset.cql.ClassPathCQLDataSet;
+import org.junit.ClassRule;
+import org.junit.extensions.cpsuite.ClasspathSuite;
+import org.junit.extensions.cpsuite.ClasspathSuite.ClassnameFilters;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+@RunWith(ClasspathSuite.class)
+@ClassnameFilters({
+        "org.thingsboard.server.dao.service.*Test",
+        "org.thingsboard.server.dao.kv.*Test",
+        "org.thingsboard.server.dao.plugin.*Test",
+        "org.thingsboard.server.dao.rule.*Test",
+        "org.thingsboard.server.dao.attributes.*Test",
+        "org.thingsboard.server.dao.timeseries.*Test"
+})
+public class DaoTestSuite {
+
+    @ClassRule
+    public static CustomCassandraCQLUnit cassandraUnit =
+            new CustomCassandraCQLUnit(
+                    Arrays.asList(new ClassPathCQLDataSet("schema.cql", false, false),
+                                  new ClassPathCQLDataSet("system-data.cql", false, false),
+                                  new ClassPathCQLDataSet("system-test.cql", false, false)),
+                    "cassandra-test.yaml", 30000l);
+
+}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/event/BaseEventServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/event/BaseEventServiceTest.java
new file mode 100644
index 0000000..ba346bf
--- /dev/null
+++ b/dao/src/test/java/org/thingsboard/server/dao/event/BaseEventServiceTest.java
@@ -0,0 +1,138 @@
+/**
+ * Copyright © 2016 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.event;
+
+import com.datastax.driver.core.utils.UUIDs;
+import org.apache.cassandra.utils.UUIDGen;
+import org.junit.Assert;
+import org.junit.Test;
+import org.thingsboard.server.common.data.DataConstants;
+import org.thingsboard.server.common.data.Event;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.EventId;
+import org.thingsboard.server.common.data.id.RuleId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TimePageData;
+import org.thingsboard.server.common.data.page.TimePageLink;
+import org.thingsboard.server.dao.service.AbstractServiceTest;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.ZoneOffset;
+import java.util.List;
+import java.util.Optional;
+
+public class BaseEventServiceTest extends AbstractServiceTest {
+
+    @Test
+    public void saveEvent() throws Exception {
+        DeviceId devId = new DeviceId(UUIDs.timeBased());
+        Event event = generateEvent(null, devId, "ALARM", UUIDs.timeBased().toString());
+        Event saved = eventService.save(event);
+        Optional<Event> loaded = eventService.findEvent(event.getTenantId(), event.getEntityId(), event.getType(), event.getUid());
+        Assert.assertTrue(loaded.isPresent());
+        Assert.assertNotNull(loaded.get());
+        Assert.assertEquals(saved, loaded.get());
+    }
+
+    @Test
+    public void saveEventIfNotExists() throws Exception {
+        DeviceId devId = new DeviceId(UUIDs.timeBased());
+        Event event = generateEvent(null, devId, "ALARM", UUIDs.timeBased().toString());
+        Optional<Event> saved = eventService.saveIfNotExists(event);
+        Assert.assertTrue(saved.isPresent());
+        saved = eventService.saveIfNotExists(event);
+        Assert.assertFalse(saved.isPresent());
+    }
+
+    @Test
+    public void findEventsByTypeAndTimeAscOrder() throws Exception {
+        long timeBeforeStartTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 11, 30).toEpochSecond(ZoneOffset.UTC);
+        long startTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 12, 0).toEpochSecond(ZoneOffset.UTC);
+        long eventTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 12, 30).toEpochSecond(ZoneOffset.UTC);
+        long endTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 13, 0).toEpochSecond(ZoneOffset.UTC);
+        long timeAfterEndTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 13, 30).toEpochSecond(ZoneOffset.UTC);
+
+        RuleId ruleId = new RuleId(UUIDs.timeBased());
+        TenantId tenantId = new TenantId(UUIDs.timeBased());
+        saveEventWithProvidedTime(timeBeforeStartTime, ruleId, tenantId);
+        Event savedEvent = saveEventWithProvidedTime(eventTime, ruleId, tenantId);
+        Event savedEvent2 = saveEventWithProvidedTime(eventTime+1, ruleId, tenantId);
+        Event savedEvent3 = saveEventWithProvidedTime(eventTime+2, ruleId, tenantId);
+        saveEventWithProvidedTime(timeAfterEndTime, ruleId, tenantId);
+
+        TimePageData<Event> events = eventService.findEvents(tenantId, ruleId, DataConstants.STATS,
+                new TimePageLink(2, startTime, endTime, true));
+
+        Assert.assertNotNull(events.getData());
+        Assert.assertTrue(events.getData().size() == 2);
+        Assert.assertTrue(events.getData().get(0).getUuidId().equals(savedEvent.getUuidId()));
+        Assert.assertTrue(events.getData().get(1).getUuidId().equals(savedEvent2.getUuidId()));
+        Assert.assertTrue(events.hasNext());
+        Assert.assertNotNull(events.getNextPageLink());
+
+        events = eventService.findEvents(tenantId, ruleId, DataConstants.STATS, events.getNextPageLink());
+
+        Assert.assertNotNull(events.getData());
+        Assert.assertTrue(events.getData().size() == 1);
+        Assert.assertTrue(events.getData().get(0).getUuidId().equals(savedEvent3.getUuidId()));
+        Assert.assertFalse(events.hasNext());
+        Assert.assertNull(events.getNextPageLink());
+    }
+
+    @Test
+    public void findEventsByTypeAndTimeDescOrder() throws Exception {
+        long timeBeforeStartTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 11, 30).toEpochSecond(ZoneOffset.UTC);
+        long startTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 12, 0).toEpochSecond(ZoneOffset.UTC);
+        long eventTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 12, 30).toEpochSecond(ZoneOffset.UTC);
+        long endTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 13, 0).toEpochSecond(ZoneOffset.UTC);
+        long timeAfterEndTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 13, 30).toEpochSecond(ZoneOffset.UTC);
+
+        RuleId ruleId = new RuleId(UUIDs.timeBased());
+        TenantId tenantId = new TenantId(UUIDs.timeBased());
+        saveEventWithProvidedTime(timeBeforeStartTime, ruleId, tenantId);
+        Event savedEvent = saveEventWithProvidedTime(eventTime, ruleId, tenantId);
+        Event savedEvent2 = saveEventWithProvidedTime(eventTime+1, ruleId, tenantId);
+        Event savedEvent3 = saveEventWithProvidedTime(eventTime+2, ruleId, tenantId);
+        saveEventWithProvidedTime(timeAfterEndTime, ruleId, tenantId);
+
+        TimePageData<Event> events = eventService.findEvents(tenantId, ruleId, DataConstants.STATS,
+                new TimePageLink(2, startTime, endTime, false));
+
+        Assert.assertNotNull(events.getData());
+        Assert.assertTrue(events.getData().size() == 2);
+        Assert.assertTrue(events.getData().get(0).getUuidId().equals(savedEvent3.getUuidId()));
+        Assert.assertTrue(events.getData().get(1).getUuidId().equals(savedEvent2.getUuidId()));
+        Assert.assertTrue(events.hasNext());
+        Assert.assertNotNull(events.getNextPageLink());
+
+        events = eventService.findEvents(tenantId, ruleId, DataConstants.STATS, events.getNextPageLink());
+
+        Assert.assertNotNull(events.getData());
+        Assert.assertTrue(events.getData().size() == 1);
+        Assert.assertTrue(events.getData().get(0).getUuidId().equals(savedEvent.getUuidId()));
+        Assert.assertFalse(events.hasNext());
+        Assert.assertNull(events.getNextPageLink());
+    }
+
+    private Event saveEventWithProvidedTime(long time, EntityId entityId, TenantId tenantId) throws IOException {
+        Event event = generateEvent(tenantId, entityId, DataConstants.STATS, null);
+        event.setId(new EventId(UUIDs.startOf(time)));
+        return eventService.save(event);
+    }
+}
\ No newline at end of file
diff --git a/dao/src/test/java/org/thingsboard/server/dao/plugin/BasePluginServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/plugin/BasePluginServiceTest.java
new file mode 100644
index 0000000..4fd3d38
--- /dev/null
+++ b/dao/src/test/java/org/thingsboard/server/dao/plugin/BasePluginServiceTest.java
@@ -0,0 +1,118 @@
+/**
+ * Copyright © 2016 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.plugin;
+
+import java.util.UUID;
+
+import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.plugin.PluginMetaData;
+import org.thingsboard.server.common.data.rule.RuleMetaData;
+import org.thingsboard.server.dao.service.AbstractServiceTest;
+import org.junit.Assert;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.datastax.driver.core.utils.UUIDs;
+import org.thingsboard.server.dao.model.ModelConstants;
+
+@Slf4j
+public class BasePluginServiceTest extends AbstractServiceTest {
+
+  @Test
+  public void savePlugin() throws Exception {
+    PluginMetaData pluginMetaData = pluginService.savePlugin(generatePlugin(null, null));
+    Assert.assertNotNull(pluginMetaData.getId());
+    Assert.assertNotNull(pluginMetaData.getAdditionalInfo());
+
+    pluginMetaData.setAdditionalInfo(mapper.readTree("{\"description\":\"test\"}"));
+    PluginMetaData newPluginMetaData = pluginService.savePlugin(pluginMetaData);
+    Assert.assertEquals(pluginMetaData.getAdditionalInfo(), newPluginMetaData.getAdditionalInfo());
+
+  }
+
+  @Test
+  public void findPluginById() throws Exception {
+    PluginMetaData expected = pluginService.savePlugin(generatePlugin(null, null));
+    Assert.assertNotNull(expected.getId());
+    PluginMetaData found = pluginService.findPluginById(expected.getId());
+    Assert.assertEquals(expected, found);
+  }
+
+  @Test
+  public void findPluginByTenantIdAndApiToken() throws Exception {
+    String token = UUID.randomUUID().toString();
+    TenantId tenantId = new TenantId(UUIDs.timeBased());
+    pluginService.savePlugin(generatePlugin(null, null));
+    pluginService.savePlugin(generatePlugin(tenantId, null));
+    pluginService.savePlugin(generatePlugin(tenantId, null));
+    pluginService.savePlugin(generatePlugin(tenantId, null));
+    PluginMetaData expected = pluginService.savePlugin(generatePlugin(tenantId, token));
+    Assert.assertNotNull(expected.getId());
+    PluginMetaData found = pluginService.findPluginByApiToken(token);
+    Assert.assertEquals(expected, found);
+  }
+
+  @Test
+  public void findSystemPlugins() throws Exception {
+    TenantId systemTenant = new TenantId(ModelConstants.NULL_UUID); // system tenant id
+    pluginService.savePlugin(generatePlugin(null, null));
+    pluginService.savePlugin(generatePlugin(null, null));
+    pluginService.savePlugin(generatePlugin(systemTenant, null));
+    pluginService.savePlugin(generatePlugin(systemTenant, null));
+    TextPageData<PluginMetaData> found = pluginService.findSystemPlugins(new TextPageLink(100));
+    Assert.assertEquals(2, found.getData().size());
+    Assert.assertFalse(found.hasNext());
+  }
+
+  @Test
+  public void findTenantPlugins() throws Exception {
+    TenantId tenantId = new TenantId(UUIDs.timeBased());
+    pluginService.savePlugin(generatePlugin(null, null));
+    pluginService.savePlugin(generatePlugin(null, null));
+    pluginService.savePlugin(generatePlugin(tenantId, null));
+    pluginService.savePlugin(generatePlugin(tenantId, null));
+    pluginService.savePlugin(generatePlugin(tenantId, null));
+    TextPageData<PluginMetaData> found = pluginService.findTenantPlugins(tenantId, new TextPageLink(100));
+    Assert.assertEquals(3, found.getData().size());
+  }
+
+  @Test
+  public void deletePluginById() throws Exception {
+    PluginMetaData expected = pluginService.savePlugin(generatePlugin(null, null));
+    Assert.assertNotNull(expected.getId());
+    pluginService.deletePluginById(expected.getId());
+    PluginMetaData found = pluginService.findPluginById(expected.getId());
+    Assert.assertNull(found);
+  }
+
+  @Test
+  public void deletePluginsByTenantId() throws Exception {
+    TenantId tenantId = new TenantId(UUIDs.timeBased());
+    pluginService.savePlugin(generatePlugin(tenantId, null));
+    pluginService.savePlugin(generatePlugin(tenantId, null));
+    pluginService.savePlugin(generatePlugin(tenantId, null));
+    TextPageData<PluginMetaData> found = pluginService.findTenantPlugins(tenantId, new TextPageLink(100));
+    Assert.assertEquals(3, found.getData().size());
+    pluginService.deletePluginsByTenantId(tenantId);
+    found = pluginService.findTenantPlugins(tenantId, new TextPageLink(100));
+    Assert.assertEquals(0, found.getData().size());
+  }
+
+}
\ No newline at end of file
diff --git a/dao/src/test/java/org/thingsboard/server/dao/rule/BaseRuleServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/rule/BaseRuleServiceTest.java
new file mode 100644
index 0000000..12566ad
--- /dev/null
+++ b/dao/src/test/java/org/thingsboard/server/dao/rule/BaseRuleServiceTest.java
@@ -0,0 +1,163 @@
+/**
+ * Copyright © 2016 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.rule;
+
+import com.datastax.driver.core.utils.UUIDs;
+import org.junit.Assert;
+import org.junit.Test;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.plugin.PluginMetaData;
+import org.thingsboard.server.common.data.rule.RuleMetaData;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.dao.service.AbstractServiceTest;
+
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+
+public class BaseRuleServiceTest extends AbstractServiceTest {
+
+    @Test
+    public void saveRule() throws Exception {
+        PluginMetaData plugin = generatePlugin(null, "testPluginToken" + ThreadLocalRandom.current().nextInt());
+        pluginService.savePlugin(plugin);
+        RuleMetaData ruleMetaData = ruleService.saveRule(generateRule(plugin.getTenantId(), null, plugin.getApiToken()));
+        Assert.assertNotNull(ruleMetaData.getId());
+        Assert.assertNotNull(ruleMetaData.getAdditionalInfo());
+        ruleMetaData.setAdditionalInfo(mapper.readTree("{\"description\":\"test\"}"));
+        RuleMetaData newRuleMetaData = ruleService.saveRule(ruleMetaData);
+        Assert.assertEquals(ruleMetaData.getAdditionalInfo(), newRuleMetaData.getAdditionalInfo());
+    }
+
+    @Test
+    public void findRuleById() throws Exception {
+        PluginMetaData plugin = generatePlugin(null, "testPluginToken" + ThreadLocalRandom.current().nextInt());
+        pluginService.savePlugin(plugin);
+
+        RuleMetaData expected = ruleService.saveRule(generateRule(plugin.getTenantId(), null, plugin.getApiToken()));
+        Assert.assertNotNull(expected.getId());
+        RuleMetaData found = ruleService.findRuleById(expected.getId());
+        Assert.assertEquals(expected, found);
+    }
+
+    @Test
+    public void findPluginRules() throws Exception {
+        TenantId tenantIdA = new TenantId(UUIDs.timeBased());
+        TenantId tenantIdB = new TenantId(UUIDs.timeBased());
+
+        PluginMetaData pluginA = generatePlugin(tenantIdA, "testPluginToken" + ThreadLocalRandom.current().nextInt());
+        PluginMetaData pluginB = generatePlugin(tenantIdB, "testPluginToken" + ThreadLocalRandom.current().nextInt());
+        pluginService.savePlugin(pluginA);
+        pluginService.savePlugin(pluginB);
+
+        ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken()));
+        ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken()));
+        ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken()));
+
+        ruleService.saveRule(generateRule(tenantIdB, null, pluginB.getApiToken()));
+        ruleService.saveRule(generateRule(tenantIdB, null, pluginB.getApiToken()));
+
+        List<RuleMetaData> foundA = ruleService.findPluginRules(pluginA.getApiToken());
+        Assert.assertEquals(3, foundA.size());
+
+        List<RuleMetaData> foundB = ruleService.findPluginRules(pluginB.getApiToken());
+        Assert.assertEquals(2, foundB.size());
+    }
+
+    @Test
+    public void findSystemRules() throws Exception {
+        TenantId systemTenant = new TenantId(ModelConstants.NULL_UUID); // system tenant id
+
+        PluginMetaData plugin = generatePlugin(systemTenant, "testPluginToken" + ThreadLocalRandom.current().nextInt());
+        pluginService.savePlugin(plugin);
+        ruleService.saveRule(generateRule(systemTenant, null, plugin.getApiToken()));
+        ruleService.saveRule(generateRule(systemTenant, null, plugin.getApiToken()));
+        ruleService.saveRule(generateRule(systemTenant, null, plugin.getApiToken()));
+        TextPageData<RuleMetaData> found = ruleService.findSystemRules(new TextPageLink(100));
+        Assert.assertEquals(3, found.getData().size());
+    }
+
+    @Test
+    public void findTenantRules() throws Exception {
+        TenantId tenantIdA = new TenantId(UUIDs.timeBased());
+        TenantId tenantIdB = new TenantId(UUIDs.timeBased());
+
+        PluginMetaData pluginA = generatePlugin(tenantIdA, "testPluginToken" + ThreadLocalRandom.current().nextInt());
+        PluginMetaData pluginB = generatePlugin(tenantIdB, "testPluginToken" + ThreadLocalRandom.current().nextInt());
+        pluginService.savePlugin(pluginA);
+        pluginService.savePlugin(pluginB);
+
+        ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken()));
+        ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken()));
+        ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken()));
+
+        ruleService.saveRule(generateRule(tenantIdB, null, pluginB.getApiToken()));
+        ruleService.saveRule(generateRule(tenantIdB, null, pluginB.getApiToken()));
+
+        TextPageData<RuleMetaData> foundA = ruleService.findTenantRules(tenantIdA, new TextPageLink(100));
+        Assert.assertEquals(3, foundA.getData().size());
+
+        TextPageData<RuleMetaData> foundB = ruleService.findTenantRules(tenantIdB, new TextPageLink(100));
+        Assert.assertEquals(2, foundB.getData().size());
+    }
+
+    @Test
+    public void deleteRuleById() throws Exception {
+        PluginMetaData plugin = generatePlugin(null, "testPluginToken" + ThreadLocalRandom.current().nextInt());
+        pluginService.savePlugin(plugin);
+
+        RuleMetaData expected = ruleService.saveRule(generateRule(plugin.getTenantId(), null, plugin.getApiToken()));
+        Assert.assertNotNull(expected.getId());
+        RuleMetaData found = ruleService.findRuleById(expected.getId());
+        Assert.assertEquals(expected, found);
+        ruleService.deleteRuleById(expected.getId());
+        found = ruleService.findRuleById(expected.getId());
+        Assert.assertNull(found);
+    }
+
+    @Test
+    public void deleteRulesByTenantId() throws Exception {
+        TenantId tenantIdA = new TenantId(UUIDs.timeBased());
+        TenantId tenantIdB = new TenantId(UUIDs.timeBased());
+
+        PluginMetaData pluginA = generatePlugin(tenantIdA, "testPluginToken" + ThreadLocalRandom.current().nextInt());
+        PluginMetaData pluginB = generatePlugin(tenantIdB, "testPluginToken" + ThreadLocalRandom.current().nextInt());
+        pluginService.savePlugin(pluginA);
+        pluginService.savePlugin(pluginB);
+
+        ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken()));
+        ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken()));
+        ruleService.saveRule(generateRule(tenantIdA, null, pluginA.getApiToken()));
+
+        ruleService.saveRule(generateRule(tenantIdB, null, pluginB.getApiToken()));
+        ruleService.saveRule(generateRule(tenantIdB, null, pluginB.getApiToken()));
+
+        TextPageData<RuleMetaData> foundA = ruleService.findTenantRules(tenantIdA, new TextPageLink(100));
+        Assert.assertEquals(3, foundA.getData().size());
+
+        TextPageData<RuleMetaData> foundB = ruleService.findTenantRules(tenantIdB, new TextPageLink(100));
+        Assert.assertEquals(2, foundB.getData().size());
+
+        ruleService.deleteRulesByTenantId(tenantIdA);
+
+        foundA = ruleService.findTenantRules(tenantIdA, new TextPageLink(100));
+        Assert.assertEquals(0, foundA.getData().size());
+
+        foundB = ruleService.findTenantRules(tenantIdB, new TextPageLink(100));
+        Assert.assertEquals(2, foundB.getData().size());
+    }
+}
\ No newline at end of file
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
new file mode 100644
index 0000000..8d60568
--- /dev/null
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
@@ -0,0 +1,221 @@
+/**
+ * Copyright © 2016 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.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+import org.thingsboard.server.common.data.BaseData;
+import org.thingsboard.server.common.data.Event;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.id.UUIDBased;
+import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
+import org.thingsboard.server.common.data.plugin.ComponentScope;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.common.data.plugin.PluginMetaData;
+import org.thingsboard.server.common.data.rule.RuleMetaData;
+import org.thingsboard.server.dao.component.ComponentDescriptorService;
+import org.thingsboard.server.dao.customer.CustomerService;
+import org.thingsboard.server.dao.dashboard.DashboardService;
+import org.thingsboard.server.dao.device.DeviceCredentialsService;
+import org.thingsboard.server.dao.device.DeviceService;
+import org.thingsboard.server.dao.event.EventService;
+import org.thingsboard.server.dao.plugin.PluginService;
+import org.thingsboard.server.dao.rule.RuleService;
+import org.thingsboard.server.dao.settings.AdminSettingsService;
+import org.thingsboard.server.dao.tenant.TenantService;
+import org.thingsboard.server.dao.timeseries.TimeseriesService;
+import org.thingsboard.server.dao.user.UserService;
+import org.thingsboard.server.dao.widget.WidgetTypeService;
+import org.thingsboard.server.dao.widget.WidgetsBundleService;
+
+import java.io.IOException;
+import java.util.Comparator;
+import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = AbstractServiceTest.class, loader = AnnotationConfigContextLoader.class)
+@TestPropertySource(locations = {"classpath:cassandra-test.properties", "classpath:application-test.properties"})
+@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
+@Configuration
+@EnableAutoConfiguration
+@ComponentScan("org.thingsboard.server")
+public abstract class AbstractServiceTest {
+
+    protected ObjectMapper mapper = new ObjectMapper();
+
+    @Autowired
+    protected UserService userService;
+
+    @Autowired
+    protected AdminSettingsService adminSettingsService;
+
+    @Autowired
+    protected TenantService tenantService;
+
+    @Autowired
+    protected CustomerService customerService;
+
+    @Autowired
+    protected DeviceService deviceService;
+
+    @Autowired
+    protected DeviceCredentialsService deviceCredentialsService;
+
+    @Autowired
+    protected WidgetsBundleService widgetsBundleService;
+
+    @Autowired
+    protected WidgetTypeService widgetTypeService;
+
+    @Autowired
+    protected DashboardService dashboardService;
+
+    @Autowired
+    protected TimeseriesService tsService;
+
+    @Autowired
+    protected PluginService pluginService;
+
+    @Autowired
+    protected RuleService ruleService;
+
+    @Autowired
+    protected EventService eventService;
+
+    @Autowired
+    private ComponentDescriptorService componentDescriptorService;
+
+    class IdComparator<D extends BaseData<? extends UUIDBased>> implements Comparator<D> {
+        @Override
+        public int compare(D o1, D o2) {
+            return o1.getId().getId().compareTo(o2.getId().getId());
+        }
+    }
+
+
+    protected Event generateEvent(TenantId tenantId, EntityId entityId, String eventType, String eventUid) throws IOException {
+        if (tenantId == null) {
+            tenantId = new TenantId(UUIDs.timeBased());
+        }
+        Event event = new Event();
+        event.setTenantId(tenantId);
+        event.setEntityId(entityId);
+        event.setType(eventType);
+        event.setUid(eventUid);
+        event.setBody(readFromResource("TestJsonData.json"));
+        return event;
+    }
+
+    protected PluginMetaData generatePlugin(TenantId tenantId, String token) throws IOException {
+        return generatePlugin(tenantId, token, "org.thingsboard.component.PluginTest", "org.thingsboard.component.ActionTest", "TestJsonDescriptor.json", "TestJsonData.json");
+    }
+
+    protected PluginMetaData generatePlugin(TenantId tenantId, String token, String clazz, String actions, String configurationDescriptorResource, String dataResource) throws IOException {
+        if (tenantId == null) {
+            tenantId = new TenantId(UUIDs.timeBased());
+        }
+        if (token == null) {
+            token = UUID.randomUUID().toString();
+        }
+        getOrCreateDescriptor(ComponentScope.TENANT, ComponentType.PLUGIN, clazz, configurationDescriptorResource, actions);
+        PluginMetaData pluginMetaData = new PluginMetaData();
+        pluginMetaData.setName("Testing");
+        pluginMetaData.setClazz(clazz);
+        pluginMetaData.setTenantId(tenantId);
+        pluginMetaData.setApiToken(token);
+        pluginMetaData.setAdditionalInfo(mapper.readTree("{\"test\":\"test\"}"));
+        try {
+            pluginMetaData.setConfiguration(readFromResource(dataResource));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        return pluginMetaData;
+    }
+
+    private ComponentDescriptor getOrCreateDescriptor(ComponentScope scope, ComponentType type, String clazz, String configurationDescriptorResource) throws IOException {
+        return getOrCreateDescriptor(scope, type, clazz, configurationDescriptorResource, null);
+    }
+
+    private ComponentDescriptor getOrCreateDescriptor(ComponentScope scope, ComponentType type, String clazz, String configurationDescriptorResource, String actions) throws IOException {
+        ComponentDescriptor descriptor = componentDescriptorService.findByClazz(clazz);
+        if (descriptor == null) {
+            descriptor = new ComponentDescriptor();
+            descriptor.setName("test");
+            descriptor.setClazz(clazz);
+            descriptor.setScope(scope);
+            descriptor.setType(type);
+            descriptor.setActions(actions);
+            descriptor.setConfigurationDescriptor(readFromResource(configurationDescriptorResource));
+            componentDescriptorService.saveComponent(descriptor);
+        }
+        return descriptor;
+    }
+
+    public JsonNode readFromResource(String resourceName) throws IOException {
+        return mapper.readTree(this.getClass().getClassLoader().getResourceAsStream(resourceName));
+    }
+
+    protected RuleMetaData generateRule(TenantId tenantId, Integer weight, String pluginToken) throws IOException {
+        if (tenantId == null) {
+            tenantId = new TenantId(UUIDs.timeBased());
+        }
+        if (weight == null) {
+            weight = ThreadLocalRandom.current().nextInt();
+        }
+
+        RuleMetaData ruleMetaData = new RuleMetaData();
+        ruleMetaData.setName("Testing");
+        ruleMetaData.setTenantId(tenantId);
+        ruleMetaData.setWeight(weight);
+        ruleMetaData.setPluginToken(pluginToken);
+
+        ruleMetaData.setAction(createNode(ComponentScope.TENANT, ComponentType.ACTION,
+                "org.thingsboard.component.ActionTest", "TestJsonDescriptor.json", "TestJsonData.json"));
+        ruleMetaData.setProcessor(createNode(ComponentScope.TENANT, ComponentType.PROCESSOR,
+                "org.thingsboard.component.ProcessorTest", "TestJsonDescriptor.json", "TestJsonData.json"));
+        ruleMetaData.setFilters(mapper.createArrayNode().add(
+                createNode(ComponentScope.TENANT, ComponentType.FILTER,
+                        "org.thingsboard.component.FilterTest", "TestJsonDescriptor.json", "TestJsonData.json")
+        ));
+
+        ruleMetaData.setAdditionalInfo(mapper.readTree("{}"));
+        return ruleMetaData;
+    }
+
+    protected JsonNode createNode(ComponentScope scope, ComponentType type, String clazz, String configurationDescriptor, String configuration) throws IOException {
+        getOrCreateDescriptor(scope, type, clazz, configurationDescriptor);
+        ObjectNode oNode = mapper.createObjectNode();
+        oNode.set("name", new TextNode("test action"));
+        oNode.set("clazz", new TextNode(clazz));
+        oNode.set("configuration", readFromResource(configuration));
+        return oNode;
+    }
+}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AdminSettingsServiceImplTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AdminSettingsServiceImplTest.java
new file mode 100644
index 0000000..4e06e49
--- /dev/null
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/AdminSettingsServiceImplTest.java
@@ -0,0 +1,99 @@
+/**
+ * Copyright © 2016 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 org.thingsboard.server.common.data.AdminSettings;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+
+public class AdminSettingsServiceImplTest extends AbstractServiceTest {
+
+    @Test
+    public void testFindAdminSettingsByKey() {
+        AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey("general");
+        Assert.assertNotNull(adminSettings);
+        adminSettings = adminSettingsService.findAdminSettingsByKey("mail");
+        Assert.assertNotNull(adminSettings);
+        adminSettings = adminSettingsService.findAdminSettingsByKey("unknown");
+        Assert.assertNull(adminSettings);
+    }
+    
+    @Test
+    public void testFindAdminSettingsById() {
+        AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey("general");
+        AdminSettings foundAdminSettings = adminSettingsService.findAdminSettingsById(adminSettings.getId());
+        Assert.assertNotNull(foundAdminSettings);
+        Assert.assertEquals(adminSettings, foundAdminSettings);
+    }
+    
+    @Test
+    public void testSaveAdminSettings() throws Exception {
+        AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey("general");
+        JsonNode json = adminSettings.getJsonValue();
+        ((ObjectNode) json).put("baseUrl", "http://myhost.org");
+        adminSettings.setJsonValue(json);
+        adminSettingsService.saveAdminSettings(adminSettings);
+        AdminSettings savedAdminSettings = adminSettingsService.findAdminSettingsByKey("general");
+        Assert.assertNotNull(savedAdminSettings);
+        Assert.assertEquals(adminSettings.getJsonValue(), savedAdminSettings.getJsonValue());
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testCreateAdminSettings() throws Exception {
+        AdminSettings adminSettings = new AdminSettings();
+        adminSettings.setKey("someKey");
+        adminSettings.setJsonValue(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class));
+        adminSettingsService.saveAdminSettings(adminSettings);
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testSaveAdminSettingsWithEmptyKey() {
+        AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey("mail");
+        adminSettings.setKey(null);
+        adminSettingsService.saveAdminSettings(adminSettings);
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testChangeAdminSettingsKey() {
+        AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey("mail");
+        adminSettings.setKey("newKey");
+        adminSettingsService.saveAdminSettings(adminSettings);
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testSaveAdminSettingsWithNewJsonStructure() throws Exception {
+        AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey("mail");
+        JsonNode json = adminSettings.getJsonValue();
+        ((ObjectNode) json).put("newKey", "my new value");
+        adminSettings.setJsonValue(json);
+        adminSettingsService.saveAdminSettings(adminSettings);
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testSaveAdminSettingsWithNonTextValue() throws Exception {
+        AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey("mail");
+        JsonNode json = adminSettings.getJsonValue();
+        ((ObjectNode) json).put("timeout", 10000L);
+        adminSettings.setJsonValue(json);
+        adminSettingsService.saveAdminSettings(adminSettings);
+    }
+}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/CustomerServiceImplTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/CustomerServiceImplTest.java
new file mode 100644
index 0000000..f9df77f
--- /dev/null
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/CustomerServiceImplTest.java
@@ -0,0 +1,249 @@
+/**
+ * Copyright © 2016 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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.After;
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.datastax.driver.core.utils.UUIDs;
+
+public class CustomerServiceImplTest extends AbstractServiceTest {
+    
+    private IdComparator<Customer> idComparator = new IdComparator<>();
+    
+    private TenantId tenantId;
+    
+    @Before
+    public void before() {
+        Tenant tenant = new Tenant();
+        tenant.setTitle("My tenant");
+        Tenant savedTenant = tenantService.saveTenant(tenant);
+        Assert.assertNotNull(savedTenant);
+        tenantId = savedTenant.getId();
+    }
+
+    @After
+    public void after() {
+        tenantService.deleteTenant(tenantId);
+    }
+
+    @Test
+    public void testSaveCustomer() {
+        Customer customer = new Customer();
+        customer.setTenantId(tenantId);
+        customer.setTitle("My customer");
+        Customer savedCustomer = customerService.saveCustomer(customer);
+        
+        Assert.assertNotNull(savedCustomer);
+        Assert.assertNotNull(savedCustomer.getId());
+        Assert.assertTrue(savedCustomer.getCreatedTime() > 0);
+        Assert.assertEquals(customer.getTenantId(), savedCustomer.getTenantId());
+        Assert.assertEquals(customer.getTitle(), savedCustomer.getTitle());
+        
+        
+        savedCustomer.setTitle("My new customer");
+        
+        customerService.saveCustomer(savedCustomer);
+        Customer foundCustomer = customerService.findCustomerById(savedCustomer.getId());
+        Assert.assertEquals(foundCustomer.getTitle(), savedCustomer.getTitle());
+        
+        customerService.deleteCustomer(savedCustomer.getId());
+    }
+    
+    @Test
+    public void testFindCustomerById() {
+        Customer customer = new Customer();
+        customer.setTenantId(tenantId);
+        customer.setTitle("My customer");
+        Customer savedCustomer = customerService.saveCustomer(customer);
+        Customer foundCustomer = customerService.findCustomerById(savedCustomer.getId());
+        Assert.assertNotNull(foundCustomer);
+        Assert.assertEquals(savedCustomer, foundCustomer);
+        customerService.deleteCustomer(savedCustomer.getId());
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testSaveCustomerWithEmptyTitle() {
+        Customer customer = new Customer();
+        customer.setTenantId(tenantId);
+        customerService.saveCustomer(customer);
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testSaveCustomerWithEmptyTenant() {
+        Customer customer = new Customer();
+        customer.setTitle("My customer");
+        customerService.saveCustomer(customer);
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testSaveCustomerWithInvalidTenant() {
+        Customer customer = new Customer();
+        customer.setTitle("My customer");
+        customer.setTenantId(new TenantId(UUIDs.timeBased()));
+        customerService.saveCustomer(customer);
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testSaveCustomerWithInvalidEmail() {
+        Customer customer = new Customer();
+        customer.setTenantId(tenantId);
+        customer.setTitle("My customer");
+        customer.setEmail("invalid@mail");
+        customerService.saveCustomer(customer);
+    }
+    
+    @Test
+    public void testDeleteCustomer() {
+        Customer customer = new Customer();
+        customer.setTitle("My customer");
+        customer.setTenantId(tenantId);
+        Customer savedCustomer = customerService.saveCustomer(customer);
+        customerService.deleteCustomer(savedCustomer.getId());
+        Customer foundCustomer = customerService.findCustomerById(savedCustomer.getId());
+        Assert.assertNull(foundCustomer);
+    }
+    
+    @Test
+    public void testFindCustomersByTenantId() {
+        Tenant tenant = new Tenant();
+        tenant.setTitle("Test tenant");
+        tenant = tenantService.saveTenant(tenant);
+        
+        TenantId tenantId = tenant.getId();
+        
+        List<Customer> customers = new ArrayList<>();
+        for (int i=0;i<135;i++) {
+            Customer customer = new Customer();
+            customer.setTenantId(tenantId);
+            customer.setTitle("Customer"+i);
+            customers.add(customerService.saveCustomer(customer));
+        }
+        
+        List<Customer> loadedCustomers = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(23);
+        TextPageData<Customer> pageData = null;
+        do {
+            pageData = customerService.findCustomersByTenantId(tenantId, pageLink);
+            loadedCustomers.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+        
+        Collections.sort(customers, idComparator);
+        Collections.sort(loadedCustomers, idComparator);
+        
+        Assert.assertEquals(customers, loadedCustomers);
+        
+        customerService.deleteCustomersByTenantId(tenantId);
+
+        pageLink = new TextPageLink(33);
+        pageData = customerService.findCustomersByTenantId(tenantId, pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertTrue(pageData.getData().isEmpty());
+        
+        tenantService.deleteTenant(tenantId);
+    }
+    
+    @Test
+    public void testFindCustomersByTenantIdAndTitle() {
+        String title1 = "Customer title 1";
+        List<Customer> customersTitle1 = new ArrayList<>();
+        for (int i=0;i<143;i++) {
+            Customer customer = new Customer();
+            customer.setTenantId(tenantId);
+            String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
+            String title = title1+suffix;
+            title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
+            customer.setTitle(title);
+            customersTitle1.add(customerService.saveCustomer(customer));
+        }
+        String title2 = "Customer title 2";
+        List<Customer> customersTitle2 = new ArrayList<>();
+        for (int i=0;i<175;i++) {
+            Customer customer = new Customer();
+            customer.setTenantId(tenantId);
+            String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
+            String title = title2+suffix;
+            title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
+            customer.setTitle(title);
+            customersTitle2.add(customerService.saveCustomer(customer));
+        }
+        
+        List<Customer> loadedCustomersTitle1 = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(15, title1);
+        TextPageData<Customer> pageData = null;
+        do {
+            pageData = customerService.findCustomersByTenantId(tenantId, pageLink);
+            loadedCustomersTitle1.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+        
+        Collections.sort(customersTitle1, idComparator);
+        Collections.sort(loadedCustomersTitle1, idComparator);
+        
+        Assert.assertEquals(customersTitle1, loadedCustomersTitle1);
+        
+        List<Customer> loadedCustomersTitle2 = new ArrayList<>();
+        pageLink = new TextPageLink(4, title2);
+        do {
+            pageData = customerService.findCustomersByTenantId(tenantId, pageLink);
+            loadedCustomersTitle2.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+
+        Collections.sort(customersTitle2, idComparator);
+        Collections.sort(loadedCustomersTitle2, idComparator);
+        
+        Assert.assertEquals(customersTitle2, loadedCustomersTitle2);
+
+        for (Customer customer : loadedCustomersTitle1) {
+            customerService.deleteCustomer(customer.getId());
+        }
+        
+        pageLink = new TextPageLink(4, title1);
+        pageData = customerService.findCustomersByTenantId(tenantId, pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertEquals(0, pageData.getData().size());
+        
+        for (Customer customer : loadedCustomersTitle2) {
+            customerService.deleteCustomer(customer.getId());
+        }
+        
+        pageLink = new TextPageLink(4, title2);
+        pageData = customerService.findCustomersByTenantId(tenantId, pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertEquals(0, pageData.getData().size());
+    }
+}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/DashboardServiceImplTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/DashboardServiceImplTest.java
new file mode 100644
index 0000000..842463c
--- /dev/null
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/DashboardServiceImplTest.java
@@ -0,0 +1,414 @@
+/**
+ * Copyright © 2016 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 org.apache.commons.lang3.RandomStringUtils;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.Dashboard;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.model.ModelConstants;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class DashboardServiceImplTest extends AbstractServiceTest {
+    
+    private IdComparator<Dashboard> idComparator = new IdComparator<>();
+    
+    private TenantId tenantId;
+
+    @Before
+    public void before() {
+        Tenant tenant = new Tenant();
+        tenant.setTitle("My tenant");
+        Tenant savedTenant = tenantService.saveTenant(tenant);
+        Assert.assertNotNull(savedTenant);
+        tenantId = savedTenant.getId();
+    }
+
+    @After
+    public void after() {
+        tenantService.deleteTenant(tenantId);
+    }
+    
+    @Test
+    public void testSaveDashboard() throws IOException {
+        Dashboard dashboard = new Dashboard();
+        dashboard.setTenantId(tenantId);
+        dashboard.setTitle("My dashboard");
+        Dashboard savedDashboard = dashboardService.saveDashboard(dashboard);
+        
+        Assert.assertNotNull(savedDashboard);
+        Assert.assertNotNull(savedDashboard.getId());
+        Assert.assertTrue(savedDashboard.getCreatedTime() > 0);
+        Assert.assertEquals(dashboard.getTenantId(), savedDashboard.getTenantId());
+        Assert.assertNotNull(savedDashboard.getCustomerId());
+        Assert.assertEquals(ModelConstants.NULL_UUID, savedDashboard.getCustomerId().getId());
+        Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle());
+        
+        savedDashboard.setTitle("My new dashboard");
+        
+        dashboardService.saveDashboard(savedDashboard);
+        Dashboard foundDashboard = dashboardService.findDashboardById(savedDashboard.getId());
+        Assert.assertEquals(foundDashboard.getTitle(), savedDashboard.getTitle());
+        
+        dashboardService.deleteDashboard(savedDashboard.getId());
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testSaveDashboardWithEmptyTitle() {
+        Dashboard dashboard = new Dashboard();
+        dashboard.setTenantId(tenantId);
+        dashboardService.saveDashboard(dashboard);
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testSaveDashboardWithEmptyTenant() {
+        Dashboard dashboard = new Dashboard();
+        dashboard.setTitle("My dashboard");
+        dashboardService.saveDashboard(dashboard);
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testSaveDashboardWithInvalidTenant() {
+        Dashboard dashboard = new Dashboard();
+        dashboard.setTitle("My dashboard");
+        dashboard.setTenantId(new TenantId(UUIDs.timeBased()));
+        dashboardService.saveDashboard(dashboard);
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testAssignDashboardToNonExistentCustomer() {
+        Dashboard dashboard = new Dashboard();
+        dashboard.setTitle("My dashboard");
+        dashboard.setTenantId(tenantId);
+        dashboard = dashboardService.saveDashboard(dashboard);
+        try {
+            dashboardService.assignDashboardToCustomer(dashboard.getId(), new CustomerId(UUIDs.timeBased()));
+        } finally {
+            dashboardService.deleteDashboard(dashboard.getId());
+        }
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testAssignDashboardToCustomerFromDifferentTenant() {
+        Dashboard dashboard = new Dashboard();
+        dashboard.setTitle("My dashboard");
+        dashboard.setTenantId(tenantId);
+        dashboard = dashboardService.saveDashboard(dashboard);
+        Tenant tenant = new Tenant();
+        tenant.setTitle("Test different tenant");
+        tenant = tenantService.saveTenant(tenant);
+        Customer customer = new Customer();
+        customer.setTenantId(tenant.getId());
+        customer.setTitle("Test different customer");
+        customer = customerService.saveCustomer(customer);
+        try {
+            dashboardService.assignDashboardToCustomer(dashboard.getId(), customer.getId());
+        } finally {
+            dashboardService.deleteDashboard(dashboard.getId());
+            tenantService.deleteTenant(tenant.getId());
+        }
+    }
+    
+    @Test
+    public void testFindDashboardById() {
+        Dashboard dashboard = new Dashboard();
+        dashboard.setTenantId(tenantId);
+        dashboard.setTitle("My dashboard");
+        Dashboard savedDashboard = dashboardService.saveDashboard(dashboard);
+        Dashboard foundDashboard = dashboardService.findDashboardById(savedDashboard.getId());
+        Assert.assertNotNull(foundDashboard);
+        Assert.assertEquals(savedDashboard, foundDashboard);
+        dashboardService.deleteDashboard(savedDashboard.getId());
+    }
+    
+    @Test
+    public void testDeleteDashboard() {
+        Dashboard dashboard = new Dashboard();
+        dashboard.setTenantId(tenantId);
+        dashboard.setTitle("My dashboard");
+        Dashboard savedDashboard = dashboardService.saveDashboard(dashboard);
+        Dashboard foundDashboard = dashboardService.findDashboardById(savedDashboard.getId());
+        Assert.assertNotNull(foundDashboard);
+        dashboardService.deleteDashboard(savedDashboard.getId());
+        foundDashboard = dashboardService.findDashboardById(savedDashboard.getId());
+        Assert.assertNull(foundDashboard);
+    }
+    
+    @Test
+    public void testFindDashboardsByTenantId() {
+        Tenant tenant = new Tenant();
+        tenant.setTitle("Test tenant");
+        tenant = tenantService.saveTenant(tenant);
+        
+        TenantId tenantId = tenant.getId();
+        
+        List<Dashboard> dashboards = new ArrayList<>();
+        for (int i=0;i<165;i++) {
+            Dashboard dashboard = new Dashboard();
+            dashboard.setTenantId(tenantId);
+            dashboard.setTitle("Dashboard"+i);
+            dashboards.add(dashboardService.saveDashboard(dashboard));
+        }
+        
+        List<Dashboard> loadedDashboards = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(16);
+        TextPageData<Dashboard> pageData = null;
+        do {
+            pageData = dashboardService.findDashboardsByTenantId(tenantId, pageLink);
+            loadedDashboards.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+        
+        Collections.sort(dashboards, idComparator);
+        Collections.sort(loadedDashboards, idComparator);
+        
+        Assert.assertEquals(dashboards, loadedDashboards);
+        
+        dashboardService.deleteDashboardsByTenantId(tenantId);
+
+        pageLink = new TextPageLink(31);
+        pageData = dashboardService.findDashboardsByTenantId(tenantId, pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertTrue(pageData.getData().isEmpty());
+        
+        tenantService.deleteTenant(tenantId);
+    }
+    
+    @Test
+    public void testFindDashboardsByTenantIdAndTitle() {
+        String title1 = "Dashboard title 1";
+        List<Dashboard> dashboardsTitle1 = new ArrayList<>();
+        for (int i=0;i<123;i++) {
+            Dashboard dashboard = new Dashboard();
+            dashboard.setTenantId(tenantId);
+            String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*17));
+            String title = title1+suffix;
+            title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
+            dashboard.setTitle(title);
+            dashboardsTitle1.add(dashboardService.saveDashboard(dashboard));
+        }
+        String title2 = "Dashboard title 2";
+        List<Dashboard> dashboardsTitle2 = new ArrayList<>();
+        for (int i=0;i<193;i++) {
+            Dashboard dashboard = new Dashboard();
+            dashboard.setTenantId(tenantId);
+            String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
+            String title = title2+suffix;
+            title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
+            dashboard.setTitle(title);
+            dashboardsTitle2.add(dashboardService.saveDashboard(dashboard));
+        }
+        
+        List<Dashboard> loadedDashboardsTitle1 = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(19, title1);
+        TextPageData<Dashboard> pageData = null;
+        do {
+            pageData = dashboardService.findDashboardsByTenantId(tenantId, pageLink);
+            loadedDashboardsTitle1.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+        
+        Collections.sort(dashboardsTitle1, idComparator);
+        Collections.sort(loadedDashboardsTitle1, idComparator);
+        
+        Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1);
+        
+        List<Dashboard> loadedDashboardsTitle2 = new ArrayList<>();
+        pageLink = new TextPageLink(4, title2);
+        do {
+            pageData = dashboardService.findDashboardsByTenantId(tenantId, pageLink);
+            loadedDashboardsTitle2.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+
+        Collections.sort(dashboardsTitle2, idComparator);
+        Collections.sort(loadedDashboardsTitle2, idComparator);
+        
+        Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2);
+
+        for (Dashboard dashboard : loadedDashboardsTitle1) {
+            dashboardService.deleteDashboard(dashboard.getId());
+        }
+        
+        pageLink = new TextPageLink(4, title1);
+        pageData = dashboardService.findDashboardsByTenantId(tenantId, pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertEquals(0, pageData.getData().size());
+        
+        for (Dashboard dashboard : loadedDashboardsTitle2) {
+            dashboardService.deleteDashboard(dashboard.getId());
+        }
+        
+        pageLink = new TextPageLink(4, title2);
+        pageData = dashboardService.findDashboardsByTenantId(tenantId, pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertEquals(0, pageData.getData().size());
+    }
+    
+    @Test
+    public void testFindDashboardsByTenantIdAndCustomerId() {
+        Tenant tenant = new Tenant();
+        tenant.setTitle("Test tenant");
+        tenant = tenantService.saveTenant(tenant);
+        
+        TenantId tenantId = tenant.getId();
+        
+        Customer customer = new Customer();
+        customer.setTitle("Test customer");
+        customer.setTenantId(tenantId);
+        customer = customerService.saveCustomer(customer);
+        CustomerId customerId = customer.getId();
+        
+        List<Dashboard> dashboards = new ArrayList<>();
+        for (int i=0;i<223;i++) {
+            Dashboard dashboard = new Dashboard();
+            dashboard.setTenantId(tenantId);
+            dashboard.setTitle("Dashboard"+i);
+            dashboard = dashboardService.saveDashboard(dashboard);
+            dashboards.add(dashboardService.assignDashboardToCustomer(dashboard.getId(), customerId));
+        }
+        
+        List<Dashboard> loadedDashboards = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(23);
+        TextPageData<Dashboard> pageData = null;
+        do {
+            pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+            loadedDashboards.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+        
+        Collections.sort(dashboards, idComparator);
+        Collections.sort(loadedDashboards, idComparator);
+        
+        Assert.assertEquals(dashboards, loadedDashboards);
+        
+        dashboardService.unassignCustomerDashboards(tenantId, customerId);
+
+        pageLink = new TextPageLink(42);
+        pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertTrue(pageData.getData().isEmpty());
+        
+        tenantService.deleteTenant(tenantId);
+    }
+    
+    @Test
+    public void testFindDashboardsByTenantIdCustomerIdAndTitle() {
+        
+        Customer customer = new Customer();
+        customer.setTitle("Test customer");
+        customer.setTenantId(tenantId);
+        customer = customerService.saveCustomer(customer);
+        CustomerId customerId = customer.getId();
+        
+        String title1 = "Dashboard title 1";
+        List<Dashboard> dashboardsTitle1 = new ArrayList<>();
+        for (int i=0;i<124;i++) {
+            Dashboard dashboard = new Dashboard();
+            dashboard.setTenantId(tenantId);
+            String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
+            String title = title1+suffix;
+            title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
+            dashboard.setTitle(title);
+            dashboard = dashboardService.saveDashboard(dashboard);
+            dashboardsTitle1.add(dashboardService.assignDashboardToCustomer(dashboard.getId(), customerId));
+        }
+        String title2 = "Dashboard title 2";
+        List<Dashboard> dashboardsTitle2 = new ArrayList<>();
+        for (int i=0;i<151;i++) {
+            Dashboard dashboard = new Dashboard();
+            dashboard.setTenantId(tenantId);
+            String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
+            String title = title2+suffix;
+            title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
+            dashboard.setTitle(title);
+            dashboard = dashboardService.saveDashboard(dashboard);
+            dashboardsTitle2.add(dashboardService.assignDashboardToCustomer(dashboard.getId(), customerId));
+        }
+        
+        List<Dashboard> loadedDashboardsTitle1 = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(24, title1);
+        TextPageData<Dashboard> pageData = null;
+        do {
+            pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+            loadedDashboardsTitle1.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+        
+        Collections.sort(dashboardsTitle1, idComparator);
+        Collections.sort(loadedDashboardsTitle1, idComparator);
+        
+        Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1);
+        
+        List<Dashboard> loadedDashboardsTitle2 = new ArrayList<>();
+        pageLink = new TextPageLink(4, title2);
+        do {
+            pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+            loadedDashboardsTitle2.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+
+        Collections.sort(dashboardsTitle2, idComparator);
+        Collections.sort(loadedDashboardsTitle2, idComparator);
+        
+        Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2);
+
+        for (Dashboard dashboard : loadedDashboardsTitle1) {
+            dashboardService.deleteDashboard(dashboard.getId());
+        }
+        
+        pageLink = new TextPageLink(4, title1);
+        pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertEquals(0, pageData.getData().size());
+        
+        for (Dashboard dashboard : loadedDashboardsTitle2) {
+            dashboardService.deleteDashboard(dashboard.getId());
+        }
+        
+        pageLink = new TextPageLink(4, title2);
+        pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertEquals(0, pageData.getData().size());
+        customerService.deleteCustomer(customerId);
+    }
+}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceCredentialsCacheTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceCredentialsCacheTest.java
new file mode 100644
index 0000000..fd71fa4
--- /dev/null
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceCredentialsCacheTest.java
@@ -0,0 +1,163 @@
+/**
+ * Copyright © 2016 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.hazelcast.core.HazelcastInstance;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.aop.framework.Advised;
+import org.springframework.aop.support.AopUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.thingsboard.server.common.data.CacheConstants;
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.id.DeviceCredentialsId;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.security.DeviceCredentials;
+import org.thingsboard.server.common.data.security.DeviceCredentialsType;
+import org.thingsboard.server.dao.device.DeviceCredentialsDao;
+import org.thingsboard.server.dao.device.DeviceCredentialsService;
+import org.thingsboard.server.dao.device.DeviceService;
+import org.thingsboard.server.dao.model.DeviceCredentialsEntity;
+
+import java.util.UUID;
+
+import static org.mockito.Mockito.*;
+
+@TestPropertySource(properties = {"cache.enabled = true"})
+public class DeviceCredentialsCacheTest extends AbstractServiceTest {
+
+    private static final String CREDENTIALS_ID_1 = RandomStringUtils.randomAlphanumeric(20);
+    private static final String CREDENTIALS_ID_2 = RandomStringUtils.randomAlphanumeric(20);
+
+    @Autowired
+    private DeviceCredentialsService deviceCredentialsService;
+
+    private DeviceCredentialsDao deviceCredentialsDao;
+    private DeviceService deviceService;
+
+    @Autowired
+    private HazelcastInstance hazelcastInstance;
+
+    private UUID deviceId = UUIDs.timeBased();
+
+    @Before
+    public void setup() throws Exception {
+        deviceCredentialsDao = mock(DeviceCredentialsDao.class);
+        deviceService = mock(DeviceService.class);
+        ReflectionTestUtils.setField(unwrapDeviceCredentialsService(), "deviceCredentialsDao", deviceCredentialsDao);
+        ReflectionTestUtils.setField(unwrapDeviceCredentialsService(), "deviceService", deviceService);
+    }
+
+    @After
+    public void cleanup() {
+        hazelcastInstance.getMap(CacheConstants.DEVICE_CREDENTIALS_CACHE).evictAll();
+    }
+
+    @Test
+    public void testFindDeviceCredentialsByCredentialsId_Cached() {
+        when(deviceCredentialsDao.findByCredentialsId(CREDENTIALS_ID_1)).thenReturn(createDummyDeviceCredentialsEntity(CREDENTIALS_ID_1));
+
+        deviceCredentialsService.findDeviceCredentialsByCredentialsId(CREDENTIALS_ID_1);
+        deviceCredentialsService.findDeviceCredentialsByCredentialsId(CREDENTIALS_ID_1);
+
+        Assert.assertEquals(1, hazelcastInstance.getMap(CacheConstants.DEVICE_CREDENTIALS_CACHE).size());
+        verify(deviceCredentialsDao, times(1)).findByCredentialsId(CREDENTIALS_ID_1);
+    }
+
+    @Test
+    public void testDeleteDeviceCredentials_EvictsCache() {
+        when(deviceCredentialsDao.findByCredentialsId(CREDENTIALS_ID_1)).thenReturn(createDummyDeviceCredentialsEntity(CREDENTIALS_ID_1));
+
+        deviceCredentialsService.findDeviceCredentialsByCredentialsId(CREDENTIALS_ID_1);
+        deviceCredentialsService.findDeviceCredentialsByCredentialsId(CREDENTIALS_ID_1);
+
+        Assert.assertEquals(1, hazelcastInstance.getMap(CacheConstants.DEVICE_CREDENTIALS_CACHE).size());
+        verify(deviceCredentialsDao, times(1)).findByCredentialsId(CREDENTIALS_ID_1);
+
+        deviceCredentialsService.deleteDeviceCredentials(createDummyDeviceCredentials(CREDENTIALS_ID_1, deviceId));
+
+        Assert.assertEquals(0, hazelcastInstance.getMap(CacheConstants.DEVICE_CREDENTIALS_CACHE).size());
+
+        deviceCredentialsService.findDeviceCredentialsByCredentialsId(CREDENTIALS_ID_1);
+        deviceCredentialsService.findDeviceCredentialsByCredentialsId(CREDENTIALS_ID_1);
+
+        Assert.assertEquals(1, hazelcastInstance.getMap(CacheConstants.DEVICE_CREDENTIALS_CACHE).size());
+        verify(deviceCredentialsDao, times(2)).findByCredentialsId(CREDENTIALS_ID_1);
+    }
+
+    @Test
+    public void testSaveDeviceCredentials_EvictsPreviousCache() throws Exception {
+        when(deviceCredentialsDao.findByCredentialsId(CREDENTIALS_ID_1)).thenReturn(createDummyDeviceCredentialsEntity(CREDENTIALS_ID_1));
+
+        deviceCredentialsService.findDeviceCredentialsByCredentialsId(CREDENTIALS_ID_1);
+        deviceCredentialsService.findDeviceCredentialsByCredentialsId(CREDENTIALS_ID_1);
+
+        Assert.assertEquals(1, hazelcastInstance.getMap(CacheConstants.DEVICE_CREDENTIALS_CACHE).size());
+        verify(deviceCredentialsDao, times(1)).findByCredentialsId(CREDENTIALS_ID_1);
+
+        when(deviceCredentialsDao.findByDeviceId(deviceId)).thenReturn(createDummyDeviceCredentialsEntity(CREDENTIALS_ID_1));
+
+        UUID deviceCredentialsId = UUIDs.timeBased();
+        when(deviceCredentialsDao.findById(deviceCredentialsId)).thenReturn(createDummyDeviceCredentialsEntity(CREDENTIALS_ID_1));
+        when(deviceService.findDeviceById(new DeviceId(deviceId))).thenReturn(new Device());
+
+        deviceCredentialsService.updateDeviceCredentials(createDummyDeviceCredentials(deviceCredentialsId, CREDENTIALS_ID_2, deviceId));
+        Assert.assertEquals(0, hazelcastInstance.getMap(CacheConstants.DEVICE_CREDENTIALS_CACHE).size());
+
+        when(deviceCredentialsDao.findByCredentialsId(CREDENTIALS_ID_1)).thenReturn(null);
+
+        deviceCredentialsService.findDeviceCredentialsByCredentialsId(CREDENTIALS_ID_1);
+        deviceCredentialsService.findDeviceCredentialsByCredentialsId(CREDENTIALS_ID_1);
+        Assert.assertEquals(0, hazelcastInstance.getMap(CacheConstants.DEVICE_CREDENTIALS_CACHE).size());
+
+        verify(deviceCredentialsDao, times(3)).findByCredentialsId(CREDENTIALS_ID_1);
+    }
+
+    private DeviceCredentialsService unwrapDeviceCredentialsService() throws Exception {
+        if (AopUtils.isAopProxy(deviceCredentialsService) && deviceCredentialsService instanceof Advised) {
+            Object target = ((Advised) deviceCredentialsService).getTargetSource().getTarget();
+            return (DeviceCredentialsService) target;
+        }
+        return null;
+    }
+
+    private DeviceCredentialsEntity createDummyDeviceCredentialsEntity(String deviceCredentialsId) {
+        DeviceCredentialsEntity result = new DeviceCredentialsEntity();
+        result.setId(UUIDs.timeBased());
+        result.setCredentialsId(deviceCredentialsId);
+        return result;
+    }
+
+    private DeviceCredentials createDummyDeviceCredentials(String deviceCredentialsId, UUID deviceId) {
+        return createDummyDeviceCredentials(null, deviceCredentialsId, deviceId);
+    }
+
+    private DeviceCredentials createDummyDeviceCredentials(UUID id, String deviceCredentialsId, UUID deviceId) {
+        DeviceCredentials result = new DeviceCredentials();
+        result.setId(new DeviceCredentialsId(id));
+        result.setDeviceId(new DeviceId(deviceId));
+        result.setCredentialsId(deviceCredentialsId);
+        result.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
+        return result;
+    }
+}
+
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceCredentialsServiceImplTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceCredentialsServiceImplTest.java
new file mode 100644
index 0000000..d7dd552
--- /dev/null
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceCredentialsServiceImplTest.java
@@ -0,0 +1,195 @@
+/**
+ * Copyright © 2016 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 org.apache.commons.lang3.RandomStringUtils;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.id.DeviceCredentialsId;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.security.DeviceCredentials;
+import org.thingsboard.server.common.data.security.DeviceCredentialsType;
+import org.thingsboard.server.dao.exception.DataValidationException;
+
+public class DeviceCredentialsServiceImplTest extends AbstractServiceTest {
+
+    private TenantId tenantId;
+
+    @Before
+    public void before() {
+        Tenant tenant = new Tenant();
+        tenant.setTitle("My tenant");
+        Tenant savedTenant = tenantService.saveTenant(tenant);
+        Assert.assertNotNull(savedTenant);
+        tenantId = savedTenant.getId();
+    }
+
+    @After
+    public void after() {
+        tenantService.deleteTenant(tenantId);
+    }
+
+    @Test(expected = DataValidationException.class)
+    public void testCreateDeviceCredentials() {
+        DeviceCredentials deviceCredentials = new DeviceCredentials();
+        deviceCredentialsService.updateDeviceCredentials(deviceCredentials);
+    }
+
+    @Test(expected = DataValidationException.class)
+    public void testSaveDeviceCredentialsWithEmptyDevice() {
+        Device device = new Device();
+        device.setName("My device");
+        device.setTenantId(tenantId);
+        device = deviceService.saveDevice(device);
+        DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getId());
+        deviceCredentials.setDeviceId(null);
+        try {
+            deviceCredentialsService.updateDeviceCredentials(deviceCredentials);
+        } finally {
+            deviceService.deleteDevice(device.getId());
+        }
+    }
+
+    @Test(expected = DataValidationException.class)
+    public void testSaveDeviceCredentialsWithEmptyCredentialsType() {
+        Device device = new Device();
+        device.setName("My device");
+        device.setTenantId(tenantId);
+        device = deviceService.saveDevice(device);
+        DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getId());
+        deviceCredentials.setCredentialsType(null);
+        try {
+            deviceCredentialsService.updateDeviceCredentials(deviceCredentials);
+        } finally {
+            deviceService.deleteDevice(device.getId());
+        }
+    }
+
+    @Test(expected = DataValidationException.class)
+    public void testSaveDeviceCredentialsWithEmptyCredentialsId() {
+        Device device = new Device();
+        device.setName("My device");
+        device.setTenantId(tenantId);
+        device = deviceService.saveDevice(device);
+        DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getId());
+        deviceCredentials.setCredentialsId(null);
+        try {
+            deviceCredentialsService.updateDeviceCredentials(deviceCredentials);
+        } finally {
+            deviceService.deleteDevice(device.getId());
+        }
+    }
+
+    @Test(expected = DataValidationException.class)
+    public void testSaveNonExistentDeviceCredentials() {
+        Device device = new Device();
+        device.setName("My device");
+        device.setTenantId(tenantId);
+        device = deviceService.saveDevice(device);
+        DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getId());
+        DeviceCredentials newDeviceCredentials = new DeviceCredentials(new DeviceCredentialsId(UUIDs.timeBased()));
+        newDeviceCredentials.setCreatedTime(deviceCredentials.getCreatedTime());
+        newDeviceCredentials.setDeviceId(deviceCredentials.getDeviceId());
+        newDeviceCredentials.setCredentialsType(deviceCredentials.getCredentialsType());
+        newDeviceCredentials.setCredentialsId(deviceCredentials.getCredentialsId());
+        try {
+            deviceCredentialsService.updateDeviceCredentials(newDeviceCredentials);
+        } finally {
+            deviceService.deleteDevice(device.getId());
+        }
+    }
+
+    @Test(expected = DataValidationException.class)
+    public void testSaveDeviceCredentialsWithNonExistentDevice() {
+        Device device = new Device();
+        device.setName("My device");
+        device.setTenantId(tenantId);
+        device = deviceService.saveDevice(device);
+        DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getId());
+        deviceCredentials.setDeviceId(new DeviceId(UUIDs.timeBased()));
+        try {
+            deviceCredentialsService.updateDeviceCredentials(deviceCredentials);
+        } finally {
+            deviceService.deleteDevice(device.getId());
+        }
+    }
+
+    @Test(expected = DataValidationException.class)
+    public void testSaveDeviceCredentialsWithInvalidCredemtialsIdLength() {
+        Device device = new Device();
+        device.setName("My device");
+        device.setTenantId(tenantId);
+        device = deviceService.saveDevice(device);
+        DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getId());
+        deviceCredentials.setCredentialsId(RandomStringUtils.randomAlphanumeric(21));
+        try {
+            deviceCredentialsService.updateDeviceCredentials(deviceCredentials);
+        } finally {
+            deviceService.deleteDevice(device.getId());
+        }
+    }
+
+    @Test
+    public void testFindDeviceCredentialsByDeviceId() {
+        Device device = new Device();
+        device.setTenantId(tenantId);
+        device.setName("My device");
+        Device savedDevice = deviceService.saveDevice(device);
+        DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getId());
+        Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
+        deviceService.deleteDevice(savedDevice.getId());
+        deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getId());
+        Assert.assertNull(deviceCredentials);
+    }
+
+    @Test
+    public void testFindDeviceCredentialsByCredentialsId() {
+        Device device = new Device();
+        device.setTenantId(tenantId);
+        device.setName("My device");
+        Device savedDevice = deviceService.saveDevice(device);
+        DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getId());
+        Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
+        DeviceCredentials foundDeviceCredentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(deviceCredentials.getCredentialsId());
+        Assert.assertEquals(deviceCredentials, foundDeviceCredentials);
+        deviceService.deleteDevice(savedDevice.getId());
+        foundDeviceCredentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(deviceCredentials.getCredentialsId());
+        Assert.assertNull(foundDeviceCredentials);
+    }
+
+    @Test
+    public void testSaveDeviceCredentials() {
+        Device device = new Device();
+        device.setTenantId(tenantId);
+        device.setName("My device");
+        Device savedDevice = deviceService.saveDevice(device);
+        DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getId());
+        Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
+        deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
+        deviceCredentials.setCredentialsId("access_token");
+        deviceCredentialsService.updateDeviceCredentials(deviceCredentials);
+        DeviceCredentials foundDeviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getId());
+        Assert.assertEquals(deviceCredentials, foundDeviceCredentials);
+        deviceService.deleteDevice(savedDevice.getId());
+    }
+}
+
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceImplTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceImplTest.java
new file mode 100644
index 0000000..f4eb22b
--- /dev/null
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceImplTest.java
@@ -0,0 +1,428 @@
+/**
+ * Copyright © 2016 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 org.apache.commons.lang3.RandomStringUtils;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.DeviceCredentialsId;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.security.DeviceCredentials;
+import org.thingsboard.server.common.data.security.DeviceCredentialsType;
+import org.thingsboard.server.dao.exception.DataValidationException;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
+
+public class DeviceServiceImplTest extends AbstractServiceTest {
+    
+    private IdComparator<Device> idComparator = new IdComparator<>();
+    
+    private TenantId tenantId;
+
+    @Before
+    public void before() {
+        Tenant tenant = new Tenant();
+        tenant.setTitle("My tenant");
+        Tenant savedTenant = tenantService.saveTenant(tenant);
+        Assert.assertNotNull(savedTenant);
+        tenantId = savedTenant.getId();
+    }
+
+    @After
+    public void after() {
+        tenantService.deleteTenant(tenantId);
+    }
+
+    @Test
+    public void testSaveDevice() {
+        Device device = new Device();
+        device.setTenantId(tenantId);
+        device.setName("My device");
+        Device savedDevice = deviceService.saveDevice(device);
+        
+        Assert.assertNotNull(savedDevice);
+        Assert.assertNotNull(savedDevice.getId());
+        Assert.assertTrue(savedDevice.getCreatedTime() > 0);
+        Assert.assertEquals(device.getTenantId(), savedDevice.getTenantId());
+        Assert.assertNotNull(savedDevice.getCustomerId());
+        Assert.assertEquals(NULL_UUID, savedDevice.getCustomerId().getId());
+        Assert.assertEquals(device.getName(), savedDevice.getName());
+        
+        DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getId());
+        Assert.assertNotNull(deviceCredentials);
+        Assert.assertNotNull(deviceCredentials.getId());
+        Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
+        Assert.assertEquals(DeviceCredentialsType.ACCESS_TOKEN, deviceCredentials.getCredentialsType());
+        Assert.assertNotNull(deviceCredentials.getCredentialsId());
+        Assert.assertEquals(20, deviceCredentials.getCredentialsId().length());
+        
+        savedDevice.setName("My new device");
+        
+        deviceService.saveDevice(savedDevice);
+        Device foundDevice = deviceService.findDeviceById(savedDevice.getId());
+        Assert.assertEquals(foundDevice.getName(), savedDevice.getName());
+        
+        deviceService.deleteDevice(savedDevice.getId());
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testSaveDeviceWithEmptyName() {
+        Device device = new Device();
+        device.setTenantId(tenantId);
+        deviceService.saveDevice(device);
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testSaveDeviceWithEmptyTenant() {
+        Device device = new Device();
+        device.setName("My device");
+        deviceService.saveDevice(device);
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testSaveDeviceWithInvalidTenant() {
+        Device device = new Device();
+        device.setName("My device");
+        device.setTenantId(new TenantId(UUIDs.timeBased()));
+        deviceService.saveDevice(device);
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testAssignDeviceToNonExistentCustomer() {
+        Device device = new Device();
+        device.setName("My device");
+        device.setTenantId(tenantId);
+        device = deviceService.saveDevice(device);
+        try {
+            deviceService.assignDeviceToCustomer(device.getId(), new CustomerId(UUIDs.timeBased()));
+        } finally {
+            deviceService.deleteDevice(device.getId());
+        }
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testAssignDeviceToCustomerFromDifferentTenant() {
+        Device device = new Device();
+        device.setName("My device");
+        device.setTenantId(tenantId);
+        device = deviceService.saveDevice(device);
+        Tenant tenant = new Tenant();
+        tenant.setTitle("Test different tenant");
+        tenant = tenantService.saveTenant(tenant);
+        Customer customer = new Customer();
+        customer.setTenantId(tenant.getId());
+        customer.setTitle("Test different customer");
+        customer = customerService.saveCustomer(customer);
+        try {
+            deviceService.assignDeviceToCustomer(device.getId(), customer.getId());
+        } finally {
+            deviceService.deleteDevice(device.getId());
+            tenantService.deleteTenant(tenant.getId());
+        }
+    }
+    
+    @Test
+    public void testFindDeviceById() {
+        Device device = new Device();
+        device.setTenantId(tenantId);
+        device.setName("My device");
+        Device savedDevice = deviceService.saveDevice(device);
+        Device foundDevice = deviceService.findDeviceById(savedDevice.getId());
+        Assert.assertNotNull(foundDevice);
+        Assert.assertEquals(savedDevice, foundDevice);
+        deviceService.deleteDevice(savedDevice.getId());
+    }
+    
+    @Test
+    public void testDeleteDevice() {
+        Device device = new Device();
+        device.setTenantId(tenantId);
+        device.setName("My device");
+        Device savedDevice = deviceService.saveDevice(device);
+        Device foundDevice = deviceService.findDeviceById(savedDevice.getId());
+        Assert.assertNotNull(foundDevice);
+        deviceService.deleteDevice(savedDevice.getId());
+        foundDevice = deviceService.findDeviceById(savedDevice.getId());
+        Assert.assertNull(foundDevice);
+        DeviceCredentials foundDeviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getId());
+        Assert.assertNull(foundDeviceCredentials);
+    }
+    
+    @Test
+    public void testFindDevicesByTenantId() {
+        Tenant tenant = new Tenant();
+        tenant.setTitle("Test tenant");
+        tenant = tenantService.saveTenant(tenant);
+        
+        TenantId tenantId = tenant.getId();
+        
+        List<Device> devices = new ArrayList<>();
+        for (int i=0;i<178;i++) {
+            Device device = new Device();
+            device.setTenantId(tenantId);
+            device.setName("Device"+i);
+            devices.add(deviceService.saveDevice(device));
+        }
+        
+        List<Device> loadedDevices = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(23);
+        TextPageData<Device> pageData = null;
+        do {
+            pageData = deviceService.findDevicesByTenantId(tenantId, pageLink);
+            loadedDevices.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+        
+        Collections.sort(devices, idComparator);
+        Collections.sort(loadedDevices, idComparator);
+        
+        Assert.assertEquals(devices, loadedDevices);
+        
+        deviceService.deleteDevicesByTenantId(tenantId);
+
+        pageLink = new TextPageLink(33);
+        pageData = deviceService.findDevicesByTenantId(tenantId, pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertTrue(pageData.getData().isEmpty());
+        
+        tenantService.deleteTenant(tenantId);
+    }
+    
+    @Test
+    public void testFindDevicesByTenantIdAndName() {
+        String title1 = "Device title 1";
+        List<Device> devicesTitle1 = new ArrayList<>();
+        for (int i=0;i<143;i++) {
+            Device device = new Device();
+            device.setTenantId(tenantId);
+            String suffix = RandomStringUtils.randomAlphanumeric(15);
+            String name = title1+suffix;
+            name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+            device.setName(name);
+            devicesTitle1.add(deviceService.saveDevice(device));
+        }
+        String title2 = "Device title 2";
+        List<Device> devicesTitle2 = new ArrayList<>();
+        for (int i=0;i<175;i++) {
+            Device device = new Device();
+            device.setTenantId(tenantId);
+            String suffix = RandomStringUtils.randomAlphanumeric(15);
+            String name = title2+suffix;
+            name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+            device.setName(name);
+            devicesTitle2.add(deviceService.saveDevice(device));
+        }
+        
+        List<Device> loadedDevicesTitle1 = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(15, title1);
+        TextPageData<Device> pageData = null;
+        do {
+            pageData = deviceService.findDevicesByTenantId(tenantId, pageLink);
+            loadedDevicesTitle1.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+        
+        Collections.sort(devicesTitle1, idComparator);
+        Collections.sort(loadedDevicesTitle1, idComparator);
+        
+        Assert.assertEquals(devicesTitle1, loadedDevicesTitle1);
+        
+        List<Device> loadedDevicesTitle2 = new ArrayList<>();
+        pageLink = new TextPageLink(4, title2);
+        do {
+            pageData = deviceService.findDevicesByTenantId(tenantId, pageLink);
+            loadedDevicesTitle2.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+
+        Collections.sort(devicesTitle2, idComparator);
+        Collections.sort(loadedDevicesTitle2, idComparator);
+        
+        Assert.assertEquals(devicesTitle2, loadedDevicesTitle2);
+
+        for (Device device : loadedDevicesTitle1) {
+            deviceService.deleteDevice(device.getId());
+        }
+        
+        pageLink = new TextPageLink(4, title1);
+        pageData = deviceService.findDevicesByTenantId(tenantId, pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertEquals(0, pageData.getData().size());
+        
+        for (Device device : loadedDevicesTitle2) {
+            deviceService.deleteDevice(device.getId());
+        }
+        
+        pageLink = new TextPageLink(4, title2);
+        pageData = deviceService.findDevicesByTenantId(tenantId, pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertEquals(0, pageData.getData().size());
+    }
+    
+    @Test
+    public void testFindDevicesByTenantIdAndCustomerId() {
+        Tenant tenant = new Tenant();
+        tenant.setTitle("Test tenant");
+        tenant = tenantService.saveTenant(tenant);
+        
+        TenantId tenantId = tenant.getId();
+        
+        Customer customer = new Customer();
+        customer.setTitle("Test customer");
+        customer.setTenantId(tenantId);
+        customer = customerService.saveCustomer(customer);
+        CustomerId customerId = customer.getId();
+        
+        List<Device> devices = new ArrayList<>();
+        for (int i=0;i<278;i++) {
+            Device device = new Device();
+            device.setTenantId(tenantId);
+            device.setName("Device"+i);
+            device = deviceService.saveDevice(device);
+            devices.add(deviceService.assignDeviceToCustomer(device.getId(), customerId));
+        }
+        
+        List<Device> loadedDevices = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(23);
+        TextPageData<Device> pageData = null;
+        do {
+            pageData = deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+            loadedDevices.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+        
+        Collections.sort(devices, idComparator);
+        Collections.sort(loadedDevices, idComparator);
+        
+        Assert.assertEquals(devices, loadedDevices);
+        
+        deviceService.unassignCustomerDevices(tenantId, customerId);
+
+        pageLink = new TextPageLink(33);
+        pageData = deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertTrue(pageData.getData().isEmpty());
+        
+        tenantService.deleteTenant(tenantId);
+    }
+    
+    @Test
+    public void testFindDevicesByTenantIdCustomerIdAndName() {
+        
+        Customer customer = new Customer();
+        customer.setTitle("Test customer");
+        customer.setTenantId(tenantId);
+        customer = customerService.saveCustomer(customer);
+        CustomerId customerId = customer.getId();
+        
+        String title1 = "Device title 1";
+        List<Device> devicesTitle1 = new ArrayList<>();
+        for (int i=0;i<175;i++) {
+            Device device = new Device();
+            device.setTenantId(tenantId);
+            String suffix = RandomStringUtils.randomAlphanumeric(15);
+            String name = title1+suffix;
+            name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+            device.setName(name);
+            device = deviceService.saveDevice(device);
+            devicesTitle1.add(deviceService.assignDeviceToCustomer(device.getId(), customerId));
+        }
+        String title2 = "Device title 2";
+        List<Device> devicesTitle2 = new ArrayList<>();
+        for (int i=0;i<143;i++) {
+            Device device = new Device();
+            device.setTenantId(tenantId);
+            String suffix = RandomStringUtils.randomAlphanumeric(15);
+            String name = title2+suffix;
+            name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+            device.setName(name);
+            device = deviceService.saveDevice(device);
+            devicesTitle2.add(deviceService.assignDeviceToCustomer(device.getId(), customerId));
+        }
+        
+        List<Device> loadedDevicesTitle1 = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(15, title1);
+        TextPageData<Device> pageData = null;
+        do {
+            pageData = deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+            loadedDevicesTitle1.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+        
+        Collections.sort(devicesTitle1, idComparator);
+        Collections.sort(loadedDevicesTitle1, idComparator);
+        
+        Assert.assertEquals(devicesTitle1, loadedDevicesTitle1);
+        
+        List<Device> loadedDevicesTitle2 = new ArrayList<>();
+        pageLink = new TextPageLink(4, title2);
+        do {
+            pageData = deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+            loadedDevicesTitle2.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+
+        Collections.sort(devicesTitle2, idComparator);
+        Collections.sort(loadedDevicesTitle2, idComparator);
+        
+        Assert.assertEquals(devicesTitle2, loadedDevicesTitle2);
+
+        for (Device device : loadedDevicesTitle1) {
+            deviceService.deleteDevice(device.getId());
+        }
+        
+        pageLink = new TextPageLink(4, title1);
+        pageData = deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertEquals(0, pageData.getData().size());
+        
+        for (Device device : loadedDevicesTitle2) {
+            deviceService.deleteDevice(device.getId());
+        }
+        
+        pageLink = new TextPageLink(4, title2);
+        pageData = deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertEquals(0, pageData.getData().size());
+        customerService.deleteCustomer(customerId);
+    }
+}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/TenantServiceImplTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/TenantServiceImplTest.java
new file mode 100644
index 0000000..074afe5
--- /dev/null
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/TenantServiceImplTest.java
@@ -0,0 +1,203 @@
+/**
+ * Copyright © 2016 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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TenantServiceImplTest extends AbstractServiceTest {
+    
+    private IdComparator<Tenant> idComparator = new IdComparator<>();
+
+    @Test
+    public void testSaveTenant() {
+        Tenant tenant = new Tenant();
+        tenant.setTitle("My tenant");
+        Tenant savedTenant = tenantService.saveTenant(tenant);
+        Assert.assertNotNull(savedTenant);
+        Assert.assertNotNull(savedTenant.getId());
+        Assert.assertTrue(savedTenant.getCreatedTime() > 0);
+        Assert.assertEquals(tenant.getTitle(), savedTenant.getTitle());
+        
+        savedTenant.setTitle("My new tenant");
+        tenantService.saveTenant(savedTenant);
+        Tenant foundTenant = tenantService.findTenantById(savedTenant.getId());
+        Assert.assertEquals(foundTenant.getTitle(), savedTenant.getTitle());
+        
+        tenantService.deleteTenant(savedTenant.getId());
+    }
+    
+    @Test
+    public void testFindTenantById() {
+        Tenant tenant = new Tenant();
+        tenant.setTitle("My tenant");
+        Tenant savedTenant = tenantService.saveTenant(tenant);
+        Tenant foundTenant = tenantService.findTenantById(savedTenant.getId());
+        Assert.assertNotNull(foundTenant);
+        Assert.assertEquals(savedTenant, foundTenant);
+        tenantService.deleteTenant(savedTenant.getId());
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testSaveTenantWithEmptyTitle() {
+        Tenant tenant = new Tenant();
+        tenantService.saveTenant(tenant);
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testSaveTenantWithInvalidEmail() {
+        Tenant tenant = new Tenant();
+        tenant.setTitle("My tenant");
+        tenant.setEmail("invalid@mail");
+        tenantService.saveTenant(tenant);
+    }
+    
+    @Test
+    public void testDeleteTenant() {
+        Tenant tenant = new Tenant();
+        tenant.setTitle("My tenant");
+        Tenant savedTenant = tenantService.saveTenant(tenant);
+        tenantService.deleteTenant(savedTenant.getId());
+        Tenant foundTenant = tenantService.findTenantById(savedTenant.getId());
+        Assert.assertNull(foundTenant);
+    }
+    
+    @Test
+    public void testFindTenants() {
+        
+        List<Tenant> tenants = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(17);
+        TextPageData<Tenant> pageData = tenantService.findTenants(pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertTrue(pageData.getData().isEmpty());
+        tenants.addAll(pageData.getData());
+        
+        for (int i=0;i<156;i++) {
+            Tenant tenant = new Tenant();
+            tenant.setTitle("Tenant"+i);
+            tenants.add(tenantService.saveTenant(tenant));
+        }
+        
+        List<Tenant> loadedTenants = new ArrayList<>();
+        pageLink = new TextPageLink(17);
+        do {
+            pageData = tenantService.findTenants(pageLink);
+            loadedTenants.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+        
+        Collections.sort(tenants, idComparator);
+        Collections.sort(loadedTenants, idComparator);
+        
+        Assert.assertEquals(tenants, loadedTenants);
+        
+        for (Tenant tenant : loadedTenants) {
+            if (!tenant.getTitle().equals("Tenant")) {
+                tenantService.deleteTenant(tenant.getId());
+            }
+        }
+        
+        pageLink = new TextPageLink(17);
+        pageData = tenantService.findTenants(pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertTrue(pageData.getData().isEmpty());
+        
+    }
+    
+    @Test
+    public void testFindTenantsByTitle() {
+        String title1 = "Tenant title 1";
+        List<Tenant> tenantsTitle1 = new ArrayList<>();
+        for (int i=0;i<134;i++) {
+            Tenant tenant = new Tenant();
+            String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
+            String title = title1+suffix;
+            title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
+            tenant.setTitle(title);
+            tenantsTitle1.add(tenantService.saveTenant(tenant));
+        }
+        String title2 = "Tenant title 2";
+        List<Tenant> tenantsTitle2 = new ArrayList<>();
+        for (int i=0;i<127;i++) {
+            Tenant tenant = new Tenant();
+            String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
+            String title = title2+suffix;
+            title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
+            tenant.setTitle(title);
+            tenantsTitle2.add(tenantService.saveTenant(tenant));
+        }
+        
+        List<Tenant> loadedTenantsTitle1 = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(15, title1);
+        TextPageData<Tenant> pageData = null;
+        do {
+            pageData = tenantService.findTenants(pageLink);
+            loadedTenantsTitle1.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+        
+        Collections.sort(tenantsTitle1, idComparator);
+        Collections.sort(loadedTenantsTitle1, idComparator);
+        
+        Assert.assertEquals(tenantsTitle1, loadedTenantsTitle1);
+        
+        List<Tenant> loadedTenantsTitle2 = new ArrayList<>();
+        pageLink = new TextPageLink(4, title2);
+        do {
+            pageData = tenantService.findTenants(pageLink);
+            loadedTenantsTitle2.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+
+        Collections.sort(tenantsTitle2, idComparator);
+        Collections.sort(loadedTenantsTitle2, idComparator);
+        
+        Assert.assertEquals(tenantsTitle2, loadedTenantsTitle2);
+
+        for (Tenant tenant : loadedTenantsTitle1) {
+            tenantService.deleteTenant(tenant.getId());
+        }
+        
+        pageLink = new TextPageLink(4, title1);
+        pageData = tenantService.findTenants(pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertEquals(0, pageData.getData().size());
+        
+        for (Tenant tenant : loadedTenantsTitle2) {
+            tenantService.deleteTenant(tenant.getId());
+        }
+        
+        pageLink = new TextPageLink(4, title2);
+        pageData = tenantService.findTenants(pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertEquals(0, pageData.getData().size());
+    }
+}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/UserServiceImplTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/UserServiceImplTest.java
new file mode 100644
index 0000000..6b2f37c
--- /dev/null
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/UserServiceImplTest.java
@@ -0,0 +1,477 @@
+/**
+ * Copyright © 2016 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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.User;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.security.Authority;
+import org.thingsboard.server.common.data.security.UserCredentials;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class UserServiceImplTest extends AbstractServiceTest {
+    
+    private IdComparator<User> idComparator = new IdComparator<>();
+
+    private TenantId tenantId;
+
+    @Before
+    public void before() {
+        Tenant tenant = new Tenant();
+        tenant.setTitle("My tenant");
+        Tenant savedTenant = tenantService.saveTenant(tenant);
+        Assert.assertNotNull(savedTenant);
+        tenantId = savedTenant.getId();
+
+        User tenantAdmin = new User();
+        tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
+        tenantAdmin.setTenantId(tenantId);
+        tenantAdmin.setEmail("tenant@thingsboard.org");
+        userService.saveUser(tenantAdmin);
+
+        Customer customer = new Customer();
+        customer.setTenantId(tenantId);
+        customer.setTitle("My customer");
+        Customer savedCustomer = customerService.saveCustomer(customer);
+
+        User customerUser = new User();
+        customerUser.setAuthority(Authority.CUSTOMER_USER);
+        customerUser.setTenantId(tenantId);
+        customerUser.setCustomerId(savedCustomer.getId());
+        customerUser.setEmail("customer@thingsboard.org");
+        userService.saveUser(customerUser);
+    }
+
+    @After
+    public void after() {
+        tenantService.deleteTenant(tenantId);
+    }
+
+    @Test
+    public void testFindUserByEmail() {
+        User user = userService.findUserByEmail("sysadmin@thingsboard.org");
+        Assert.assertNotNull(user);
+        Assert.assertEquals(Authority.SYS_ADMIN, user.getAuthority());
+        user = userService.findUserByEmail("tenant@thingsboard.org");
+        Assert.assertNotNull(user);
+        Assert.assertEquals(Authority.TENANT_ADMIN, user.getAuthority());
+        user = userService.findUserByEmail("customer@thingsboard.org");
+        Assert.assertNotNull(user);
+        Assert.assertEquals(Authority.CUSTOMER_USER, user.getAuthority());
+        user = userService.findUserByEmail("fake@thingsboard.org");
+        Assert.assertNull(user);
+    }
+
+    @Test
+    public void testFindUserById() {
+        User user = userService.findUserByEmail("sysadmin@thingsboard.org");
+        Assert.assertNotNull(user);
+        User foundUser = userService.findUserById(user.getId());
+        Assert.assertNotNull(foundUser);
+        Assert.assertEquals(user, foundUser);
+    }
+    
+    @Test
+    public void testFindUserCredentials() {
+        User user = userService.findUserByEmail("sysadmin@thingsboard.org");
+        Assert.assertNotNull(user);
+        UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getId());
+        Assert.assertNotNull(userCredentials);
+    }
+    
+    @Test
+    public void testSaveUser() {
+        User tenantAdminUser = userService.findUserByEmail("tenant@thingsboard.org");
+        User user = new User();
+        user.setAuthority(Authority.TENANT_ADMIN);
+        user.setTenantId(tenantAdminUser.getTenantId());
+        user.setEmail("tenant2@thingsboard.org");
+        User savedUser = userService.saveUser(user);
+        Assert.assertNotNull(savedUser);
+        Assert.assertNotNull(savedUser.getId());
+        Assert.assertTrue(savedUser.getCreatedTime() > 0);
+        Assert.assertEquals(user.getEmail(), savedUser.getEmail());
+        Assert.assertEquals(user.getTenantId(), savedUser.getTenantId());
+        Assert.assertEquals(user.getAuthority(), savedUser.getAuthority());
+        UserCredentials userCredentials = userService.findUserCredentialsByUserId(savedUser.getId());
+        Assert.assertNotNull(userCredentials);
+        Assert.assertNotNull(userCredentials.getId());
+        Assert.assertNotNull(userCredentials.getUserId());
+        Assert.assertNotNull(userCredentials.getActivateToken());
+        
+        savedUser.setFirstName("Joe");
+        savedUser.setLastName("Downs");
+        
+        userService.saveUser(savedUser);
+        savedUser = userService.findUserById(savedUser.getId());
+        Assert.assertEquals("Joe", savedUser.getFirstName());
+        Assert.assertEquals("Downs", savedUser.getLastName());        
+        
+        userService.deleteUser(savedUser.getId());
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testSaveUserWithSameEmail() {
+        User tenantAdminUser = userService.findUserByEmail("tenant@thingsboard.org");
+        tenantAdminUser.setEmail("sysadmin@thingsboard.org");
+        userService.saveUser(tenantAdminUser);
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testSaveUserWithInvalidEmail() {
+        User tenantAdminUser = userService.findUserByEmail("tenant@thingsboard.org");
+        tenantAdminUser.setEmail("tenant_thingsboard.org");
+        userService.saveUser(tenantAdminUser);
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testSaveUserWithEmptyEmail() {
+        User tenantAdminUser = userService.findUserByEmail("tenant@thingsboard.org");
+        tenantAdminUser.setEmail(null);
+        userService.saveUser(tenantAdminUser);
+    }
+    
+    @Test(expected = DataValidationException.class)
+    public void testSaveUserWithoutTenant() {
+        User tenantAdminUser = userService.findUserByEmail("tenant@thingsboard.org");
+        tenantAdminUser.setTenantId(null);
+        userService.saveUser(tenantAdminUser);
+    }
+    
+    @Test
+    public void testDeleteUser() {
+        User tenantAdminUser = userService.findUserByEmail("tenant@thingsboard.org");
+        User user = new User();
+        user.setAuthority(Authority.TENANT_ADMIN);
+        user.setTenantId(tenantAdminUser.getTenantId());
+        user.setEmail("tenant2@thingsboard.org");
+        User savedUser = userService.saveUser(user);
+        Assert.assertNotNull(savedUser);
+        Assert.assertNotNull(savedUser.getId());
+        User foundUser = userService.findUserById(savedUser.getId());
+        Assert.assertNotNull(foundUser);
+        UserCredentials userCredentials = userService.findUserCredentialsByUserId(foundUser.getId());
+        Assert.assertNotNull(userCredentials);
+        userService.deleteUser(foundUser.getId());
+        userCredentials = userService.findUserCredentialsByUserId(foundUser.getId());
+        foundUser = userService.findUserById(foundUser.getId());
+        Assert.assertNull(foundUser);
+        Assert.assertNull(userCredentials);
+    }
+    
+    @Test
+    public void testFindTenantAdmins() {
+        User tenantAdminUser = userService.findUserByEmail("tenant@thingsboard.org");
+        TextPageData<User> pageData = userService.findTenantAdmins(tenantAdminUser.getTenantId(), new TextPageLink(10));
+        Assert.assertFalse(pageData.hasNext());
+        List<User> users = pageData.getData();
+        Assert.assertEquals(1, users.size());
+        Assert.assertEquals(tenantAdminUser, users.get(0));
+        
+        Tenant tenant = new Tenant();
+        tenant.setTitle("Test tenant");
+        tenant = tenantService.saveTenant(tenant);
+        
+        TenantId tenantId = tenant.getId();
+        
+        List<User> tenantAdmins = new ArrayList<>();
+        for (int i=0;i<124;i++) {
+            User user = new User();
+            user.setAuthority(Authority.TENANT_ADMIN);
+            user.setTenantId(tenantId);
+            user.setEmail("testTenant" + i + "@thingsboard.org");
+            tenantAdmins.add(userService.saveUser(user));
+        }
+        
+        List<User> loadedTenantAdmins = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(33);
+        do {
+            pageData = userService.findTenantAdmins(tenantId, pageLink);
+            loadedTenantAdmins.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+        
+        Collections.sort(tenantAdmins, idComparator);
+        Collections.sort(loadedTenantAdmins, idComparator);
+        
+        Assert.assertEquals(tenantAdmins, loadedTenantAdmins);
+        
+        tenantService.deleteTenant(tenantId);
+        
+        pageLink = new TextPageLink(33);
+        pageData = userService.findTenantAdmins(tenantId, pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertTrue(pageData.getData().isEmpty());
+        
+    }
+    
+    @Test
+    public void testFindTenantAdminsByEmail() {
+        Tenant tenant = new Tenant();
+        tenant.setTitle("Test tenant");
+        tenant = tenantService.saveTenant(tenant);
+        
+        TenantId tenantId = tenant.getId();
+        
+        String email1 = "testEmail1";        
+        List<User> tenantAdminsEmail1 = new ArrayList<>();
+        
+        for (int i=0;i<94;i++) {
+            User user = new User();
+            user.setAuthority(Authority.TENANT_ADMIN);
+            user.setTenantId(tenantId);
+            String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10));
+            String email = email1+suffix+ "@thingsboard.org";
+            email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase();
+            user.setEmail(email);
+            tenantAdminsEmail1.add(userService.saveUser(user));
+        }
+        
+        String email2 = "testEmail2";        
+        List<User> tenantAdminsEmail2 = new ArrayList<>();
+        
+        for (int i=0;i<132;i++) {
+            User user = new User();
+            user.setAuthority(Authority.TENANT_ADMIN);
+            user.setTenantId(tenantId);
+            String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10));
+            String email = email2+suffix+ "@thingsboard.org";
+            email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase();
+            user.setEmail(email);
+            tenantAdminsEmail2.add(userService.saveUser(user));
+        }
+        
+        List<User> loadedTenantAdminsEmail1 = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(33, email1);
+        TextPageData<User> pageData = null;
+        do {
+            pageData = userService.findTenantAdmins(tenantId, pageLink);
+            loadedTenantAdminsEmail1.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+        
+        Collections.sort(tenantAdminsEmail1, idComparator);
+        Collections.sort(loadedTenantAdminsEmail1, idComparator);
+        
+        Assert.assertEquals(tenantAdminsEmail1, loadedTenantAdminsEmail1);
+        
+        List<User> loadedTenantAdminsEmail2 = new ArrayList<>();
+        pageLink = new TextPageLink(16, email2);
+        do {
+            pageData = userService.findTenantAdmins(tenantId, pageLink);
+            loadedTenantAdminsEmail2.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+        
+        Collections.sort(tenantAdminsEmail2, idComparator);
+        Collections.sort(loadedTenantAdminsEmail2, idComparator);
+        
+        Assert.assertEquals(tenantAdminsEmail2, loadedTenantAdminsEmail2);
+        
+        for (User user : loadedTenantAdminsEmail1) {
+            userService.deleteUser(user.getId());
+        }
+        
+        pageLink = new TextPageLink(4, email1);
+        pageData = userService.findTenantAdmins(tenantId, pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertEquals(0, pageData.getData().size());
+        
+        for (User user : loadedTenantAdminsEmail2) {
+            userService.deleteUser(user.getId());
+        }
+        
+        pageLink = new TextPageLink(4, email2);
+        pageData = userService.findTenantAdmins(tenantId, pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertEquals(0, pageData.getData().size());
+        
+        tenantService.deleteTenant(tenantId);
+    }
+    
+    @Test
+    public void testFindCustomerUsers() {
+        User customerUser = userService.findUserByEmail("customer@thingsboard.org");
+        TextPageData<User> pageData = userService.findCustomerUsers(customerUser.getTenantId(),
+                customerUser.getCustomerId(), new TextPageLink(10));
+        Assert.assertFalse(pageData.hasNext());
+        List<User> users = pageData.getData();
+        Assert.assertEquals(1, users.size());
+        Assert.assertEquals(customerUser, users.get(0));
+        
+        Tenant tenant = new Tenant();
+        tenant.setTitle("Test tenant");
+        tenant = tenantService.saveTenant(tenant);
+        
+        TenantId tenantId = tenant.getId();
+        
+        Customer customer = new Customer();
+        customer.setTitle("Test customer");
+        customer.setTenantId(tenantId);
+        customer = customerService.saveCustomer(customer);
+        
+        CustomerId customerId = customer.getId();
+        
+        List<User> customerUsers = new ArrayList<>();
+        for (int i=0;i<156;i++) {
+            User user = new User();
+            user.setAuthority(Authority.CUSTOMER_USER);
+            user.setTenantId(tenantId);
+            user.setCustomerId(customerId);
+            user.setEmail("testCustomer" + i + "@thingsboard.org");
+            customerUsers.add(userService.saveUser(user));
+        }
+        
+        List<User> loadedCustomerUsers = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(33);
+        do {
+            pageData = userService.findCustomerUsers(tenantId, customerId, pageLink);
+            loadedCustomerUsers.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+        
+        Collections.sort(customerUsers, idComparator);
+        Collections.sort(loadedCustomerUsers, idComparator);
+        
+        Assert.assertEquals(customerUsers, loadedCustomerUsers);
+        
+        tenantService.deleteTenant(tenantId);
+        
+        pageData = userService.findCustomerUsers(tenantId, customerId, pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertTrue(pageData.getData().isEmpty());
+        
+    }
+    
+    @Test
+    public void testFindCustomerUsersByEmail() {
+        Tenant tenant = new Tenant();
+        tenant.setTitle("Test tenant");
+        tenant = tenantService.saveTenant(tenant);
+        
+        TenantId tenantId = tenant.getId();
+        
+        Customer customer = new Customer();
+        customer.setTitle("Test customer");
+        customer.setTenantId(tenantId);
+        customer = customerService.saveCustomer(customer);
+        
+        CustomerId customerId = customer.getId();
+        
+        String email1 = "testEmail1";        
+        List<User> customerUsersEmail1 = new ArrayList<>();
+        
+        for (int i=0;i<124;i++) {
+            User user = new User();
+            user.setAuthority(Authority.CUSTOMER_USER);
+            user.setTenantId(tenantId);
+            user.setCustomerId(customerId);
+            String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10));
+            String email = email1+suffix+ "@thingsboard.org";
+            email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase();
+            user.setEmail(email);
+            customerUsersEmail1.add(userService.saveUser(user));
+        }
+        
+        String email2 = "testEmail2";        
+        List<User> customerUsersEmail2 = new ArrayList<>();
+        
+        for (int i=0;i<132;i++) {
+            User user = new User();
+            user.setAuthority(Authority.CUSTOMER_USER);
+            user.setTenantId(tenantId);
+            user.setCustomerId(customerId);
+            String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10));
+            String email = email2+suffix+ "@thingsboard.org";
+            email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase();
+            user.setEmail(email);
+            customerUsersEmail2.add(userService.saveUser(user));
+        }
+        
+        List<User> loadedCustomerUsersEmail1 = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(33, email1);
+        TextPageData<User> pageData = null;
+        do {
+            pageData = userService.findCustomerUsers(tenantId, customerId, pageLink);
+            loadedCustomerUsersEmail1.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+        
+        Collections.sort(customerUsersEmail1, idComparator);
+        Collections.sort(loadedCustomerUsersEmail1, idComparator);
+        
+        Assert.assertEquals(customerUsersEmail1, loadedCustomerUsersEmail1);
+        
+        List<User> loadedCustomerUsersEmail2 = new ArrayList<>();
+        pageLink = new TextPageLink(16, email2);
+        do {
+            pageData = userService.findCustomerUsers(tenantId, customerId, pageLink);
+            loadedCustomerUsersEmail2.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+        
+        Collections.sort(customerUsersEmail2, idComparator);
+        Collections.sort(loadedCustomerUsersEmail2, idComparator);
+        
+        Assert.assertEquals(customerUsersEmail2, loadedCustomerUsersEmail2);
+        
+        for (User user : loadedCustomerUsersEmail1) {
+            userService.deleteUser(user.getId());
+        }
+        
+        pageLink = new TextPageLink(4, email1);
+        pageData = userService.findCustomerUsers(tenantId, customerId, pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertEquals(0, pageData.getData().size());
+        
+        for (User user : loadedCustomerUsersEmail2) {
+            userService.deleteUser(user.getId());
+        }
+        
+        pageLink = new TextPageLink(4, email2);
+        pageData = userService.findCustomerUsers(tenantId, customerId, pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertEquals(0, pageData.getData().size());
+        
+        tenantService.deleteTenant(tenantId);
+    }
+
+}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/WidgetsBundleServiceImplTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/WidgetsBundleServiceImplTest.java
new file mode 100644
index 0000000..09bb7ba
--- /dev/null
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/WidgetsBundleServiceImplTest.java
@@ -0,0 +1,430 @@
+/**
+ * Copyright © 2016 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 org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.widget.WidgetsBundle;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.model.ModelConstants;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class WidgetsBundleServiceImplTest extends AbstractServiceTest {
+
+    private IdComparator<WidgetsBundle> idComparator = new IdComparator<>();
+
+    private TenantId tenantId;
+
+    @Before
+    public void before() {
+        Tenant tenant = new Tenant();
+        tenant.setTitle("My tenant");
+        Tenant savedTenant = tenantService.saveTenant(tenant);
+        Assert.assertNotNull(savedTenant);
+        tenantId = savedTenant.getId();
+    }
+
+    @After
+    public void after() {
+        tenantService.deleteTenant(tenantId);
+    }
+
+    @Test
+    public void testSaveWidgetsBundle() throws IOException {
+        WidgetsBundle widgetsBundle = new WidgetsBundle();
+        widgetsBundle.setTenantId(tenantId);
+        widgetsBundle.setTitle("My first widgets bundle");
+
+        WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
+
+        Assert.assertNotNull(savedWidgetsBundle);
+        Assert.assertNotNull(savedWidgetsBundle.getId());
+        Assert.assertNotNull(savedWidgetsBundle.getAlias());
+        Assert.assertTrue(savedWidgetsBundle.getCreatedTime() > 0);
+        Assert.assertEquals(widgetsBundle.getTenantId(), savedWidgetsBundle.getTenantId());
+        Assert.assertEquals(widgetsBundle.getTitle(), savedWidgetsBundle.getTitle());
+
+        savedWidgetsBundle.setTitle("My new widgets bundle");
+
+        widgetsBundleService.saveWidgetsBundle(savedWidgetsBundle);
+        WidgetsBundle foundWidgetsBundle = widgetsBundleService.findWidgetsBundleById(savedWidgetsBundle.getId());
+        Assert.assertEquals(foundWidgetsBundle.getTitle(), savedWidgetsBundle.getTitle());
+
+        widgetsBundleService.deleteWidgetsBundle(savedWidgetsBundle.getId());
+    }
+
+    @Test(expected = DataValidationException.class)
+    public void testSaveWidgetsBundleWithEmptyTitle() {
+        WidgetsBundle widgetsBundle = new WidgetsBundle();
+        widgetsBundle.setTenantId(tenantId);
+        widgetsBundleService.saveWidgetsBundle(widgetsBundle);
+    }
+
+    @Test(expected = DataValidationException.class)
+    public void testSaveWidgetsBundleWithInvalidTenant() {
+        WidgetsBundle widgetsBundle = new WidgetsBundle();
+        widgetsBundle.setTitle("My widgets bundle");
+        widgetsBundle.setTenantId(new TenantId(UUIDs.timeBased()));
+        widgetsBundleService.saveWidgetsBundle(widgetsBundle);
+    }
+
+    @Test(expected = DataValidationException.class)
+    public void testUpdateWidgetsBundleTenant() {
+        WidgetsBundle widgetsBundle = new WidgetsBundle();
+        widgetsBundle.setTitle("My widgets bundle");
+        widgetsBundle.setTenantId(tenantId);
+        WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
+        savedWidgetsBundle.setTenantId(new TenantId(ModelConstants.NULL_UUID));
+        try {
+            widgetsBundleService.saveWidgetsBundle(savedWidgetsBundle);
+        } finally {
+            widgetsBundleService.deleteWidgetsBundle(savedWidgetsBundle.getId());
+        }
+    }
+
+    @Test(expected = DataValidationException.class)
+    public void testUpdateWidgetsBundleAlias() {
+        WidgetsBundle widgetsBundle = new WidgetsBundle();
+        widgetsBundle.setTitle("My widgets bundle");
+        widgetsBundle.setTenantId(tenantId);
+        WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
+        savedWidgetsBundle.setAlias("new_alias");
+        try {
+            widgetsBundleService.saveWidgetsBundle(savedWidgetsBundle);
+        } finally {
+            widgetsBundleService.deleteWidgetsBundle(savedWidgetsBundle.getId());
+        }
+    }
+
+    @Test
+    public void testFindWidgetsBundleById() {
+        WidgetsBundle widgetsBundle = new WidgetsBundle();
+        widgetsBundle.setTenantId(tenantId);
+        widgetsBundle.setTitle("My widgets bundle");
+        WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
+        WidgetsBundle foundWidgetsBundle = widgetsBundleService.findWidgetsBundleById(savedWidgetsBundle.getId());
+        Assert.assertNotNull(foundWidgetsBundle);
+        Assert.assertEquals(savedWidgetsBundle, foundWidgetsBundle);
+        widgetsBundleService.deleteWidgetsBundle(savedWidgetsBundle.getId());
+    }
+
+    @Test
+    public void testFindWidgetsBundleByTenantIdAndAlias() {
+        WidgetsBundle widgetsBundle = new WidgetsBundle();
+        widgetsBundle.setTenantId(tenantId);
+        widgetsBundle.setTitle("My widgets bundle");
+        WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
+        WidgetsBundle foundWidgetsBundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(tenantId, savedWidgetsBundle.getAlias());
+        Assert.assertNotNull(foundWidgetsBundle);
+        Assert.assertEquals(savedWidgetsBundle, foundWidgetsBundle);
+        widgetsBundleService.deleteWidgetsBundle(savedWidgetsBundle.getId());
+    }
+
+    @Test
+    public void testDeleteWidgetsBundle() {
+        WidgetsBundle widgetsBundle = new WidgetsBundle();
+        widgetsBundle.setTenantId(tenantId);
+        widgetsBundle.setTitle("My widgets bundle");
+        WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
+        WidgetsBundle foundWidgetsBundle = widgetsBundleService.findWidgetsBundleById(savedWidgetsBundle.getId());
+        Assert.assertNotNull(foundWidgetsBundle);
+        widgetsBundleService.deleteWidgetsBundle(savedWidgetsBundle.getId());
+        foundWidgetsBundle = widgetsBundleService.findWidgetsBundleById(savedWidgetsBundle.getId());
+        Assert.assertNull(foundWidgetsBundle);
+    }
+
+    @Test
+    public void testFindSystemWidgetsBundlesByPageLink() {
+
+        TenantId tenantId = new TenantId(ModelConstants.NULL_UUID);
+
+        List<WidgetsBundle> systemWidgetsBundles = widgetsBundleService.findSystemWidgetsBundles();
+        List<WidgetsBundle> createdWidgetsBundles = new ArrayList<>();
+        for (int i=0;i<235;i++) {
+            WidgetsBundle widgetsBundle = new WidgetsBundle();
+            widgetsBundle.setTenantId(tenantId);
+            widgetsBundle.setTitle("Widgets bundle "+i);
+            createdWidgetsBundles.add(widgetsBundleService.saveWidgetsBundle(widgetsBundle));
+        }
+
+        List<WidgetsBundle> widgetsBundles = new ArrayList<>(createdWidgetsBundles);
+        widgetsBundles.addAll(systemWidgetsBundles);
+
+        List<WidgetsBundle> loadedWidgetsBundles = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(19);
+        TextPageData<WidgetsBundle> pageData = null;
+        do {
+            pageData = widgetsBundleService.findSystemWidgetsBundlesByPageLink(pageLink);
+            loadedWidgetsBundles.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+
+        Collections.sort(widgetsBundles, idComparator);
+        Collections.sort(loadedWidgetsBundles, idComparator);
+
+        Assert.assertEquals(widgetsBundles, loadedWidgetsBundles);
+
+        for (WidgetsBundle widgetsBundle : createdWidgetsBundles) {
+            widgetsBundleService.deleteWidgetsBundle(widgetsBundle.getId());
+        }
+
+        loadedWidgetsBundles = widgetsBundleService.findSystemWidgetsBundles();
+
+        Collections.sort(systemWidgetsBundles, idComparator);
+        Collections.sort(loadedWidgetsBundles, idComparator);
+
+        Assert.assertEquals(systemWidgetsBundles, loadedWidgetsBundles);
+    }
+
+    @Test
+    public void testFindSystemWidgetsBundles() {
+        TenantId tenantId = new TenantId(ModelConstants.NULL_UUID);
+
+        List<WidgetsBundle> systemWidgetsBundles = widgetsBundleService.findSystemWidgetsBundles();
+
+        List<WidgetsBundle> createdWidgetsBundles = new ArrayList<>();
+        for (int i=0;i<135;i++) {
+            WidgetsBundle widgetsBundle = new WidgetsBundle();
+            widgetsBundle.setTenantId(tenantId);
+            widgetsBundle.setTitle("Widgets bundle "+i);
+            createdWidgetsBundles.add(widgetsBundleService.saveWidgetsBundle(widgetsBundle));
+        }
+
+        List<WidgetsBundle> widgetsBundles = new ArrayList<>(createdWidgetsBundles);
+        widgetsBundles.addAll(systemWidgetsBundles);
+
+        List<WidgetsBundle> loadedWidgetsBundles = widgetsBundleService.findSystemWidgetsBundles();
+
+        Collections.sort(widgetsBundles, idComparator);
+        Collections.sort(loadedWidgetsBundles, idComparator);
+
+        Assert.assertEquals(widgetsBundles, loadedWidgetsBundles);
+
+        for (WidgetsBundle widgetsBundle : createdWidgetsBundles) {
+            widgetsBundleService.deleteWidgetsBundle(widgetsBundle.getId());
+        }
+
+        loadedWidgetsBundles = widgetsBundleService.findSystemWidgetsBundles();
+
+        Collections.sort(systemWidgetsBundles, idComparator);
+        Collections.sort(loadedWidgetsBundles, idComparator);
+
+        Assert.assertEquals(systemWidgetsBundles, loadedWidgetsBundles);
+    }
+
+    @Test
+    public void testFindTenantWidgetsBundlesByTenantId() {
+        Tenant tenant = new Tenant();
+        tenant.setTitle("Test tenant");
+        tenant = tenantService.saveTenant(tenant);
+
+        TenantId tenantId = tenant.getId();
+
+        List<WidgetsBundle> widgetsBundles = new ArrayList<>();
+        for (int i=0;i<127;i++) {
+            WidgetsBundle widgetsBundle = new WidgetsBundle();
+            widgetsBundle.setTenantId(tenantId);
+            widgetsBundle.setTitle("Widgets bundle "+i);
+            widgetsBundles.add(widgetsBundleService.saveWidgetsBundle(widgetsBundle));
+        }
+
+        List<WidgetsBundle> loadedWidgetsBundles = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(11);
+        TextPageData<WidgetsBundle> pageData = null;
+        do {
+            pageData = widgetsBundleService.findTenantWidgetsBundlesByTenantId(tenantId, pageLink);
+            loadedWidgetsBundles.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+
+        Collections.sort(widgetsBundles, idComparator);
+        Collections.sort(loadedWidgetsBundles, idComparator);
+
+        Assert.assertEquals(widgetsBundles, loadedWidgetsBundles);
+
+        widgetsBundleService.deleteWidgetsBundlesByTenantId(tenantId);
+
+        pageLink = new TextPageLink(15);
+        pageData = widgetsBundleService.findTenantWidgetsBundlesByTenantId(tenantId, pageLink);
+        Assert.assertFalse(pageData.hasNext());
+        Assert.assertTrue(pageData.getData().isEmpty());
+
+        tenantService.deleteTenant(tenantId);
+    }
+
+    @Test
+    public void testFindAllWidgetsBundlesByTenantIdAndPageLink() {
+
+        List<WidgetsBundle> systemWidgetsBundles = widgetsBundleService.findSystemWidgetsBundles();
+
+        Tenant tenant = new Tenant();
+        tenant.setTitle("Test tenant");
+        tenant = tenantService.saveTenant(tenant);
+
+        TenantId tenantId = tenant.getId();
+        TenantId systemTenantId = new TenantId(ModelConstants.NULL_UUID);
+
+        List<WidgetsBundle> createdWidgetsBundles = new ArrayList<>();
+        List<WidgetsBundle> createdSystemWidgetsBundles = new ArrayList<>();
+        for (int i=0;i<177;i++) {
+            WidgetsBundle widgetsBundle = new WidgetsBundle();
+            widgetsBundle.setTenantId(i % 2 == 0 ? tenantId : systemTenantId);
+            widgetsBundle.setTitle((i % 2 == 0 ? "Widgets bundle " : "System widget bundle ") + i);
+            WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
+            createdWidgetsBundles.add(savedWidgetsBundle);
+            if (i % 2 == 1) {
+                createdSystemWidgetsBundles.add(savedWidgetsBundle);
+            }
+        }
+
+        List<WidgetsBundle> widgetsBundles = new ArrayList<>(createdWidgetsBundles);
+        widgetsBundles.addAll(systemWidgetsBundles);
+
+        List<WidgetsBundle> loadedWidgetsBundles = new ArrayList<>();
+        TextPageLink pageLink = new TextPageLink(17);
+        TextPageData<WidgetsBundle> pageData = null;
+        do {
+            pageData = widgetsBundleService.findAllTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, pageLink);
+            loadedWidgetsBundles.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+
+        Collections.sort(widgetsBundles, idComparator);
+        Collections.sort(loadedWidgetsBundles, idComparator);
+
+        Assert.assertEquals(widgetsBundles, loadedWidgetsBundles);
+
+        widgetsBundleService.deleteWidgetsBundlesByTenantId(tenantId);
+
+        loadedWidgetsBundles.clear();
+        pageLink = new TextPageLink(14);
+        do {
+            pageData = widgetsBundleService.findAllTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, pageLink);
+            loadedWidgetsBundles.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+
+        List<WidgetsBundle> allSystemWidgetsBundles = new ArrayList<>(systemWidgetsBundles);
+        allSystemWidgetsBundles.addAll(createdSystemWidgetsBundles);
+
+        Collections.sort(allSystemWidgetsBundles, idComparator);
+        Collections.sort(loadedWidgetsBundles, idComparator);
+
+        Assert.assertEquals(allSystemWidgetsBundles, loadedWidgetsBundles);
+
+        for (WidgetsBundle widgetsBundle : createdSystemWidgetsBundles) {
+            widgetsBundleService.deleteWidgetsBundle(widgetsBundle.getId());
+        }
+
+        loadedWidgetsBundles.clear();
+        pageLink = new TextPageLink(18);
+        do {
+            pageData = widgetsBundleService.findAllTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, pageLink);
+            loadedWidgetsBundles.addAll(pageData.getData());
+            if (pageData.hasNext()) {
+                pageLink = pageData.getNextPageLink();
+            }
+        } while (pageData.hasNext());
+
+        Collections.sort(systemWidgetsBundles, idComparator);
+        Collections.sort(loadedWidgetsBundles, idComparator);
+
+        Assert.assertEquals(systemWidgetsBundles, loadedWidgetsBundles);
+
+        tenantService.deleteTenant(tenantId);
+    }
+
+    @Test
+    public void testFindAllWidgetsBundlesByTenantId() {
+
+        List<WidgetsBundle> systemWidgetsBundles = widgetsBundleService.findSystemWidgetsBundles();
+
+        Tenant tenant = new Tenant();
+        tenant.setTitle("Test tenant");
+        tenant = tenantService.saveTenant(tenant);
+
+        TenantId tenantId = tenant.getId();
+        TenantId systemTenantId = new TenantId(ModelConstants.NULL_UUID);
+
+        List<WidgetsBundle> createdWidgetsBundles = new ArrayList<>();
+        List<WidgetsBundle> createdSystemWidgetsBundles = new ArrayList<>();
+        for (int i=0;i<277;i++) {
+            WidgetsBundle widgetsBundle = new WidgetsBundle();
+            widgetsBundle.setTenantId(i % 2 == 0 ? tenantId : systemTenantId);
+            widgetsBundle.setTitle((i % 2 == 0 ? "Widgets bundle " : "System widget bundle ") + i);
+            WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
+            createdWidgetsBundles.add(savedWidgetsBundle);
+            if (i % 2 == 1) {
+                createdSystemWidgetsBundles.add(savedWidgetsBundle);
+            }
+        }
+
+        List<WidgetsBundle> widgetsBundles = new ArrayList<>(createdWidgetsBundles);
+        widgetsBundles.addAll(systemWidgetsBundles);
+
+        List<WidgetsBundle> loadedWidgetsBundles = widgetsBundleService.findAllTenantWidgetsBundlesByTenantId(tenantId);
+
+        Collections.sort(widgetsBundles, idComparator);
+        Collections.sort(loadedWidgetsBundles, idComparator);
+
+        Assert.assertEquals(widgetsBundles, loadedWidgetsBundles);
+
+        widgetsBundleService.deleteWidgetsBundlesByTenantId(tenantId);
+
+        loadedWidgetsBundles = widgetsBundleService.findAllTenantWidgetsBundlesByTenantId(tenantId);
+
+        List<WidgetsBundle> allSystemWidgetsBundles = new ArrayList<>(systemWidgetsBundles);
+        allSystemWidgetsBundles.addAll(createdSystemWidgetsBundles);
+
+        Collections.sort(allSystemWidgetsBundles, idComparator);
+        Collections.sort(loadedWidgetsBundles, idComparator);
+
+        Assert.assertEquals(allSystemWidgetsBundles, loadedWidgetsBundles);
+
+        for (WidgetsBundle widgetsBundle : createdSystemWidgetsBundles) {
+            widgetsBundleService.deleteWidgetsBundle(widgetsBundle.getId());
+        }
+
+        loadedWidgetsBundles = widgetsBundleService.findAllTenantWidgetsBundlesByTenantId(tenantId);
+
+        Collections.sort(systemWidgetsBundles, idComparator);
+        Collections.sort(loadedWidgetsBundles, idComparator);
+
+        Assert.assertEquals(systemWidgetsBundles, loadedWidgetsBundles);
+
+        tenantService.deleteTenant(tenantId);
+    }
+
+}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/WidgetTypeServiceImplTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/WidgetTypeServiceImplTest.java
new file mode 100644
index 0000000..7a26c32
--- /dev/null
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/WidgetTypeServiceImplTest.java
@@ -0,0 +1,324 @@
+/**
+ * Copyright © 2016 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.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.User;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.widget.WidgetType;
+import org.thingsboard.server.common.data.widget.WidgetsBundle;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.model.ModelConstants;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class WidgetTypeServiceImplTest extends AbstractServiceTest {
+
+    private IdComparator<WidgetType> idComparator = new IdComparator<>();
+
+    private TenantId tenantId;
+
+    @Before
+    public void before() {
+        Tenant tenant = new Tenant();
+        tenant.setTitle("My tenant");
+        Tenant savedTenant = tenantService.saveTenant(tenant);
+        Assert.assertNotNull(savedTenant);
+        tenantId = savedTenant.getId();
+    }
+
+    @After
+    public void after() {
+        tenantService.deleteTenant(tenantId);
+    }
+
+    @Test
+    public void testSaveWidgetType() throws IOException {
+        WidgetsBundle widgetsBundle = new WidgetsBundle();
+        widgetsBundle.setTenantId(tenantId);
+        widgetsBundle.setTitle("Widgets bundle");
+        WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
+
+
+        WidgetType widgetType = new WidgetType();
+        widgetType.setTenantId(tenantId);
+        widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
+        widgetType.setName("Widget Type");
+        widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class));
+        WidgetType savedWidgetType = widgetTypeService.saveWidgetType(widgetType);
+
+        Assert.assertNotNull(savedWidgetType);
+        Assert.assertNotNull(savedWidgetType.getId());
+        Assert.assertNotNull(savedWidgetType.getAlias());
+        Assert.assertTrue(savedWidgetType.getCreatedTime() > 0);
+        Assert.assertEquals(widgetType.getTenantId(), savedWidgetType.getTenantId());
+        Assert.assertEquals(widgetType.getName(), savedWidgetType.getName());
+        Assert.assertEquals(widgetType.getDescriptor(), savedWidgetType.getDescriptor());
+        Assert.assertEquals(savedWidgetsBundle.getAlias(), savedWidgetType.getBundleAlias());
+
+        savedWidgetType.setName("New Widget Type");
+
+        widgetTypeService.saveWidgetType(savedWidgetType);
+        WidgetType foundWidgetType = widgetTypeService.findWidgetTypeById(savedWidgetType.getId());
+        Assert.assertEquals(foundWidgetType.getName(), savedWidgetType.getName());
+
+        widgetsBundleService.deleteWidgetsBundle(savedWidgetsBundle.getId());
+    }
+
+    @Test(expected = DataValidationException.class)
+    public void testSaveWidgetTypeWithEmptyName() throws IOException {
+        WidgetsBundle widgetsBundle = new WidgetsBundle();
+        widgetsBundle.setTenantId(tenantId);
+        widgetsBundle.setTitle("Widgets bundle");
+        WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
+
+        WidgetType widgetType = new WidgetType();
+        widgetType.setTenantId(tenantId);
+        widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
+        widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class));
+        try {
+            widgetTypeService.saveWidgetType(widgetType);
+        } finally {
+            widgetsBundleService.deleteWidgetsBundle(savedWidgetsBundle.getId());
+        }
+    }
+
+    @Test(expected = DataValidationException.class)
+    public void testSaveWidgetTypeWithEmptyBundleAlias() throws IOException {
+        WidgetType widgetType = new WidgetType();
+        widgetType.setTenantId(tenantId);
+        widgetType.setName("Widget Type");
+        widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class));
+        widgetTypeService.saveWidgetType(widgetType);
+    }
+
+    @Test(expected = DataValidationException.class)
+    public void testSaveWidgetTypeWithEmptyDescriptor() throws IOException {
+        WidgetsBundle widgetsBundle = new WidgetsBundle();
+        widgetsBundle.setTenantId(tenantId);
+        widgetsBundle.setTitle("Widgets bundle");
+        WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
+
+        WidgetType widgetType = new WidgetType();
+        widgetType.setTenantId(tenantId);
+        widgetType.setName("Widget Type");
+        widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
+        widgetType.setDescriptor(new ObjectMapper().readValue("{}", JsonNode.class));
+        try {
+            widgetTypeService.saveWidgetType(widgetType);
+        } finally {
+            widgetsBundleService.deleteWidgetsBundle(savedWidgetsBundle.getId());
+        }
+    }
+
+    @Test(expected = DataValidationException.class)
+    public void testSaveWidgetTypeWithInvalidTenant() throws IOException {
+        WidgetsBundle widgetsBundle = new WidgetsBundle();
+        widgetsBundle.setTenantId(tenantId);
+        widgetsBundle.setTitle("Widgets bundle");
+        WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
+
+        WidgetType widgetType = new WidgetType();
+        widgetType.setTenantId(new TenantId(UUIDs.timeBased()));
+        widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
+        widgetType.setName("Widget Type");
+        widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class));
+        try {
+            widgetTypeService.saveWidgetType(widgetType);
+        } finally {
+            widgetsBundleService.deleteWidgetsBundle(savedWidgetsBundle.getId());
+        }
+    }
+
+    @Test(expected = DataValidationException.class)
+    public void testSaveWidgetTypeWithInvalidBundleAlias() throws IOException {
+        WidgetType widgetType = new WidgetType();
+        widgetType.setTenantId(tenantId);
+        widgetType.setBundleAlias("some_alias");
+        widgetType.setName("Widget Type");
+        widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class));
+        widgetTypeService.saveWidgetType(widgetType);
+    }
+
+    @Test(expected = DataValidationException.class)
+    public void testUpdateWidgetTypeTenant() throws IOException {
+        WidgetsBundle widgetsBundle = new WidgetsBundle();
+        widgetsBundle.setTenantId(tenantId);
+        widgetsBundle.setTitle("Widgets bundle");
+        WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
+
+        WidgetType widgetType = new WidgetType();
+        widgetType.setTenantId(tenantId);
+        widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
+        widgetType.setName("Widget Type");
+        widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class));
+        WidgetType savedWidgetType = widgetTypeService.saveWidgetType(widgetType);
+        savedWidgetType.setTenantId(new TenantId(ModelConstants.NULL_UUID));
+        try {
+            widgetTypeService.saveWidgetType(savedWidgetType);
+        } finally {
+            widgetsBundleService.deleteWidgetsBundle(savedWidgetsBundle.getId());
+        }
+    }
+
+    @Test(expected = DataValidationException.class)
+    public void testUpdateWidgetTypeBundleAlias() throws IOException {
+        WidgetsBundle widgetsBundle = new WidgetsBundle();
+        widgetsBundle.setTenantId(tenantId);
+        widgetsBundle.setTitle("Widgets bundle");
+        WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
+
+        WidgetType widgetType = new WidgetType();
+        widgetType.setTenantId(tenantId);
+        widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
+        widgetType.setName("Widget Type");
+        widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class));
+        WidgetType savedWidgetType = widgetTypeService.saveWidgetType(widgetType);
+        savedWidgetType.setBundleAlias("some_alias");
+        try {
+            widgetTypeService.saveWidgetType(savedWidgetType);
+        } finally {
+            widgetsBundleService.deleteWidgetsBundle(savedWidgetsBundle.getId());
+        }
+    }
+
+    @Test(expected = DataValidationException.class)
+    public void testUpdateWidgetTypeAlias() throws IOException {
+        WidgetsBundle widgetsBundle = new WidgetsBundle();
+        widgetsBundle.setTenantId(tenantId);
+        widgetsBundle.setTitle("Widgets bundle");
+        WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
+
+        WidgetType widgetType = new WidgetType();
+        widgetType.setTenantId(tenantId);
+        widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
+        widgetType.setName("Widget Type");
+        widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class));
+        WidgetType savedWidgetType = widgetTypeService.saveWidgetType(widgetType);
+        savedWidgetType.setAlias("some_alias");
+        try {
+            widgetTypeService.saveWidgetType(savedWidgetType);
+        } finally {
+            widgetsBundleService.deleteWidgetsBundle(savedWidgetsBundle.getId());
+        }
+    }
+
+    @Test
+    public void testFindWidgetTypeById() throws IOException {
+        WidgetsBundle widgetsBundle = new WidgetsBundle();
+        widgetsBundle.setTenantId(tenantId);
+        widgetsBundle.setTitle("Widgets bundle");
+        WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
+
+        WidgetType widgetType = new WidgetType();
+        widgetType.setTenantId(tenantId);
+        widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
+        widgetType.setName("Widget Type");
+        widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class));
+        WidgetType savedWidgetType = widgetTypeService.saveWidgetType(widgetType);
+        WidgetType foundWidgetType = widgetTypeService.findWidgetTypeById(savedWidgetType.getId());
+        Assert.assertNotNull(foundWidgetType);
+        Assert.assertEquals(savedWidgetType, foundWidgetType);
+
+        widgetsBundleService.deleteWidgetsBundle(savedWidgetsBundle.getId());
+    }
+
+    @Test
+    public void testFindWidgetTypeByTenantIdBundleAliasAndAlias() throws IOException {
+        WidgetsBundle widgetsBundle = new WidgetsBundle();
+        widgetsBundle.setTenantId(tenantId);
+        widgetsBundle.setTitle("Widgets bundle");
+        WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
+
+        WidgetType widgetType = new WidgetType();
+        widgetType.setTenantId(tenantId);
+        widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
+        widgetType.setName("Widget Type");
+        widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class));
+        WidgetType savedWidgetType = widgetTypeService.saveWidgetType(widgetType);
+        WidgetType foundWidgetType = widgetTypeService.findWidgetTypeByTenantIdBundleAliasAndAlias(tenantId, savedWidgetsBundle.getAlias(), savedWidgetType.getAlias());
+        Assert.assertNotNull(foundWidgetType);
+        Assert.assertEquals(savedWidgetType, foundWidgetType);
+
+        widgetsBundleService.deleteWidgetsBundle(savedWidgetsBundle.getId());
+    }
+
+    @Test
+    public void testDeleteWidgetType() throws IOException {
+        WidgetsBundle widgetsBundle = new WidgetsBundle();
+        widgetsBundle.setTenantId(tenantId);
+        widgetsBundle.setTitle("Widgets bundle");
+        WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
+
+        WidgetType widgetType = new WidgetType();
+        widgetType.setTenantId(tenantId);
+        widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
+        widgetType.setName("Widget Type");
+        widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class));
+        WidgetType savedWidgetType = widgetTypeService.saveWidgetType(widgetType);
+        WidgetType foundWidgetType = widgetTypeService.findWidgetTypeById(savedWidgetType.getId());
+        Assert.assertNotNull(foundWidgetType);
+        widgetTypeService.deleteWidgetType(savedWidgetType.getId());
+        foundWidgetType = widgetTypeService.findWidgetTypeById(savedWidgetType.getId());
+        Assert.assertNull(foundWidgetType);
+
+        widgetsBundleService.deleteWidgetsBundle(savedWidgetsBundle.getId());
+    }
+
+    @Test
+    public void testFindWidgetTypesByTenantIdAndBundleAlias() throws IOException {
+        WidgetsBundle widgetsBundle = new WidgetsBundle();
+        widgetsBundle.setTenantId(tenantId);
+        widgetsBundle.setTitle("Widgets bundle");
+        WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
+
+        List<WidgetType> widgetTypes = new ArrayList<>();
+        for (int i=0;i<121;i++) {
+            WidgetType widgetType = new WidgetType();
+            widgetType.setTenantId(tenantId);
+            widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
+            widgetType.setName("Widget Type " + i);
+            widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class));
+            widgetTypes.add(widgetTypeService.saveWidgetType(widgetType));
+        }
+
+        List<WidgetType> loadedWidgetTypes = widgetTypeService.findWidgetTypesByTenantIdAndBundleAlias(tenantId, savedWidgetsBundle.getAlias());
+
+        Collections.sort(widgetTypes, idComparator);
+        Collections.sort(loadedWidgetTypes, idComparator);
+
+        Assert.assertEquals(widgetTypes, loadedWidgetTypes);
+
+        widgetTypeService.deleteWidgetTypesByTenantIdAndBundleAlias(tenantId, savedWidgetsBundle.getAlias());
+
+        loadedWidgetTypes = widgetTypeService.findWidgetTypesByTenantIdAndBundleAlias(tenantId, savedWidgetsBundle.getAlias());
+
+        Assert.assertTrue(loadedWidgetTypes.isEmpty());
+
+        widgetsBundleService.deleteWidgetsBundle(savedWidgetsBundle.getId());
+    }
+
+}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/timeseries/TimeseriesServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/timeseries/TimeseriesServiceTest.java
new file mode 100644
index 0000000..91dd973
--- /dev/null
+++ b/dao/src/test/java/org/thingsboard/server/dao/timeseries/TimeseriesServiceTest.java
@@ -0,0 +1,140 @@
+/**
+ * Copyright © 2016 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.timeseries;
+
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.ResultSetFuture;
+import com.datastax.driver.core.utils.UUIDs;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Assert;
+import org.thingsboard.server.common.data.DataConstants;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.dao.service.AbstractServiceTest;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.thingsboard.server.common.data.kv.*;
+
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * @author Andrew Shvayka
+ */
+
+@Slf4j
+public class TimeseriesServiceTest extends AbstractServiceTest {
+
+    private static final String STRING_KEY = "stringKey";
+    private static final String LONG_KEY = "longKey";
+    private static final String DOUBLE_KEY = "doubleKey";
+    private static final String BOOLEAN_KEY = "booleanKey";
+
+    public static final int PARTITION_MINUTES = 1100;
+
+    private static final long TS = 42L;
+
+    KvEntry stringKvEntry = new StringDataEntry(STRING_KEY, "value");
+    KvEntry longKvEntry = new LongDataEntry(LONG_KEY, Long.MAX_VALUE);
+    KvEntry doubleKvEntry = new DoubleDataEntry(DOUBLE_KEY, Double.MAX_VALUE);
+    KvEntry booleanKvEntry = new BooleanDataEntry(BOOLEAN_KEY, Boolean.TRUE);
+
+    @Test
+    public void testFindAllLatest() throws Exception {
+        DeviceId deviceId = new DeviceId(UUIDs.timeBased());
+
+        saveEntries(deviceId, TS - 2);
+        saveEntries(deviceId, TS - 1);
+        saveEntries(deviceId, TS);
+
+        ResultSetFuture rsFuture = tsService.findAllLatest(DataConstants.DEVICE, deviceId);
+        List<TsKvEntry> tsList = tsService.convertResultSetToTsKvEntryList(rsFuture.get());
+
+        assertNotNull(tsList);
+        assertEquals(4, tsList.size());
+        for (int i = 0; i < tsList.size(); i++) {
+            assertEquals(TS, tsList.get(i).getTs());
+        }
+
+        Collections.sort(tsList, (o1, o2) -> o1.getKey().compareTo(o2.getKey()));
+
+        List<TsKvEntry> expected = Arrays.asList(
+                toTsEntry(TS, stringKvEntry),
+                toTsEntry(TS, longKvEntry),
+                toTsEntry(TS, doubleKvEntry),
+                toTsEntry(TS, booleanKvEntry));
+        Collections.sort(expected, (o1, o2) -> o1.getKey().compareTo(o2.getKey()));
+
+        assertEquals(expected, tsList);
+    }
+
+    @Test
+    public void testFindLatest() throws Exception {
+        DeviceId deviceId = new DeviceId(UUIDs.timeBased());
+
+        saveEntries(deviceId, TS - 2);
+        saveEntries(deviceId, TS - 1);
+        saveEntries(deviceId, TS);
+
+        List<ResultSet> rs = tsService.findLatest(DataConstants.DEVICE, deviceId, Collections.singleton(STRING_KEY)).get();
+        Assert.assertEquals(1, rs.size());
+        Assert.assertEquals(toTsEntry(TS, stringKvEntry), tsService.convertResultToTsKvEntry(rs.get(0).one()));
+    }
+
+    @Test
+    public void testFindDeviceTsDataByQuery() throws Exception {
+        DeviceId deviceId = new DeviceId(UUIDs.timeBased());
+        LocalDateTime localDateTime = LocalDateTime.now(ZoneOffset.UTC).minusMinutes(PARTITION_MINUTES);
+        log.debug("Start event time is {}", localDateTime);
+        List<TsKvEntry> entries = new ArrayList<>(PARTITION_MINUTES);
+
+        for (int i = 0; i < PARTITION_MINUTES; i++) {
+            long time = localDateTime.plusMinutes(i).toInstant(ZoneOffset.UTC).toEpochMilli();
+            BasicTsKvEntry tsKvEntry = new BasicTsKvEntry(time, stringKvEntry);
+            tsService.save(DataConstants.DEVICE, deviceId, tsKvEntry).get();
+            entries.add(tsKvEntry);
+        }
+        log.debug("Saved all records {}", localDateTime);
+        List<TsKvEntry> list = tsService.find(DataConstants.DEVICE, deviceId, new BaseTsKvQuery(STRING_KEY, entries.get(599).getTs(),
+                LocalDateTime.now(ZoneOffset.UTC).toInstant(ZoneOffset.UTC).toEpochMilli()));
+        log.debug("Fetched records {}", localDateTime);
+        List<TsKvEntry> expected = entries.subList(600, PARTITION_MINUTES);
+        assertEquals(expected.size(), list.size());
+        assertEquals(expected, list);
+    }
+
+
+    private void saveEntries(DeviceId deviceId, long ts) throws ExecutionException, InterruptedException {
+        tsService.save(DataConstants.DEVICE, deviceId, toTsEntry(ts, stringKvEntry)).get();
+        tsService.save(DataConstants.DEVICE, deviceId, toTsEntry(ts, longKvEntry)).get();
+        tsService.save(DataConstants.DEVICE, deviceId, toTsEntry(ts, doubleKvEntry)).get();
+        tsService.save(DataConstants.DEVICE, deviceId, toTsEntry(ts, booleanKvEntry)).get();
+    }
+
+    private static TsKvEntry toTsEntry(long ts, KvEntry entry) {
+        return new BasicTsKvEntry(ts, entry);
+    }
+
+
+}
diff --git a/dao/src/test/resources/application-test.properties b/dao/src/test/resources/application-test.properties
new file mode 100644
index 0000000..b526b00
--- /dev/null
+++ b/dao/src/test/resources/application-test.properties
@@ -0,0 +1,7 @@
+cache.enabled=false
+cache.device_credentials.time_to_live=3600
+cache.device_credentials.max_size=1000000
+
+zk.enabled=false
+zk.url=localhost:2181
+zk.zk_dir=/thingsboard
\ No newline at end of file
diff --git a/dao/src/test/resources/cassandra-test.properties b/dao/src/test/resources/cassandra-test.properties
new file mode 100644
index 0000000..82fcbe1
--- /dev/null
+++ b/dao/src/test/resources/cassandra-test.properties
@@ -0,0 +1,49 @@
+cassandra.cluster_name=Thingsboard Cluster
+
+cassandra.keyspace_name=thingsboard
+
+cassandra.url=127.0.0.1:9142
+
+cassandra.ssl=false
+
+cassandra.jmx=false
+
+cassandra.metrics=true
+
+cassandra.compression=none
+
+cassandra.init_timeout_ms=60000
+
+cassandra.init_retry_interval_ms=3000
+
+cassandra.credentials=false
+
+cassandra.username=
+
+cassandra.password=
+
+cassandra.socket.connect_timeout=5000
+
+cassandra.socket.read_timeout=12000
+
+cassandra.socket.keep_alive=true
+
+cassandra.socket.reuse_address=true
+
+cassandra.socket.so_linger=
+
+cassandra.socket.tcp_no_delay=false
+
+cassandra.socket.receive_buffer_size=
+
+cassandra.socket.send_buffer_size=
+
+cassandra.query.read_consistency_level=ONE
+
+cassandra.query.write_consistency_level=ONE
+
+cassandra.query.default_fetch_size=2000
+
+cassandra.query.ts_key_value_partitioning=HOURS
+
+cassandra.query.max_limit_per_request=1000
diff --git a/dao/src/test/resources/cassandra-test.yaml b/dao/src/test/resources/cassandra-test.yaml
new file mode 100644
index 0000000..6463f64
--- /dev/null
+++ b/dao/src/test/resources/cassandra-test.yaml
@@ -0,0 +1,590 @@
+# Cassandra storage config YAML
+
+# NOTE:
+#   See http://wiki.apache.org/cassandra/StorageConfiguration for
+#   full explanations of configuration directives
+# /NOTE
+
+# The name of the cluster. This is mainly used to prevent machines in
+# one logical cluster from joining another.
+cluster_name: 'Test Cluster'
+
+# You should always specify InitialToken when setting up a production
+# cluster for the first time, and often when adding capacity later.
+# The principle is that each node should be given an equal slice of
+# the token ring; see http://wiki.apache.org/cassandra/Operations
+# for more details.
+#
+# If blank, Cassandra will request a token bisecting the range of
+# the heaviest-loaded existing node.  If there is no load information
+# available, such as is the case with a new cluster, it will pick
+# a random token, which will lead to hot spots.
+num_tokens: 1
+
+initial_token: 0
+
+# See http://wiki.apache.org/cassandra/HintedHandoff
+hinted_handoff_enabled: true
+# this defines the maximum amount of time a dead host will have hints
+# generated.  After it has been dead this long, new hints for it will not be
+# created until it has been seen alive and gone down again.
+max_hint_window_in_ms: 10800000 # 3 hours
+# Maximum throttle in KBs per second, per delivery thread.  This will be
+# reduced proportionally to the number of nodes in the cluster.  (If there
+# are two nodes in the cluster, each delivery thread will use the maximum
+# rate; if there are three, each will throttle to half of the maximum,
+# since we expect two nodes to be delivering hints simultaneously.)
+hinted_handoff_throttle_in_kb: 1024
+# Number of threads with which to deliver hints;
+# Consider increasing this number when you have multi-dc deployments, since
+# cross-dc handoff tends to be slower
+max_hints_delivery_threads: 2
+
+# The following setting populates the page cache on memtable flush and compaction
+# WARNING: Enable this setting only when the whole node's data fits in memory.
+# Defaults to: false
+# populate_io_cache_on_flush: false
+
+# Authentication backend, implementing IAuthenticator; used to identify users
+# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthenticator,
+# PasswordAuthenticator}.
+#
+# - AllowAllAuthenticator performs no checks - set it to disable authentication.
+# - PasswordAuthenticator relies on username/password pairs to authenticate
+#   users. It keeps usernames and hashed passwords in system_auth.credentials table.
+#   Please increase system_auth keyspace replication factor if you use this authenticator.
+authenticator: AllowAllAuthenticator
+
+# Authorization backend, implementing IAuthorizer; used to limit access/provide permissions
+# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthorizer,
+# CassandraAuthorizer}.
+#
+# - AllowAllAuthorizer allows any action to any user - set it to disable authorization.
+# - CassandraAuthorizer stores permissions in system_auth.permissions table. Please
+#   increase system_auth keyspace replication factor if you use this authorizer.
+authorizer: AllowAllAuthorizer
+
+# Validity period for permissions cache (fetching permissions can be an
+# expensive operation depending on the authorizer, CassandraAuthorizer is
+# one example). Defaults to 2000, set to 0 to disable.
+# Will be disabled automatically for AllowAllAuthorizer.
+permissions_validity_in_ms: 2000
+
+
+# The partitioner is responsible for distributing rows (by key) across
+# nodes in the cluster.  Any IPartitioner may be used, including your
+# own as long as it is on the classpath.  Out of the box, Cassandra
+# provides org.apache.cassandra.dht.{Murmur3Partitioner, RandomPartitioner
+# ByteOrderedPartitioner, OrderPreservingPartitioner (deprecated)}.
+#
+# - RandomPartitioner distributes rows across the cluster evenly by md5.
+#   This is the default prior to 1.2 and is retained for compatibility.
+# - Murmur3Partitioner is similar to RandomPartioner but uses Murmur3_128
+#   Hash Function instead of md5.  When in doubt, this is the best option.
+# - ByteOrderedPartitioner orders rows lexically by key bytes.  BOP allows
+#   scanning rows in key order, but the ordering can generate hot spots
+#   for sequential insertion workloads.
+# - OrderPreservingPartitioner is an obsolete form of BOP, that stores
+# - keys in a less-efficient format and only works with keys that are
+#   UTF8-encoded Strings.
+# - CollatingOPP collates according to EN,US rules rather than lexical byte
+#   ordering.  Use this as an example if you need custom collation.
+#
+# See http://wiki.apache.org/cassandra/Operations for more on
+# partitioners and token selection.
+partitioner: org.apache.cassandra.dht.Murmur3Partitioner
+
+# directories where Cassandra should store data on disk.
+data_file_directories:
+    - target/embeddedCassandra/data
+
+# commit log
+commitlog_directory: target/embeddedCassandra/commitlog
+
+hints_directory: target/embeddedCassandra/hints
+
+# policy for data disk failures:
+# stop: shut down gossip and Thrift, leaving the node effectively dead, but
+#       can still be inspected via JMX.
+# best_effort: stop using the failed disk and respond to requests based on
+#              remaining available sstables.  This means you WILL see obsolete
+#              data at CL.ONE!
+# ignore: ignore fatal errors and let requests fail, as in pre-1.2 Cassandra
+disk_failure_policy: stop
+
+
+# Maximum size of the key cache in memory.
+#
+# Each key cache hit saves 1 seek and each row cache hit saves 2 seeks at the
+# minimum, sometimes more. The key cache is fairly tiny for the amount of
+# time it saves, so it's worthwhile to use it at large numbers.
+# The row cache saves even more time, but must store the whole values of
+# its rows, so it is extremely space-intensive. It's best to only use the
+# row cache if you have hot rows or static rows.
+#
+# NOTE: if you reduce the size, you may not get you hottest keys loaded on startup.
+#
+# Default value is empty to make it "auto" (min(5% of Heap (in MB), 100MB)). Set to 0 to disable key cache.
+key_cache_size_in_mb:
+
+# Duration in seconds after which Cassandra should
+# safe the keys cache. Caches are saved to saved_caches_directory as
+# specified in this configuration file.
+#
+# Saved caches greatly improve cold-start speeds, and is relatively cheap in
+# terms of I/O for the key cache. Row cache saving is much more expensive and
+# has limited use.
+#
+# Default is 14400 or 4 hours.
+key_cache_save_period: 14400
+
+# Number of keys from the key cache to save
+# Disabled by default, meaning all keys are going to be saved
+# key_cache_keys_to_save: 100
+
+# Maximum size of the row cache in memory.
+# NOTE: if you reduce the size, you may not get you hottest keys loaded on startup.
+#
+# Default value is 0, to disable row caching.
+row_cache_size_in_mb: 0
+
+# Duration in seconds after which Cassandra should
+# safe the row cache. Caches are saved to saved_caches_directory as specified
+# in this configuration file.
+#
+# Saved caches greatly improve cold-start speeds, and is relatively cheap in
+# terms of I/O for the key cache. Row cache saving is much more expensive and
+# has limited use.
+#
+# Default is 0 to disable saving the row cache.
+row_cache_save_period: 0
+
+# Number of keys from the row cache to save
+# Disabled by default, meaning all keys are going to be saved
+# row_cache_keys_to_save: 100
+
+# saved caches
+saved_caches_directory: target/embeddedCassandra/saved_caches
+
+# commitlog_sync may be either "periodic" or "batch."
+# When in batch mode, Cassandra won't ack writes until the commit log
+# has been fsynced to disk.  It will wait up to
+# commitlog_sync_batch_window_in_ms milliseconds for other writes, before
+# performing the sync.
+#
+# commitlog_sync: batch
+# commitlog_sync_batch_window_in_ms: 50
+#
+# the other option is "periodic" where writes may be acked immediately
+# and the CommitLog is simply synced every commitlog_sync_period_in_ms
+# milliseconds.
+commitlog_sync: periodic
+commitlog_sync_period_in_ms: 10000
+
+# The size of the individual commitlog file segments.  A commitlog
+# segment may be archived, deleted, or recycled once all the data
+# in it (potentially from each columnfamily in the system) has been
+# flushed to sstables.
+#
+# The default size is 32, which is almost always fine, but if you are
+# archiving commitlog segments (see commitlog_archiving.properties),
+# then you probably want a finer granularity of archiving; 8 or 16 MB
+# is reasonable.
+commitlog_segment_size_in_mb: 32
+
+# any class that implements the SeedProvider interface and has a
+# constructor that takes a Map<String, String> of parameters will do.
+seed_provider:
+    # Addresses of hosts that are deemed contact points.
+    # Cassandra nodes use this list of hosts to find each other and learn
+    # the topology of the ring.  You must change this if you are running
+    # multiple nodes!
+    - class_name: org.apache.cassandra.locator.SimpleSeedProvider
+      parameters:
+          # seeds is actually a comma-delimited list of addresses.
+          # Ex: "<ip1>,<ip2>,<ip3>"
+          - seeds: "127.0.0.1"
+
+
+# For workloads with more data than can fit in memory, Cassandra's
+# bottleneck will be reads that need to fetch data from
+# disk. "concurrent_reads" should be set to (16 * number_of_drives) in
+# order to allow the operations to enqueue low enough in the stack
+# that the OS and drives can reorder them.
+#
+# On the other hand, since writes are almost never IO bound, the ideal
+# number of "concurrent_writes" is dependent on the number of cores in
+# your system; (8 * number_of_cores) is a good rule of thumb.
+concurrent_reads: 32
+concurrent_writes: 32
+
+# Total memory to use for memtables.  Cassandra will flush the largest
+# memtable when this much memory is used.
+# If omitted, Cassandra will set it to 1/3 of the heap.
+# memtable_total_space_in_mb: 2048
+
+# Total space to use for commitlogs.
+# If space gets above this value (it will round up to the next nearest
+# segment multiple), Cassandra will flush every dirty CF in the oldest
+# segment and remove it.
+# commitlog_total_space_in_mb: 4096
+
+# This sets the amount of memtable flush writer threads.  These will
+# be blocked by disk io, and each one will hold a memtable in memory
+# while blocked. If you have a large heap and many data directories,
+# you can increase this value for better flush performance.
+# By default this will be set to the amount of data directories defined.
+#memtable_flush_writers: 1
+
+# the number of full memtables to allow pending flush, that is,
+# waiting for a writer thread.  At a minimum, this should be set to
+# the maximum number of secondary indexes created on a single CF.
+#memtable_flush_queue_size: 4
+
+# Whether to, when doing sequential writing, fsync() at intervals in
+# order to force the operating system to flush the dirty
+# buffers. Enable this to avoid sudden dirty buffer flushing from
+# impacting read latencies. Almost always a good idea on SSD:s; not
+# necessarily on platters.
+trickle_fsync: false
+trickle_fsync_interval_in_kb: 10240
+
+# TCP port, for commands and data
+storage_port: 7010
+
+# SSL port, for encrypted communication.  Unused unless enabled in
+# encryption_options
+ssl_storage_port: 7011
+
+# Address to bind to and tell other Cassandra nodes to connect to. You
+# _must_ change this if you want multiple nodes to be able to
+# communicate!
+#
+# Leaving it blank leaves it up to InetAddress.getLocalHost(). This
+# will always do the Right Thing *if* the node is properly configured
+# (hostname, name resolution, etc), and the Right Thing is to use the
+# address associated with the hostname (it might not be).
+#
+# Setting this to 0.0.0.0 is always wrong.
+listen_address: 127.0.0.1
+
+start_native_transport: true
+# port for the CQL native transport to listen for clients on
+native_transport_port: 9142
+
+# Whether to start the thrift rpc server.
+start_rpc: false
+
+# Address to broadcast to other Cassandra nodes
+# Leaving this blank will set it to the same value as listen_address
+# broadcast_address: 1.2.3.4
+
+# The address to bind the Thrift RPC service to -- clients connect
+# here. Unlike ListenAddress above, you *can* specify 0.0.0.0 here if
+# you want Thrift to listen on all interfaces.
+#
+# Leaving this blank has the same effect it does for ListenAddress,
+# (i.e. it will be based on the configured hostname of the node).
+rpc_address: localhost
+# port for Thrift to listen for clients on
+rpc_port: 9171
+
+# enable or disable keepalive on rpc connections
+rpc_keepalive: true
+
+# Cassandra provides three options for the RPC Server:
+#
+# sync  -> One connection per thread in the rpc pool (see below).
+#          For a very large number of clients, memory will be your limiting
+#          factor; on a 64 bit JVM, 128KB is the minimum stack size per thread.
+#          Connection pooling is very, very strongly recommended.
+#
+# async -> Nonblocking server implementation with one thread to serve
+#          rpc connections.  This is not recommended for high throughput use
+#          cases. Async has been tested to be about 50% slower than sync
+#          or hsha and is deprecated: it will be removed in the next major release.
+#
+# hsha  -> Stands for "half synchronous, half asynchronous." The rpc thread pool
+#          (see below) is used to manage requests, but the threads are multiplexed
+#          across the different clients.
+#
+# The default is sync because on Windows hsha is about 30% slower.  On Linux,
+# sync/hsha performance is about the same, with hsha of course using less memory.
+rpc_server_type: sync
+
+# Uncomment rpc_min|max|thread to set request pool size.
+# You would primarily set max for the sync server to safeguard against
+# misbehaved clients; if you do hit the max, Cassandra will block until one
+# disconnects before accepting more.  The defaults for sync are min of 16 and max
+# unlimited.
+#
+# For the Hsha server, the min and max both default to quadruple the number of
+# CPU cores.
+#
+# This configuration is ignored by the async server.
+#
+# rpc_min_threads: 16
+# rpc_max_threads: 2048
+
+# uncomment to set socket buffer sizes on rpc connections
+# rpc_send_buff_size_in_bytes:
+# rpc_recv_buff_size_in_bytes:
+
+# Frame size for thrift (maximum field length).
+# 0 disables TFramedTransport in favor of TSocket. This option
+# is deprecated; we strongly recommend using Framed mode.
+thrift_framed_transport_size_in_mb: 15
+
+# The max length of a thrift message, including all fields and
+# internal thrift overhead.
+thrift_max_message_length_in_mb: 16
+
+# Set to true to have Cassandra create a hard link to each sstable
+# flushed or streamed locally in a backups/ subdirectory of the
+# Keyspace data.  Removing these links is the operator's
+# responsibility.
+incremental_backups: false
+
+# Whether or not to take a snapshot before each compaction.  Be
+# careful using this option, since Cassandra won't clean up the
+# snapshots for you.  Mostly useful if you're paranoid when there
+# is a data format change.
+snapshot_before_compaction: false
+
+# Whether or not a snapshot is taken of the data before keyspace truncation
+# or dropping of column families. The STRONGLY advised default of true
+# should be used to provide data safety. If you set this flag to false, you will
+# lose data on truncation or drop.
+auto_snapshot: false
+
+# Add column indexes to a row after its contents reach this size.
+# Increase if your column values are large, or if you have a very large
+# number of columns.  The competing causes are, Cassandra has to
+# deserialize this much of the row to read a single column, so you want
+# it to be small - at least if you do many partial-row reads - but all
+# the index data is read for each access, so you don't want to generate
+# that wastefully either.
+column_index_size_in_kb: 64
+
+# Size limit for rows being compacted in memory.  Larger rows will spill
+# over to disk and use a slower two-pass compaction process.  A message
+# will be logged specifying the row key.
+#in_memory_compaction_limit_in_mb: 64
+
+# Number of simultaneous compactions to allow, NOT including
+# validation "compactions" for anti-entropy repair.  Simultaneous
+# compactions can help preserve read performance in a mixed read/write
+# workload, by mitigating the tendency of small sstables to accumulate
+# during a single long running compactions. The default is usually
+# fine and if you experience problems with compaction running too
+# slowly or too fast, you should look at
+# compaction_throughput_mb_per_sec first.
+#
+# This setting has no effect on LeveledCompactionStrategy.
+#
+# concurrent_compactors defaults to the number of cores.
+# Uncomment to make compaction mono-threaded, the pre-0.8 default.
+#concurrent_compactors: 1
+
+# Multi-threaded compaction. When enabled, each compaction will use
+# up to one thread per core, plus one thread per sstable being merged.
+# This is usually only useful for SSD-based hardware: otherwise,
+# your concern is usually to get compaction to do LESS i/o (see:
+# compaction_throughput_mb_per_sec), not more.
+#multithreaded_compaction: false
+
+# Throttles compaction to the given total throughput across the entire
+# system. The faster you insert data, the faster you need to compact in
+# order to keep the sstable count down, but in general, setting this to
+# 16 to 32 times the rate you are inserting data is more than sufficient.
+# Setting this to 0 disables throttling. Note that this account for all types
+# of compaction, including validation compaction.
+compaction_throughput_mb_per_sec: 16
+
+# Track cached row keys during compaction, and re-cache their new
+# positions in the compacted sstable.  Disable if you use really large
+# key caches.
+#compaction_preheat_key_cache: true
+
+# Throttles all outbound streaming file transfers on this node to the
+# given total throughput in Mbps. This is necessary because Cassandra does
+# mostly sequential IO when streaming data during bootstrap or repair, which
+# can lead to saturating the network connection and degrading rpc performance.
+# When unset, the default is 200 Mbps or 25 MB/s.
+# stream_throughput_outbound_megabits_per_sec: 200
+
+# How long the coordinator should wait for read operations to complete
+read_request_timeout_in_ms: 5000
+# How long the coordinator should wait for seq or index scans to complete
+range_request_timeout_in_ms: 10000
+# How long the coordinator should wait for writes to complete
+write_request_timeout_in_ms: 10000
+# How long a coordinator should continue to retry a CAS operation
+# that contends with other proposals for the same row
+cas_contention_timeout_in_ms: 1000
+# How long the coordinator should wait for truncates to complete
+# (This can be much longer, because unless auto_snapshot is disabled
+# we need to flush first so we can snapshot before removing the data.)
+truncate_request_timeout_in_ms: 60000
+# The default timeout for other, miscellaneous operations
+request_timeout_in_ms: 10000
+
+# Enable operation timeout information exchange between nodes to accurately
+# measure request timeouts.  If disabled, replicas will assume that requests
+# were forwarded to them instantly by the coordinator, which means that
+# under overload conditions we will waste that much extra time processing
+# already-timed-out requests.
+#
+# Warning: before enabling this property make sure to ntp is installed
+# and the times are synchronized between the nodes.
+cross_node_timeout: false
+
+# Enable socket timeout for streaming operation.
+# When a timeout occurs during streaming, streaming is retried from the start
+# of the current file. This _can_ involve re-streaming an important amount of
+# data, so you should avoid setting the value too low.
+# Default value is 0, which never timeout streams.
+# streaming_socket_timeout_in_ms: 0
+
+# phi value that must be reached for a host to be marked down.
+# most users should never need to adjust this.
+# phi_convict_threshold: 8
+
+# endpoint_snitch -- Set this to a class that implements
+# IEndpointSnitch.  The snitch has two functions:
+# - it teaches Cassandra enough about your network topology to route
+#   requests efficiently
+# - it allows Cassandra to spread replicas around your cluster to avoid
+#   correlated failures. It does this by grouping machines into
+#   "datacenters" and "racks."  Cassandra will do its best not to have
+#   more than one replica on the same "rack" (which may not actually
+#   be a physical location)
+#
+# IF YOU CHANGE THE SNITCH AFTER DATA IS INSERTED INTO THE CLUSTER,
+# YOU MUST RUN A FULL REPAIR, SINCE THE SNITCH AFFECTS WHERE REPLICAS
+# ARE PLACED.
+#
+# Out of the box, Cassandra provides
+#  - SimpleSnitch:
+#    Treats Strategy order as proximity. This improves cache locality
+#    when disabling read repair, which can further improve throughput.
+#    Only appropriate for single-datacenter deployments.
+#  - PropertyFileSnitch:
+#    Proximity is determined by rack and data center, which are
+#    explicitly configured in cassandra-topology.properties.
+#  - RackInferringSnitch:
+#    Proximity is determined by rack and data center, which are
+#    assumed to correspond to the 3rd and 2nd octet of each node's
+#    IP address, respectively.  Unless this happens to match your
+#    deployment conventions (as it did Facebook's), this is best used
+#    as an example of writing a custom Snitch class.
+#  - Ec2Snitch:
+#    Appropriate for EC2 deployments in a single Region.  Loads Region
+#    and Availability Zone information from the EC2 API. The Region is
+#    treated as the Datacenter, and the Availability Zone as the rack.
+#    Only private IPs are used, so this will not work across multiple
+#    Regions.
+#  - Ec2MultiRegionSnitch:
+#    Uses public IPs as broadcast_address to allow cross-region
+#    connectivity.  (Thus, you should set seed addresses to the public
+#    IP as well.) You will need to open the storage_port or
+#    ssl_storage_port on the public IP firewall.  (For intra-Region
+#    traffic, Cassandra will switch to the private IP after
+#    establishing a connection.)
+#
+# You can use a custom Snitch by setting this to the full class name
+# of the snitch, which will be assumed to be on your classpath.
+endpoint_snitch: SimpleSnitch
+
+# controls how often to perform the more expensive part of host score
+# calculation
+dynamic_snitch_update_interval_in_ms: 100
+# controls how often to reset all host scores, allowing a bad host to
+# possibly recover
+dynamic_snitch_reset_interval_in_ms: 600000
+# if set greater than zero and read_repair_chance is < 1.0, this will allow
+# 'pinning' of replicas to hosts in order to increase cache capacity.
+# The badness threshold will control how much worse the pinned host has to be
+# before the dynamic snitch will prefer other replicas over it.  This is
+# expressed as a double which represents a percentage.  Thus, a value of
+# 0.2 means Cassandra would continue to prefer the static snitch values
+# until the pinned host was 20% worse than the fastest.
+dynamic_snitch_badness_threshold: 0.1
+
+# request_scheduler -- Set this to a class that implements
+# RequestScheduler, which will schedule incoming client requests
+# according to the specific policy. This is useful for multi-tenancy
+# with a single Cassandra cluster.
+# NOTE: This is specifically for requests from the client and does
+# not affect inter node communication.
+# org.apache.cassandra.scheduler.NoScheduler - No scheduling takes place
+# org.apache.cassandra.scheduler.RoundRobinScheduler - Round robin of
+# client requests to a node with a separate queue for each
+# request_scheduler_id. The scheduler is further customized by
+# request_scheduler_options as described below.
+request_scheduler: org.apache.cassandra.scheduler.NoScheduler
+
+# Scheduler Options vary based on the type of scheduler
+# NoScheduler - Has no options
+# RoundRobin
+#  - throttle_limit -- The throttle_limit is the number of in-flight
+#                      requests per client.  Requests beyond
+#                      that limit are queued up until
+#                      running requests can complete.
+#                      The value of 80 here is twice the number of
+#                      concurrent_reads + concurrent_writes.
+#  - default_weight -- default_weight is optional and allows for
+#                      overriding the default which is 1.
+#  - weights -- Weights are optional and will default to 1 or the
+#               overridden default_weight. The weight translates into how
+#               many requests are handled during each turn of the
+#               RoundRobin, based on the scheduler id.
+#
+# request_scheduler_options:
+#    throttle_limit: 80
+#    default_weight: 5
+#    weights:
+#      Keyspace1: 1
+#      Keyspace2: 5
+
+# request_scheduler_id -- An identifer based on which to perform
+# the request scheduling. Currently the only valid option is keyspace.
+# request_scheduler_id: keyspace
+
+# index_interval controls the sampling of entries from the primrary
+# row index in terms of space versus time.  The larger the interval,
+# the smaller and less effective the sampling will be.  In technicial
+# terms, the interval coresponds to the number of index entries that
+# are skipped between taking each sample.  All the sampled entries
+# must fit in memory.  Generally, a value between 128 and 512 here
+# coupled with a large key cache size on CFs results in the best trade
+# offs.  This value is not often changed, however if you have many
+# very small rows (many to an OS page), then increasing this will
+# often lower memory usage without a impact on performance.
+index_interval: 128
+
+# Enable or disable inter-node encryption
+# Default settings are TLS v1, RSA 1024-bit keys (it is imperative that
+# users generate their own keys) TLS_RSA_WITH_AES_128_CBC_SHA as the cipher
+# suite for authentication, key exchange and encryption of the actual data transfers.
+# NOTE: No custom encryption options are enabled at the moment
+# The available internode options are : all, none, dc, rack
+#
+# If set to dc cassandra will encrypt the traffic between the DCs
+# If set to rack cassandra will encrypt the traffic between the racks
+#
+# The passwords used in these options must match the passwords used when generating
+# the keystore and truststore.  For instructions on generating these files, see:
+# http://download.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html#CreateKeystore
+#
+server_encryption_options:
+    internode_encryption: none
+    keystore: conf/.keystore
+    keystore_password: cassandra
+    truststore: conf/.truststore
+    truststore_password: cassandra
+    # More advanced defaults below:
+    # protocol: TLS
+    # algorithm: SunX509
+    # store_type: JKS
+    # cipher_suites: [TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA]
\ No newline at end of file
diff --git a/dao/src/test/resources/logback.xml b/dao/src/test/resources/logback.xml
new file mode 100644
index 0000000..0969bbe
--- /dev/null
+++ b/dao/src/test/resources/logback.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<configuration>
+    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <logger name="org.thingsboard.server.dao" level="TRACE"/>
+    <logger name="org.apache.cassandra" level="WARN"/>
+    <logger name="org.cassandraunit" level="INFO" />
+    <logger name="org.apache.cassandra" level="INFO" />
+
+    <root level="WARN">
+        <appender-ref ref="console"/>
+    </root>
+
+</configuration>
diff --git a/dao/src/test/resources/system-test.cql b/dao/src/test/resources/system-test.cql
new file mode 100644
index 0000000..da5d1f1
--- /dev/null
+++ b/dao/src/test/resources/system-test.cql
@@ -0,0 +1,2 @@
+TRUNCATE thingsboard.plugin;
+TRUNCATE thingsboard.rule;
\ No newline at end of file
diff --git a/dao/src/test/resources/TestJsonData.json b/dao/src/test/resources/TestJsonData.json
new file mode 100644
index 0000000..22e3ab3
--- /dev/null
+++ b/dao/src/test/resources/TestJsonData.json
@@ -0,0 +1,5 @@
+{
+  "fieldA": "field A value",
+  "fieldB": "field B value",
+  "fieldC": 42
+}
\ No newline at end of file
diff --git a/dao/src/test/resources/TestJsonDescriptor.json b/dao/src/test/resources/TestJsonDescriptor.json
new file mode 100644
index 0000000..4c8c7a4
--- /dev/null
+++ b/dao/src/test/resources/TestJsonDescriptor.json
@@ -0,0 +1,27 @@
+{
+  "schema": {
+    "title": "Simple Schema",
+    "type": "object",
+    "properties": {
+      "fieldA": {
+        "type": "string"
+      },
+      "fieldB": {
+        "type": "string"
+      },
+      "fieldC": {
+        "description": "Age in years",
+        "type": "integer",
+        "minimum": 0
+      }
+    },
+    "required": [
+      "fieldA",
+      "fieldB",
+      "fieldC"
+    ]
+  },
+  "form": [
+    "*"
+  ]
+}
\ No newline at end of file

ui/.babelrc 9(+9 -0)

diff --git a/ui/.babelrc b/ui/.babelrc
new file mode 100644
index 0000000..7093a61
--- /dev/null
+++ b/ui/.babelrc
@@ -0,0 +1,9 @@
+{
+  "presets": [
+    "react",
+    "es2015"
+  ],
+  "plugins": [
+    "react-hot-loader/babel"
+  ]
+}

ui/.eslintrc 15(+15 -0)

diff --git a/ui/.eslintrc b/ui/.eslintrc
new file mode 100644
index 0000000..5cb89e5
--- /dev/null
+++ b/ui/.eslintrc
@@ -0,0 +1,15 @@
+{
+  "extends": [
+    "eslint:recommended",
+    "plugin:import/errors",
+    "plugin:import/warnings",
+    "angular"
+  ],
+  "parser": "babel-eslint",
+  "settings": {
+    "import/ignore": [
+      "node_modules",
+      "\\.tpl\\.html$"
+    ]
+  }
+}

ui/.gitignore 2(+2 -0)

diff --git a/ui/.gitignore b/ui/.gitignore
new file mode 100644
index 0000000..09daa48
--- /dev/null
+++ b/ui/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+.tern-project

ui/.jshintrc 13(+13 -0)

diff --git a/ui/.jshintrc b/ui/.jshintrc
new file mode 100644
index 0000000..6f00218
--- /dev/null
+++ b/ui/.jshintrc
@@ -0,0 +1,13 @@
+{
+  "globalstrict": true,
+  "globals": {
+    "angular": false,
+    "describe": false,
+    "it": false,
+    "expect": false,
+    "beforeEach": false,
+    "afterEach": false,
+    "module": false,
+    "inject": false
+  }
+}
\ No newline at end of file

ui/package.json 123(+123 -0)

diff --git a/ui/package.json b/ui/package.json
new file mode 100644
index 0000000..b8a0775
--- /dev/null
+++ b/ui/package.json
@@ -0,0 +1,123 @@
+{
+  "name": "thingsboard",
+  "private": true,
+  "version": "0.0.1",
+  "description": "Thingsboard UI",
+  "licenses": [
+    {
+      "type": "Apache-2.0",
+      "url": "http://www.apache.org/licenses/LICENSE-2.0"
+    }
+  ],
+  "scripts": {
+    "start": "babel-node --max_old_space_size=4096 server.js",
+    "build": "NODE_ENV=production webpack -p"
+  },
+  "dependencies": {
+    "ace-builds": "^1.2.5",
+    "angular": "^1.5.8",
+    "angular-animate": "^1.5.8",
+    "angular-aria": "^1.5.8",
+    "angular-breadcrumb": "^0.4.1",
+    "angular-carousel": "^1.0.1",
+    "angular-cookies": "^1.5.8",
+    "angular-drag-and-drop-lists": "^1.4.0",
+    "angular-fullscreen": "git://github.com/fabiobiondi/angular-fullscreen.git#master",
+    "angular-gridster": "^0.13.14",
+    "angular-hotkeys": "^1.7.0",
+    "angular-jwt": "^0.1.6",
+    "angular-material": "^1.1.1",
+    "angular-material-data-table": "^0.10.9",
+    "angular-material-icons": "^0.7.1",
+    "angular-messages": "^1.5.8",
+    "angular-route": "^1.5.8",
+    "angular-sanitize": "^1.5.8",
+    "angular-storage": "0.0.15",
+    "angular-touch": "^1.5.8",
+    "angular-translate": "^2.12.1",
+    "angular-translate-handler-log": "^2.12.1",
+    "angular-translate-interpolation-messageformat": "^2.12.1",
+    "angular-translate-loader-static-files": "^2.12.1",
+    "angular-translate-storage-cookie": "^2.12.1",
+    "angular-translate-storage-local": "^2.12.1",
+    "angular-ui-ace": "^0.2.3",
+    "angular-ui-router": "^0.3.1",
+    "angular-websocket": "^2.0.1",
+    "brace": "^0.8.0",
+    "canvas-gauges": "^2.0.9",
+    "clipboard": "^1.5.15",
+    "compass-sass-mixins": "^0.12.7",
+    "font-awesome": "^4.6.3",
+    "jquery": "^3.1.0",
+    "js-beautify": "^1.6.4",
+    "json-schema-defaults": "^0.2.0",
+    "justgage": "^1.2.2",
+    "material-ui": "^0.16.1",
+    "md-color-picker": "^0.2.6",
+    "mdPickers": "git://github.com/alenaksu/mdPickers.git#0.7.5",
+    "moment": "^2.15.0",
+    "ngclipboard": "^1.1.1",
+    "ngreact": "^0.3.0",
+    "objectpath": "^1.2.1",
+    "oclazyload": "^1.0.9",
+    "raphael": "^2.2.7",
+    "rc-select": "^6.6.1",
+    "react": "^15.3.2",
+    "react-ace": "^3.7.0",
+    "react-dom": "^15.3.2",
+    "react-schema-form": "^0.2.10",
+    "react-tap-event-plugin": "^1.0.0",
+    "reactcss": "^1.0.9",
+    "sass-material-colors": "0.0.5",
+    "schema-inspector": "^1.6.6",
+    "split.js": "^1.0.7",
+    "tinycolor2": "^1.4.1",
+    "v-accordion": "^1.6.0"
+  },
+  "devDependencies": {
+    "babel-cli": "^6.18.0",
+    "babel-core": "^6.14.0",
+    "babel-eslint": "^6.1.2",
+    "babel-loader": "^6.2.5",
+    "babel-preset-es2015": "^6.14.0",
+    "babel-preset-react": "^6.16.0",
+    "connect-history-api-fallback": "^1.3.0",
+    "copy-webpack-plugin": "^3.0.1",
+    "css-loader": "^0.25.0",
+    "eslint": "^3.4.0",
+    "eslint-config-angular": "^0.5.0",
+    "eslint-loader": "^1.5.0",
+    "eslint-plugin-angular": "^1.3.1",
+    "eslint-plugin-import": "^1.14.0",
+    "extract-text-webpack-plugin": "^1.0.1",
+    "file-loader": "^0.9.0",
+    "html-loader": "^0.4.3",
+    "html-minifier": "^3.2.2",
+    "html-minifier-loader": "^1.3.4",
+    "html-webpack-plugin": "^2.22.0",
+    "img-loader": "^1.3.1",
+    "less": "^2.7.1",
+    "less-loader": "^2.2.3",
+    "ng-annotate-loader": "^0.1.1",
+    "ngtemplate-loader": "^1.3.1",
+    "node-sass": "^3.9.3",
+    "postcss-loader": "^0.13.0",
+    "react-hot-loader": "^3.0.0-beta.6",
+    "sass-loader": "^4.0.2",
+    "style-loader": "^0.13.1",
+    "url-loader": "^0.5.7",
+    "webpack": "^1.13.2",
+    "webpack-dev-middleware": "^1.6.1",
+    "webpack-dev-server": "^1.15.1",
+    "webpack-hot-middleware": "^2.12.2"
+  },
+  "engine": "node >= 5.9.0",
+  "nyc": {
+    "exclude": [
+      "test",
+      "__tests__",
+      "node_modules",
+      "target"
+    ]
+  }
+}

ui/pom.xml 142(+142 -0)

diff --git a/ui/pom.xml b/ui/pom.xml
new file mode 100644
index 0000000..c9e4c68
--- /dev/null
+++ b/ui/pom.xml
@@ -0,0 +1,142 @@
+<!--
+
+    Copyright © 2016 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.thingsboard</groupId>
+        <version>0.0.1-SNAPSHOT</version>
+        <artifactId>server</artifactId>
+    </parent>
+    <groupId>org.thingsboard.server</groupId>
+    <artifactId>ui</artifactId>
+    <packaging>jar</packaging>
+
+    <name>Thingsboard Server UI</name>
+    <url>http://thingsboard.org</url>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <main.dir>${basedir}/..</main.dir>
+    </properties>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>${project.build.directory}/generated-resources</directory>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <groupId>com.github.eirslett</groupId>
+                <artifactId>frontend-maven-plugin</artifactId>
+                <version>1.0</version>
+                <configuration>
+                    <installDirectory>target</installDirectory>
+                    <workingDirectory>${basedir}</workingDirectory>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>install node and npm</id>
+                        <goals>
+                            <goal>install-node-and-npm</goal>
+                        </goals>
+                        <configuration>
+                            <nodeVersion>v6.9.1</nodeVersion>
+                            <npmVersion>3.10.8</npmVersion>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>npm install</id>
+                        <goals>
+                            <goal>npm</goal>
+                        </goals>
+                        <configuration>
+                            <arguments>install</arguments>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+    <profiles>
+        <profile>
+            <id>npm-build</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>com.github.eirslett</groupId>
+                        <artifactId>frontend-maven-plugin</artifactId>
+                        <version>1.0</version>
+                        <configuration>
+                            <installDirectory>target</installDirectory>
+                            <workingDirectory>${basedir}</workingDirectory>
+                        </configuration>
+                        <executions>
+                            <execution>
+                                <id>npm build</id>
+                                <goals>
+                                    <goal>npm</goal>
+                                </goals>
+                                <configuration>
+                                    <arguments>run build</arguments>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>npm-start</id>
+            <activation>
+                <property>
+                    <name>npm-start</name>
+                </property>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>com.github.eirslett</groupId>
+                        <artifactId>frontend-maven-plugin</artifactId>
+                        <version>1.0</version>
+                        <configuration>
+                            <installDirectory>target</installDirectory>
+                            <workingDirectory>${basedir}</workingDirectory>
+                        </configuration>
+                        <executions>
+                            <execution>
+                                <id>npm start</id>
+                                <goals>
+                                    <goal>npm</goal>
+                                </goals>
+
+                                <configuration>
+                                    <arguments>start</arguments>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+</project>

ui/server.js 75(+75 -0)

diff --git a/ui/server.js b/ui/server.js
new file mode 100644
index 0000000..842eb9c
--- /dev/null
+++ b/ui/server.js
@@ -0,0 +1,75 @@
+/*
+ * Copyright © 2016 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-commonjs */
+/* eslint-disable global-require */
+/* eslint-disable import/no-nodejs-modules */
+
+const path = require('path');
+const webpack = require('webpack');
+const historyApiFallback = require("connect-history-api-fallback");
+const webpackDevMiddleware = require('webpack-dev-middleware');
+const webpackHotMiddleware = require('webpack-hot-middleware');
+const config = require('./webpack.config');
+
+const express = require('express');
+const http = require('http');
+const httpProxy = require('http-proxy');
+const forwardHost = 'localhost';
+const forwardPort = 8080;
+
+const app = express();
+const server = http.createServer(app);
+
+const PORT = 3000;
+
+const compiler = webpack(config);
+
+app.use(historyApiFallback());
+app.use(webpackDevMiddleware(compiler, {noInfo: true, publicPath: config.output.publicPath}));
+app.use(webpackHotMiddleware(compiler));
+
+const root = path.join(__dirname, '/src');
+
+app.use('/static', express.static(root));
+
+const apiProxy = httpProxy.createProxyServer({
+    target: {
+        host: forwardHost,
+        port: forwardPort
+    }
+});
+
+console.info(`Forwarding API requests to http://${forwardHost}:${forwardPort}`);
+
+app.all('/api/*', (req, res) => {
+    apiProxy.web(req, res);
+});
+
+app.get('*', function(req, res) {
+    res.sendFile(path.join(__dirname, 'src/index.html'));
+});
+
+server.on('upgrade', (req, socket, head) => {
+    apiProxy.ws(req, socket, head);
+});
+
+server.listen(PORT, '0.0.0.0', (error) => {
+    if (error) {
+        console.error(error);
+    } else {
+        console.info(`==> 🌎  Listening on port ${PORT}. Open up http://localhost:${PORT}/ in your browser.`);
+    }
+});
diff --git a/ui/src/app/admin/admin.controller.js b/ui/src/app/admin/admin.controller.js
new file mode 100644
index 0000000..f59ae80
--- /dev/null
+++ b/ui/src/app/admin/admin.controller.js
@@ -0,0 +1,54 @@
+/*
+ * Copyright © 2016 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 AdminController(adminService, toast, $scope, $rootScope, $state, $translate) {
+
+    var vm = this;
+    vm.save = save;
+    vm.sendTestMail = sendTestMail;
+    vm.smtpProtocols = ('smtp smtps').split(' ').map(function (protocol) {
+        return protocol;
+    });
+
+    $translate('admin.test-mail-sent').then(function (translation) {
+        vm.testMailSent = translation;
+    }, function (translationId) {
+        vm.testMailSent = translationId;
+    });
+
+
+    loadSettings();
+
+    function loadSettings() {
+        adminService.getAdminSettings($state.$current.data.key).then(function success(settings) {
+            vm.settings = settings;
+        });
+    }
+
+    function save() {
+        adminService.saveAdminSettings(vm.settings).then(function success(settings) {
+            vm.settings = settings;
+            vm.settingsForm.$setPristine();
+        });
+    }
+
+    function sendTestMail() {
+        adminService.sendTestMail(vm.settings).then(function success() {
+            toast.showSuccess($translate.instant('admin.test-mail-sent'));
+        });
+    }
+
+}
diff --git a/ui/src/app/admin/admin.routes.js b/ui/src/app/admin/admin.routes.js
new file mode 100644
index 0000000..8f7c71a
--- /dev/null
+++ b/ui/src/app/admin/admin.routes.js
@@ -0,0 +1,73 @@
+/*
+ * Copyright © 2016 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 generalSettingsTemplate from '../admin/general-settings.tpl.html';
+import outgoingMailSettingsTemplate from '../admin/outgoing-mail-settings.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function AdminRoutes($stateProvider) {
+    $stateProvider
+        .state('home.settings', {
+            url: '/settings',
+            module: 'private',
+            auth: ['SYS_ADMIN'],
+            redirectTo: 'home.settings.general',
+            ncyBreadcrumb: {
+                label: '{"icon": "settings", "label": "admin.system-settings"}'
+            }
+        })
+        .state('home.settings.general', {
+            url: '/general',
+            module: 'private',
+            auth: ['SYS_ADMIN'],
+            views: {
+                "content@home": {
+                    templateUrl: generalSettingsTemplate,
+                    controllerAs: 'vm',
+                    controller: 'AdminController'
+                }
+            },
+            data: {
+                key: 'general',
+                pageTitle: 'admin.general-settings'
+            },
+            ncyBreadcrumb: {
+                label: '{"icon": "settings_applications", "label": "admin.general"}'
+            }
+        })
+        .state('home.settings.outgoing-mail', {
+            url: '/outgoing-mail',
+            module: 'private',
+            auth: ['SYS_ADMIN'],
+            views: {
+                "content@home": {
+                    templateUrl: outgoingMailSettingsTemplate,
+                    controllerAs: 'vm',
+                    controller: 'AdminController'
+                }
+            },
+            data: {
+                key: 'mail',
+                pageTitle: 'admin.outgoing-mail-settings'
+            },
+            ncyBreadcrumb: {
+                label: '{"icon": "mail", "label": "admin.outgoing-mail"}'
+            }
+        });
+}
diff --git a/ui/src/app/admin/general-settings.tpl.html b/ui/src/app/admin/general-settings.tpl.html
new file mode 100644
index 0000000..70cbfcf
--- /dev/null
+++ b/ui/src/app/admin/general-settings.tpl.html
@@ -0,0 +1,45 @@
+<!--
+
+    Copyright © 2016 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 layout="row" width="100%" layout-wrap> 
+	<md-card flex-gt-sm="60" flex="100" >
+       <md-card-title>
+          <md-card-title-text>
+           		<span translate class="md-headline">admin.general-settings</span>
+          </md-card-title-text>
+        </md-card-title>	
+   	    <md-progress-linear md-mode="indeterminate" ng-show="loading"></md-progress-linear>
+  	    <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
+        <md-card-content>	        
+			<form name="vm.settingsForm" ng-submit="vm.save()" tb-confirm-on-exit confirm-form="vm.settingsForm">
+				<fieldset ng-disabled="loading">
+					<md-input-container class="md-block">
+						<label translate>admin.base-url</label>
+						<input required name="baseUrl" ng-model="vm.settings.jsonValue.baseUrl">
+						<div ng-messages="vm.settingsForm.baseUrl.$error">
+	         					<div translate ng-message="required">admin.base-url-required</div>
+	       				</div>				
+					</md-input-container>
+					<div layout="row" layout-align="end center" width="100%" layout-wrap>
+						<md-button ng-disabled="loading || vm.settingsForm.$invalid || !vm.settingsForm.$dirty" type="submit" class="md-raised md-primary">{{'action.save' | translate}}</md-button>
+					</div>
+				</fieldset>
+			</form>
+		</md-card-content> 
+	</md-card>
+</div>
+
diff --git a/ui/src/app/admin/index.js b/ui/src/app/admin/index.js
new file mode 100644
index 0000000..087d912
--- /dev/null
+++ b/ui/src/app/admin/index.js
@@ -0,0 +1,36 @@
+/*
+ * Copyright © 2016 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 uiRouter from 'angular-ui-router';
+import ngMaterial from 'angular-material';
+import ngMessages from 'angular-messages';
+import thingsboardApiAdmin from '../api/admin.service';
+import thingsboardConfirmOnExit from '../components/confirm-on-exit.directive';
+import thingsboardToast from '../services/toast';
+
+import AdminRoutes from './admin.routes';
+import AdminController from './admin.controller';
+
+export default angular.module('thingsboard.admin', [
+    uiRouter,
+    ngMaterial,
+    ngMessages,
+    thingsboardApiAdmin,
+    thingsboardConfirmOnExit,
+    thingsboardToast
+])
+    .config(AdminRoutes)
+    .controller('AdminController', AdminController)
+    .name;
diff --git a/ui/src/app/admin/outgoing-mail-settings.tpl.html b/ui/src/app/admin/outgoing-mail-settings.tpl.html
new file mode 100644
index 0000000..6e0914f
--- /dev/null
+++ b/ui/src/app/admin/outgoing-mail-settings.tpl.html
@@ -0,0 +1,99 @@
+<!--
+
+    Copyright © 2016 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 layout="row" width="100%" layout-wrap tb-help="'outgoingMailSettings'" help-container-id="help-container">
+	<md-card flex-gt-sm="60" flex="100" >
+       <md-card-title>
+          <md-card-title-text layout="row">
+           	  <span translate class="md-headline">admin.outgoing-mail-settings</span>
+			  <span flex></span>
+			  <div id="help-container"></div>
+          </md-card-title-text>
+        </md-card-title>	
+   	    <md-progress-linear md-mode="indeterminate" ng-show="loading"></md-progress-linear>
+  	    <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
+        <md-card-content>	        		
+			<form name="vm.settingsForm" ng-submit="vm.save()" tb-confirm-on-exit confirm-form="vm.settingsForm">
+				<fieldset ng-disabled="loading">
+					<md-input-container class="md-block">
+						<label translate>admin.mail-from</label>
+						<input required name="mailFrom" ng-model="vm.settings.jsonValue.mailFrom">
+						<div ng-messages="vm.settingsForm.mailFrom.$error">
+	         					<div translate ng-message="required">admin.mail-from-required</div>
+	       				</div>				
+					</md-input-container>
+					<md-input-container class="md-block">
+						<label translate>admin.smtp-protocol</label>
+			            <md-select ng-disabled="loading" ng-model="vm.settings.jsonValue.smtpProtocol">
+			              	<md-option ng-repeat="smtpProtocol in vm.smtpProtocols" value="{{smtpProtocol}}">
+			                	{{smtpProtocol.toUpperCase()}}
+			             	 </md-option>
+			            </md-select>
+					</md-input-container>	
+					<div layout-gt-sm="row">
+						<md-input-container class="md-block" flex="100" flex-gt-sm="60">
+							<label translate>admin.smtp-host</label>
+							<input required name="smtpHost" ng-model="vm.settings.jsonValue.smtpHost"
+								   placeholder="localhost">	
+							<div ng-messages="vm.settingsForm.smtpHost.$error">
+		         					<div translate ng-message="required">admin.smtp-host-required</div>
+		       				</div>				
+						</md-input-container>	
+						<md-input-container class="md-block" flex="100" flex-gt-sm="40">
+							<label translate>admin.smtp-port</label>
+							<input required name="smtpPort" ng-model="vm.settings.jsonValue.smtpPort"
+									placeholder="25" 
+									ng-pattern="/^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/"
+									md-maxlength="5">	
+				            <div ng-messages="vm.settingsForm.smtpPort.$error" role="alert" multiple>
+				              <div translate ng-message="required">admin.smtp-port-required</div>
+				              <div translate ng-message="pattern">admin.smtp-port-invalid</div>
+				              <div translate ng-message="md-maxlength">admin.smtp-port-invalid</div>
+				            </div>	
+						</md-input-container>						
+					</div>						
+					<md-input-container class="md-block">
+						<label translate>admin.timeout-msec</label>
+						<input required name="timeout" ng-model="vm.settings.jsonValue.timeout"
+							   placeholder="10000" 
+							   ng-pattern="/^[0-9]{1,6}$/"
+							   md-maxlength="6">	
+						<div ng-messages="vm.settingsForm.timeout.$error" role="alert" multiple>
+	         				  <div translate ng-message="required">admin.timeout-required</div>
+				              <div translate ng-message="pattern">admin.timeout-invalid</div>
+				              <div translate ng-message="md-maxlength">admin.timeout-invalid</div>
+	       				</div>				
+					</md-input-container>	
+					<md-checkbox ng-disabled="loading" ng-true-value="'true'" ng-false-value="'false'"
+								 aria-label="{{ 'admin.enable-tls' | translate }}" ng-model="vm.settings.jsonValue.enableTls">{{ 'admin.enable-tls' | translate }}</md-checkbox>
+					<md-input-container class="md-block">
+						<label translate>common.username</label>
+						<input name="username" placeholder="{{ 'common.enter-username' | translate }}" ng-model="vm.settings.jsonValue.username">
+					</md-input-container>				
+					<md-input-container class="md-block">
+						<label translate>common.password</label>
+						<input name="password" placeholder="{{ 'common.enter-password' | translate }}" type="password" ng-model="vm.settings.jsonValue.password">
+					</md-input-container>				
+					<div layout="row" layout-align="end center" width="100%" layout-wrap>
+						<md-button ng-disabled="loading || vm.settingsForm.$invalid" ng-click="vm.sendTestMail()" class="md-raised">{{'admin.send-test-mail' | translate}}</md-button>
+						<md-button ng-disabled="loading || vm.settingsForm.$invalid || !vm.settingsForm.$dirty" type="submit" class="md-raised md-primary">{{'action.save' | translate}}</md-button>
+					</div>
+				</fieldset>
+			</form>
+	    </md-card-content>
+    </md-card>
+</div>
diff --git a/ui/src/app/api/admin.service.js b/ui/src/app/api/admin.service.js
new file mode 100644
index 0000000..e49a017
--- /dev/null
+++ b/ui/src/app/api/admin.service.js
@@ -0,0 +1,63 @@
+/*
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default angular.module('thingsboard.api.admin', [])
+    .factory('adminService', AdminService)
+    .name;
+
+/*@ngInject*/
+function AdminService($http, $q) {
+
+    var service = {
+        getAdminSettings: getAdminSettings,
+        saveAdminSettings: saveAdminSettings,
+        sendTestMail: sendTestMail
+    }
+
+    return service;
+
+    function getAdminSettings(key) {
+        var deferred = $q.defer();
+        var url = '/api/admin/settings/' + key;
+        $http.get(url, null).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail() {
+            deferred.reject();
+        });
+        return deferred.promise;
+    }
+
+    function saveAdminSettings(settings) {
+        var deferred = $q.defer();
+        var url = '/api/admin/settings';
+        $http.post(url, settings).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function sendTestMail(settings) {
+        var deferred = $q.defer();
+        var url = '/api/admin/settings/testMail';
+        $http.post(url, settings).then(function success() {
+            deferred.resolve();
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+}
diff --git a/ui/src/app/api/component-descriptor.service.js b/ui/src/app/api/component-descriptor.service.js
new file mode 100644
index 0000000..7f108ff
--- /dev/null
+++ b/ui/src/app/api/component-descriptor.service.js
@@ -0,0 +1,87 @@
+/*
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default angular.module('thingsboard.api.componentDescriptor', [])
+    .factory('componentDescriptorService', ComponentDescriptorService).name;
+
+/*@ngInject*/
+function ComponentDescriptorService($http, $q) {
+
+    var componentsByType = {};
+    var componentsByClazz = {};
+    var actionsByPlugin = {};
+
+    var service = {
+        getComponentDescriptorsByType: getComponentDescriptorsByType,
+        getComponentDescriptorByClazz: getComponentDescriptorByClazz,
+        getPluginActionsByPluginClazz: getPluginActionsByPluginClazz
+    }
+
+    return service;
+
+    function getComponentDescriptorsByType(componentType) {
+        var deferred = $q.defer();
+        if (componentsByType[componentType]) {
+            deferred.resolve(componentsByType[componentType]);
+        } else {
+            var url = '/api/components/' + componentType;
+            $http.get(url, null).then(function success(response) {
+                componentsByType[componentType] = response.data;
+                for (var i = 0; i < componentsByType[componentType].length; i++) {
+                    var component = componentsByType[componentType][i];
+                    componentsByClazz[component.clazz] = component;
+                }
+                deferred.resolve(componentsByType[componentType]);
+            }, function fail() {
+                deferred.reject();
+            });
+
+        }
+        return deferred.promise;
+    }
+
+    function getComponentDescriptorByClazz(componentDescriptorClazz) {
+        var deferred = $q.defer();
+        if (componentsByClazz[componentDescriptorClazz]) {
+            deferred.resolve(componentsByClazz[componentDescriptorClazz]);
+        } else {
+            var url = '/api/component/' + componentDescriptorClazz;
+            $http.get(url, null).then(function success(response) {
+                componentsByClazz[componentDescriptorClazz] = response.data;
+                deferred.resolve(componentsByClazz[componentDescriptorClazz]);
+            }, function fail() {
+                deferred.reject();
+            });
+        }
+        return deferred.promise;
+    }
+
+    function getPluginActionsByPluginClazz(pluginClazz) {
+        var deferred = $q.defer();
+        if (actionsByPlugin[pluginClazz]) {
+            deferred.resolve(actionsByPlugin[pluginClazz]);
+        } else {
+            var url = '/api/components/actions/' + pluginClazz;
+            $http.get(url, null).then(function success(response) {
+                actionsByPlugin[pluginClazz] = response.data;
+                deferred.resolve(actionsByPlugin[pluginClazz]);
+            }, function fail() {
+                deferred.reject();
+            });
+        }
+        return deferred.promise;
+    }
+
+}
diff --git a/ui/src/app/api/customer.service.js b/ui/src/app/api/customer.service.js
new file mode 100644
index 0000000..b6570ef
--- /dev/null
+++ b/ui/src/app/api/customer.service.js
@@ -0,0 +1,85 @@
+/*
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default angular.module('thingsboard.api.customer', [])
+    .factory('customerService', CustomerService)
+    .name;
+
+/*@ngInject*/
+function CustomerService($http, $q) {
+
+    var service = {
+        getCustomers: getCustomers,
+        getCustomer: getCustomer,
+        deleteCustomer: deleteCustomer,
+        saveCustomer: saveCustomer
+    }
+
+    return service;
+
+    function getCustomers(pageLink) {
+        var deferred = $q.defer();
+        var url = '/api/customers?limit=' + pageLink.limit;
+        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 getCustomer(customerId) {
+        var deferred = $q.defer();
+        var url = '/api/customer/' + customerId;
+        $http.get(url, null).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function saveCustomer(customer) {
+        var deferred = $q.defer();
+        var url = '/api/customer';
+        $http.post(url, customer).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function deleteCustomer(customerId) {
+        var deferred = $q.defer();
+        var url = '/api/customer/' + customerId;
+        $http.delete(url).then(function success() {
+            deferred.resolve();
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+}
diff --git a/ui/src/app/api/dashboard.service.js b/ui/src/app/api/dashboard.service.js
new file mode 100644
index 0000000..3709fc8
--- /dev/null
+++ b/ui/src/app/api/dashboard.service.js
@@ -0,0 +1,129 @@
+/*
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default angular.module('thingsboard.api.dashboard', [])
+    .factory('dashboardService', DashboardService).name;
+
+/*@ngInject*/
+function DashboardService($http, $q) {
+
+    var service = {
+        assignDashboardToCustomer: assignDashboardToCustomer,
+        getCustomerDashboards: getCustomerDashboards,
+        getDashboard: getDashboard,
+        getTenantDashboards: getTenantDashboards,
+        deleteDashboard: deleteDashboard,
+        saveDashboard: saveDashboard,
+        unassignDashboardFromCustomer: unassignDashboardFromCustomer
+    }
+
+    return service;
+
+    function getTenantDashboards(pageLink) {
+        var deferred = $q.defer();
+        var url = '/api/tenant/dashboards?limit=' + pageLink.limit;
+        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 getCustomerDashboards(customerId, pageLink) {
+        var deferred = $q.defer();
+        var url = '/api/customer/' + customerId + '/dashboards?limit=' + pageLink.limit;
+        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 getDashboard(dashboardId) {
+        var deferred = $q.defer();
+        var url = '/api/dashboard/' + dashboardId;
+        $http.get(url, null).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function saveDashboard(dashboard) {
+        var deferred = $q.defer();
+        var url = '/api/dashboard';
+        $http.post(url, dashboard).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function deleteDashboard(dashboardId) {
+        var deferred = $q.defer();
+        var url = '/api/dashboard/' + dashboardId;
+        $http.delete(url).then(function success() {
+            deferred.resolve();
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function assignDashboardToCustomer(customerId, dashboardId) {
+        var deferred = $q.defer();
+        var url = '/api/customer/' + customerId + '/dashboard/' + dashboardId;
+        $http.post(url, null).then(function success() {
+            deferred.resolve();
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function unassignDashboardFromCustomer(dashboardId) {
+        var deferred = $q.defer();
+        var url = '/api/customer/dashboard/' + dashboardId;
+        $http.delete(url).then(function success() {
+            deferred.resolve();
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+}
diff --git a/ui/src/app/api/datasource.service.js b/ui/src/app/api/datasource.service.js
new file mode 100644
index 0000000..495b555
--- /dev/null
+++ b/ui/src/app/api/datasource.service.js
@@ -0,0 +1,490 @@
+/*
+ * Copyright © 2016 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 thingsboardApiDevice from './device.service';
+import thingsboardApiTelemetryWebsocket from './telemetry-websocket.service';
+import thingsboardTypes from '../common/types.constant';
+import thingsboardUtils from '../common/utils.service';
+
+export default angular.module('thingsboard.api.datasource', [thingsboardApiDevice, thingsboardApiTelemetryWebsocket, thingsboardTypes, thingsboardUtils])
+    .factory('datasourceService', DatasourceService)
+    .name;
+
+/*@ngInject*/
+function DatasourceService($timeout, $log, telemetryWebsocketService, types, utils) {
+
+    var subscriptions = {};
+
+    var service = {
+        subscribeToDatasource: subscribeToDatasource,
+        unsubscribeFromDatasource: unsubscribeFromDatasource
+    }
+
+    return service;
+
+
+    function subscribeToDatasource(listener) {
+        var datasource = listener.datasource;
+
+        if (datasource.type === types.datasourceType.device && !listener.deviceId) {
+            return;
+        }
+
+        var subscriptionDataKeys = [];
+        for (var d in datasource.dataKeys) {
+            var dataKey = datasource.dataKeys[d];
+            var subscriptionDataKey = {
+                name: dataKey.name,
+                type: dataKey.type,
+                funcBody: dataKey.funcBody,
+                postFuncBody: dataKey.postFuncBody
+            }
+            subscriptionDataKeys.push(subscriptionDataKey);
+        }
+
+        var datasourceSubscription = {
+            datasourceType: datasource.type,
+            dataKeys: subscriptionDataKeys,
+            type: listener.widget.type
+        };
+
+        if (listener.widget.type === types.widgetType.timeseries.value) {
+            datasourceSubscription.subscriptionTimewindow = listener.subscriptionTimewindow;
+        }
+        if (datasourceSubscription.datasourceType === types.datasourceType.device) {
+            datasourceSubscription.deviceId = listener.deviceId;
+        }
+
+        listener.datasourceSubscriptionKey = utils.objectHashCode(datasourceSubscription);
+        var subscription;
+        if (subscriptions[listener.datasourceSubscriptionKey]) {
+            subscription = subscriptions[listener.datasourceSubscriptionKey];
+            subscription.syncListener(listener);
+        } else {
+            subscription = new DatasourceSubscription(datasourceSubscription, telemetryWebsocketService, $timeout, $log, types, utils);
+            subscriptions[listener.datasourceSubscriptionKey] = subscription;
+            subscription.start();
+        }
+        subscription.addListener(listener);
+    }
+
+    function unsubscribeFromDatasource(listener) {
+        if (listener.datasourceSubscriptionKey) {
+            if (subscriptions[listener.datasourceSubscriptionKey]) {
+                var subscription = subscriptions[listener.datasourceSubscriptionKey];
+                subscription.removeListener(listener);
+                if (!subscription.hasListeners()) {
+                    subscription.unsubscribe();
+                    delete subscriptions[listener.datasourceSubscriptionKey];
+                }
+            }
+            listener.datasourceSubscriptionKey = null;
+        }
+    }
+
+}
+
+function DatasourceSubscription(datasourceSubscription, telemetryWebsocketService, $timeout, $log, types, utils) {
+
+    var listeners = [];
+    var datasourceType = datasourceSubscription.datasourceType;
+    var datasourceData = {};
+    var dataKeys = {};
+    var subscribers = {};
+    var history = datasourceSubscription.subscriptionTimewindow &&
+        datasourceSubscription.subscriptionTimewindow.fixedWindow;
+    var realtime = datasourceSubscription.subscriptionTimewindow &&
+        datasourceSubscription.subscriptionTimewindow.realtimeWindowMs;
+    var dataGenFunction = null;
+    var timer;
+    var frequency;
+
+    var subscription = {
+        addListener: addListener,
+        hasListeners: hasListeners,
+        removeListener: removeListener,
+        syncListener: syncListener,
+        start: start,
+        unsubscribe: unsubscribe
+    }
+
+    initializeSubscription();
+
+    return subscription;
+
+    function initializeSubscription() {
+        for (var i = 0; i < datasourceSubscription.dataKeys.length; i++) {
+            var dataKey = angular.copy(datasourceSubscription.dataKeys[i]);
+            dataKey.index = i;
+            var key;
+            if (datasourceType === types.datasourceType.function) {
+                key = utils.objectHashCode(dataKey);
+                if (!dataKey.func) {
+                    dataKey.func = new Function("time", "prevValue", dataKey.funcBody);
+                }
+                datasourceData[key] = [];
+                dataKeys[key] = dataKey;
+            } else if (datasourceType === types.datasourceType.device) {
+                key = dataKey.name + '_' + dataKey.type;
+                if (dataKey.postFuncBody && !dataKey.postFunc) {
+                    dataKey.postFunc = new Function("time", "value", "prevValue", dataKey.postFuncBody);
+                }
+                var dataKeysList = dataKeys[key];
+                if (!dataKeysList) {
+                    dataKeysList = [];
+                    dataKeys[key] = dataKeysList;
+                }
+                var index = dataKeysList.push(dataKey) - 1;
+                datasourceData[key + '_' + index] = [];
+            }
+            dataKey.key = key;
+        }
+        if (datasourceType === types.datasourceType.function) {
+            frequency = 1000;
+            if (datasourceSubscription.type === types.widgetType.timeseries.value) {
+                dataGenFunction = generateSeries;
+                var window;
+                if (realtime) {
+                    window = datasourceSubscription.subscriptionTimewindow.realtimeWindowMs;
+                } else {
+                    window = datasourceSubscription.subscriptionTimewindow.fixedWindow.endTimeMs -
+                        datasourceSubscription.subscriptionTimewindow.fixedWindow.startTimeMs;
+                }
+                frequency = window / 1000 * 5;
+            } else if (datasourceSubscription.type === types.widgetType.latest.value) {
+                dataGenFunction = generateLatest;
+                frequency = 1000;
+            }
+        }
+    }
+
+    function addListener(listener) {
+        listeners.push(listener);
+        if (history) {
+            start();
+        }
+    }
+
+    function hasListeners() {
+        return listeners.length > 0;
+    }
+
+    function removeListener(listener) {
+        listeners.splice(listeners.indexOf(listener), 1);
+    }
+
+    function syncListener(listener) {
+        var key;
+        var dataKey;
+        if (datasourceType === types.datasourceType.function) {
+            for (key in dataKeys) {
+                dataKey = dataKeys[key];
+                listener.dataUpdated(datasourceData[key],
+                    listener.datasourceIndex,
+                    dataKey.index);
+            }
+        } else if (datasourceType === types.datasourceType.device) {
+            for (key in dataKeys) {
+                var dataKeysList = dataKeys[key];
+                for (var i = 0; i < dataKeysList.length; i++) {
+                    dataKey = dataKeysList[i];
+                    var datasourceKey = key + '_' + i;
+                    listener.dataUpdated(datasourceData[datasourceKey],
+                        listener.datasourceIndex,
+                        dataKey.index);
+                }
+            }
+        }
+    }
+
+    function start() {
+        if (history && !hasListeners()) {
+            return;
+        }
+        //$log.debug("started!");
+        if (datasourceType === types.datasourceType.device) {
+
+            //send subscribe command
+
+            var tsKeys = '';
+            var attrKeys = '';
+
+            for (var key in dataKeys) {
+                var dataKeysList = dataKeys[key];
+                var dataKey = dataKeysList[0];
+                if (dataKey.type === types.dataKeyType.timeseries) {
+                    if (tsKeys.length > 0) {
+                        tsKeys += ',';
+                    }
+                    tsKeys += dataKey.name;
+                } else if (dataKey.type === types.dataKeyType.attribute) {
+                    if (attrKeys.length > 0) {
+                        attrKeys += ',';
+                    }
+                    attrKeys += dataKey.name;
+                }
+            }
+
+            if (tsKeys.length > 0) {
+
+                var subscriber;
+                var subscriptionCommand;
+
+                if (history) {
+
+                    var historyCommand = {
+                        deviceId: datasourceSubscription.deviceId,
+                        keys: tsKeys,
+                        startTs: datasourceSubscription.subscriptionTimewindow.fixedWindow.startTimeMs,
+                        endTs: datasourceSubscription.subscriptionTimewindow.fixedWindow.endTimeMs
+                    };
+
+                    subscriber = {
+                        historyCommand: historyCommand,
+                        type: types.dataKeyType.timeseries,
+                        onData: function (data) {
+                            onData(data, types.dataKeyType.timeseries);
+                        }
+                    };
+
+                    telemetryWebsocketService.subscribe(subscriber);
+                    subscribers[subscriber.historyCommand.cmdId] = subscriber;
+
+                } else {
+
+                    subscriptionCommand = {
+                        deviceId: datasourceSubscription.deviceId,
+                        keys: tsKeys
+                    };
+
+                    if (datasourceSubscription.type === types.widgetType.timeseries.value) {
+                        subscriptionCommand.timeWindow = datasourceSubscription.subscriptionTimewindow.realtimeWindowMs;
+                    }
+
+                    subscriber = {
+                        subscriptionCommand: subscriptionCommand,
+                        type: types.dataKeyType.timeseries,
+                        onData: function (data) {
+                            onData(data, types.dataKeyType.timeseries);
+                        }
+                    };
+
+                    telemetryWebsocketService.subscribe(subscriber);
+                    subscribers[subscriber.subscriptionCommand.cmdId] = subscriber;
+
+                }
+            }
+
+            if (attrKeys.length > 0) {
+
+                subscriptionCommand = {
+                    deviceId: datasourceSubscription.deviceId,
+                    keys: attrKeys
+                };
+
+                subscriber = {
+                    subscriptionCommand: subscriptionCommand,
+                    type: types.dataKeyType.attribute,
+                    onData: function (data) {
+                        onData(data, types.dataKeyType.attribute);
+                    }
+                };
+
+                telemetryWebsocketService.subscribe(subscriber);
+                subscribers[subscriber.cmdId] = subscriber;
+
+            }
+
+        } else if (dataGenFunction) {
+            if (history) {
+                onTick();
+            } else {
+                timer = $timeout(onTick, 0, false);
+            }
+        }
+
+    }
+
+    function unsubscribe() {
+        if (timer) {
+            $timeout.cancel(timer);
+        }
+        if (datasourceType === types.datasourceType.device) {
+            for (var cmdId in subscribers) {
+                telemetryWebsocketService.unsubscribe(subscribers[cmdId]);
+            }
+            subscribers = {};
+        }
+        //$log.debug("unsibscribed!");
+    }
+
+    function boundToInterval(data, timewindowMs) {
+        if (data.length > 1) {
+            var start = data[0][0];
+            var end = data[data.length - 1][0];
+            var i = 0;
+            var currentInterval = end - start;
+            while (currentInterval > timewindowMs && i < data.length - 2) {
+                i++;
+                start = data[i][0];
+                currentInterval = end - start;
+            }
+            if (i > 1) {
+                data.splice(0, i - 1);
+            }
+        }
+        return data;
+    }
+
+    function generateSeries(dataKey) {
+
+        var data = [];
+        var startTime;
+        var endTime;
+
+        if (realtime) {
+            endTime = (new Date).getTime();
+            if (dataKey.lastUpdateTime) {
+                startTime = dataKey.lastUpdateTime + frequency;
+            } else {
+                startTime = endTime - datasourceSubscription.subscriptionTimewindow.realtimeWindowMs;
+            }
+        } else {
+            startTime = datasourceSubscription.subscriptionTimewindow.fixedWindow.startTimeMs;
+            endTime = datasourceSubscription.subscriptionTimewindow.fixedWindow.endTimeMs;
+        }
+        var prevSeries;
+        var datasourceKeyData = datasourceData[dataKey.key];
+        if (datasourceKeyData.length > 0) {
+            prevSeries = datasourceKeyData[datasourceKeyData.length - 1];
+        } else {
+            prevSeries = [0, 0];
+        }
+        for (var time = startTime; time <= endTime; time += frequency) {
+            var series = [];
+            series.push(time);
+            var value = dataKey.func(time, prevSeries[1]);
+            series.push(value);
+            data.push(series);
+            prevSeries = series;
+        }
+        if (data.length > 0) {
+            dataKey.lastUpdateTime = data[data.length - 1][0];
+        }
+        if (realtime) {
+            datasourceData[dataKey.key] = boundToInterval(datasourceKeyData.concat(data),
+                datasourceSubscription.subscriptionTimewindow.realtimeWindowMs);
+        } else {
+            datasourceData[dataKey.key] = data;
+        }
+        for (var i in listeners) {
+            var listener = listeners[i];
+            listener.dataUpdated(datasourceData[dataKey.key],
+                listener.datasourceIndex,
+                dataKey.index);
+        }
+    }
+
+    function generateLatest(dataKey) {
+        var prevSeries;
+        var datasourceKeyData = datasourceData[dataKey.key];
+        if (datasourceKeyData.length > 0) {
+            prevSeries = datasourceKeyData[datasourceKeyData.length - 1];
+        } else {
+            prevSeries = [0, 0];
+        }
+        var series = [];
+        var time = (new Date).getTime();
+        series.push(time);
+        var value = dataKey.func(time, prevSeries[1]);
+        series.push(value);
+        datasourceData[dataKey.key] = [series];
+        for (var i in listeners) {
+            var listener = listeners[i];
+            listener.dataUpdated(datasourceData[dataKey.key],
+                listener.datasourceIndex,
+                dataKey.index);
+        }
+    }
+
+    function onTick() {
+        for (var key in dataKeys) {
+            dataGenFunction(dataKeys[key]);
+        }
+        if (!history) {
+            timer = $timeout(onTick, frequency / 2, false);
+        }
+    }
+
+    function onData(sourceData, type) {
+        for (var keyName in sourceData) {
+            var keyData = sourceData[keyName];
+            var key = keyName + '_' + type;
+            var dataKeyList = dataKeys[key];
+            for (var keyIndex = 0; keyIndex < dataKeyList.length; keyIndex++) {
+                var datasourceKey = key + "_" + keyIndex;
+                if (datasourceData[datasourceKey]) {
+                    var dataKey = dataKeyList[keyIndex];
+                    var data = [];
+                    var prevSeries;
+                    var datasourceKeyData = datasourceData[datasourceKey];
+                    if (datasourceKeyData.length > 0) {
+                        prevSeries = datasourceKeyData[datasourceKeyData.length - 1];
+                    } else {
+                        prevSeries = [0, 0];
+                    }
+                    if (datasourceSubscription.type === types.widgetType.timeseries.value) {
+                        var series, time, value;
+                        for (var i in keyData) {
+                            series = keyData[i];
+                            time = series[0];
+                            value = Number(series[1]);
+                            if (dataKey.postFunc) {
+                                value = dataKey.postFunc(time, value, prevSeries[1]);
+                            }
+                            series = [time, value];
+                            data.push(series);
+                            prevSeries = series;
+                        }
+                    } else if (datasourceSubscription.type === types.widgetType.latest.value) {
+                        if (keyData.length > 0) {
+                            series = keyData[0];
+                            time = series[0];
+                            value = series[1];
+                            if (dataKey.postFunc) {
+                                value = dataKey.postFunc(time, value, prevSeries[1]);
+                            }
+                            series = [time, value];
+                            data.push(series);
+                        }
+                    }
+                    if (data.length > 0) {
+                        if (realtime) {
+                            datasourceData[datasourceKey] = boundToInterval(datasourceKeyData.concat(data), datasourceSubscription.subscriptionTimewindow.realtimeWindowMs);
+                        } else {
+                            datasourceData[datasourceKey] = data;
+                        }
+                        for (var i2 in listeners) {
+                            var listener = listeners[i2];
+                            listener.dataUpdated(datasourceData[datasourceKey],
+                                listener.datasourceIndex,
+                                dataKey.index);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/ui/src/app/api/device.service.js b/ui/src/app/api/device.service.js
new file mode 100644
index 0000000..cf38d12
--- /dev/null
+++ b/ui/src/app/api/device.service.js
@@ -0,0 +1,381 @@
+/*
+ * Copyright © 2016 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 thingsboardTypes from '../common/types.constant';
+
+export default angular.module('thingsboard.api.device', [thingsboardTypes])
+    .factory('deviceService', DeviceService)
+    .name;
+
+/*@ngInject*/
+function DeviceService($http, $q, $filter, telemetryWebsocketService, types) {
+
+
+    var deviceAttributesSubscriptionMap = {};
+
+    var service = {
+        assignDeviceToCustomer: assignDeviceToCustomer,
+        deleteDevice: deleteDevice,
+        getCustomerDevices: getCustomerDevices,
+        getDevice: getDevice,
+        getDeviceCredentials: getDeviceCredentials,
+        getDeviceKeys: getDeviceKeys,
+        getDeviceTimeseriesValues: getDeviceTimeseriesValues,
+        getTenantDevices: getTenantDevices,
+        saveDevice: saveDevice,
+        saveDeviceCredentials: saveDeviceCredentials,
+        unassignDeviceFromCustomer: unassignDeviceFromCustomer,
+        getDeviceAttributes: getDeviceAttributes,
+        subscribeForDeviceAttributes: subscribeForDeviceAttributes,
+        unsubscribeForDeviceAttributes: unsubscribeForDeviceAttributes,
+        saveDeviceAttributes: saveDeviceAttributes,
+        deleteDeviceAttributes: deleteDeviceAttributes,
+        sendOneWayRpcCommand: sendOneWayRpcCommand,
+        sendTwoWayRpcCommand: sendTwoWayRpcCommand
+    }
+
+    return service;
+
+    function getTenantDevices(pageLink) {
+        var deferred = $q.defer();
+        var url = '/api/tenant/devices?limit=' + pageLink.limit;
+        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 getCustomerDevices(customerId, pageLink) {
+        var deferred = $q.defer();
+        var url = '/api/customer/' + customerId + '/devices?limit=' + pageLink.limit;
+        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 getDevice(deviceId) {
+        var deferred = $q.defer();
+        var url = '/api/device/' + deviceId;
+        $http.get(url, null).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function saveDevice(device) {
+        var deferred = $q.defer();
+        var url = '/api/device';
+        $http.post(url, device).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function deleteDevice(deviceId) {
+        var deferred = $q.defer();
+        var url = '/api/device/' + deviceId;
+        $http.delete(url).then(function success() {
+            deferred.resolve();
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function getDeviceCredentials(deviceId) {
+        var deferred = $q.defer();
+        var url = '/api/device/' + deviceId + '/credentials';
+        $http.get(url, null).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function saveDeviceCredentials(deviceCredentials) {
+        var deferred = $q.defer();
+        var url = '/api/device/credentials';
+        $http.post(url, deviceCredentials).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function assignDeviceToCustomer(customerId, deviceId) {
+        var deferred = $q.defer();
+        var url = '/api/customer/' + customerId + '/device/' + deviceId;
+        $http.post(url, null).then(function success() {
+            deferred.resolve();
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function unassignDeviceFromCustomer(deviceId) {
+        var deferred = $q.defer();
+        var url = '/api/customer/device/' + deviceId;
+        $http.delete(url).then(function success() {
+            deferred.resolve();
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function getDeviceKeys(deviceId, query, type) {
+        var deferred = $q.defer();
+        var url = '/api/plugins/telemetry/' + deviceId + '/keys/';
+        if (type === types.dataKeyType.timeseries) {
+            url += 'timeseries';
+        } else if (type === types.dataKeyType.attribute) {
+            url += 'attributes';
+        }
+        $http.get(url, null).then(function success(response) {
+            var result = [];
+            if (response.data) {
+                if (query) {
+                    var dataKeys = response.data;
+                    var lowercaseQuery = angular.lowercase(query);
+                    for (var i in dataKeys) {
+                        if (angular.lowercase(dataKeys[i]).indexOf(lowercaseQuery) === 0) {
+                            result.push(dataKeys[i]);
+                        }
+                    }
+                } else {
+                    result = response.data;
+                }
+            }
+            deferred.resolve(result);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function getDeviceTimeseriesValues(deviceId, keys, startTs, endTs, limit) {
+        var deferred = $q.defer();
+        var url = '/api/plugins/telemetry/' + deviceId + '/values/timeseries';
+        url += '?keys=' + keys;
+        url += '&startTs=' + startTs;
+        url += '&endTs=' + endTs;
+        if (angular.isDefined(limit)) {
+            url += '&limit=' + limit;
+        }
+        $http.get(url, null).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function processDeviceAttributes(attributes, query, deferred, successCallback, update) {
+        attributes = $filter('orderBy')(attributes, query.order);
+        if (query.search != null) {
+            attributes = $filter('filter')(attributes, {key: query.search});
+        }
+        var responseData = {
+            count: attributes.length
+        }
+        var startIndex = query.limit * (query.page - 1);
+        responseData.data = attributes.slice(startIndex, startIndex + query.limit);
+        successCallback(responseData, update);
+        if (deferred) {
+            deferred.resolve();
+        }
+    }
+
+    function getDeviceAttributes(deviceId, attributeScope, query, successCallback) {
+        var deferred = $q.defer();
+        var subscriptionId = deviceId + attributeScope;
+        var das = deviceAttributesSubscriptionMap[subscriptionId];
+        if (das) {
+            if (das.attributes) {
+                processDeviceAttributes(das.attributes, query, deferred, successCallback);
+                das.subscriptionCallback = function(attributes) {
+                    processDeviceAttributes(attributes, query, null, successCallback, true);
+                }
+            } else {
+                das.subscriptionCallback = function(attributes) {
+                    processDeviceAttributes(attributes, query, deferred, successCallback);
+                    das.subscriptionCallback = function(attributes) {
+                        processDeviceAttributes(attributes, query, null, successCallback, true);
+                    }
+                }
+            }
+        } else {
+            var url = '/api/plugins/telemetry/' + deviceId + '/values/attributes/' + attributeScope;
+            $http.get(url, null).then(function success(response) {
+                processDeviceAttributes(response.data, query, deferred, successCallback);
+            }, function fail() {
+                deferred.reject();
+            });
+        }
+        return deferred;
+    }
+
+    function onSubscriptionData(data, subscriptionId) {
+        var deviceAttributesSubscription = deviceAttributesSubscriptionMap[subscriptionId];
+        if (deviceAttributesSubscription) {
+            if (!deviceAttributesSubscription.attributes) {
+                deviceAttributesSubscription.attributes = [];
+                deviceAttributesSubscription.keys = {};
+            }
+            var attributes = deviceAttributesSubscription.attributes;
+            var keys = deviceAttributesSubscription.keys;
+            for (var key in data) {
+                var index = keys[key];
+                var attribute;
+                if (index > -1) {
+                    attribute = attributes[index];
+                } else {
+                    attribute = {
+                        key: key
+                    };
+                    index = attributes.push(attribute)-1;
+                    keys[key] = index;
+                }
+                var attrData = data[key][0];
+                attribute.lastUpdateTs = attrData[0];
+                attribute.value = attrData[1];
+            }
+            if (deviceAttributesSubscription.subscriptionCallback) {
+                deviceAttributesSubscription.subscriptionCallback(attributes);
+            }
+        }
+    }
+
+    function subscribeForDeviceAttributes(deviceId, attributeScope) {
+        var subscriptionId = deviceId + attributeScope;
+        var deviceAttributesSubscription = deviceAttributesSubscriptionMap[subscriptionId];
+        if (!deviceAttributesSubscription) {
+            var subscriptionCommand = {
+                deviceId: deviceId
+            };
+
+            var type = attributeScope === types.latestTelemetry.value ?
+                types.dataKeyType.timeseries : types.dataKeyType.attribute;
+
+            var subscriber = {
+                subscriptionCommand: subscriptionCommand,
+                type: type,
+                onData: function (data) {
+                    onSubscriptionData(data, subscriptionId);
+                }
+            };
+            telemetryWebsocketService.subscribe(subscriber);
+            deviceAttributesSubscription = {
+                subscriber: subscriber,
+                attributes: null
+            }
+            deviceAttributesSubscriptionMap[subscriptionId] = deviceAttributesSubscription;
+        }
+        return subscriptionId;
+    }
+    function unsubscribeForDeviceAttributes(subscriptionId) {
+        var deviceAttributesSubscription = deviceAttributesSubscriptionMap[subscriptionId];
+        if (deviceAttributesSubscription) {
+            telemetryWebsocketService.unsubscribe(deviceAttributesSubscription.subscriber);
+            delete deviceAttributesSubscriptionMap[subscriptionId];
+        }
+    }
+
+    function saveDeviceAttributes(deviceId, attributeScope, attributes) {
+        var deferred = $q.defer();
+        var attributesData = {};
+        for (var a in attributes) {
+            attributesData[attributes[a].key] = attributes[a].value;
+        }
+        var url = '/api/plugins/telemetry/' + deviceId + '/' + attributeScope;
+        $http.post(url, attributesData).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function deleteDeviceAttributes(deviceId, attributeScope, attributes) {
+        var deferred = $q.defer();
+        var keys = '';
+        for (var i = 0; i < attributes.length; i++) {
+            if (i > 0) {
+                keys += ',';
+            }
+            keys += attributes[i].key;
+        }
+        var url = '/api/plugins/telemetry/' + deviceId + '/' + attributeScope + '?keys=' + keys;
+        $http.delete(url).then(function success() {
+            deferred.resolve();
+        }, function fail() {
+            deferred.reject();
+        });
+        return deferred.promise;
+    }
+
+    function sendOneWayRpcCommand(deviceId, requestBody) {
+        var deferred = $q.defer();
+        var url = '/api/plugins/rpc/oneway/' + deviceId;
+        $http.post(url, requestBody).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(rejection) {
+            deferred.reject(rejection);
+        });
+        return deferred.promise;
+    }
+
+    function sendTwoWayRpcCommand(deviceId, requestBody) {
+        var deferred = $q.defer();
+        var url = '/api/plugins/rpc/twoway/' + deviceId;
+        $http.post(url, requestBody).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(rejection) {
+            deferred.reject(rejection);
+        });
+        return deferred.promise;
+    }
+
+}
diff --git a/ui/src/app/api/event.service.js b/ui/src/app/api/event.service.js
new file mode 100644
index 0000000..39074e0
--- /dev/null
+++ b/ui/src/app/api/event.service.js
@@ -0,0 +1,50 @@
+/*
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default angular.module('thingsboard.api.event', [])
+    .factory('eventService', EventService)
+    .name;
+
+/*@ngInject*/
+function EventService($http, $q) {
+
+    var service = {
+        getEvents: getEvents
+    }
+
+    return service;
+
+    function getEvents (entityType, entityId, eventType, tenantId, pageLink) {
+        var deferred = $q.defer();
+        var url = '/api/events/'+entityType+'/'+entityId+'/'+eventType+'?tenantId=' + tenantId + '&limit=' + pageLink.limit;
+
+        if (angular.isDefined(pageLink.startTime)) {
+            url += '&startTime=' + pageLink.startTime;
+        }
+        if (angular.isDefined(pageLink.endTime)) {
+            url += '&endTime=' + pageLink.endTime;
+        }
+        if (angular.isDefined(pageLink.idOffset)) {
+            url += '&offset=' + pageLink.idOffset;
+        }
+        $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/api/login.service.js b/ui/src/app/api/login.service.js
new file mode 100644
index 0000000..e904e43
--- /dev/null
+++ b/ui/src/app/api/login.service.js
@@ -0,0 +1,95 @@
+/*
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default angular.module('thingsboard.api.login', [])
+    .factory('loginService', LoginService)
+    .name;
+
+/*@ngInject*/
+function LoginService($http, $q) {
+
+    var service = {
+        activate: activate,
+        changePassword: changePassword,
+        hasUser: hasUser,
+        login: login,
+        resetPassword: resetPassword,
+        sendResetPasswordLink: sendResetPasswordLink,
+    }
+
+    return service;
+
+    function hasUser() {
+        return true;
+    }
+
+    function login(user) {
+        var deferred = $q.defer();
+        var loginRequest = {
+            username: user.name,
+            password: user.password
+        };
+        $http.post('/api/auth/login', loginRequest).then(function success(response) {
+            deferred.resolve(response);
+        }, function fail(response) {
+            deferred.reject(response);
+        });
+        return deferred.promise;
+    }
+
+    function sendResetPasswordLink(email) {
+        var deferred = $q.defer();
+        var url = '/api/noauth/resetPasswordByEmail?email=' + email;
+        $http.post(url, null).then(function success(response) {
+            deferred.resolve(response);
+        }, function fail() {
+            deferred.reject();
+        });
+        return deferred.promise;
+    }
+
+    function resetPassword(resetToken, password) {
+        var deferred = $q.defer();
+        var url = '/api/noauth/resetPassword?resetToken=' + resetToken + '&password=' + password;
+        $http.post(url, null).then(function success(response) {
+            deferred.resolve(response);
+        }, function fail() {
+            deferred.reject();
+        });
+        return deferred.promise;
+    }
+
+    function activate(activateToken, password) {
+        var deferred = $q.defer();
+        var url = '/api/noauth/activate?activateToken=' + activateToken + '&password=' + password;
+        $http.post(url, null).then(function success(response) {
+            deferred.resolve(response);
+        }, function fail() {
+            deferred.reject();
+        });
+        return deferred.promise;
+    }
+
+    function changePassword(currentPassword, newPassword) {
+        var deferred = $q.defer();
+        var url = '/api/auth/changePassword?currentPassword=' + currentPassword + '&newPassword=' + newPassword;
+        $http.post(url, null).then(function success(response) {
+            deferred.resolve(response);
+        }, function fail() {
+            deferred.reject();
+        });
+        return deferred.promise;
+    }
+}
diff --git a/ui/src/app/api/plugin.service.js b/ui/src/app/api/plugin.service.js
new file mode 100644
index 0000000..83a99a6
--- /dev/null
+++ b/ui/src/app/api/plugin.service.js
@@ -0,0 +1,216 @@
+/*
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default angular.module('thingsboard.api.plugin', [])
+    .factory('pluginService', PluginService).name;
+
+/*@ngInject*/
+function PluginService($http, $q, $rootScope, $filter, componentDescriptorService, types, utils) {
+
+    var allPlugins = undefined;
+    var allActionPlugins = undefined;
+    var systemPlugins = undefined;
+    var tenantPlugins = undefined;
+
+    $rootScope.pluginServiceStateChangeStartHandle = $rootScope.$on('$stateChangeStart', function () {
+        invalidatePluginsCache();
+    });
+
+    var service = {
+        getSystemPlugins: getSystemPlugins,
+        getTenantPlugins: getTenantPlugins,
+        getAllPlugins: getAllPlugins,
+        getAllActionPlugins: getAllActionPlugins,
+        getPluginByToken: getPluginByToken,
+        getPlugin: getPlugin,
+        deletePlugin: deletePlugin,
+        savePlugin: savePlugin,
+        activatePlugin: activatePlugin,
+        suspendPlugin: suspendPlugin
+    }
+
+    return service;
+
+    function invalidatePluginsCache() {
+        allPlugins = undefined;
+        allActionPlugins = undefined;
+        systemPlugins = undefined;
+        tenantPlugins = undefined;
+    }
+
+    function loadPluginsCache() {
+        var deferred = $q.defer();
+        if (!allPlugins) {
+            var url = '/api/plugins';
+            $http.get(url, null).then(function success(response) {
+                componentDescriptorService.getComponentDescriptorsByType(types.componentType.plugin).then(
+                    function success(pluginComponents) {
+                        allPlugins = response.data;
+                        allActionPlugins = [];
+                        systemPlugins = [];
+                        tenantPlugins = [];
+                        allPlugins = $filter('orderBy')(allPlugins, ['+name', '-createdTime']);
+                        var pluginHasActionsByClazz = {};
+                        for (var index in pluginComponents) {
+                            pluginHasActionsByClazz[pluginComponents[index].clazz] =
+                                (pluginComponents[index].actions != null && pluginComponents[index].actions.length > 0);
+                        }
+                        for (var i = 0; i < allPlugins.length; i++) {
+                            var plugin = allPlugins[i];
+                            if (pluginHasActionsByClazz[plugin.clazz] === true) {
+                                allActionPlugins.push(plugin);
+                            }
+                            if (plugin.tenantId.id === types.id.nullUid) {
+                                systemPlugins.push(plugin);
+                            } else {
+                                tenantPlugins.push(plugin);
+                            }
+                        }
+                        deferred.resolve();
+                    },
+                    function fail() {
+                        deferred.reject();
+                    }
+                );
+            }, function fail() {
+                deferred.reject();
+            });
+        } else {
+            deferred.resolve();
+        }
+        return deferred.promise;
+    }
+
+    function getSystemPlugins(pageLink) {
+        var deferred = $q.defer();
+        loadPluginsCache().then(
+            function success() {
+                utils.filterSearchTextEntities(systemPlugins, 'name', pageLink, deferred);
+            },
+            function fail() {
+                deferred.reject();
+            }
+        );
+        return deferred.promise;
+    }
+
+    function getTenantPlugins(pageLink) {
+        var deferred = $q.defer();
+        loadPluginsCache().then(
+            function success() {
+                utils.filterSearchTextEntities(tenantPlugins, 'name', pageLink, deferred);
+            },
+            function fail() {
+                deferred.reject();
+            }
+        );
+        return deferred.promise;
+    }
+
+    function getAllActionPlugins(pageLink) {
+        var deferred = $q.defer();
+        loadPluginsCache().then(
+            function success() {
+                utils.filterSearchTextEntities(allActionPlugins, 'name', pageLink, deferred);
+            },
+            function fail() {
+                deferred.reject();
+            }
+        );
+        return deferred.promise;
+    }
+
+    function getAllPlugins(pageLink) {
+        var deferred = $q.defer();
+        loadPluginsCache().then(
+            function success() {
+                utils.filterSearchTextEntities(allPlugins, 'name', pageLink, deferred);
+            },
+            function fail() {
+                deferred.reject();
+            }
+        );
+        return deferred.promise;
+    }
+
+    function getPluginByToken(pluginToken) {
+        var deferred = $q.defer();
+        var url = '/api/plugin/token/' + pluginToken;
+        $http.get(url, null).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail() {
+            deferred.reject();
+        });
+        return deferred.promise;
+    }
+
+    function getPlugin(pluginId) {
+        var deferred = $q.defer();
+        var url = '/api/plugin/' + pluginId;
+        $http.get(url, null).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function savePlugin(plugin) {
+        var deferred = $q.defer();
+        var url = '/api/plugin';
+        $http.post(url, plugin).then(function success(response) {
+            invalidatePluginsCache();
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function deletePlugin(pluginId) {
+        var deferred = $q.defer();
+        var url = '/api/plugin/' + pluginId;
+        $http.delete(url).then(function success() {
+            invalidatePluginsCache();
+            deferred.resolve();
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function activatePlugin(pluginId) {
+        var deferred = $q.defer();
+        var url = '/api/plugin/' + pluginId + '/activate';
+        $http.post(url, null).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function suspendPlugin(pluginId) {
+        var deferred = $q.defer();
+        var url = '/api/plugin/' + pluginId + '/suspend';
+        $http.post(url, null).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+}
diff --git a/ui/src/app/api/rule.service.js b/ui/src/app/api/rule.service.js
new file mode 100644
index 0000000..b27f405
--- /dev/null
+++ b/ui/src/app/api/rule.service.js
@@ -0,0 +1,182 @@
+/*
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default angular.module('thingsboard.api.rule', [])
+    .factory('ruleService', RuleService).name;
+
+/*@ngInject*/
+function RuleService($http, $q, $rootScope, $filter, types, utils) {
+
+    var allRules = undefined;
+    var systemRules = undefined;
+    var tenantRules = undefined;
+
+    $rootScope.ruleServiceStateChangeStartHandle = $rootScope.$on('$stateChangeStart', function () {
+        invalidateRulesCache();
+    });
+
+    var service = {
+        getSystemRules: getSystemRules,
+        getTenantRules: getTenantRules,
+        getAllRules: getAllRules,
+        getRulesByPluginToken: getRulesByPluginToken,
+        getRule: getRule,
+        deleteRule: deleteRule,
+        saveRule: saveRule,
+        activateRule: activateRule,
+        suspendRule: suspendRule
+    }
+
+    return service;
+
+    function invalidateRulesCache() {
+        allRules = undefined;
+        systemRules = undefined;
+        tenantRules = undefined;
+    }
+
+    function loadRulesCache() {
+        var deferred = $q.defer();
+        if (!allRules) {
+            var url = '/api/rules';
+            $http.get(url, null).then(function success(response) {
+                allRules = response.data;
+                systemRules = [];
+                tenantRules = [];
+                allRules = $filter('orderBy')(allRules, ['+name', '-createdTime']);
+                for (var i = 0; i < allRules.length; i++) {
+                    var rule = allRules[i];
+                    if (rule.tenantId.id === types.id.nullUid) {
+                        systemRules.push(rule);
+                    } else {
+                        tenantRules.push(rule);
+                    }
+                }
+                deferred.resolve();
+            }, function fail() {
+                deferred.reject();
+            });
+        } else {
+            deferred.resolve();
+        }
+        return deferred.promise;
+    }
+
+    function getSystemRules(pageLink) {
+        var deferred = $q.defer();
+        loadRulesCache().then(
+            function success() {
+                utils.filterSearchTextEntities(systemRules, 'name', pageLink, deferred);
+            },
+            function fail() {
+                deferred.reject();
+            }
+        );
+        return deferred.promise;
+    }
+
+    function getTenantRules(pageLink) {
+        var deferred = $q.defer();
+        loadRulesCache().then(
+            function success() {
+                utils.filterSearchTextEntities(tenantRules, 'name', pageLink, deferred);
+            },
+            function fail() {
+                deferred.reject();
+            }
+        );
+        return deferred.promise;
+    }
+
+    function getAllRules(pageLink) {
+        var deferred = $q.defer();
+        loadRulesCache().then(
+            function success() {
+                utils.filterSearchTextEntities(allRules, 'name', pageLink, deferred);
+            },
+            function fail() {
+                deferred.reject();
+            }
+        );
+        return deferred.promise;
+    }
+
+    function getRulesByPluginToken(pluginToken) {
+        var deferred = $q.defer();
+        var url = '/api/rule/token/' + pluginToken;
+        $http.get(url, null).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail() {
+            deferred.reject();
+        });
+        return deferred.promise;
+    }
+
+    function getRule(ruleId) {
+        var deferred = $q.defer();
+        var url = '/api/rule/' + ruleId;
+        $http.get(url, null).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function saveRule(rule) {
+        var deferred = $q.defer();
+        var url = '/api/rule';
+        $http.post(url, rule).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function deleteRule(ruleId) {
+        var deferred = $q.defer();
+        var url = '/api/rule/' + ruleId;
+        $http.delete(url).then(function success() {
+            deferred.resolve();
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function activateRule(ruleId) {
+        var deferred = $q.defer();
+        var url = '/api/rule/' + ruleId + '/activate';
+        $http.post(url, null).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function suspendRule(ruleId) {
+        var deferred = $q.defer();
+        var url = '/api/rule/' + ruleId + '/suspend';
+        $http.post(url, null).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+}
diff --git a/ui/src/app/api/telemetry-websocket.service.js b/ui/src/app/api/telemetry-websocket.service.js
new file mode 100644
index 0000000..f7e35eb
--- /dev/null
+++ b/ui/src/app/api/telemetry-websocket.service.js
@@ -0,0 +1,170 @@
+/*
+ * Copyright © 2016 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 'angular-websocket';
+import thingsboardTypes from '../common/types.constant';
+
+export default angular.module('thingsboard.api.telemetryWebsocket', [thingsboardTypes])
+    .factory('telemetryWebsocketService', TelemetryWebsocketService)
+    .name;
+
+/*@ngInject*/
+function TelemetryWebsocketService($log, $websocket, $timeout, $window, types, userService) {
+
+    var isOpening = false,
+        isOpened = false,
+        lastCmdId = 0,
+        subscribers = {},
+        subscribersCount = 0,
+        cmdsWrapper = {
+            tsSubCmds: [],
+            historyCmds: [],
+            attrSubCmds: []
+        },
+        telemetryUri,
+        dataStream,
+        location = $window.location,
+        socketCloseTimer;
+
+    if (location.protocol === "https:") {
+        telemetryUri = "wss:";
+    } else {
+        telemetryUri = "ws:";
+    }
+    telemetryUri += "//" + location.hostname + ":" + location.port;
+    telemetryUri += "/api/ws/plugins/telemetry";
+
+    var service = {
+        subscribe: subscribe,
+        unsubscribe: unsubscribe
+    }
+
+    return service;
+
+    function publishCommands () {
+        if (isOpened && (cmdsWrapper.tsSubCmds.length > 0 ||
+            cmdsWrapper.historyCmds.length > 0 ||
+            cmdsWrapper.attrSubCmds.length > 0)) {
+            $log.debug("Sending subscription commands!");
+            dataStream.send(angular.copy(cmdsWrapper)).then(function () {
+                $log.debug("Subscription commands were sent!");
+                checkToClose();
+            });
+            cmdsWrapper.tsSubCmds = [];
+            cmdsWrapper.historyCmds = [];
+            cmdsWrapper.attrSubCmds = [];
+        }
+        tryOpenSocket();
+    }
+
+    function onError (message) {
+        $log.debug("Websocket error:");
+        $log.debug(message);
+        isOpening = false;
+    }
+
+    function onOpen () {
+        $log.debug("Websocket opened");
+        isOpening = false;
+        isOpened = true;
+        publishCommands();
+    }
+
+    function onClose () {
+        $log.debug("Websocket closed");
+        isOpening = false;
+        isOpened = false;
+    }
+
+    function onMessage (message) {
+        if (message.data) {
+            var data = angular.fromJson(message.data);
+            if (data.subscriptionId) {
+                var subscriber = subscribers[data.subscriptionId];
+                if (subscriber && data.data) {
+                    subscriber.onData(data.data);
+                }
+            }
+        }
+        checkToClose();
+    }
+
+    function nextCmdId () {
+        lastCmdId++;
+        return lastCmdId;
+    }
+
+    function subscribe (subscriber) {
+        var cmdId = nextCmdId();
+        subscribers[cmdId] = subscriber;
+        subscribersCount++;
+        if (angular.isDefined(subscriber.subscriptionCommand)) {
+            subscriber.subscriptionCommand.cmdId = cmdId;
+            if (subscriber.type === types.dataKeyType.timeseries) {
+                cmdsWrapper.tsSubCmds.push(subscriber.subscriptionCommand);
+            } else if (subscriber.type === types.dataKeyType.attribute) {
+                cmdsWrapper.attrSubCmds.push(subscriber.subscriptionCommand);
+            }
+        } else if (angular.isDefined(subscriber.historyCommand)) {
+            subscriber.historyCommand.cmdId = cmdId;
+            cmdsWrapper.historyCmds.push(subscriber.historyCommand);
+        }
+        publishCommands();
+    }
+
+    function unsubscribe (subscriber) {
+        if (subscriber.subscriptionCommand) {
+            subscriber.subscriptionCommand.unsubscribe = true;
+            if (subscriber.type === types.dataKeyType.timeseries) {
+                cmdsWrapper.tsSubCmds.push(subscriber.subscriptionCommand);
+            } else if (subscriber.type === types.dataKeyType.attribute) {
+                cmdsWrapper.attrSubCmds.push(subscriber.subscriptionCommand);
+            }
+            delete subscribers[subscriber.subscriptionCommand.cmdId];
+        } else if (subscriber.historyCommand) {
+            delete subscribers[subscriber.historyCommand.cmdId];
+        }
+        subscribersCount--;
+        publishCommands();
+    }
+
+    function checkToClose () {
+        if (subscribersCount === 0 && isOpened) {
+            if (!socketCloseTimer) {
+                socketCloseTimer = $timeout(closeSocket, 90000, false);
+            }
+        }
+    }
+
+    function tryOpenSocket () {
+        if (!isOpened && !isOpening) {
+            isOpening = true;
+            dataStream = $websocket(telemetryUri + '?token=' + userService.getJwtToken());
+            dataStream.onError(onError);
+            dataStream.onOpen(onOpen);
+            dataStream.onClose(onClose);
+            dataStream.onMessage(onMessage);
+        }
+        if (socketCloseTimer) {
+            $timeout.cancel(socketCloseTimer);
+        }
+    }
+
+    function closeSocket() {
+        if (isOpened) {
+            dataStream.close();
+        }
+    }
+}
diff --git a/ui/src/app/api/tenant.service.js b/ui/src/app/api/tenant.service.js
new file mode 100644
index 0000000..ab87038
--- /dev/null
+++ b/ui/src/app/api/tenant.service.js
@@ -0,0 +1,85 @@
+/*
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default angular.module('thingsboard.api.tenant', [])
+    .factory('tenantService', TenantService)
+    .name;
+
+/*@ngInject*/
+function TenantService($http, $q) {
+
+    var service = {
+        deleteTenant: deleteTenant,
+        getTenant: getTenant,
+        getTenants: getTenants,
+        saveTenant: saveTenant,
+    }
+
+    return service;
+
+    function getTenants (pageLink) {
+        var deferred = $q.defer();
+        var url = '/api/tenants?limit=' + pageLink.limit;
+        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 getTenant (tenantId) {
+        var deferred = $q.defer();
+        var url = '/api/tenant/' + tenantId;
+        $http.get(url, null).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function saveTenant (tenant) {
+        var deferred = $q.defer();
+        var url = '/api/tenant';
+        $http.post(url, tenant).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function deleteTenant (tenantId) {
+        var deferred = $q.defer();
+        var url = '/api/tenant/' + tenantId;
+        $http.delete(url).then(function success() {
+            deferred.resolve();
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+}
diff --git a/ui/src/app/api/user.service.js b/ui/src/app/api/user.service.js
new file mode 100644
index 0000000..ee679c6
--- /dev/null
+++ b/ui/src/app/api/user.service.js
@@ -0,0 +1,334 @@
+/*
+ * Copyright © 2016 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 thingsboardApiLogin  from './login.service';
+import angularStorage from 'angular-storage';
+
+export default angular.module('thingsboard.api.user', [thingsboardApiLogin,
+    angularStorage])
+    .factory('userService', UserService)
+    .name;
+
+/*@ngInject*/
+function UserService($http, $q, $rootScope, store, jwtHelper, $translate) {
+    var currentUser = null,
+        userLoaded = false;
+
+    var refreshTokenQueue = [];
+
+    var service = {
+        deleteUser: deleteUser,
+        getAuthority: getAuthority,
+        isAuthenticated: isAuthenticated,
+        getCurrentUser: getCurrentUser,
+        getCustomerUsers: getCustomerUsers,
+        getUser: getUser,
+        getTenantAdmins: getTenantAdmins,
+        isUserLoaded: isUserLoaded,
+        saveUser: saveUser,
+        sendActivationEmail: sendActivationEmail,
+        setUserFromJwtToken: setUserFromJwtToken,
+        getJwtToken: getJwtToken,
+        clearJwtToken: clearJwtToken,
+        isJwtTokenValid : isJwtTokenValid,
+        validateJwtToken: validateJwtToken,
+        refreshJwtToken: refreshJwtToken,
+        refreshTokenPending: refreshTokenPending,
+        updateAuthorizationHeader: updateAuthorizationHeader,
+        logout: logout
+    }
+
+    loadUser(true).then(function success() {
+        notifyUserLoaded();
+    }, function fail() {
+        notifyUserLoaded();
+    });
+
+    return service;
+
+    function updateAndValidateToken(token, prefix) {
+        var valid = false;
+        var tokenData = jwtHelper.decodeToken(token);
+        var issuedAt = tokenData.iat;
+        var expTime = tokenData.exp;
+        if (issuedAt && expTime) {
+            var ttl = expTime - issuedAt;
+            if (ttl > 0) {
+                var clientExpiration = new Date().valueOf() + ttl*1000;
+                store.set(prefix, token);
+                store.set(prefix + '_expiration', clientExpiration);
+                valid = true;
+            }
+        }
+        if (!valid) {
+            $rootScope.$broadcast('unauthenticated');
+        }
+    }
+
+    function clearTokenData() {
+        store.remove('jwt_token');
+        store.remove('jwt_token_expiration');
+        store.remove('refresh_token');
+        store.remove('refresh_token_expiration');
+    }
+
+    function setUserFromJwtToken(jwtToken, refreshToken, notify, doLogout) {
+        currentUser = null;
+        if (!jwtToken) {
+            clearTokenData();
+            if (notify) {
+                $rootScope.$broadcast('unauthenticated', doLogout);
+            }
+        } else {
+            updateAndValidateToken(jwtToken, 'jwt_token');
+            updateAndValidateToken(refreshToken, 'refresh_token');
+            if (notify) {
+                loadUser(false).then(function success() {
+                    $rootScope.$broadcast('authenticated');
+                }, function fail() {
+                    $rootScope.$broadcast('unauthenticated');
+                });
+            } else {
+                loadUser(false);
+            }
+        }
+    }
+
+    function isAuthenticated() {
+        return store.get('jwt_token');
+    }
+
+    function getJwtToken() {
+        return store.get('jwt_token');
+    }
+
+    function logout() {
+        clearJwtToken(true);
+    }
+
+    function clearJwtToken(doLogout) {
+        setUserFromJwtToken(null, null, true, doLogout);
+    }
+
+    function isJwtTokenValid() {
+        return isTokenValid('jwt_token');
+    }
+
+    function isTokenValid(prefix) {
+        var clientExpiration = store.get(prefix + '_expiration');
+        return clientExpiration && clientExpiration > new Date().valueOf();
+    }
+
+    function validateJwtToken(doRefresh) {
+        var deferred = $q.defer();
+        if (!isTokenValid('jwt_token')) {
+            if (doRefresh) {
+                refreshJwtToken().then(function success() {
+                    deferred.resolve();
+                }, function fail() {
+                    deferred.reject();
+                });
+            } else {
+                clearJwtToken(false);
+                deferred.reject();
+            }
+        } else {
+            deferred.resolve();
+        }
+        return deferred.promise;
+    }
+
+    function resolveRefreshTokenQueue(data) {
+        for (var q in refreshTokenQueue) {
+            refreshTokenQueue[q].resolve(data);
+        }
+        refreshTokenQueue = [];
+    }
+
+    function rejectRefreshTokenQueue(message) {
+        for (var q in refreshTokenQueue) {
+            refreshTokenQueue[q].reject(message);
+        }
+        refreshTokenQueue = [];
+    }
+
+    function refreshTokenPending() {
+        return refreshTokenQueue.length > 0;
+    }
+
+    function refreshJwtToken() {
+        var deferred = $q.defer();
+        refreshTokenQueue.push(deferred);
+        if (refreshTokenQueue.length === 1) {
+            var refreshToken = store.get('refresh_token');
+            var refreshTokenValid = isTokenValid('refresh_token');
+            setUserFromJwtToken(null, null, false, false);
+            if (!refreshTokenValid) {
+                rejectRefreshTokenQueue($translate.instant('access.refresh-token-expired'));
+            } else {
+                var refreshTokenRequest = {
+                    refreshToken: refreshToken
+                };
+                $http.post('/api/auth/token', refreshTokenRequest).then(function success(response) {
+                    var token = response.data.token;
+                    var refreshToken = response.data.refreshToken;
+                    setUserFromJwtToken(token, refreshToken, false);
+                    resolveRefreshTokenQueue(response.data);
+                }, function fail() {
+                    clearJwtToken(false);
+                    rejectRefreshTokenQueue($translate.instant('access.refresh-token-failed'));
+                });
+            }
+        }
+        return deferred.promise;
+    }
+
+    function getCurrentUser() {
+        return currentUser;
+    }
+
+    function getAuthority() {
+        if (currentUser) {
+            return currentUser.authority;
+        } else {
+            return '';
+        }
+    }
+
+    function isUserLoaded() {
+        return userLoaded;
+    }
+
+    function loadUser(doTokenRefresh) {
+        var deferred = $q.defer();
+        if (!currentUser) {
+            validateJwtToken(doTokenRefresh).then(function success() {
+                var jwtToken = store.get('jwt_token');
+                currentUser = jwtHelper.decodeToken(jwtToken);
+                if (currentUser && currentUser.scopes && currentUser.scopes.length > 0) {
+                    currentUser.authority = currentUser.scopes[0];
+                } else if (currentUser) {
+                    currentUser.authority = "ANONYMOUS";
+                }
+                deferred.resolve();
+            }, function fail() {
+                deferred.reject();
+            });
+        } else {
+            deferred.resolve();
+        }
+        return deferred.promise;
+    }
+
+    function notifyUserLoaded() {
+        if (!userLoaded) {
+            userLoaded = true;
+            $rootScope.$broadcast('userLoaded');
+        }
+    }
+
+    function updateAuthorizationHeader(headers) {
+        var jwtToken = store.get('jwt_token');
+        if (jwtToken) {
+            headers['X-Authorization'] = 'Bearer ' + jwtToken;
+        }
+        return jwtToken;
+    }
+
+    function getTenantAdmins(tenantId, pageLink) {
+        var deferred = $q.defer();
+        var url = '/api/tenant/' + tenantId + '/users?limit=' + pageLink.limit;
+        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 getCustomerUsers(customerId, pageLink) {
+        var deferred = $q.defer();
+        var url = '/api/customer/' + customerId + '/users?limit=' + pageLink.limit;
+        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 saveUser(user) {
+        var deferred = $q.defer();
+        var url = '/api/user';
+        $http.post(url, user).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function getUser(userId) {
+        var deferred = $q.defer();
+        var url = '/api/user/' + userId;
+        $http.get(url).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function deleteUser(userId) {
+        var deferred = $q.defer();
+        var url = '/api/user/' + userId;
+        $http.delete(url).then(function success() {
+            deferred.resolve();
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function sendActivationEmail(email) {
+        var deferred = $q.defer();
+        var url = '/api/user/sendActivationMail?email=' + email;
+        $http.post(url, null).then(function success(response) {
+            deferred.resolve(response);
+        }, function fail() {
+            deferred.reject();
+        });
+        return deferred.promise;
+    }
+
+}
diff --git a/ui/src/app/api/widget.service.js b/ui/src/app/api/widget.service.js
new file mode 100644
index 0000000..d7885b3
--- /dev/null
+++ b/ui/src/app/api/widget.service.js
@@ -0,0 +1,601 @@
+/*
+ * Copyright © 2016 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 $ from 'jquery';
+import moment from 'moment';
+import tinycolor from 'tinycolor2';
+
+import thinsboardLedLight from '../components/led-light.directive';
+
+import TbAnalogueLinearGauge from '../widget/lib/analogue-linear-gauge';
+import TbAnalogueRadialGauge from '../widget/lib/analogue-radial-gauge';
+import TbDigitalGauge from '../widget/lib/digital-gauge';
+
+import 'oclazyload';
+import cssjs from '../../vendor/css.js/css';
+
+import thingsboardTypes from '../common/types.constant';
+import thingsboardUtils from '../common/utils.service';
+
+export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thinsboardLedLight, thingsboardTypes, thingsboardUtils])
+    .factory('widgetService', WidgetService)
+    .name;
+
+/*@ngInject*/
+function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, types, utils) {
+
+    $window.$ = $;
+    $window.moment = moment;
+    $window.tinycolor = tinycolor;
+    $window.lazyLoad = $ocLazyLoad;
+
+    $window.TbAnalogueLinearGauge = TbAnalogueLinearGauge;
+    $window.TbAnalogueRadialGauge = TbAnalogueRadialGauge;
+    $window.TbDigitalGauge = TbDigitalGauge;
+
+    var cssParser = new cssjs();
+    cssParser.testMode = false;
+
+    var missingWidgetType;
+    var errorWidgetType;
+
+    var editingWidgetType;
+
+    var widgetsInfoInMemoryCache = {};
+
+    var allWidgetsBundles = undefined;
+    var systemWidgetsBundles = undefined;
+    var tenantWidgetsBundles = undefined;
+
+    $rootScope.widgetServiceStateChangeStartHandle = $rootScope.$on('$stateChangeStart', function () {
+        invalidateWidgetsBundleCache();
+    });
+
+    initEditingWidgetType();
+    initWidgetPlaceholders();
+
+    var service = {
+        getWidgetTemplate: getWidgetTemplate,
+        getSystemWidgetsBundles: getSystemWidgetsBundles,
+        getTenantWidgetsBundles: getTenantWidgetsBundles,
+        getAllWidgetsBundles: getAllWidgetsBundles,
+        getSystemWidgetsBundlesByPageLink: getSystemWidgetsBundlesByPageLink,
+        getTenantWidgetsBundlesByPageLink: getTenantWidgetsBundlesByPageLink,
+        getAllWidgetsBundlesByPageLink: getAllWidgetsBundlesByPageLink,
+        getWidgetsBundleByAlias: getWidgetsBundleByAlias,
+        saveWidgetsBundle: saveWidgetsBundle,
+        getWidgetsBundle: getWidgetsBundle,
+        deleteWidgetsBundle: deleteWidgetsBundle,
+        getBundleWidgetTypes: getBundleWidgetTypes,
+        getWidgetInfo: getWidgetInfo,
+        getInstantWidgetInfo: getInstantWidgetInfo,
+        deleteWidgetType: deleteWidgetType,
+        saveWidgetType: saveWidgetType,
+        getWidgetType: getWidgetType,
+        getWidgetTypeById: getWidgetTypeById,
+        toWidgetInfo: toWidgetInfo
+    }
+
+    return service;
+
+    function initEditingWidgetType() {
+        if ($rootScope.widgetEditMode) {
+            editingWidgetType =
+                toWidgetType({
+                    widgetName: $rootScope.editWidgetInfo.widgetName,
+                    alias: 'customWidget',
+                    type: $rootScope.editWidgetInfo.type,
+                    sizeX: $rootScope.editWidgetInfo.sizeX,
+                    sizeY: $rootScope.editWidgetInfo.sizeY,
+                    resources: $rootScope.editWidgetInfo.resources,
+                    templateHtml: $rootScope.editWidgetInfo.templateHtml,
+                    templateCss: $rootScope.editWidgetInfo.templateCss,
+                    controllerScript: $rootScope.editWidgetInfo.controllerScript,
+                    settingsSchema: $rootScope.editWidgetInfo.settingsSchema,
+                    dataKeySettingsSchema: $rootScope.editWidgetInfo.dataKeySettingsSchema,
+                    defaultConfig: $rootScope.editWidgetInfo.defaultConfig
+            }, {id: '1'}, { id: types.id.nullUid }, 'customWidgetBundle');
+        }
+    }
+
+    function initWidgetPlaceholders() {
+
+        missingWidgetType = {
+            widgetName: 'Widget type not found',
+            alias: 'undefined',
+            sizeX: 8,
+            sizeY: 6,
+            resources: [],
+            templateHtml: '<div class="tb-widget-error-container"><div translate class="tb-widget-error-msg">widget.widget-type-not-found</div></div>',
+            templateCss: '',
+            controllerScript: 'fns.init = function(containerElement, settings, datasources,\n    data) {}\n\n\nfns.redraw = function(containerElement, width, height, data) {};',
+            settingsSchema: '{}\n',
+            dataKeySettingsSchema: '{}\n',
+            defaultConfig: '{\n' +
+            '"title": "Widget type not found",\n' +
+            '"datasources": [],\n' +
+            '"settings": {}\n' +
+            '}\n'
+        };
+
+        errorWidgetType = {
+            widgetName: 'Error loading widget',
+            alias: 'error',
+            sizeX: 8,
+            sizeY: 6,
+            resources: [],
+            templateHtml: '<div class="tb-widget-error-container"><div translate class="tb-widget-error-msg">widget.widget-type-load-error</div>',
+            templateCss: '',
+            controllerScript: 'fns.init = function(containerElement, settings, datasources,\n    data) {}\n\n\nfns.redraw = function(containerElement, width, height, data) {};',
+            settingsSchema: '{}\n',
+            dataKeySettingsSchema: '{}\n',
+            defaultConfig: '{\n' +
+            '"title": "Widget failed to load",\n' +
+            '"datasources": [],\n' +
+            '"settings": {}\n' +
+            '}\n'
+        };
+    }
+
+    function toWidgetInfo(widgetType) {
+
+        var widgetInfo = {
+            widgetName: widgetType.name,
+            alias: widgetType.alias
+        }
+
+        var descriptor = widgetType.descriptor;
+
+        widgetInfo.type = descriptor.type;
+        widgetInfo.sizeX = descriptor.sizeX;
+        widgetInfo.sizeY = descriptor.sizeY;
+        widgetInfo.resources = descriptor.resources;
+        widgetInfo.templateHtml = descriptor.templateHtml;
+        widgetInfo.templateCss = descriptor.templateCss;
+        widgetInfo.controllerScript = descriptor.controllerScript;
+        widgetInfo.settingsSchema = descriptor.settingsSchema;
+        widgetInfo.dataKeySettingsSchema = descriptor.dataKeySettingsSchema;
+        widgetInfo.defaultConfig = descriptor.defaultConfig;
+
+        return widgetInfo;
+    }
+
+    function toWidgetType(widgetInfo, id, tenantId, bundleAlias) {
+        var widgetType = {
+            id: id,
+            tenantId: tenantId,
+            bundleAlias: bundleAlias,
+            alias: widgetInfo.alias,
+            name: widgetInfo.widgetName
+        }
+
+        var descriptor = {
+            type: widgetInfo.type,
+            sizeX: widgetInfo.sizeX,
+            sizeY: widgetInfo.sizeY,
+            resources: widgetInfo.resources,
+            templateHtml: widgetInfo.templateHtml,
+            templateCss: widgetInfo.templateCss,
+            controllerScript: widgetInfo.controllerScript,
+            settingsSchema: widgetInfo.settingsSchema,
+            dataKeySettingsSchema: widgetInfo.dataKeySettingsSchema,
+            defaultConfig: widgetInfo.defaultConfig
+        }
+
+        widgetType.descriptor = descriptor;
+
+        return widgetType;
+    }
+
+    function getWidgetTemplate(type) {
+        var deferred = $q.defer();
+        var templateWidgetType = types.widgetType.timeseries;
+        for (var t in types.widgetType) {
+            var widgetType = types.widgetType[t];
+            if (widgetType.value === type) {
+                templateWidgetType = widgetType;
+                break;
+            }
+        }
+        getWidgetType(templateWidgetType.template.bundleAlias,
+                      templateWidgetType.template.alias, true).then(
+                          function success(widgetType) {
+                              var widgetInfo = toWidgetInfo(widgetType);
+                              widgetInfo.alias = undefined;
+                              deferred.resolve(widgetInfo);
+                          },
+                          function fail() {
+                              deferred.reject();
+                          }
+        );
+        return deferred.promise;
+    }
+
+    /** Cache functions **/
+
+    function getWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem) {
+        var key = (isSystem ? 'sys_' : '') + bundleAlias + '_' + widgetTypeAlias;
+        return widgetsInfoInMemoryCache[key];
+    }
+
+    function putWidgetInfoToCache(widgetInfo, bundleAlias, widgetTypeAlias, isSystem) {
+        var key = (isSystem ? 'sys_' : '') + bundleAlias + '_' + widgetTypeAlias;
+        widgetsInfoInMemoryCache[key] = widgetInfo;
+    }
+
+    function deleteWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem) {
+        var key = (isSystem ? 'sys_' : '') + bundleAlias + '_' + widgetTypeAlias;
+        delete widgetsInfoInMemoryCache[key];
+    }
+
+    function deleteWidgetsBundleFromCache(bundleAlias, isSystem) {
+        var key = (isSystem ? 'sys_' : '') + bundleAlias;
+        for (var cacheKey in widgetsInfoInMemoryCache) {
+            if (cacheKey.startsWith(key)) {
+                delete widgetsInfoInMemoryCache[cacheKey];
+            }
+        }
+    }
+
+    /** Bundle functions **/
+
+    function invalidateWidgetsBundleCache() {
+        allWidgetsBundles = undefined;
+        systemWidgetsBundles = undefined;
+        tenantWidgetsBundles = undefined;
+    }
+
+    function loadWidgetsBundleCache() {
+        var deferred = $q.defer();
+        if (!allWidgetsBundles) {
+            var url = '/api/widgetsBundles';
+            $http.get(url, null).then(function success(response) {
+                allWidgetsBundles = response.data;
+                systemWidgetsBundles = [];
+                tenantWidgetsBundles = [];
+                allWidgetsBundles = $filter('orderBy')(allWidgetsBundles, ['+title', '-createdTime']);
+                for (var i = 0; i < allWidgetsBundles.length; i++) {
+                    var widgetsBundle = allWidgetsBundles[i];
+                    if (widgetsBundle.tenantId.id === types.id.nullUid) {
+                        systemWidgetsBundles.push(widgetsBundle);
+                    } else {
+                        tenantWidgetsBundles.push(widgetsBundle);
+                    }
+                }
+                deferred.resolve();
+            }, function fail() {
+                deferred.reject();
+            });
+        } else {
+            deferred.resolve();
+        }
+        return deferred.promise;
+    }
+
+
+    function getSystemWidgetsBundles() {
+        var deferred = $q.defer();
+        loadWidgetsBundleCache().then(
+            function success() {
+                deferred.resolve(systemWidgetsBundles);
+            },
+            function fail() {
+                deferred.reject();
+            }
+        );
+        return deferred.promise;
+    }
+
+    function getTenantWidgetsBundles() {
+        var deferred = $q.defer();
+        loadWidgetsBundleCache().then(
+            function success() {
+                deferred.resolve(tenantWidgetsBundles);
+            },
+            function fail() {
+                deferred.reject();
+            }
+        );
+        return deferred.promise;
+    }
+
+    function getAllWidgetsBundles() {
+        var deferred = $q.defer();
+        loadWidgetsBundleCache().then(
+            function success() {
+                deferred.resolve(allWidgetsBundles);
+            },
+            function fail() {
+                deferred.reject();
+            }
+        );
+        return deferred.promise;
+    }
+
+    function getSystemWidgetsBundlesByPageLink(pageLink) {
+        var deferred = $q.defer();
+        loadWidgetsBundleCache().then(
+            function success() {
+                utils.filterSearchTextEntities(systemWidgetsBundles, 'title', pageLink, deferred);
+            },
+            function fail() {
+                deferred.reject();
+            }
+        );
+        return deferred.promise;
+    }
+
+    function getTenantWidgetsBundlesByPageLink(pageLink) {
+        var deferred = $q.defer();
+        loadWidgetsBundleCache().then(
+            function success() {
+                utils.filterSearchTextEntities(tenantWidgetsBundles, 'title', pageLink, deferred);
+            },
+            function fail() {
+                deferred.reject();
+            }
+        );
+        return deferred.promise;
+    }
+
+    function getAllWidgetsBundlesByPageLink(pageLink) {
+        var deferred = $q.defer();
+        loadWidgetsBundleCache().then(
+            function success() {
+                utils.filterSearchTextEntities(allWidgetsBundles, 'title', pageLink, deferred);
+            },
+            function fail() {
+                deferred.reject();
+            }
+        );
+        return deferred.promise;
+    }
+
+    function getWidgetsBundleByAlias(bundleAlias) {
+        var deferred = $q.defer();
+        loadWidgetsBundleCache().then(
+            function success() {
+                var widgetsBundles = $filter('filter')(allWidgetsBundles, {alias: bundleAlias});
+                if (widgetsBundles.length > 0) {
+                    deferred.resolve(widgetsBundles[0]);
+                } else {
+                    deferred.resolve();
+                }
+            },
+            function fail() {
+                deferred.reject();
+            }
+        );
+        return deferred.promise;
+    }
+
+    function saveWidgetsBundle(widgetsBundle) {
+        var deferred = $q.defer();
+        var url = '/api/widgetsBundle';
+        $http.post(url, widgetsBundle).then(function success(response) {
+            invalidateWidgetsBundleCache();
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    function getWidgetsBundle(widgetsBundleId) {
+        var deferred = $q.defer();
+
+        var url = '/api/widgetsBundle/' + widgetsBundleId;
+        $http.get(url, null).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+
+        return deferred.promise;
+    }
+
+    function deleteWidgetsBundle(widgetsBundleId) {
+        var deferred = $q.defer();
+
+        getWidgetsBundle(widgetsBundleId).then(
+            function success(response) {
+                var widgetsBundle = response;
+                var url = '/api/widgetsBundle/' + widgetsBundleId;
+                $http.delete(url).then(function success() {
+                    invalidateWidgetsBundleCache();
+                    deleteWidgetsBundleFromCache(widgetsBundle.alias,
+                        widgetsBundle.tenantId.id === types.id.nullUid);
+                    deferred.resolve();
+                }, function fail(response) {
+                    deferred.reject(response.data);
+                });
+            },
+            function fail() {
+                deferred.reject();
+            }
+        );
+
+        return deferred.promise;
+    }
+
+    function getBundleWidgetTypes(bundleAlias, isSystem) {
+        var deferred = $q.defer();
+        var url = '/api/widgetTypes?isSystem=' + (isSystem ? 'true' : 'false') +
+                    '&bundleAlias='+bundleAlias;
+        $http.get(url, null).then(function success(response) {
+            deferred.resolve(response.data);
+        }, function fail(response) {
+            deferred.reject(response.data);
+        });
+        return deferred.promise;
+    }
+
+    /** Widget type functions **/
+
+    function getInstantWidgetInfo(widget) {
+        var widgetInfo = getWidgetInfoFromCache(widget.bundleAlias, widget.typeAlias, widget.isSystemType);
+        if (widgetInfo) {
+            return widgetInfo;
+        } else {
+            return {};
+        }
+    }
+
+    function getWidgetInfo(bundleAlias, widgetTypeAlias, isSystem) {
+        var deferred = $q.defer();
+        var widgetInfo = getWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem);
+        if (widgetInfo) {
+            deferred.resolve(widgetInfo);
+        } else {
+            if ($rootScope.widgetEditMode) {
+                loadWidget(editingWidgetType, bundleAlias, isSystem, deferred);
+            } else {
+                getWidgetType(bundleAlias, widgetTypeAlias, isSystem).then(
+                    function success(widgetType) {
+                        loadWidget(widgetType, bundleAlias, isSystem, deferred);
+                    }, function fail() {
+                        deferred.resolve(missingWidgetType);
+                    }
+                );
+            }
+        }
+        return deferred.promise;
+    }
+
+    function loadWidget(widgetType, bundleAlias, isSystem, deferred) {
+        var widgetInfo = toWidgetInfo(widgetType);
+        loadWidgetResources(widgetInfo, bundleAlias, isSystem).then(
+            function success() {
+                putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem);
+                deferred.resolve(widgetInfo);
+            }, function fail(errorMessages) {
+                widgetInfo = angular.copy(errorWidgetType);
+                for (var e in errorMessages) {
+                    var error = errorMessages[e];
+                    widgetInfo.templateHtml += '<div class="tb-widget-error-msg">' + error + '</div>';
+                }
+                widgetInfo.templateHtml += '</div>';
+                deferred.resolve(widgetInfo);
+            }
+        );
+    }
+
+    function getWidgetType(bundleAlias, widgetTypeAlias, isSystem) {
+        var deferred = $q.defer();
+        var url = '/api/widgetType?isSystem=' + (isSystem ? 'true' : 'false') +
+            '&bundleAlias='+bundleAlias+'&alias='+widgetTypeAlias;
+        $http.get(url, null).then(function success(response) {
+            var widgetType = response.data;
+            deferred.resolve(widgetType);
+        }, function fail() {
+            deferred.reject();
+        });
+        return deferred.promise;
+    }
+
+    function getWidgetTypeById(widgetTypeId) {
+        var deferred = $q.defer();
+        var url = '/api/widgetType/' + widgetTypeId;
+        $http.get(url, null).then(function success(response) {
+            var widgetType = response.data;
+            deferred.resolve(widgetType);
+        }, function fail() {
+            deferred.reject();
+        });
+        return deferred.promise;
+    }
+
+    function deleteWidgetType(bundleAlias, widgetTypeAlias, isSystem) {
+        var deferred = $q.defer();
+        getWidgetType(bundleAlias, widgetTypeAlias, isSystem).then(
+            function success(widgetType) {
+                var url = '/api/widgetType/' + widgetType.id.id;
+                $http.delete(url).then(function success() {
+                    deleteWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem);
+                    deferred.resolve();
+                }, function fail() {
+                    deferred.reject();
+                });
+            },
+            function fail() {
+                deferred.reject();
+            }
+        );
+        return deferred.promise;
+    }
+
+    function saveWidgetType(widgetInfo, id, bundleAlias) {
+        var deferred = $q.defer();
+        var widgetType = toWidgetType(widgetInfo, id, undefined, bundleAlias);
+        var url = '/api/widgetType';
+        $http.post(url, widgetType).then(function success(response) {
+            var widgetType = response.data;
+            deleteWidgetInfoFromCache(widgetType.bundleAlias, widgetType.alias, widgetType.tenantId.id === types.id.nullUid);
+            deferred.resolve(widgetType);
+        }, function fail() {
+            deferred.reject();
+        });
+        return deferred.promise;
+    }
+
+    function loadWidgetResources(widgetInfo, bundleAlias, isSystem) {
+
+        var deferred = $q.defer();
+        var errors = [];
+
+        var widgetNamespace = "widget-type-" + (isSystem ? 'sys-' : '') + bundleAlias + '-' + widgetInfo.alias;
+        cssParser.cssPreviewNamespace = widgetNamespace;
+        cssParser.createStyleElement(widgetNamespace, widgetInfo.templateCss);
+
+        function loadNextOrComplete(i) {
+            i++;
+            if (i < widgetInfo.resources.length) {
+                loadNext(i);
+            } else {
+                if (errors.length > 0) {
+                    deferred.reject(errors);
+                } else {
+                    deferred.resolve();
+                }
+            }
+        }
+
+        function loadNext(i) {
+            var resourceUrl = widgetInfo.resources[i].url;
+            if (resourceUrl && resourceUrl.length > 0) {
+                $ocLazyLoad.load(resourceUrl).then(
+                    function success() {
+                        loadNextOrComplete(i);
+                    },
+                    function fail() {
+                        errors.push('Failed to load widget resource: \'' + resourceUrl + '\'');
+                        loadNextOrComplete(i);
+                    }
+                );
+            } else {
+                loadNextOrComplete(i);
+            }
+        }
+
+        if (widgetInfo.resources.length > 0) {
+            loadNext(0);
+        } else {
+            deferred.resolve();
+        }
+
+        return deferred.promise;
+    }
+
+}
diff --git a/ui/src/app/app.config.js b/ui/src/app/app.config.js
new file mode 100644
index 0000000..12d60f6
--- /dev/null
+++ b/ui/src/app/app.config.js
@@ -0,0 +1,147 @@
+/*
+ * Copyright © 2016 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 injectTapEventPlugin from 'react-tap-event-plugin';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import mdiIconSet from '../svg/mdi.svg';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+const PRIMARY_BACKGROUND_COLOR = "#305680";//#2856b6";//"#3f51b5";
+const SECONDARY_BACKGROUND_COLOR = "#527dad";
+const HUE3_COLOR = "#a7c1de";
+
+/*@ngInject*/
+export default function AppConfig($provide,
+                                  $urlRouterProvider,
+                                  $locationProvider,
+                                  $mdIconProvider,
+                                  $mdThemingProvider,
+                                  $httpProvider,
+                                  $translateProvider,
+                                  storeProvider) {
+
+    injectTapEventPlugin();
+    $locationProvider.html5Mode(true);
+    $urlRouterProvider.otherwise('/home');
+    storeProvider.setCaching(false);
+
+    $translateProvider.useStaticFilesLoader({
+        prefix: 'static/locale/',// path to translations files
+        suffix: '.json'// suffix, currently- extension of the translations
+    });
+
+    $translateProvider.useSanitizeValueStrategy('sanitize');
+    $translateProvider.preferredLanguage('en_US');
+    $translateProvider.useLocalStorage();
+    $translateProvider.useMissingTranslationHandlerLog();
+    $translateProvider.addInterpolation('$translateMessageFormatInterpolation');
+
+    $httpProvider.interceptors.push('globalInterceptor');
+
+    $provide.decorator("$exceptionHandler", ['$delegate', '$injector', function ($delegate, $injector) {
+        return function (exception, cause) {
+            var rootScope = $injector.get("$rootScope");
+            var $window = $injector.get("$window");
+            var utils = $injector.get("utils");
+            if (rootScope.widgetEditMode) {
+                var parentScope = $window.parent.angular.element($window.frameElement).scope();
+                var data = utils.parseException(exception);
+                parentScope.$emit('widgetException', data);
+                parentScope.$apply();
+            }
+            $delegate(exception, cause);
+        };
+    }]);
+
+    $mdIconProvider.iconSet('mdi', mdiIconSet);
+
+    configureTheme();
+
+    function blueGrayTheme() {
+        var tbPrimaryPalette = $mdThemingProvider.extendPalette('blue-grey');
+        var tbAccentPalette = $mdThemingProvider.extendPalette('orange', {
+            'contrastDefaultColor': 'light'
+        });
+
+        $mdThemingProvider.definePalette('tb-primary', tbPrimaryPalette);
+        $mdThemingProvider.definePalette('tb-accent', tbAccentPalette);
+
+        $mdThemingProvider.theme('default')
+            .primaryPalette('tb-primary')
+            .accentPalette('tb-accent');
+
+        $mdThemingProvider.theme('tb-dark')
+            .primaryPalette('tb-primary')
+            .accentPalette('tb-accent')
+            .backgroundPalette('tb-primary')
+            .dark();
+    }
+
+    function indigoTheme() {
+        var tbPrimaryPalette = $mdThemingProvider.extendPalette('indigo', {
+            '500': PRIMARY_BACKGROUND_COLOR,
+            '600': SECONDARY_BACKGROUND_COLOR,
+            'A100': HUE3_COLOR
+        });
+
+        var tbAccentPalette = $mdThemingProvider.extendPalette('deep-orange');
+
+        $mdThemingProvider.definePalette('tb-primary', tbPrimaryPalette);
+        $mdThemingProvider.definePalette('tb-accent', tbAccentPalette);
+
+        var tbDarkPrimaryPalette = $mdThemingProvider.extendPalette('tb-primary', {
+            '500': '#9fa8da'
+        });
+
+        var tbDarkPrimaryBackgroundPalette = $mdThemingProvider.extendPalette('tb-primary', {
+            '800': PRIMARY_BACKGROUND_COLOR
+        });
+
+        $mdThemingProvider.definePalette('tb-dark-primary', tbDarkPrimaryPalette);
+        $mdThemingProvider.definePalette('tb-dark-primary-background', tbDarkPrimaryBackgroundPalette);
+
+        $mdThemingProvider.theme('default')
+            .primaryPalette('tb-primary')
+            .accentPalette('tb-accent');
+
+        $mdThemingProvider.theme('tb-dark')
+            .primaryPalette('tb-dark-primary')
+            .accentPalette('tb-accent')
+            .backgroundPalette('tb-dark-primary-background')
+            .dark();
+    }
+
+    function configureTheme() {
+
+        var theme = 'indigo';
+
+        if (theme === 'blueGray') {
+            blueGrayTheme();
+        } else {
+            indigoTheme();
+        }
+
+        $mdThemingProvider.theme('tb-search-input', 'default')
+            .primaryPalette('tb-primary')
+            .backgroundPalette('tb-primary');
+
+        $mdThemingProvider.setDefaultTheme('default');
+        $mdThemingProvider.alwaysWatchTheme(true);
+    }
+
+}
\ No newline at end of file

ui/src/app/app.js 105(+105 -0)

diff --git a/ui/src/app/app.js b/ui/src/app/app.js
new file mode 100644
index 0000000..3d55ac1
--- /dev/null
+++ b/ui/src/app/app.js
@@ -0,0 +1,105 @@
+/*
+ * Copyright © 2016 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 angular from 'angular';
+import ngMaterial from 'angular-material';
+import ngMdIcons from 'angular-material-icons';
+import ngCookies from 'angular-cookies';
+import 'angular-translate';
+import 'angular-translate-loader-static-files';
+import 'angular-translate-storage-local';
+import 'angular-translate-storage-cookie';
+import 'angular-translate-handler-log';
+import 'angular-translate-interpolation-messageformat';
+import 'md-color-picker';
+import mdPickers from 'mdPickers';
+import ngSanitize from 'angular-sanitize';
+import vAccordion from 'v-accordion';
+import ngAnimate from 'angular-animate';
+import 'angular-websocket';
+import uiRouter from 'angular-ui-router';
+import angularJwt from 'angular-jwt';
+import 'angular-drag-and-drop-lists';
+import mdDataTable from 'angular-material-data-table';
+import ngTouch from 'angular-touch';
+import 'angular-carousel';
+import 'clipboard';
+import 'ngclipboard';
+import 'react';
+import 'react-dom';
+import 'material-ui';
+import 'react-schema-form';
+import react from 'ngreact';
+
+import thingsboardLogin from './login';
+import thingsboardDialogs from './components/datakey-config-dialog.controller';
+import thingsboardMenu from './services/menu.service';
+import thingsboardUtils from './common/utils.service';
+import thingsboardTypes from './common/types.constant';
+import thingsboardHelp from './help/help.directive';
+import thingsboardToast from './services/toast';
+import thingsboardHome from './layout';
+import thingsboardApiLogin from './api/login.service';
+import thingsboardApiDevice from './api/device.service';
+import thingsboardApiUser from './api/user.service';
+
+import 'font-awesome/css/font-awesome.min.css';
+import 'angular-material/angular-material.min.css';
+import 'angular-material-icons/angular-material-icons.css';
+import 'angular-gridster/dist/angular-gridster.min.css';
+import 'v-accordion/dist/v-accordion.min.css'
+import 'md-color-picker/dist/mdColorPicker.min.css';
+import 'mdPickers/dist/mdPickers.min.css';
+import 'angular-hotkeys/build/hotkeys.min.css';
+import 'angular-carousel/dist/angular-carousel.min.css';
+import '../scss/main.scss';
+
+import AppConfig from './app.config';
+import GlobalInterceptor from './global-interceptor.service';
+import AppRun from './app.run';
+
+angular.module('thingsboard', [
+    ngMaterial,
+    ngMdIcons,
+    ngCookies,
+    'pascalprecht.translate',
+    'mdColorPicker',
+    mdPickers,
+    ngSanitize,
+    vAccordion,
+    ngAnimate,
+    'ngWebSocket',
+    angularJwt,
+    'dndLists',
+    mdDataTable,
+    ngTouch,
+    'angular-carousel',
+    'ngclipboard',
+    react.name,
+    thingsboardLogin,
+    thingsboardDialogs,
+    thingsboardMenu,
+    thingsboardUtils,
+    thingsboardTypes,
+    thingsboardHelp,
+    thingsboardToast,
+    thingsboardHome,
+    thingsboardApiLogin,
+    thingsboardApiDevice,
+    thingsboardApiUser,
+    uiRouter])
+    .config(AppConfig)
+    .factory('globalInterceptor', GlobalInterceptor)
+    .run(AppRun);

ui/src/app/app.run.js 166(+166 -0)

diff --git a/ui/src/app/app.run.js b/ui/src/app/app.run.js
new file mode 100644
index 0000000..934f021
--- /dev/null
+++ b/ui/src/app/app.run.js
@@ -0,0 +1,166 @@
+/*
+ * Copyright © 2016 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 AppRun($rootScope, $window, $log, $state, $mdDialog, $filter, loginService, userService, $translate) {
+
+    var frame = $window.frameElement;
+    var unauthorizedDialog = null;
+    var forbiddenDialog = null;
+
+    if (frame) {
+        var dataWidgetAttr = angular.element(frame).attr('data-widget');
+        if (dataWidgetAttr) {
+            $rootScope.editWidgetInfo = angular.fromJson(dataWidgetAttr);
+            $rootScope.widgetEditMode = true;
+        }
+    }
+
+    initWatchers();
+
+    function initWatchers() {
+        $rootScope.unauthenticatedHandle = $rootScope.$on('unauthenticated', function (event, doLogout) {
+            if (doLogout) {
+                $state.go('login');
+            } else {
+                checkCurrentState();
+            }
+        });
+
+        $rootScope.authenticatedHandle = $rootScope.$on('authenticated', function () {
+            checkCurrentState();
+        });
+
+        $rootScope.forbiddenHandle = $rootScope.$on('forbidden', function () {
+            showForbiddenDialog();
+        });
+
+        $rootScope.stateChangeStartHandle = $rootScope.$on('$stateChangeStart', function (evt, to, params) {
+            if (userService.isUserLoaded() === true) {
+                if (userService.isAuthenticated()) {
+                    var authority = userService.getAuthority();
+                    if (to.module === 'public') {
+                        evt.preventDefault();
+                        $state.go('home', params);
+                    } else if (angular.isDefined(to.auth) &&
+                        to.auth.indexOf(authority) === -1) {
+                        evt.preventDefault();
+                        showForbiddenDialog();
+                    } else if (to.redirectTo) {
+                        evt.preventDefault();
+                        $state.go(to.redirectTo, params)
+                    }
+                } else {
+                    if (to.module === 'private') {
+                        evt.preventDefault();
+                        if (to.url === '/home') {
+                            $state.go('login', params);
+                        } else {
+                            showUnauthorizedDialog();
+                        }
+                    }
+                }
+            } else {
+                evt.preventDefault();
+                $rootScope.userLoadedHandle = $rootScope.$on('userLoaded', function () {
+                    $rootScope.userLoadedHandle();
+                    $state.go(to.name, params);
+                });
+            }
+        })
+
+        $rootScope.pageTitle = 'Thingsboard';
+
+        $rootScope.stateChangeSuccessHandle = $rootScope.$on('$stateChangeSuccess', function (evt, to) {
+            if (angular.isDefined(to.data.pageTitle)) {
+                $translate(to.data.pageTitle).then(function (translation) {
+                    $rootScope.pageTitle = 'Thingsboard | ' + translation;
+                }, function (translationId) {
+                    $rootScope.pageTitle = 'Thingsboard | ' + translationId;
+                });
+            }
+        })
+    }
+
+    function checkCurrentState() {
+        if (userService.isUserLoaded() === true) {
+            var module = $state.$current.module;
+            if (userService.isAuthenticated()) {
+                if ($state.$current.module === 'public') {
+                    $state.go('home');
+                }
+            } else {
+                if (angular.isUndefined(module) || !module) {
+                    //$state.go('login');
+                } else if ($state.$current.module === 'private') {
+                    showUnauthorizedDialog();
+                }
+            }
+        } else {
+            showUnauthorizedDialog();
+        }
+    }
+
+    function showUnauthorizedDialog() {
+        if (unauthorizedDialog === null) {
+            $translate(['access.unauthorized-access',
+                        'access.unauthorized-access-text',
+                        'access.unauthorized',
+                        'action.cancel',
+                        'action.sign-in']).then(function (translations) {
+                if (unauthorizedDialog === null) {
+                    unauthorizedDialog = $mdDialog.confirm()
+                        .title(translations['access.unauthorized-access'])
+                        .textContent(translations['access.unauthorized-access-text'])
+                        .ariaLabel(translations['access.unauthorized'])
+                        .cancel(translations['action.cancel'])
+                        .ok(translations['action.sign-in']);
+                    $mdDialog.show(unauthorizedDialog).then(function () {
+                        unauthorizedDialog = null;
+                        $state.go('login');
+                    }, function () {
+                        unauthorizedDialog = null;
+                    });
+                }
+            });
+        }
+    }
+
+    function showForbiddenDialog() {
+        if (forbiddenDialog === null) {
+            $translate(['access.access-forbidden',
+                'access.access-forbidden-text',
+                'access.access-forbidden',
+                'action.cancel',
+                'action.sign-in']).then(function (translations) {
+                if (forbiddenDialog === null) {
+                    forbiddenDialog = $mdDialog.confirm()
+                        .title(translations['access.access-forbidden'])
+                        .htmlContent(translations['access.access-forbidden-text'])
+                        .ariaLabel(translations['access.access-forbidden'])
+                        .cancel(translations['action.cancel'])
+                        .ok(translations['action.sign-in']);
+                    $mdDialog.show(forbiddenDialog).then(function () {
+                        forbiddenDialog = null;
+                        userService.logout();
+                    }, function () {
+                        forbiddenDialog = null;
+                    });
+                }
+            });
+        }
+    }
+}
diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js
new file mode 100644
index 0000000..54ca208
--- /dev/null
+++ b/ui/src/app/common/types.constant.js
@@ -0,0 +1,151 @@
+/*
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default angular.module('thingsboard.types', [])
+    .constant('types',
+        {
+            serverErrorCode: {
+                general: 2,
+                authentication: 10,
+                jwtTokenExpired: 11,
+                permissionDenied: 20,
+                invalidArguments: 30,
+                badRequestParams: 31,
+                itemNotFound: 32
+            },
+            entryPoints: {
+                login: "/api/auth/login",
+                tokenRefresh: "/api/auth/token",
+                nonTokenBased: "/api/noauth"
+            },
+            id: {
+                nullUid: "13814000-1dd2-11b2-8080-808080808080",
+            },
+            datasourceType: {
+                function: "function",
+                device: "device"
+            },
+            dataKeyType: {
+                timeseries: "timeseries",
+                attribute: "attribute",
+                function: "function"
+            },
+            componentType: {
+                filter: "FILTER",
+                processor: "PROCESSOR",
+                action: "ACTION",
+                plugin: "PLUGIN"
+            },
+            entityType: {
+                tenant: "TENANT",
+                device: "DEVICE",
+                customer: "CUSTOMER",
+                rule: "RULE",
+                plugin: "PLUGIN"
+            },
+            eventType: {
+                alarm: {
+                    value: "ALARM",
+                    name: "event.type-alarm"
+                },
+                error: {
+                    value: "ERROR",
+                    name: "event.type-error"
+                },
+                lcEvent: {
+                    value: "LC_EVENT",
+                    name: "event.type-lc-event"
+                },
+                stats: {
+                    value: "STATS",
+                    name: "event.type-stats"
+                }
+            },
+            latestTelemetry: {
+                value: "LATEST_TELEMETRY",
+                name: "attribute.scope-latest-telemetry",
+                clientSide: true
+            },
+            deviceAttributesScope: {
+                client: {
+                    value: "CLIENT_SCOPE",
+                    name: "attribute.scope-client",
+                    clientSide: true
+                },
+                server: {
+                    value: "SERVER_SCOPE",
+                    name: "attribute.scope-server",
+                    clientSide: false
+                },
+                shared: {
+                    value: "SHARED_SCOPE",
+                    name: "attribute.scope-shared",
+                    clientSide: false
+                }
+            },
+            valueType: {
+                string: {
+                    value: "string",
+                    name: "value.string",
+                    icon: "mdi:format-text"
+                },
+                integer: {
+                    value: "integer",
+                    name: "value.integer",
+                    icon: "mdi:numeric"
+                },
+                double: {
+                    value: "double",
+                    name: "value.double",
+                    icon: "mdi:numeric"
+                },
+                boolean: {
+                    value: "boolean",
+                    name: "value.boolean",
+                    icon: "mdi:checkbox-marked-outline"
+                }
+            },
+            widgetType: {
+                timeseries: {
+                    value: "timeseries",
+                    name: "widget.timeseries",
+                    template: {
+                        bundleAlias: "charts",
+                        alias: "basic_timeseries"
+                    }
+                },
+                latest: {
+                    value: "latest",
+                    name: "widget.latest-values",
+                    template: {
+                        bundleAlias: "cards",
+                        alias: "attributes_card"
+                    }
+                },
+                rpc: {
+                    value: "rpc",
+                    name: "widget.rpc",
+                    template: {
+                        bundleAlias: "gpio_widgets",
+                        alias: "basic_gpio_control"
+                    }
+                }
+            },
+            systemBundleAlias: {
+                charts: "charts",
+                cards: "cards"
+            }
+        }
+    ).name;
diff --git a/ui/src/app/common/utils.service.js b/ui/src/app/common/utils.service.js
new file mode 100644
index 0000000..d6dd0c0
--- /dev/null
+++ b/ui/src/app/common/utils.service.js
@@ -0,0 +1,265 @@
+/*
+ * Copyright © 2016 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 tinycolor from "tinycolor2";
+import jsonSchemaDefaults from "json-schema-defaults";
+import thingsboardTypes from "./types.constant";
+
+export default angular.module('thingsboard.utils', [thingsboardTypes])
+    .factory('utils', Utils)
+    .name;
+
+/*@ngInject*/
+function Utils($mdColorPalette, types) {
+
+    var predefinedFunctions = {},
+        predefinedFunctionsList = [],
+        materialColors = [];
+
+    predefinedFunctions['Sin'] = "return Math.round(1000*Math.sin(time/5000));";
+    predefinedFunctions['Cos'] = "return Math.round(1000*Math.cos(time/5000));";
+    predefinedFunctions['Random'] =
+        "var value = prevValue + Math.random() * 100 - 50;\n" +
+        "var multiplier = Math.pow(10, 2 || 0);\n" +
+        "var value = Math.round(value * multiplier) / multiplier;\n" +
+        "if (value < -1000) {\n" +
+        "	value = -1000;\n" +
+        "} else if (value > 1000) {\n" +
+        "	value = 1000;\n" +
+        "}\n" +
+        "return value;";
+
+    for (var func in predefinedFunctions) {
+        predefinedFunctionsList.push(func);
+    }
+
+    var colorPalettes = ['blue', 'green', 'red', 'amber', 'blue-grey', 'purple', 'light-green', 'indigo', 'pink', 'yellow', 'light-blue', 'orange', 'deep-purple', 'lime', 'teal', 'brown', 'cyan', 'deep-orange', 'grey'];
+    var colorSpectrum = ['500', 'A700', '600', '700', '800', '900', '300', '400', 'A200', 'A400'];
+
+    angular.forEach($mdColorPalette, function (value, key) {
+        angular.forEach(value, function (color, label) {
+            if (colorSpectrum.indexOf(label) > -1) {
+                var rgb = 'rgb(' + color.value[0] + ',' + color.value[1] + ',' + color.value[2] + ')';
+                color = tinycolor(rgb);
+                var isDark = color.isDark();
+                var colorItem = {
+                    value: color.toHexString(),
+                    group: key,
+                    label: label,
+                    isDark: isDark
+                };
+                materialColors.push(colorItem);
+            }
+        });
+    });
+
+    materialColors.sort(function (colorItem1, colorItem2) {
+        var spectrumIndex1 = colorSpectrum.indexOf(colorItem1.label);
+        var spectrumIndex2 = colorSpectrum.indexOf(colorItem2.label);
+        var result = spectrumIndex1 - spectrumIndex2;
+        if (result === 0) {
+            var paletteIndex1 = colorPalettes.indexOf(colorItem1.group);
+            var paletteIndex2 = colorPalettes.indexOf(colorItem2.group);
+            result = paletteIndex1 - paletteIndex2;
+        }
+        return result;
+    });
+
+    var defaultDataKey = {
+        name: 'f(x)',
+        type: types.dataKeyType.function,
+        label: 'Sin',
+        color: getMaterialColor(0),
+        funcBody: getPredefinedFunctionBody('Sin'),
+        settings: {},
+        _hash: Math.random()
+    };
+
+    var defaultDatasource = {
+        type: types.datasourceType.function,
+        name: types.datasourceType.function,
+        dataKeys: [angular.copy(defaultDataKey)]
+    };
+
+    var service = {
+        getDefaultDatasource: getDefaultDatasource,
+        getDefaultDatasourceJson: getDefaultDatasourceJson,
+        getMaterialColor: getMaterialColor,
+        getPredefinedFunctionBody: getPredefinedFunctionBody,
+        getPredefinedFunctionsList: getPredefinedFunctionsList,
+        genMaterialColor: genMaterialColor,
+        objectHashCode: objectHashCode,
+        parseException: parseException,
+        isDescriptorSchemaNotEmpty: isDescriptorSchemaNotEmpty,
+        filterSearchTextEntities: filterSearchTextEntities
+    }
+
+    return service;
+
+    function getPredefinedFunctionsList() {
+        return predefinedFunctionsList;
+    }
+
+    function getPredefinedFunctionBody(func) {
+        return predefinedFunctions[func];
+    }
+
+    function getMaterialColor(index) {
+        var colorIndex = index % materialColors.length;
+        return materialColors[colorIndex].value;
+    }
+
+    function genMaterialColor(str) {
+        var hash = Math.abs(hashCode(str));
+        return getMaterialColor(hash);
+    }
+
+    function hashCode(str) {
+        var hash = 0;
+        var i, char;
+        if (str.length == 0) return hash;
+        for (i = 0; i < str.length; i++) {
+            char = str.charCodeAt(i);
+            hash = ((hash << 5) - hash) + char;
+            hash = hash & hash; // Convert to 32bit integer
+        }
+        return hash;
+    }
+
+    function objectHashCode(obj) {
+        var hash = 0;
+        if (obj) {
+            var str = angular.toJson(obj);
+            hash = hashCode(str);
+        }
+        return hash;
+    }
+
+    function parseException(exception) {
+        var data = {};
+        if (exception) {
+            if (angular.isString(exception) || exception instanceof String) {
+                data.message = exception;
+            } else {
+                if (exception.name) {
+                    data.name = exception.name;
+                } else {
+                    data.name = 'UnknownError';
+                }
+                if (exception.message) {
+                    data.message = exception.message;
+                }
+                if (exception.lineNumber) {
+                    data.lineNumber = exception.lineNumber;
+                    if (exception.columnNumber) {
+                        data.columnNumber = exception.columnNumber;
+                    }
+                } else if (exception.stack) {
+                    var lineInfoRegexp = /(.*<anonymous>):(\d*)(:)?(\d*)?/g;
+                    var lineInfoGroups = lineInfoRegexp.exec(exception.stack);
+                    if (lineInfoGroups != null && lineInfoGroups.length >= 3) {
+                        data.lineNumber = lineInfoGroups[2] - 2;
+                        if (lineInfoGroups.length >= 5) {
+                            data.columnNumber = lineInfoGroups[4];
+                        }
+                    }
+                }
+            }
+        }
+        return data;
+    }
+
+    function getDefaultDatasource(dataKeySchema) {
+        var datasource = angular.copy(defaultDatasource);
+        if (angular.isDefined(dataKeySchema)) {
+            datasource.dataKeys[0].settings = jsonSchemaDefaults(dataKeySchema);
+        }
+        return datasource;
+    }
+
+    function getDefaultDatasourceJson(dataKeySchema) {
+        return angular.toJson(getDefaultDatasource(dataKeySchema));
+    }
+
+    function isDescriptorSchemaNotEmpty(descriptor) {
+        if (descriptor && descriptor.schema && descriptor.schema.properties) {
+            for(var prop in descriptor.schema.properties) {
+                if (descriptor.schema.properties.hasOwnProperty(prop)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    function filterSearchTextEntities(entities, searchTextField, pageLink, deferred) {
+        var response = {
+            data: [],
+            hasNext: false,
+            nextPageLink: null
+        };
+        var limit = pageLink.limit;
+        var textSearch = '';
+        if (pageLink.textSearch) {
+            textSearch = pageLink.textSearch.toLowerCase();
+        }
+
+        for (var i=0;i<entities.length;i++) {
+            var entity = entities[i];
+            var text = entity[searchTextField].toLowerCase();
+            var createdTime = entity.createdTime;
+            if (pageLink.textOffset && pageLink.textOffset.length > 0) {
+                var comparison = text.localeCompare(pageLink.textOffset);
+                if (comparison === 0
+                    && createdTime < pageLink.createdTimeOffset) {
+                    response.data.push(entity);
+                    if (response.data.length === limit) {
+                        break;
+                    }
+                } else if (comparison > 0 && text.startsWith(textSearch)) {
+                    response.data.push(entity);
+                    if (response.data.length === limit) {
+                        break;
+                    }
+                }
+            } else if (textSearch.length > 0) {
+                if (text.startsWith(textSearch)) {
+                    response.data.push(entity);
+                    if (response.data.length === limit) {
+                        break;
+                    }
+                }
+            } else {
+                response.data.push(entity);
+                if (response.data.length === limit) {
+                    break;
+                }
+            }
+        }
+        if (response.data.length === limit) {
+            var lastEntity = response.data[limit-1];
+            response.nextPageLink = {
+                limit: pageLink.limit,
+                textSearch: textSearch,
+                idOffset: lastEntity.id.id,
+                createdTimeOffset: lastEntity.createdTime,
+                textOffset: lastEntity[searchTextField].toLowerCase()
+            };
+            response.hasNext = true;
+        }
+        deferred.resolve(response);
+    }
+
+}
diff --git a/ui/src/app/component/component.directive.js b/ui/src/app/component/component.directive.js
new file mode 100644
index 0000000..2fe6c83
--- /dev/null
+++ b/ui/src/app/component/component.directive.js
@@ -0,0 +1,75 @@
+/*
+ * Copyright © 2016 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 componentTemplate from './component.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function ComponentDirective($compile, $templateCache, $document, $mdDialog, componentDialogService, componentDescriptorService) {
+
+    var linker = function (scope, element) {
+
+        var template = $templateCache.get(componentTemplate);
+
+        element.html(template);
+
+        scope.componentTypeName = '';
+
+        scope.loadComponentTypeName = function () {
+            componentDescriptorService.getComponentDescriptorByClazz(scope.component.clazz).then(
+                function success(component) {
+                    scope.componentTypeName = component.name;
+                },
+                function fail() {}
+            );
+        }
+
+        scope.$watch('component', function(newVal) {
+                if (newVal) {
+                    scope.loadComponentTypeName();
+                }
+            }
+        );
+
+        scope.openComponent = function($event) {
+            componentDialogService.openComponentDialog($event, false,
+                scope.readOnly, scope.title, scope.type, scope.pluginClazz,
+                angular.copy(scope.component)).then(
+                    function success(component) {
+                        scope.component = component;
+                    },
+                    function fail() {}
+            );
+        }
+
+        $compile(element.contents())(scope);
+    }
+
+    return {
+        restrict: "E",
+        link: linker,
+        scope: {
+            component: '=',
+            type: '=',
+            pluginClazz: '=',
+            title: '@',
+            readOnly: '=',
+            onRemoveComponent: '&'
+        }
+    };
+}
diff --git a/ui/src/app/component/component.tpl.html b/ui/src/app/component/component.tpl.html
new file mode 100644
index 0000000..cc55059
--- /dev/null
+++ b/ui/src/app/component/component.tpl.html
@@ -0,0 +1,58 @@
+<!--
+
+    Copyright © 2016 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="md-whiteframe-4dp" flex layout="row" layout-align="start center"
+     style="padding: 0 0 0 10px; margin: 5px;">
+    <span flex="50">
+        {{ component.name }}
+    </span>
+    <span flex="50">
+        {{ componentTypeName }}
+    </span>
+    <span ng-if="readOnly" style="min-width: 40px; min-height: 40px; margin: 0 6px;"></br></span>
+    <md-button ng-disabled="loading" class="md-icon-button md-primary"
+               style="min-width: 40px;"
+               ng-click="openComponent($event)"
+               aria-label="{{ (readOnly ? 'action.view' : 'action.edit') | translate }}">
+        <md-tooltip ng-if="readOnly" md-direction="top">
+            {{ 'action.view' | translate }}
+        </md-tooltip>
+        <md-tooltip ng-if="!readOnly" md-direction="top">
+            {{ 'action.edit' | translate }}
+        </md-tooltip>
+        <md-icon ng-if="readOnly" aria-label="{{ 'action.view' | translate }}"
+                 class="material-icons">
+            more_horiz
+        </md-icon>
+        <md-icon ng-if="!readOnly" aria-label="{{ 'action.edit' | translate }}"
+                 class="material-icons">
+            edit
+        </md-icon>
+    </md-button>
+    <md-button ng-if="!readOnly" ng-disabled="loading" class="md-icon-button md-primary"
+               style="min-width: 40px;"
+               ng-click="onRemoveComponent({event: $event})"
+               aria-label="{{ 'action.remove' | translate }}">
+        <md-tooltip md-direction="top">
+            {{ 'action.remove' | translate }}
+        </md-tooltip>
+        <md-icon aria-label="{{ 'action.delete' | translate }}"
+                 class="material-icons">
+            close
+        </md-icon>
+    </md-button>
+</div>
\ No newline at end of file
diff --git a/ui/src/app/component/component-dialog.controller.js b/ui/src/app/component/component-dialog.controller.js
new file mode 100644
index 0000000..fb1d1da
--- /dev/null
+++ b/ui/src/app/component/component-dialog.controller.js
@@ -0,0 +1,105 @@
+/*
+ * Copyright © 2016 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 ComponentDialogController($mdDialog, $q, $scope, componentDescriptorService, types, utils, helpLinks, isAdd, isReadOnly, componentInfo) {
+
+    var vm = this;
+
+    vm.isReadOnly = isReadOnly;
+    vm.isAdd = isAdd;
+    vm.componentInfo = componentInfo;
+    if (isAdd) {
+        vm.componentInfo.component = {};
+    }
+
+    vm.componentHasSchema = false;
+    vm.componentDescriptors = [];
+
+    if (vm.componentInfo.component && !vm.componentInfo.component.configuration) {
+        vm.componentInfo.component.configuration = {};
+    }
+
+    vm.helpLinkIdForComponent = helpLinkIdForComponent;
+    vm.save = save;
+    vm.cancel = cancel;
+
+    $scope.$watch("vm.componentInfo.component.clazz", function (newValue, prevValue) {
+        if (newValue != prevValue) {
+            if (newValue && prevValue) {
+                vm.componentInfo.component.configuration = {};
+            }
+            loadComponentDescriptor();
+        }
+    });
+
+    var componentDescriptorsPromise =
+        vm.componentInfo.type === types.componentType.action
+            ? componentDescriptorService.getPluginActionsByPluginClazz(vm.componentInfo.pluginClazz)
+            : componentDescriptorService.getComponentDescriptorsByType(vm.componentInfo.type);
+
+    componentDescriptorsPromise.then(
+        function success(componentDescriptors) {
+            vm.componentDescriptors = componentDescriptors;
+            if (vm.componentDescriptors.length === 1 && isAdd && !vm.componentInfo.component.clazz) {
+                vm.componentInfo.component.clazz = vm.componentDescriptors[0].clazz;
+            }
+        },
+        function fail() {
+        }
+    );
+
+    loadComponentDescriptor();
+
+    function loadComponentDescriptor () {
+        if (vm.componentInfo.component.clazz) {
+            componentDescriptorService.getComponentDescriptorByClazz(vm.componentInfo.component.clazz).then(
+                function success(componentDescriptor) {
+                    vm.componentDescriptor = componentDescriptor;
+                    vm.componentHasSchema = utils.isDescriptorSchemaNotEmpty(vm.componentDescriptor.configurationDescriptor);
+                },
+                function fail() {
+                }
+            );
+        } else {
+            vm.componentHasSchema = false;
+        }
+    }
+
+    function helpLinkIdForComponent() {
+        switch (vm.componentInfo.type) {
+            case types.componentType.filter: {
+                return helpLinks.getFilterLink(vm.componentInfo.component);
+            }
+            case types.componentType.processor: {
+                return helpLinks.getProcessorLink(vm.componentInfo.component);
+            }
+            case types.componentType.action: {
+                return helpLinks.getPluginActionLink(vm.componentInfo.component);
+            }
+
+        }
+    }
+
+
+    function cancel () {
+        $mdDialog.cancel();
+    }
+
+    function save () {
+        $mdDialog.hide(vm.componentInfo.component);
+    }
+
+}
diff --git a/ui/src/app/component/component-dialog.service.js b/ui/src/app/component/component-dialog.service.js
new file mode 100644
index 0000000..a9ad10f
--- /dev/null
+++ b/ui/src/app/component/component-dialog.service.js
@@ -0,0 +1,60 @@
+/*
+ * Copyright © 2016 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 componentDialogTemplate from './component-dialog.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function ComponentDialogService($mdDialog, $document, $q) {
+
+    var service = {
+        openComponentDialog: openComponentDialog
+    }
+
+    return service;
+
+    function openComponentDialog($event, isAdd, readOnly, title, type, pluginClazz, component) {
+        var deferred = $q.defer();
+        var componentInfo = {
+            title: title,
+            type: type,
+            pluginClazz: pluginClazz
+        };
+        if (component) {
+            componentInfo.component = angular.copy(component);
+        }
+        $mdDialog.show({
+            controller: 'ComponentDialogController',
+            controllerAs: 'vm',
+            templateUrl: componentDialogTemplate,
+            locals: {isAdd: isAdd,
+                isReadOnly: readOnly,
+                componentInfo: componentInfo},
+            parent: angular.element($document[0].body),
+            fullscreen: true,
+            targetEvent: $event,
+            skipHide: true
+        }).then(function (component) {
+            deferred.resolve(component);
+        }, function () {
+            deferred.reject();
+        });
+        return deferred.promise;
+    }
+
+}
\ No newline at end of file
diff --git a/ui/src/app/component/component-dialog.tpl.html b/ui/src/app/component/component-dialog.tpl.html
new file mode 100644
index 0000000..1720b92
--- /dev/null
+++ b/ui/src/app/component/component-dialog.tpl.html
@@ -0,0 +1,79 @@
+<!--
+
+    Copyright © 2016 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="{{ vm.componentInfo.title | translate }}" tb-help="vm.helpLinkIdForComponent()" help-container-id="help-container">
+    <form name="theForm" ng-submit="vm.save()">
+        <md-toolbar>
+            <div class="md-toolbar-tools">
+                <h2 translate>{{ vm.componentInfo.title }}</h2>
+                <span flex></span>
+                <div id="help-container"></div>
+                <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-show="loading"></md-progress-linear>
+        <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
+        <md-dialog-content>
+            <div class="md-dialog-content tb-filter">
+                <fieldset ng-disabled="loading || vm.isReadOnly">
+                    <section flex layout="row">
+                        <md-input-container flex class="md-block">
+                            <label translate>rule.component-name</label>
+                            <input required name="componentName" ng-model="vm.componentInfo.component.name">
+                            <div ng-messages="theForm.componentName.$error">
+                                <div translate ng-message="required">rule.component-name-required</div>
+                            </div>
+                        </md-input-container>
+                        <md-input-container flex class="md-block">
+                            <label translate>rule.component-type</label>
+                            <md-select required name="componentType" ng-model="vm.componentInfo.component.clazz" ng-disabled="loading || vm.isReadOnly">
+                                <md-option ng-repeat="componentDescriptor in vm.componentDescriptors" ng-value="componentDescriptor.clazz">
+                                    {{componentDescriptor.name}}
+                                </md-option>
+                            </md-select>
+                            <div ng-messages="theForm.componentType.$error">
+                                <div translate ng-message="required">rule.component-type-required</div>
+                            </div>
+                        </md-input-container>
+                    </section>
+                    <md-card flex class="plugin-config" ng-if="vm.componentHasSchema">
+                        <md-card-content>
+                            <tb-json-form schema="vm.componentDescriptor.configurationDescriptor.schema"
+                                          form="vm.componentDescriptor.configurationDescriptor.form"
+                                          model="vm.componentInfo.component.configuration"
+                                          readonly="loading || vm.isReadOnly"
+                                          form-control="theForm">
+                            </tb-json-form>
+                        </md-card-content>
+                    </md-card>
+                </fieldset>
+            </div>
+        </md-dialog-content>
+        <md-dialog-actions layout="row">
+            <span flex></span>
+            <md-button ng-if="!vm.isReadOnly" ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit"
+                       class="md-raised md-primary">
+                {{ (vm.isAdd ? 'action.add' : '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>
diff --git a/ui/src/app/component/index.js b/ui/src/app/component/index.js
new file mode 100644
index 0000000..306f7fb
--- /dev/null
+++ b/ui/src/app/component/index.js
@@ -0,0 +1,28 @@
+/*
+ * Copyright © 2016 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 thingsboardApiComponentDescriptor from '../api/component-descriptor.service';
+
+import ComponentDialogService from './component-dialog.service';
+import ComponentDialogController from './component-dialog.controller';
+import ComponentDirective from './component.directive';
+
+export default angular.module('thingsboard.component', [
+    thingsboardApiComponentDescriptor
+])
+    .factory('componentDialogService', ComponentDialogService)
+    .controller('ComponentDialogController', ComponentDialogController)
+    .directive('tbComponent', ComponentDirective)
+    .name;
diff --git a/ui/src/app/components/circular-progress.directive.js b/ui/src/app/components/circular-progress.directive.js
new file mode 100644
index 0000000..e384056
--- /dev/null
+++ b/ui/src/app/components/circular-progress.directive.js
@@ -0,0 +1,74 @@
+/*
+ * Copyright © 2016 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 $ from 'jquery';
+
+export default angular.module('thingsboard.directives.circularProgress', [])
+    .directive('tbCircularProgress', CircularProgress)
+    .name;
+
+/* eslint-disable angular/angularelement */
+
+/*@ngInject*/
+function CircularProgress($compile) {
+
+    var linker = function (scope, element) {
+
+        var circularProgressElement = angular.element('<md-progress-circular style="margin: auto;" md-mode="indeterminate" md-diameter="20"></md-progress-circular>');
+
+        $compile(circularProgressElement)(scope);
+
+        var children = null;
+        var cssWidth = element.prop('style')['width'];
+        var width = null;
+        if (!cssWidth) {
+            $(element).css('width', width + 'px');
+        }
+
+        scope.$watch('circularProgress', function (newCircularProgress, prevCircularProgress) {
+            if (newCircularProgress != prevCircularProgress) {
+                if (newCircularProgress) {
+                    if (!cssWidth) {
+                        $(element).css('width', '');
+                        width = element.prop('offsetWidth');
+                        $(element).css('width', width + 'px');
+                    }
+                    children = $(element).children();
+                    $(element).empty();
+                    $(element).append($(circularProgressElement));
+                } else {
+                    $(element).empty();
+                    $(element).append(children);
+                    if (cssWidth) {
+                        $(element).css('width', cssWidth);
+                    } else {
+                        $(element).css('width', '');
+                    }
+                }
+            }
+        });
+
+    }
+
+    return {
+        restrict: "A",
+        link: linker,
+        scope: {
+            circularProgress: "=tbCircularProgress"
+        }
+    };
+}
+
+/* eslint-enable angular/angularelement */
diff --git a/ui/src/app/components/confirm-on-exit.directive.js b/ui/src/app/components/confirm-on-exit.directive.js
new file mode 100644
index 0000000..d346b38
--- /dev/null
+++ b/ui/src/app/components/confirm-on-exit.directive.js
@@ -0,0 +1,56 @@
+/*
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default angular.module('thingsboard.directives.confirmOnExit', [])
+    .directive('tbConfirmOnExit', ConfirmOnExit)
+    .name;
+
+/*@ngInject*/
+function ConfirmOnExit($state, $mdDialog, $window, $filter) {
+    return {
+        link: function ($scope) {
+
+            $window.onbeforeunload = function () {
+                if (($scope.confirmForm && $scope.confirmForm.$dirty) || $scope.isDirty) {
+                    return $filter('translate')('confirm-on-exit.message');
+                }
+            }
+            $scope.$on('$stateChangeStart', function (event, next, current, params) {
+                if (($scope.confirmForm && $scope.confirmForm.$dirty) || $scope.isDirty) {
+                    event.preventDefault();
+                    var confirm = $mdDialog.confirm()
+                        .title($filter('translate')('confirm-on-exit.title'))
+                        .htmlContent($filter('translate')('confirm-on-exit.html-message'))
+                        .ariaLabel($filter('translate')('confirm-on-exit.title'))
+                        .cancel($filter('translate')('action.cancel'))
+                        .ok($filter('translate')('action.ok'));
+                    $mdDialog.show(confirm).then(function () {
+                        if ($scope.confirmForm) {
+                            $scope.confirmForm.$setPristine();
+                        } else {
+                            $scope.isDirty = false;
+                        }
+                        $state.go(next.name, params);
+                    }, function () {
+                    });
+                }
+            });
+        },
+        scope: {
+            confirmForm: '=',
+            isDirty: '='
+        }
+    };
+}
\ No newline at end of file
diff --git a/ui/src/app/components/contact.directive.js b/ui/src/app/components/contact.directive.js
new file mode 100644
index 0000000..c88f844
--- /dev/null
+++ b/ui/src/app/components/contact.directive.js
@@ -0,0 +1,300 @@
+/*
+ * Copyright © 2016 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 contactTemplate from './contact.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+export default angular.module('thingsboard.directives.contact', [])
+    .directive('tbContact', Contact)
+    .name;
+
+/*@ngInject*/
+function Contact($compile, $templateCache) {
+    var countries = [
+        "Afghanistan",
+        "Åland Islands",
+        "Albania",
+        "Algeria",
+        "American Samoa",
+        "Andorra",
+        "Angola",
+        "Anguilla",
+        "Antarctica",
+        "Antigua and Barbuda",
+        "Argentina",
+        "Armenia",
+        "Aruba",
+        "Australia",
+        "Austria",
+        "Azerbaijan",
+        "Bahamas",
+        "Bahrain",
+        "Bangladesh",
+        "Barbados",
+        "Belarus",
+        "Belgium",
+        "Belize",
+        "Benin",
+        "Bermuda",
+        "Bhutan",
+        "Bolivia",
+        "Bonaire, Sint Eustatius and Saba",
+        "Bosnia and Herzegovina",
+        "Botswana",
+        "Bouvet Island",
+        "Brazil",
+        "British Indian Ocean Territory",
+        "Brunei Darussalam",
+        "Bulgaria",
+        "Burkina Faso",
+        "Burundi",
+        "Cambodia",
+        "Cameroon",
+        "Canada",
+        "Cape Verde",
+        "Cayman Islands",
+        "Central African Republic",
+        "Chad",
+        "Chile",
+        "China",
+        "Christmas Island",
+        "Cocos (Keeling) Islands",
+        "Colombia",
+        "Comoros",
+        "Congo",
+        "Congo, The Democratic Republic of the",
+        "Cook Islands",
+        "Costa Rica",
+        "Côte d'Ivoire",
+        "Croatia",
+        "Cuba",
+        "Curaçao",
+        "Cyprus",
+        "Czech Republic",
+        "Denmark",
+        "Djibouti",
+        "Dominica",
+        "Dominican Republic",
+        "Ecuador",
+        "Egypt",
+        "El Salvador",
+        "Equatorial Guinea",
+        "Eritrea",
+        "Estonia",
+        "Ethiopia",
+        "Falkland Islands (Malvinas)",
+        "Faroe Islands",
+        "Fiji",
+        "Finland",
+        "France",
+        "French Guiana",
+        "French Polynesia",
+        "French Southern Territories",
+        "Gabon",
+        "Gambia",
+        "Georgia",
+        "Germany",
+        "Ghana",
+        "Gibraltar",
+        "Greece",
+        "Greenland",
+        "Grenada",
+        "Guadeloupe",
+        "Guam",
+        "Guatemala",
+        "Guernsey",
+        "Guinea",
+        "Guinea-Bissau",
+        "Guyana",
+        "Haiti",
+        "Heard Island and McDonald Islands",
+        "Holy See (Vatican City State)",
+        "Honduras",
+        "Hong Kong",
+        "Hungary",
+        "Iceland",
+        "India",
+        "Indonesia",
+        "Iran, Islamic Republic of",
+        "Iraq",
+        "Ireland",
+        "Isle of Man",
+        "Israel",
+        "Italy",
+        "Jamaica",
+        "Japan",
+        "Jersey",
+        "Jordan",
+        "Kazakhstan",
+        "Kenya",
+        "Kiribati",
+        "Korea, Democratic People's Republic of",
+        "Korea, Republic of",
+        "Kuwait",
+        "Kyrgyzstan",
+        "Lao People's Democratic Republic",
+        "Latvia",
+        "Lebanon",
+        "Lesotho",
+        "Liberia",
+        "Libya",
+        "Liechtenstein",
+        "Lithuania",
+        "Luxembourg",
+        "Macao",
+        "Macedonia, Republic Of",
+        "Madagascar",
+        "Malawi",
+        "Malaysia",
+        "Maldives",
+        "Mali",
+        "Malta",
+        "Marshall Islands",
+        "Martinique",
+        "Mauritania",
+        "Mauritius",
+        "Mayotte",
+        "Mexico",
+        "Micronesia, Federated States of",
+        "Moldova, Republic of",
+        "Monaco",
+        "Mongolia",
+        "Montenegro",
+        "Montserrat",
+        "Morocco",
+        "Mozambique",
+        "Myanmar",
+        "Namibia",
+        "Nauru",
+        "Nepal",
+        "Netherlands",
+        "New Caledonia",
+        "New Zealand",
+        "Nicaragua",
+        "Niger",
+        "Nigeria",
+        "Niue",
+        "Norfolk Island",
+        "Northern Mariana Islands",
+        "Norway",
+        "Oman",
+        "Pakistan",
+        "Palau",
+        "Palestinian Territory, Occupied",
+        "Panama",
+        "Papua New Guinea",
+        "Paraguay",
+        "Peru",
+        "Philippines",
+        "Pitcairn",
+        "Poland",
+        "Portugal",
+        "Puerto Rico",
+        "Qatar",
+        "Reunion",
+        "Romania",
+        "Russian Federation",
+        "Rwanda",
+        "Saint Barthélemy",
+        "Saint Helena, Ascension and Tristan da Cunha",
+        "Saint Kitts and Nevis",
+        "Saint Lucia",
+        "Saint Martin (French Part)",
+        "Saint Pierre and Miquelon",
+        "Saint Vincent and the Grenadines",
+        "Samoa",
+        "San Marino",
+        "Sao Tome and Principe",
+        "Saudi Arabia",
+        "Senegal",
+        "Serbia",
+        "Seychelles",
+        "Sierra Leone",
+        "Singapore",
+        "Sint Maarten (Dutch Part)",
+        "Slovakia",
+        "Slovenia",
+        "Solomon Islands",
+        "Somalia",
+        "South Africa",
+        "South Georgia and the South Sandwich Islands",
+        "South Sudan",
+        "Spain",
+        "Sri Lanka",
+        "Sudan",
+        "Suriname",
+        "Svalbard and Jan Mayen",
+        "Swaziland",
+        "Sweden",
+        "Switzerland",
+        "Syrian Arab Republic",
+        "Taiwan",
+        "Tajikistan",
+        "Tanzania, United Republic of",
+        "Thailand",
+        "Timor-Leste",
+        "Togo",
+        "Tokelau",
+        "Tonga",
+        "Trinidad and Tobago",
+        "Tunisia",
+        "Turkey",
+        "Turkmenistan",
+        "Turks and Caicos Islands",
+        "Tuvalu",
+        "Uganda",
+        "Ukraine",
+        "United Arab Emirates",
+        "United Kingdom",
+        "United States",
+        "United States Minor Outlying Islands",
+        "Uruguay",
+        "Uzbekistan",
+        "Vanuatu",
+        "Venezuela",
+        "Viet Nam",
+        "Virgin Islands, British",
+        "Virgin Islands, U.S.",
+        "Wallis and Futuna",
+        "Western Sahara",
+        "Yemen",
+        "Zambia",
+        "Zimbabwe"
+    ];
+
+    var linker = function (scope, element) {
+
+        scope.countries = countries;
+
+        var template = $templateCache.get(contactTemplate);
+
+        element.html(template);
+
+        $compile(element.contents())(scope);
+    }
+
+    return {
+        restrict: "E",
+        link: linker,
+        scope: {
+            contact: '=',
+            isEdit: '=',
+            theForm: '='
+        }
+    };
+}
diff --git a/ui/src/app/components/contact.tpl.html b/ui/src/app/components/contact.tpl.html
new file mode 100644
index 0000000..8eb0fdd
--- /dev/null
+++ b/ui/src/app/components/contact.tpl.html
@@ -0,0 +1,59 @@
+<!--
+
+    Copyright © 2016 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-input-container class="md-block">
+	<label translate>contact.country</label>
+    <md-select ng-disabled="!isEdit" name="country" ng-model="contact.country">
+       <md-option ng-repeat="country in countries" value="{{country}}">
+         {{country}}
+       </md-option>
+    </md-select>
+</md-input-container>	
+<div layout-gt-sm="row">
+	<md-input-container class="md-block">
+		<label translate>contact.city</label>
+		<input name="city" ng-model="contact.city">	
+	</md-input-container>					
+	<md-input-container class="md-block">
+		<label translate>contact.state</label>
+		<input name="state" ng-model="contact.state">	
+	</md-input-container>					
+	<md-input-container class="md-block">
+		<label translate>contact.postal-code</label>
+		<input name="zip" ng-model="contact.zip" ng-pattern="/^([0-9]*)$/">	
+	    <div ng-messages="theForm.zip.$error" role="alert" multiple>
+	      <div translate ng-message="pattern">contact.postal-code-invalid</div>
+	    </div>	
+	</md-input-container>	
+</div>				
+<md-input-container class="md-block">
+	<label translate>contact.address</label>
+	<input name="address" ng-model="contact.address">	
+</md-input-container>					
+<md-input-container class="md-block">
+	<label translate>contact.address2</label>
+	<input name="address2" ng-model="contact.address2">	
+</md-input-container>					
+<md-input-container class="md-block">
+	<label translate>contact.phone</label>
+	<input name="phone" ng-model="contact.phone">	
+</md-input-container>					
+<md-input-container class="md-block">
+	<label translate>contact.email</label>
+	<input name="email" type="email" ng-model="contact.email">	
+</md-input-container>					
+	
\ No newline at end of file
diff --git a/ui/src/app/components/contact-short.filter.js b/ui/src/app/components/contact-short.filter.js
new file mode 100644
index 0000000..80c2c93
--- /dev/null
+++ b/ui/src/app/components/contact-short.filter.js
@@ -0,0 +1,54 @@
+/*
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default angular.module('thingsboard.filters.contactShort', [])
+    .filter('contactShort', ContactShort)
+    .name;
+
+/*@ngInject*/
+function ContactShort($filter) {
+    return function (contact) {
+        var contactShort = '';
+        if (contact) {
+            if (contact.address) {
+                contactShort += contact.address;
+                contactShort += ' ';
+            }
+            if (contact.address2) {
+                contactShort += contact.address2;
+                contactShort += ' ';
+            }
+            if (contact.city) {
+                contactShort += contact.city;
+                contactShort += ' ';
+            }
+            if (contact.state) {
+                contactShort += contact.state;
+                contactShort += ' ';
+            }
+            if (contact.zip) {
+                contactShort += contact.zip;
+                contactShort += ' ';
+            }
+            if (contact.country) {
+                contactShort += contact.country;
+            }
+        }
+        if (contactShort === '') {
+            contactShort = $filter('translate')('contact.no-address');
+        }
+        return contactShort;
+    };
+}
diff --git a/ui/src/app/components/dashboard.directive.js b/ui/src/app/components/dashboard.directive.js
new file mode 100644
index 0000000..ce7668f
--- /dev/null
+++ b/ui/src/app/components/dashboard.directive.js
@@ -0,0 +1,427 @@
+/*
+ * Copyright © 2016 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 './dashboard.scss';
+
+import $ from 'jquery';
+import gridster from 'angular-gridster';
+import thingsboardTypes from '../common/types.constant';
+import thingsboardApiWidget from '../api/widget.service';
+import thingsboardWidget from './widget.directive';
+import thingsboardToast from '../services/toast';
+import thingsboardTimewindow from './timewindow.directive';
+import thingsboardEvents from './tb-event-directives';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import dashboardTemplate from './dashboard.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/* eslint-disable angular/angularelement */
+
+export default angular.module('thingsboard.directives.dashboard', [thingsboardTypes,
+    thingsboardToast,
+    thingsboardApiWidget,
+    thingsboardWidget,
+    thingsboardTimewindow,
+    thingsboardEvents,
+    gridster.name])
+    .directive('tbDashboard', Dashboard)
+    .name;
+
+/*@ngInject*/
+function Dashboard() {
+    return {
+        restrict: "E",
+        scope: true,
+        bindToController: {
+            widgets: '=',
+            deviceAliasList: '=',
+            columns: '=',
+            isEdit: '=',
+            isMobile: '=',
+            isMobileDisabled: '=?',
+            isEditActionEnabled: '=',
+            isRemoveActionEnabled: '=',
+            onEditWidget: '&?',
+            onRemoveWidget: '&?',
+            onWidgetClicked: '&?',
+            loadWidgets: '&?',
+            onInit: '&?',
+            onInitFailed: '&?'
+        },
+        controller: DashboardController,
+        controllerAs: 'vm',
+        templateUrl: dashboardTemplate
+    };
+}
+
+/*@ngInject*/
+function DashboardController($scope, $rootScope, $element, $timeout, $log, toast, types) {
+
+    var highlightedMode = false;
+    var highlightedIndex = -1;
+    var mouseDownIndex = -1;
+    var widgetMouseMoved = false;
+
+    var gridsterParent = null;
+    var gridsterElement = null;
+    var gridster = null;
+
+    var vm = this;
+
+    vm.isMobileDisabled = angular.isDefined(vm.isMobileDisabled) ? vm.isMobileDisabled : false;
+
+    vm.dashboardLoading = true;
+    vm.visibleRect = {
+        top: 0,
+        bottom: 0,
+        left: 0,
+        right: 0
+    };
+    vm.gridsterOpts = {
+        floating: false,
+        maxRows: 100,
+        columns: vm.columns ? vm.columns : 24,
+        minSizeX: 2,
+        minSizeY: 2,
+        defaultSizeX: 8,
+        defaultSizeY: 6,
+        resizable: {
+            enabled: vm.isEdit
+        },
+        draggable: {
+            enabled: vm.isEdit
+        },
+        isMobile: vm.isMobileDisabled ? false : vm.isMobile,
+        mobileBreakPoint: vm.isMobileDisabled ? 0 : (vm.isMobile ? 20000 : 960),
+        margins: [10, 10],
+        saveGridItemCalculatedHeightInMobile: true
+    };
+
+    vm.isWidgetExpanded = false;
+    vm.isHighlighted = isHighlighted;
+    vm.isNotHighlighted = isNotHighlighted;
+    vm.highlightWidget = highlightWidget;
+    vm.resetHighlight = resetHighlight;
+
+    vm.onWidgetFullscreenChanged = onWidgetFullscreenChanged;
+    vm.widgetMouseDown = widgetMouseDown;
+    vm.widgetMouseMove = widgetMouseMove;
+    vm.widgetMouseUp = widgetMouseUp;
+
+    vm.widgetColor = widgetColor;
+    vm.widgetBackgroundColor = widgetBackgroundColor;
+    vm.widgetPadding = widgetPadding;
+    vm.showWidgetTitle = showWidgetTitle;
+    vm.hasTimewindow = hasTimewindow;
+    vm.editWidget = editWidget;
+    vm.removeWidget = removeWidget;
+    vm.loading = loading;
+
+    //$element[0].onmousemove=function(){
+    //    widgetMouseMove();
+   // }
+
+    gridsterParent = $('#gridster-parent', $element);
+    gridsterElement = angular.element($('#gridster-child', gridsterParent));
+
+    gridsterParent.scroll(function () {
+        updateVisibleRect();
+    });
+
+    gridsterParent.resize(function () {
+        updateVisibleRect();
+    });
+
+    $scope.$watch('vm.isMobile', function () {
+        vm.gridsterOpts.isMobile = vm.isMobileDisabled ? false : vm.isMobile;
+        vm.gridsterOpts.mobileBreakPoint = vm.isMobileDisabled ? 0 : (vm.isMobile ? 20000 : 960);
+    });
+
+    $scope.$watch('vm.isMobileDisabled', function () {
+        vm.gridsterOpts.isMobile = vm.isMobileDisabled ? false : vm.isMobile;
+        vm.gridsterOpts.mobileBreakPoint = vm.isMobileDisabled ? 0 : (vm.isMobile ? 20000 : 960);
+    });
+
+    $scope.$watch('vm.columns', function () {
+        vm.gridsterOpts.columns = vm.columns ? vm.columns : 24;
+    });
+
+    $scope.$watch('vm.isEdit', function () {
+        vm.gridsterOpts.resizable.enabled = vm.isEdit;
+        vm.gridsterOpts.draggable.enabled = vm.isEdit;
+        $scope.$broadcast('toggleDashboardEditMode', vm.isEdit);
+    });
+
+    $scope.$watch('vm.deviceAliasList', function () {
+        $scope.$broadcast('deviceAliasListChanged', vm.deviceAliasList);
+    }, true);
+
+    $scope.$on('gridster-resized', function (event, sizes, theGridster) {
+        if (checkIsLocalGridsterElement(theGridster)) {
+            gridster = theGridster;
+            updateVisibleRect(false, true);
+        }
+    });
+
+    $scope.$on('gridster-mobile-changed', function (event, theGridster) {
+        if (checkIsLocalGridsterElement(theGridster)) {
+            gridster = theGridster;
+            if (gridster.isMobile) {
+                vm.gridsterOpts.rowHeight = 70;
+            } else {
+                vm.gridsterOpts.rowHeight = 'match';
+            }
+            $timeout(function () {
+                updateVisibleRect(true);
+            }, 500, false);
+        }
+    });
+
+    $scope.$on('widgetPositionChanged', function () {
+        vm.widgets.sort(function (widget1, widget2) {
+            var res = widget1.row - widget2.row;
+            if (res === 0) {
+                res = widget1.col - widget2.col;
+            }
+            return res;
+        });
+    });
+
+    loadDashboard();
+
+    function loadDashboard() {
+        resetWidgetClick();
+        $timeout(function () {
+            if (vm.loadWidgets) {
+                var promise = vm.loadWidgets();
+                if (promise) {
+                    promise.then(function () {
+                        dashboardLoaded();
+                    }, function () {
+                        dashboardLoaded();
+                    });
+                } else {
+                    dashboardLoaded();
+                }
+            } else {
+                dashboardLoaded();
+            }
+        }, 0, false);
+    }
+
+    function updateVisibleRect (force, containerResized) {
+        if (gridster) {
+            var position = $(gridster.$element).position()
+            if (position) {
+                var viewportWidth = gridsterParent.width();
+                var viewportHeight = gridsterParent.height();
+                var top = -position.top;
+                var bottom = top + viewportHeight;
+                var left = -position.left;
+                var right = left + viewportWidth;
+
+                var newVisibleRect = {
+                    top: gridster.pixelsToRows(top),
+                    topPx: top,
+                    bottom: gridster.pixelsToRows(bottom),
+                    bottomPx: bottom,
+                    left: gridster.pixelsToColumns(left),
+                    right: gridster.pixelsToColumns(right),
+                    isMobile: gridster.isMobile,
+                    curRowHeight: gridster.curRowHeight,
+                    containerResized: containerResized
+                };
+
+                if (force ||
+                    newVisibleRect.top != vm.visibleRect.top ||
+                    newVisibleRect.topPx != vm.visibleRect.topPx ||
+                    newVisibleRect.bottom != vm.visibleRect.bottom ||
+                    newVisibleRect.bottomPx != vm.visibleRect.bottomPx ||
+                    newVisibleRect.left != vm.visibleRect.left ||
+                    newVisibleRect.right != vm.visibleRect.right ||
+                    newVisibleRect.isMobile != vm.visibleRect.isMobile ||
+                    newVisibleRect.curRowHeight != vm.visibleRect.curRowHeight ||
+                    newVisibleRect.containerResized != vm.visibleRect.containerResized) {
+                    vm.visibleRect = newVisibleRect;
+                    $scope.$broadcast('visibleRectChanged', vm.visibleRect);
+                }
+            }
+        }
+    }
+
+    function checkIsLocalGridsterElement (gridster) {
+        return gridsterElement[0] == gridster.$element[0];
+    }
+
+    function resetWidgetClick () {
+        mouseDownIndex = -1;
+        widgetMouseMoved = false;
+    }
+
+    function onWidgetFullscreenChanged(expanded, widget) {
+        vm.isWidgetExpanded = expanded;
+        $scope.$broadcast('onWidgetFullscreenChanged', vm.isWidgetExpanded, widget);
+    }
+
+    function widgetMouseDown ($event, widget) {
+        mouseDownIndex = vm.widgets.indexOf(widget);
+        widgetMouseMoved = false;
+    }
+
+    function widgetMouseMove () {
+        if (mouseDownIndex > -1) {
+            widgetMouseMoved = true;
+        }
+    }
+
+    function widgetMouseUp ($event, widget) {
+        $timeout(function () {
+            if (!widgetMouseMoved && mouseDownIndex > -1) {
+                var index = vm.widgets.indexOf(widget);
+                if (index === mouseDownIndex) {
+                    widgetClicked($event, widget);
+                }
+            }
+            mouseDownIndex = -1;
+            widgetMouseMoved = false;
+        }, 0);
+    }
+
+    function widgetClicked ($event, widget) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        if (vm.onWidgetClicked) {
+            vm.onWidgetClicked({event: $event, widget: widget});
+        }
+    }
+
+    function editWidget ($event, widget) {
+        resetWidgetClick();
+        if ($event) {
+            $event.stopPropagation();
+        }
+        if (vm.isEditActionEnabled && vm.onEditWidget) {
+            vm.onEditWidget({event: $event, widget: widget});
+        }
+    }
+
+    function removeWidget($event, widget) {
+        resetWidgetClick();
+        if ($event) {
+            $event.stopPropagation();
+        }
+        if (vm.isRemoveActionEnabled && vm.onRemoveWidget) {
+            vm.onRemoveWidget({event: $event, widget: widget});
+        }
+    }
+
+    function highlightWidget(widgetIndex, delay) {
+        highlightedMode = true;
+        highlightedIndex = widgetIndex;
+        var item = $('.gridster-item', gridster.$element)[widgetIndex];
+        if (item) {
+            var height = $(item).outerHeight(true);
+            var rectHeight = gridsterParent.height();
+            var offset = (rectHeight - height) / 2;
+            var scrollTop = item.offsetTop;
+            if (offset > 0) {
+                scrollTop -= offset;
+            }
+            gridsterParent.animate({
+                scrollTop: scrollTop
+            }, delay);
+        }
+    }
+
+    function resetHighlight() {
+        highlightedMode = false;
+        highlightedIndex = -1;
+    }
+
+    function isHighlighted(widget) {
+        return highlightedMode && vm.widgets.indexOf(widget) === highlightedIndex;
+    }
+
+    function isNotHighlighted(widget) {
+        return highlightedMode && vm.widgets.indexOf(widget) != highlightedIndex;
+    }
+
+    function widgetColor(widget) {
+        if (widget.config.color) {
+            return widget.config.color;
+        } else {
+            return 'rgba(0, 0, 0, 0.87)';
+        }
+    }
+
+    function widgetBackgroundColor(widget) {
+        if (widget.config.backgroundColor) {
+            return widget.config.backgroundColor;
+        } else {
+            return '#fff';
+        }
+    }
+
+    function widgetPadding(widget) {
+        if (widget.config.padding) {
+            return widget.config.padding;
+        } else {
+            return '8px';
+        }
+    }
+
+    function showWidgetTitle(widget) {
+        if (angular.isDefined(widget.config.showTitle)) {
+            return widget.config.showTitle;
+        } else {
+            return true;
+        }
+    }
+
+    function hasTimewindow(widget) {
+        return widget.type === types.widgetType.timeseries.value;
+    }
+
+    function adoptMaxRows() {
+        if (vm.widgets) {
+            var maxRows = vm.gridsterOpts.maxRows;
+            for (var i = 0; i < vm.widgets.length; i++) {
+                var w = vm.widgets[i];
+                var bottom = w.row + w.sizeY;
+                maxRows = Math.max(maxRows, bottom);
+            }
+            vm.gridsterOpts.maxRows = Math.max(maxRows, vm.gridsterOpts.maxRows);
+        }
+    }
+
+    function dashboardLoaded() {
+        adoptMaxRows();
+        vm.dashboardLoading = false;
+        if (vm.onInit) {
+            vm.onInit({dashboard: vm});
+        }
+    }
+
+    function loading() {
+        return $rootScope.loading;
+    }
+
+}
+
+/* eslint-enable angular/angularelement */
diff --git a/ui/src/app/components/dashboard.scss b/ui/src/app/components/dashboard.scss
new file mode 100644
index 0000000..1b08cdd
--- /dev/null
+++ b/ui/src/app/components/dashboard.scss
@@ -0,0 +1,108 @@
+/**
+ * Copyright © 2016 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 "~compass-sass-mixins/lib/compass";
+
+div.tb-widget {
+  position: relative;
+  height: 100%;
+  margin: 0;
+  overflow: hidden;
+  @include transition(all .2s ease-in-out);
+
+  .tb-widget-title {
+    max-height: 60px;
+
+    padding-top: 5px;
+    padding-left: 5px;
+    overflow: hidden;
+
+    tb-timewindow {
+      font-size: 14px;
+      opacity: 0.85;
+    }
+  }
+
+  .tb-widget-actions {
+    position: absolute;
+    top: 8px;
+    right: 8px;
+    z-index: 1;
+    margin: 0px;
+
+    .md-button.md-icon-button {
+      margin: 0px !important;
+      padding: 0px !important;
+      line-height: 20px;
+      width: 32px;
+      height: 32px;
+      min-width: 32px;
+      min-height: 32px;
+      md-icon {
+        width: 20px;
+        height: 20px;
+        min-width: 20px;
+        min-height: 20px;
+        font-size: 20px;
+      }
+    }
+  }
+
+  .tb-widget-content {
+    tb-widget {
+      width: 100%;
+      position: relative;
+    }
+  }
+}
+
+div.tb-widget.tb-highlighted {
+  border: 1px solid #039be5;
+  box-shadow: 0 0 20px #039be5;
+}
+
+div.tb-widget.tb-not-highlighted {
+  opacity: 0.5;
+}
+
+tb-dashboard {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+}
+
+md-content.tb-dashboard-content {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+}
+
+.tb-widget-error-container {
+  position: absolute;
+  background-color: #fff;
+  width: 100%;
+  height: 100%;
+}
+
+.tb-widget-error-msg {
+  color: red;
+  font-size: 16px;
+  word-wrap: break-word;
+  padding: 5px;
+}
diff --git a/ui/src/app/components/dashboard.tpl.html b/ui/src/app/components/dashboard.tpl.html
new file mode 100644
index 0000000..1127cb0
--- /dev/null
+++ b/ui/src/app/components/dashboard.tpl.html
@@ -0,0 +1,80 @@
+<!--
+
+    Copyright © 2016 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 flex layout="column" class="tb-progress-cover" layout-align="center center"
+   ng-show="(vm.loading() || vm.dashboardLoading) && !vm.isEdit">
+	<md-progress-circular md-mode="indeterminate" class="md-warn" md-diameter="100"></md-progress-circular>
+</md-content>
+<md-content id="gridster-parent" class="tb-dashboard-content" flex layout-wrap>
+	<div id="gridster-child" gridster="vm.gridsterOpts">
+		<ul>
+<!-- 			    			ng-click="widgetClicked($event, widget)"  --> 
+			<li gridster-item="widget" ng-repeat="widget in vm.widgets">
+			    <div tb-expand-fullscreen expand-button-id="expand-button" on-fullscreen-changed="vm.onWidgetFullscreenChanged(expanded, widget)" layout="column" class="tb-widget md-whiteframe-4dp"
+			    			ng-class="{'tb-highlighted': vm.isHighlighted(widget), 'tb-not-highlighted': vm.isNotHighlighted(widget)}"
+					 		tb-mousedown="vm.widgetMouseDown($event, widget)"
+					 		tb-mousemove="vm.widgetMouseMove($event, widget)"
+			    			tb-mouseup="vm.widgetMouseUp($event, widget)"
+			        style="
+			        cursor: pointer;
+			        color: {{vm.widgetColor(widget)}};
+			        background-color: {{vm.widgetBackgroundColor(widget)}};
+			        padding: {{vm.widgetPadding(widget)}}
+			        ">
+			    	<div class="tb-widget-title" layout="column" ng-show="vm.showWidgetTitle(widget) || vm.hasTimewindow(widget)">
+			    		<span ng-show="vm.showWidgetTitle(widget)" class="md-subhead">{{widget.config.title}}</span>
+				    	<tb-timewindow ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow>
+			    	</div>
+					<div class="tb-widget-actions" layout="row" layout-align="start center">
+						<md-button id="expand-button"
+								   aria-label="{{ 'fullscreen.fullscreen' | translate }}"
+								   class="md-icon-button md-primary"></md-button>
+						<md-button ng-show="vm.isEditActionEnabled && !vm.isWidgetExpanded"
+								   ng-disabled="vm.loading()"
+								   class="md-icon-button md-primary"
+								   ng-click="vm.editWidget($event, widget)"
+								   aria-label="{{ 'widget.edit' | translate }}">
+							<md-tooltip md-direction="top">
+								{{ 'widget.edit' | translate }}
+							</md-tooltip>
+							<md-icon class="material-icons">
+								edit
+							</md-icon>
+						</md-button>
+						<md-button ng-show="vm.isRemoveActionEnabled && !vm.isWidgetExpanded"
+				            ng-disabled="vm.loading()"
+				            class="md-icon-button md-primary"
+				            ng-click="vm.removeWidget($event, widget)"
+				            aria-label="{{ 'widget.remove' | translate }}">
+			                <md-tooltip md-direction="top">
+          						{{ 'widget.remove' | translate }}
+        					</md-tooltip>
+					        <md-icon class="material-icons">
+					          close
+					        </md-icon>		        					
+				        </md-button>	 						
+					</div>		    		
+			    	<div flex layout="column" class="tb-widget-content">
+			        	<div flex tb-widget
+			        		 locals="{ visibleRect: vm.visibleRect, widget: widget, deviceAliasList: vm.deviceAliasList, isPreview: vm.isEdit }">
+						</div>
+			    	</div>
+			    </div>
+			</li>
+		</ul>
+	</div>
+</md-content>
\ No newline at end of file
diff --git a/ui/src/app/components/dashboard-select.directive.js b/ui/src/app/components/dashboard-select.directive.js
new file mode 100644
index 0000000..8f007a7
--- /dev/null
+++ b/ui/src/app/components/dashboard-select.directive.js
@@ -0,0 +1,114 @@
+/*
+ * Copyright © 2016 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 './dashboard-select.scss';
+
+import thingsboardApiDashboard from '../api/dashboard.service';
+import thingsboardApiUser from '../api/user.service';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import dashboardSelectTemplate from './dashboard-select.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+
+export default angular.module('thingsboard.directives.dashboardSelect', [thingsboardApiDashboard, thingsboardApiUser])
+    .directive('tbDashboardSelect', DashboardSelect)
+    .name;
+
+/*@ngInject*/
+function DashboardSelect($compile, $templateCache, $q, dashboardService, userService) {
+
+    var linker = function (scope, element, attrs, ngModelCtrl) {
+        var template = $templateCache.get(dashboardSelectTemplate);
+        element.html(template);
+
+        scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
+        scope.dashboard = null;
+        scope.dashboardSearchText = '';
+
+        scope.dashboardFetchFunction = dashboardService.getTenantDashboards;
+        if (angular.isDefined(scope.dashboardsScope)) {
+            if (scope.dashboardsScope === 'customer') {
+                scope.dashboardFetchFunction = dashboardService.getCustomerDashboards;
+            } else {
+                scope.dashboardFetchFunction = dashboardService.getTenantDashboards;
+            }
+        } else {
+            if (userService.getAuthority() === 'TENANT_ADMIN') {
+                scope.dashboardFetchFunction = dashboardService.getTenantDashboards;
+            } else if (userService.getAuthority() === 'CUSTOMER_USER') {
+                scope.dashboardFetchFunction = dashboardService.getCustomerDashboards;
+            }
+        }
+
+        scope.fetchDashboards = function(searchText) {
+            var pageLink = {limit: 10, textSearch: searchText};
+
+            var deferred = $q.defer();
+
+            scope.dashboardFetchFunction(pageLink).then(function success(result) {
+                deferred.resolve(result.data);
+            }, function fail() {
+                deferred.reject();
+            });
+
+            return deferred.promise;
+        }
+
+        scope.dashboardSearchTextChanged = function() {
+        }
+
+        scope.updateView = function () {
+            ngModelCtrl.$setViewValue(scope.dashboard);
+        }
+
+        ngModelCtrl.$render = function () {
+            if (ngModelCtrl.$viewValue) {
+                scope.dashboard = ngModelCtrl.$viewValue;
+            }
+        }
+
+        scope.$watch('dashboard', function () {
+            scope.updateView();
+        });
+
+        if (scope.selectFirstDashboard) {
+            var pageLink = {limit: 1, textSearch: ''};
+            scope.dashboardFetchFunction(pageLink).then(function success(result) {
+                var dashboards = result.data;
+                if (dashboards.length > 0) {
+                    scope.dashboard = dashboards[0];
+                }
+            }, function fail() {
+            });
+        }
+
+        $compile(element.contents())(scope);
+    }
+
+    return {
+        restrict: "E",
+        require: "^ngModel",
+        link: linker,
+        scope: {
+            dashboardsScope: '@',
+            theForm: '=?',
+            tbRequired: '=?',
+            selectFirstDashboard: '='
+        }
+    };
+}
diff --git a/ui/src/app/components/dashboard-select.scss b/ui/src/app/components/dashboard-select.scss
new file mode 100644
index 0000000..63ad8ce
--- /dev/null
+++ b/ui/src/app/components/dashboard-select.scss
@@ -0,0 +1,30 @@
+/**
+ * Copyright © 2016 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-dashboard-autocomplete {
+  .tb-not-found {
+    display: block;
+    line-height: 1.5;
+    height: 48px;
+  }
+  .tb-dashboard-item {
+    display: block;
+    height: 48px;
+  }
+  li {
+    height: auto !important;
+    white-space: normal !important;
+  }
+}
diff --git a/ui/src/app/components/dashboard-select.tpl.html b/ui/src/app/components/dashboard-select.tpl.html
new file mode 100644
index 0000000..32eb12f
--- /dev/null
+++ b/ui/src/app/components/dashboard-select.tpl.html
@@ -0,0 +1,42 @@
+<!--
+
+    Copyright © 2016 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"
+                 md-input-name="dashboard"
+                 ng-model="dashboard"
+                 md-selected-item="dashboard"
+                 md-search-text="dashboardSearchText"
+                 md-search-text-change="dashboardSearchTextChanged()"
+                 md-items="item in fetchDashboards(dashboardSearchText)"
+                 md-item-text="item.title"
+                 md-min-length="0"
+                 placeholder="{{ 'dashboard.select-dashboard' | translate }}"
+                 md-menu-class="tb-dashboard-autocomplete">
+    <md-item-template>
+        <div class="tb-dashboard-item">
+            <span md-highlight-text="dashboardSearchText" md-highlight-flags="^i">{{item.title}}</span>
+        </div>
+    </md-item-template>
+    <md-not-found>
+        <div class="tb-not-found">
+            <span translate translate-values='{ dashboard: dashboardSearchText }'>dashboard.no-dashboards-matching</span>
+        </div>
+    </md-not-found>
+    <div ng-messages="theForm.dashboard.$error">
+        <div translate ng-message="required">dashboard.dashboard-required</div>
+    </div>
+</md-autocomplete>
diff --git a/ui/src/app/components/datakey-config.directive.js b/ui/src/app/components/datakey-config.directive.js
new file mode 100644
index 0000000..0ae91ae
--- /dev/null
+++ b/ui/src/app/components/datakey-config.directive.js
@@ -0,0 +1,139 @@
+/*
+ * Copyright © 2016 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 './datakey-config.scss';
+
+import thingsboardJsonForm from "./json-form.directive";
+import thingsboardTypes from '../common/types.constant';
+import thingsboardJsFunc from './js-func.directive';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import datakeyConfigTemplate from './datakey-config.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+export default angular.module('thingsboard.directives.datakeyConfig', [thingsboardTypes, thingsboardJsFunc, thingsboardJsonForm])
+    .directive('tbDatakeyConfig', DatakeyConfig)
+    .name;
+
+/*@ngInject*/
+function DatakeyConfig($compile, $templateCache, $q, types) {
+
+    var linker = function (scope, element, attrs, ngModelCtrl) {
+        var template = $templateCache.get(datakeyConfigTemplate);
+
+        if (scope.datakeySettingsSchema.schema) {
+            scope.dataKeySchema = scope.datakeySettingsSchema.schema;
+            scope.dataKeyForm = scope.datakeySettingsSchema.form || ['*'];
+            template = '<md-tabs md-border-bottom class="tb-datakey-config">\n' +
+                '<md-tab label="{{\'datakey.settings\' | translate}}">\n' +
+                template +
+                '</md-tab>\n' +
+                '<md-tab label="{{\'datakey.advanced\' | translate}}">\n' +
+                '<md-content class="md-padding" layout="column">\n' +
+                '<div style="overflow: auto;">\n' +
+                '<ng-form name="ngform" ' +
+                'layout="column" ' +
+                'layout-padding>' +
+                '<tb-json-form schema="dataKeySchema"' +
+                'form="dataKeyForm"' +
+                'model="model.settings"' +
+                'form-control="ngform">' +
+                '</tb-json-form>' +
+                '</ng-form>\n' +
+                '</div>\n' +
+                '</md-content>\n' +
+                '</md-tab>\n' +
+                '</md-tabs>';
+        }
+
+        element.html(template);
+
+        scope.types = types;
+        scope.selectedKey = null;
+        scope.keySearchText = null;
+        scope.usePostProcessing = false;
+
+        scope.functions = {};
+
+        ngModelCtrl.$render = function () {
+            scope.model = {};
+            if (ngModelCtrl.$viewValue) {
+                scope.model.type = ngModelCtrl.$viewValue.type;
+                scope.model.name = ngModelCtrl.$viewValue.name;
+                scope.model.label = ngModelCtrl.$viewValue.label;
+                scope.model.color = ngModelCtrl.$viewValue.color;
+                scope.model.funcBody = ngModelCtrl.$viewValue.funcBody;
+                scope.model.postFuncBody = ngModelCtrl.$viewValue.postFuncBody;
+                scope.model.usePostProcessing = scope.model.postFuncBody ? true : false;
+                scope.model.settings = ngModelCtrl.$viewValue.settings;
+            }
+        };
+
+        scope.$watch('model', function (newVal, oldVal) {
+            if (newVal.usePostProcessing != oldVal.usePostProcessing) {
+                if (scope.model.usePostProcessing && !scope.model.postFuncBody) {
+                    scope.model.postFuncBody = "return value;";
+                } else if (!scope.model.usePostProcessing && scope.model.postFuncBody) {
+                    delete scope.model.postFuncBody;
+                }
+            }
+            if (ngModelCtrl.$viewValue) {
+                var value = ngModelCtrl.$viewValue;
+                value.type = scope.model.type;
+                value.name = scope.model.name;
+                value.label = scope.model.label;
+                value.color = scope.model.color;
+                value.funcBody = scope.model.funcBody;
+                if (!scope.model.postFuncBody) {
+                    delete value.postFuncBody;
+                } else {
+                    value.postFuncBody = scope.model.postFuncBody;
+                }
+                ngModelCtrl.$setViewValue(value);
+            }
+        }, true);
+
+        scope.keysSearch = function (searchText) {
+            if (scope.deviceAlias) {
+                var deferred = $q.defer();
+                scope.fetchDeviceKeys({deviceAliasId: scope.deviceAlias.id, query: searchText, type: scope.model.type})
+                    .then(function (keys) {
+                        keys.push(searchText);
+                        deferred.resolve(keys);
+                    }, function (e) {
+                        deferred.reject(e);
+                    });
+                return deferred.promise;
+            } else {
+                return $q.when([]);
+            }
+        };
+
+        $compile(element.contents())(scope);
+    }
+
+    return {
+        restrict: 'E',
+        require: '^ngModel',
+        scope: {
+            deviceAlias: '=',
+            fetchDeviceKeys: '&',
+            datakeySettingsSchema: '='
+        },
+        link: linker
+    };
+}
\ No newline at end of file
diff --git a/ui/src/app/components/datakey-config.scss b/ui/src/app/components/datakey-config.scss
new file mode 100644
index 0000000..2fe24fe
--- /dev/null
+++ b/ui/src/app/components/datakey-config.scss
@@ -0,0 +1,28 @@
+/**
+ * Copyright © 2016 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-datakey-config {
+  min-width: 500px !important;
+  min-height: 500px !important;
+  md-content {
+    background-color: #fff;
+  }
+}
+
+tb-datakey-config {
+  md-content {
+    background-color: #fff;
+  }
+}
diff --git a/ui/src/app/components/datakey-config.tpl.html b/ui/src/app/components/datakey-config.tpl.html
new file mode 100644
index 0000000..eecaa5a
--- /dev/null
+++ b/ui/src/app/components/datakey-config.tpl.html
@@ -0,0 +1,71 @@
+<!--
+
+    Copyright © 2016 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 class="md-padding" layout="column">
+    <md-autocomplete ng-if="model.type === types.dataKeyType.timeseries || model.type === types.dataKeyType.attribute"
+    	   style="padding-bottom: 8px;"
+           ng-required="true"
+   		   md-no-cache="true"
+   		   id="key"
+   		   ng-model="model.name"
+		   md-selected-item="model.name"
+		   md-search-text="keySearchText"
+		   md-items="item in keysSearch(keySearchText)"
+	 	   md-item-text="item"
+		   md-min-length="0"
+           placeholder="Key name"
+           md-floating-label="Key">
+           <span md-highlight-text="keySearchText" md-highlight-flags="^i">{{item}}</span>
+    </md-autocomplete>
+	<div layout="row" layout-align="start center">     
+		<md-input-container flex class="md-block">
+			<label translate>datakey.label</label>
+			<input ng-required="true" name="label" ng-model="model.label">	
+		</md-input-container>
+		<div flex md-color-picker
+		    ng-required="true"
+		    ng-model="model.color"
+		    label="{{ 'datakey.color' | translate }}"
+		    icon="format_color_fill"
+		    default="#999"
+		    md-color-clear-button="false"
+		    open-on-input="true"
+		    md-color-generic-palette="false"
+		    md-color-history="false">
+		</div>    
+	</div>
+	<section layout="column" ng-if="model.type === types.dataKeyType.function">
+		<span translate>datakey.data-generation-func</span>
+		<br/>
+		<tb-js-func ng-model="model.funcBody"
+				 function-args="{{ ['time', 'prevValue'] }}"
+				 validation-args="{{ [1, 1] }}"
+				 result-type="any">
+		</tb-js-func>
+	</section>	
+	<section layout="column" ng-if="model.type === types.dataKeyType.timeseries || model.type === types.dataKeyType.attribute">
+        <md-checkbox ng-model="model.usePostProcessing" aria-label="{{ 'datakey.use-data-post-processing-func' | translate }}">
+			{{ 'datakey.use-data-post-processing-func' | translate }}
+        </md-checkbox>		
+		<tb-js-func ng-if="model.usePostProcessing"
+		         ng-model="model.postFuncBody"
+				 function-args="{{ ['time', 'value', 'prevValue'] }}"
+				 validation-args="{{ [1, 1, 1] }}"
+				 result-type="any">
+		</tb-js-func>
+	</section>
+</md-content>
\ No newline at end of file
diff --git a/ui/src/app/components/datakey-config-dialog.controller.js b/ui/src/app/components/datakey-config-dialog.controller.js
new file mode 100644
index 0000000..a041ed9
--- /dev/null
+++ b/ui/src/app/components/datakey-config-dialog.controller.js
@@ -0,0 +1,56 @@
+/*
+ * Copyright © 2016 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 thingsboardDatakeyConfig from './datakey-config.directive';
+
+export default angular.module('thingsboard.dialogs.datakeyConfigDialog', [thingsboardDatakeyConfig])
+    .controller('DatakeyConfigDialogController', DatakeyConfigDialogController)
+    .name;
+
+/*@ngInject*/
+function DatakeyConfigDialogController($scope, $mdDialog, deviceService, dataKey, dataKeySettingsSchema, deviceAlias, deviceAliases) {
+
+    var vm = this;
+
+    vm.dataKey = dataKey;
+    vm.dataKeySettingsSchema = dataKeySettingsSchema;
+    vm.deviceAlias = deviceAlias;
+    vm.deviceAliases = deviceAliases;
+
+    vm.hide = function () {
+        $mdDialog.hide();
+    };
+
+    vm.cancel = function () {
+        $mdDialog.cancel();
+    };
+
+    vm.fetchDeviceKeys = function (deviceAliasId, query, type) {
+        var deviceId = vm.deviceAliases[deviceAliasId];
+        if (deviceId) {
+            return deviceService.getDeviceKeys(deviceId, query, type);
+        } else {
+            return [];
+        }
+    };
+
+    vm.save = function () {
+        $scope.$broadcast('form-submit');
+        if ($scope.theForm.$valid) {
+            $scope.theForm.$setPristine();
+            $mdDialog.hide(vm.dataKey);
+        }
+    };
+}
\ No newline at end of file
diff --git a/ui/src/app/components/datakey-config-dialog.tpl.html b/ui/src/app/components/datakey-config-dialog.tpl.html
new file mode 100644
index 0000000..89d04c5
--- /dev/null
+++ b/ui/src/app/components/datakey-config-dialog.tpl.html
@@ -0,0 +1,46 @@
+<!--
+
+    Copyright © 2016 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="{{ 'datakey.configuration' | translate }}" style="width: 600px;">
+	<form name="theForm" ng-submit="vm.save()">
+		<md-toolbar>
+	      <div class="md-toolbar-tools">
+	        <h2 translate>datakey.configuration</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-show="loading"></md-progress-linear>
+  	    <span style="min-height: 5px;" flex="" ng-show="!loading"></span>	    
+	    <md-dialog-content>
+			<tb-datakey-config ng-model="vm.dataKey"
+                               fetch-device-keys="vm.fetchDeviceKeys(deviceAliasId, query, type)"
+                               device-alias="vm.deviceAlias"
+                               datakey-settings-schema="vm.dataKeySettingsSchema">
+			</tb-datakey-config>
+	    </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">
+		  		{{ '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>
diff --git a/ui/src/app/components/datasource.directive.js b/ui/src/app/components/datasource.directive.js
new file mode 100644
index 0000000..0a6fd2c
--- /dev/null
+++ b/ui/src/app/components/datasource.directive.js
@@ -0,0 +1,89 @@
+/*
+ * Copyright © 2016 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 './datasource.scss';
+
+import thingsboardTypes from '../common/types.constant';
+import thingsboardDatasourceFunc from './datasource-func.directive'
+import thingsboardDatasourceDevice from './datasource-device.directive';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import datasourceTemplate from './datasource.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+export default angular.module('thingsboard.directives.datasource', [thingsboardTypes, thingsboardDatasourceFunc, thingsboardDatasourceDevice])
+    .directive('tbDatasource', Datasource)
+    .name;
+
+/*@ngInject*/
+function Datasource($compile, $templateCache, types) {
+
+    var linker = function (scope, element, attrs, ngModelCtrl) {
+
+        var template = $templateCache.get(datasourceTemplate);
+        element.html(template);
+
+        scope.types = types;
+
+        if (scope.functionsOnly) {
+            scope.datasourceTypes = [types.datasourceType.function];
+        } else{
+            scope.datasourceTypes = [types.datasourceType.device, types.datasourceType.function];
+        }
+
+        scope.updateView = function () {
+            if (!scope.model.dataKeys) {
+                scope.model.dataKeys = [];
+            }
+            ngModelCtrl.$setViewValue(scope.model);
+        }
+
+        scope.$watch('model.type', function (newType, prevType) {
+            if (newType != prevType) {
+                scope.model.dataKeys = [];
+            }
+        });
+
+        scope.$watch('model', function () {
+            scope.updateView();
+        }, true);
+
+        ngModelCtrl.$render = function () {
+            scope.model = {};
+            if (ngModelCtrl.$viewValue) {
+                scope.model = ngModelCtrl.$viewValue;
+            }
+        };
+
+        $compile(element.contents())(scope);
+    }
+
+    return {
+        restrict: "E",
+        require: "^ngModel",
+        scope: {
+            deviceAliases: '=',
+            widgetType: '=',
+            functionsOnly: '=',
+            datakeySettingsSchema: '=',
+            generateDataKey: '&',
+            fetchDeviceKeys: '&',
+            onCreateDeviceAlias: '&'
+        },
+        link: linker
+    };
+}
diff --git a/ui/src/app/components/datasource.scss b/ui/src/app/components/datasource.scss
new file mode 100644
index 0000000..0b22134
--- /dev/null
+++ b/ui/src/app/components/datasource.scss
@@ -0,0 +1,54 @@
+/**
+ * Copyright © 2016 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-datasource {
+   #device-autocomplete {
+     height: 30px;
+     margin-top: 18px;
+     md-autocomplete-wrap {
+       height: 30px;
+     }
+     input, input:not(.md-input) {
+       height: 30px;
+     }
+   }
+   #datasourceType {
+  }
+}
+
+@mixin tb-checkered-bg() {
+  background-color: #fff;
+  background-image: linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd),
+  linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd);
+  background-size: 8px 8px;
+  background-position: 0 0, 4px 4px;
+}
+
+.tb-color-preview {
+  content: '';
+  width: 24px;
+  height: 24px;
+  border: 2px solid #fff;
+  border-radius: 50%;
+  box-shadow: 0 3px 1px -2px rgba(0, 0, 0, .14), 0 2px 2px 0 rgba(0, 0, 0, .098), 0 1px 5px 0 rgba(0, 0, 0, .084);
+  position: relative;
+  overflow: hidden;
+  @include tb-checkered-bg();
+
+  .tb-color-result {
+    width: 100%;
+    height: 100%;
+  }
+}
diff --git a/ui/src/app/components/datasource.tpl.html b/ui/src/app/components/datasource.tpl.html
new file mode 100644
index 0000000..e698cc3
--- /dev/null
+++ b/ui/src/app/components/datasource.tpl.html
@@ -0,0 +1,46 @@
+<!--
+
+    Copyright © 2016 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<section flex layout='row' layout-align="start center" class="tb-datasource">
+    <md-input-container style="min-width: 110px;">
+        <md-select placeholder="{{ 'datasource.type' | translate }}" required id="datasourceType" ng-model="model.type">
+            <md-option ng-repeat="datasourceType in datasourceTypes" value="{{datasourceType}}">
+                {{ datasourceType | translate }}
+            </md-option>
+        </md-select>
+    </md-input-container>
+    <section flex layout='row' layout-align="start center" class="datasource" ng-switch on="model.type">
+        <tb-datasource-func flex style="padding-left: 8px;"
+                            ng-switch-default
+                            ng-model="model"
+                            datakey-settings-schema="datakeySettingsSchema"
+                            ng-required="model.type === types.datasourceType.function"
+                            generate-data-key="generateDataKey({chip: chip, type: type})">
+        </tb-datasource-func>
+        <tb-datasource-device flex style="padding-left: 4px; padding-right: 4px;"
+                              ng-model="model"
+                              datakey-settings-schema="datakeySettingsSchema"
+                              ng-switch-when="device"
+                              ng-required="model.type === types.datasourceType.device"
+                              widget-type="widgetType"
+                              device-aliases="deviceAliases"
+                              generate-data-key="generateDataKey({chip: chip, type: type})"
+                              fetch-device-keys="fetchDeviceKeys({deviceAliasId: deviceAliasId, query: query, type: type})"
+                              on-create-device-alias="onCreateDeviceAlias({event: event, alias: alias})">
+        </tb-datasource-device>
+    </section>
+</section>
diff --git a/ui/src/app/components/datasource-device.directive.js b/ui/src/app/components/datasource-device.directive.js
new file mode 100644
index 0000000..889a886
--- /dev/null
+++ b/ui/src/app/components/datasource-device.directive.js
@@ -0,0 +1,248 @@
+/*
+ * Copyright © 2016 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 './datasource-device.scss';
+
+import 'md-color-picker';
+import tinycolor from 'tinycolor2';
+import $ from 'jquery';
+import thingsboardTypes from '../common/types.constant';
+import thingsboardDatakeyConfigDialog from './datakey-config-dialog.controller';
+import thingsboardTruncate from './truncate.filter';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import datasourceDeviceTemplate from './datasource-device.tpl.html';
+import datakeyConfigDialogTemplate from './datakey-config-dialog.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/* eslint-disable angular/angularelement */
+
+export default angular.module('thingsboard.directives.datasourceDevice', [thingsboardTruncate, thingsboardTypes, thingsboardDatakeyConfigDialog])
+    .directive('tbDatasourceDevice', DatasourceDevice)
+    .name;
+
+/*@ngInject*/
+function DatasourceDevice($compile, $templateCache, $q, $mdDialog, $window, $document, $mdColorPicker, $mdConstant, types) {
+
+    var linker = function (scope, element, attrs, ngModelCtrl) {
+        var template = $templateCache.get(datasourceDeviceTemplate);
+        element.html(template);
+
+        scope.ngModelCtrl = ngModelCtrl;
+        scope.types = types;
+
+        scope.selectedTimeseriesDataKey = null;
+        scope.timeseriesDataKeySearchText = null;
+
+        scope.selectedAttributeDataKey = null;
+        scope.attributeDataKeySearchText = null;
+
+        scope.updateValidity = function () {
+            if (ngModelCtrl.$viewValue) {
+                var value = ngModelCtrl.$viewValue;
+                var dataValid = angular.isDefined(value) && value != null;
+                ngModelCtrl.$setValidity('deviceData', dataValid);
+                if (dataValid) {
+                    ngModelCtrl.$setValidity('deviceAlias',
+                        angular.isDefined(value.deviceAliasId) &&
+                        value.deviceAliasId != null);
+                    ngModelCtrl.$setValidity('deviceKeys',
+                        angular.isDefined(value.dataKeys) &&
+                        value.dataKeys != null &&
+                        value.dataKeys.length > 0);
+                }
+            }
+        };
+
+        scope.$watch('deviceAlias', function () {
+            if (ngModelCtrl.$viewValue) {
+                if (scope.deviceAlias) {
+                    ngModelCtrl.$viewValue.deviceAliasId = scope.deviceAlias.id;
+                } else {
+                    ngModelCtrl.$viewValue.deviceAliasId = null;
+                }
+                scope.updateValidity();
+                scope.selectedDeviceAliasChange();
+            }
+        });
+
+        scope.$watch('timeseriesDataKeys', function () {
+            if (ngModelCtrl.$viewValue) {
+                var dataKeys = [];
+                dataKeys = dataKeys.concat(scope.timeseriesDataKeys);
+                dataKeys = dataKeys.concat(scope.attributeDataKeys);
+                ngModelCtrl.$viewValue.dataKeys = dataKeys;
+                scope.updateValidity();
+            }
+        }, true);
+
+        scope.$watch('attributeDataKeys', function () {
+            if (ngModelCtrl.$viewValue) {
+                var dataKeys = [];
+                dataKeys = dataKeys.concat(scope.timeseriesDataKeys);
+                dataKeys = dataKeys.concat(scope.attributeDataKeys);
+                ngModelCtrl.$viewValue.dataKeys = dataKeys;
+                scope.updateValidity();
+            }
+        }, true);
+
+        ngModelCtrl.$render = function () {
+            if (ngModelCtrl.$viewValue) {
+                var deviceAliasId = ngModelCtrl.$viewValue.deviceAliasId;
+                if (scope.deviceAliases[deviceAliasId]) {
+                    scope.deviceAlias = {id: deviceAliasId, alias: scope.deviceAliases[deviceAliasId].alias,
+                        deviceId: scope.deviceAliases[deviceAliasId].deviceId};
+                } else {
+                    scope.deviceAlias = null;
+                }
+                var timeseriesDataKeys = [];
+                var attributeDataKeys = [];
+                for (var d in ngModelCtrl.$viewValue.dataKeys) {
+                    var dataKey = ngModelCtrl.$viewValue.dataKeys[d];
+                    if (dataKey.type === types.dataKeyType.timeseries) {
+                        timeseriesDataKeys.push(dataKey);
+                    } else if (dataKey.type === types.dataKeyType.attribute) {
+                        attributeDataKeys.push(dataKey);
+                    }
+                }
+                scope.timeseriesDataKeys = timeseriesDataKeys;
+                scope.attributeDataKeys = attributeDataKeys;
+            }
+        };
+
+        scope.textIsNotEmpty = function(text) {
+            return (text && text != null && text.length > 0) ? true : false;
+        }
+
+        scope.selectedDeviceAliasChange = function () {
+            if (!scope.timeseriesDataKeySearchText || scope.timeseriesDataKeySearchText === '') {
+                scope.timeseriesDataKeySearchText = scope.timeseriesDataKeySearchText === '' ? null : '';
+            }
+            if (!scope.attributeDataKeySearchText || scope.attributeDataKeySearchText === '') {
+                scope.attributeDataKeySearchText = scope.attributeDataKeySearchText === '' ? null : '';
+            }
+        };
+
+        scope.transformTimeseriesDataKeyChip = function (chip) {
+            return scope.generateDataKey({chip: chip, type: types.dataKeyType.timeseries});
+        };
+
+        scope.transformAttributeDataKeyChip = function (chip) {
+            return scope.generateDataKey({chip: chip, type: types.dataKeyType.attribute});
+        };
+
+        scope.showColorPicker = function (event, dataKey) {
+            $mdColorPicker.show({
+                value: dataKey.color,
+                defaultValue: '#fff',
+                random: tinycolor.random(),
+                clickOutsideToClose: false,
+                hasBackdrop: false,
+                skipHide: true,
+                preserveScope: false,
+
+                mdColorAlphaChannel: true,
+                mdColorSpectrum: true,
+                mdColorSliders: true,
+                mdColorGenericPalette: false,
+                mdColorMaterialPalette: true,
+                mdColorHistory: false,
+                mdColorDefaultTab: 2,
+
+                $event: event
+
+            }).then(function (color) {
+                dataKey.color = color;
+                ngModelCtrl.$setDirty();
+            });
+        }
+
+        scope.editDataKey = function (event, dataKey, index) {
+
+            $mdDialog.show({
+                controller: 'DatakeyConfigDialogController',
+                controllerAs: 'vm',
+                templateUrl: datakeyConfigDialogTemplate,
+                locals: {
+                    dataKey: angular.copy(dataKey),
+                    dataKeySettingsSchema: scope.datakeySettingsSchema,
+                    deviceAlias: scope.deviceAlias,
+                    deviceAliases: scope.deviceAliases
+                },
+                parent: angular.element($document[0].body),
+                fullscreen: true,
+                targetEvent: event,
+                skipHide: true,
+                onComplete: function () {
+                    var w = angular.element($window);
+                    w.triggerHandler('resize');
+                }
+            }).then(function (dataKey) {
+                if (dataKey.type === types.dataKeyType.timeseries) {
+                    scope.timeseriesDataKeys[index] = dataKey;
+                } else if (dataKey.type === types.dataKeyType.attribute) {
+                    scope.attributeDataKeys[index] = dataKey;
+                }
+                ngModelCtrl.$setDirty();
+            }, function () {
+            });
+        };
+
+        scope.dataKeysSearch = function (searchText, type) {
+            if (scope.deviceAlias) {
+                var deferred = $q.defer();
+                scope.fetchDeviceKeys({deviceAliasId: scope.deviceAlias.id, query: searchText, type: type})
+                    .then(function (dataKeys) {
+                        deferred.resolve(dataKeys);
+                    }, function (e) {
+                        deferred.reject(e);
+                    });
+                return deferred.promise;
+            } else {
+                return $q.when([]);
+            }
+        };
+
+        scope.createKey = function (event, chipsId) {
+            var chipsChild = $(chipsId, element)[0].firstElementChild;
+            var el = angular.element(chipsChild);
+            var chipBuffer = el.scope().$mdChipsCtrl.getChipBuffer();
+            event.preventDefault();
+            event.stopPropagation();
+            el.scope().$mdChipsCtrl.appendChip(chipBuffer.trim());
+            el.scope().$mdChipsCtrl.resetChipBuffer();
+        }
+
+        $compile(element.contents())(scope);
+    }
+
+    return {
+        restrict: "E",
+        require: "^ngModel",
+        scope: {
+            widgetType: '=',
+            deviceAliases: '=',
+            datakeySettingsSchema: '=',
+            generateDataKey: '&',
+            fetchDeviceKeys: '&',
+            onCreateDeviceAlias: '&'
+        },
+        link: linker
+    };
+}
+
+/* eslint-enable angular/angularelement */
diff --git a/ui/src/app/components/datasource-device.scss b/ui/src/app/components/datasource-device.scss
new file mode 100644
index 0000000..5e01d1f
--- /dev/null
+++ b/ui/src/app/components/datasource-device.scss
@@ -0,0 +1,29 @@
+/**
+ * Copyright © 2016 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-device-alias-autocomplete, .tb-timeseries-datakey-autocomplete, .tb-attribute-datakey-autocomplete {
+  .tb-not-found {
+    display: block;
+    line-height: 1.5;
+    height: 48px;
+    .tb-no-entries {
+      line-height: 48px;
+    }
+  }
+  li {
+    height: auto !important;
+    white-space: normal !important;
+  }
+}
diff --git a/ui/src/app/components/datasource-device.tpl.html b/ui/src/app/components/datasource-device.tpl.html
new file mode 100644
index 0000000..c9f4b10
--- /dev/null
+++ b/ui/src/app/components/datasource-device.tpl.html
@@ -0,0 +1,127 @@
+<!--
+
+    Copyright © 2016 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<section flex layout='row' layout-align="start center">
+	   <tb-device-alias-select flex="40"
+							  tb-required="true"
+							  device-aliases="deviceAliases"
+							  ng-model="deviceAlias"
+							  on-create-device-alias="onCreateDeviceAlias({event: event, alias: alias})">
+	   </tb-device-alias-select>
+	   <section flex="120" layout='column'>
+		   <section flex layout='row' layout-align="start center">
+			   <md-chips flex style="padding-left: 4px;"
+						 id="timeseries_datakey_chips"
+						 ng-required="true"
+						 ng-model="timeseriesDataKeys" md-autocomplete-snap
+						 md-transform-chip="transformTimeseriesDataKeyChip($chip)"
+						 md-require-match="false">
+					  <md-autocomplete
+							md-no-cache="true"
+							id="timeseries_datakey"
+							md-selected-item="selectedTimeseriesDataKey"
+							md-search-text="timeseriesDataKeySearchText"
+							md-items="item in dataKeysSearch(timeseriesDataKeySearchText, types.dataKeyType.timeseries)"
+							md-item-text="item.name"
+							md-min-length="0"
+							placeholder="{{'datakey.timeseries' | translate }}"
+							md-menu-class="tb-timeseries-datakey-autocomplete">
+							<span md-highlight-text="timeseriesDataKeySearchText" md-highlight-flags="^i">{{item}}</span>
+							<md-not-found>
+							  <div class="tb-not-found">
+								  <div class="tb-no-entries" ng-if="!textIsNotEmpty(timeseriesDataKeySearchText)">
+									  <span translate>device.no-keys-found</span>
+								  </div>
+								  <div ng-if="textIsNotEmpty(timeseriesDataKeySearchText)">
+									  <span translate translate-values='{ key: "{{timeseriesDataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>device.no-key-matching</span>
+									  <span>
+											<a translate ng-click="createKey($event, '#timeseries_datakey_chips')">device.create-new-key</a>
+									  </span>
+								  </div>
+							  </div>
+							</md-not-found>
+					  </md-autocomplete>
+					  <md-chip-template>
+						<div layout="row" layout-align="start center">
+							<div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
+								<div class="tb-color-result" ng-style="{background: $chip.color}"></div>
+							</div>
+							<div>
+							  {{$chip.label}}:
+							  <strong ng-if="!$chip.postFuncBody">{{$chip.name}}</strong>
+							  <strong ng-if="$chip.postFuncBody">f({{$chip.name}})</strong>
+							</div>
+							<md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">
+								<md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>
+							</md-button>
+						</div>
+					  </md-chip-template>
+			   </md-chips>
+			   <md-chips flex ng-if="widgetType === types.widgetType.latest.value" style="padding-left: 4px;"
+                         id="attribute_datakey_chips"
+                         ng-required="true"
+                         ng-model="attributeDataKeys" md-autocomplete-snap
+                         md-transform-chip="transformAttributeDataKeyChip($chip)"
+                         md-require-match="false">
+					  <md-autocomplete
+							md-no-cache="true"
+							id="attribute_datakey"
+							md-selected-item="selectedAttributeDataKey"
+							md-search-text="attributeDataKeySearchText"
+							md-items="item in dataKeysSearch(attributeDataKeySearchText, types.dataKeyType.attribute)"
+							md-item-text="item.name"
+							md-min-length="0"
+							placeholder="{{'datakey.attributes' | translate }}"
+							md-menu-class="tb-attribute-datakey-autocomplete">
+							<span md-highlight-text="attributeDataKeySearchText" md-highlight-flags="^i">{{item}}</span>
+						    <md-not-found>
+							  <div class="tb-not-found">
+								  <div class="tb-no-entries" ng-if="!textIsNotEmpty(attributeDataKeySearchText)">
+									  <span translate>device.no-keys-found</span>
+								  </div>
+								  <div ng-if="textIsNotEmpty(attributeDataKeySearchText)">
+									  <span translate translate-values='{ key: "{{attributeDataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>device.no-key-matching</span>
+									  <span>
+											<a translate ng-click="createKey($event, '#attribute_datakey_chips')">device.create-new-key</a>
+									  </span>
+								  </div>
+							  </div>
+						    </md-not-found>
+					  </md-autocomplete>
+					  <md-chip-template>
+						<div layout="row" layout-align="start center">
+							<div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
+								<div class="tb-color-result" ng-style="{background: $chip.color}"></div>
+							</div>
+							<div>
+							  {{$chip.label}}:
+							  <strong ng-if="!$chip.postFuncBody">{{$chip.name}}</strong>
+							  <strong ng-if="$chip.postFuncBody">f({{$chip.name}})</strong>
+							</div>
+							<md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">
+								<md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>
+							</md-button>
+						</div>
+					  </md-chip-template>
+			   </md-chips>
+		   </section>
+		   <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">
+			    <div translate ng-message="deviceKeys" ng-if="widgetType === types.widgetType.timeseries.value" class="tb-error-message">datakey.timeseries-required</div>
+				<div translate ng-message="deviceKeys" ng-if="widgetType === types.widgetType.latest.value" class="tb-error-message">datakey.timeseries-or-attributes-required</div>
+			</div>
+	   </section>
+</section>
diff --git a/ui/src/app/components/datasource-func.directive.js b/ui/src/app/components/datasource-func.directive.js
new file mode 100644
index 0000000..37f48d3
--- /dev/null
+++ b/ui/src/app/components/datasource-func.directive.js
@@ -0,0 +1,182 @@
+/*
+ * Copyright © 2016 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 './datasource-func.scss';
+
+import 'md-color-picker';
+import tinycolor from 'tinycolor2';
+import $ from 'jquery';
+import thingsboardTypes from '../common/types.constant';
+import thingsboardUtils from '../common/utils.service';
+import thingsboardDatakeyConfigDialog from './datakey-config-dialog.controller';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import datasourceFuncTemplate from './datasource-func.tpl.html';
+import datakeyConfigDialogTemplate from './datakey-config-dialog.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/* eslint-disable angular/angularelement */
+
+export default angular.module('thingsboard.directives.datasourceFunc', [thingsboardTypes, thingsboardUtils, thingsboardDatakeyConfigDialog])
+    .directive('tbDatasourceFunc', DatasourceFunc)
+    .name;
+
+/*@ngInject*/
+function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, $mdColorPicker, types, utils) {
+
+    var linker = function (scope, element, attrs, ngModelCtrl) {
+        var template = $templateCache.get(datasourceFuncTemplate);
+        element.html(template);
+
+        scope.ngModelCtrl = ngModelCtrl;
+        scope.functionTypes = utils.getPredefinedFunctionsList();
+
+        scope.selectedDataKey = null;
+        scope.dataKeySearchText = null;
+
+        scope.updateValidity = function () {
+            if (ngModelCtrl.$viewValue) {
+                var value = ngModelCtrl.$viewValue;
+                var dataValid = angular.isDefined(value) && value != null;
+                ngModelCtrl.$setValidity('deviceData', dataValid);
+                if (dataValid) {
+                    ngModelCtrl.$setValidity('funcTypes',
+                        angular.isDefined(value.dataKeys) &&
+                        value.dataKeys != null &&
+                        value.dataKeys.length > 0);
+                }
+            }
+        };
+
+        scope.$watch('funcDataKeys', function () {
+            if (ngModelCtrl.$viewValue) {
+                var dataKeys = [];
+                dataKeys = dataKeys.concat(scope.funcDataKeys);
+                ngModelCtrl.$viewValue.dataKeys = dataKeys;
+                scope.updateValidity();
+            }
+        }, true);
+
+        ngModelCtrl.$render = function () {
+            if (ngModelCtrl.$viewValue) {
+                var funcDataKeys = [];
+                if (ngModelCtrl.$viewValue.dataKeys) {
+                    funcDataKeys = funcDataKeys.concat(ngModelCtrl.$viewValue.dataKeys);
+                }
+                scope.funcDataKeys = funcDataKeys;
+            }
+        };
+
+        scope.transformDataKeyChip = function (chip) {
+            return scope.generateDataKey({chip: chip, type: types.dataKeyType.function});
+        };
+
+        scope.showColorPicker = function (event, dataKey) {
+            $mdColorPicker.show({
+                value: dataKey.color,
+                defaultValue: '#fff',
+                random: tinycolor.random(),
+                clickOutsideToClose: false,
+                hasBackdrop: false,
+                skipHide: true,
+                preserveScope: false,
+
+                mdColorAlphaChannel: true,
+                mdColorSpectrum: true,
+                mdColorSliders: true,
+                mdColorGenericPalette: false,
+                mdColorMaterialPalette: true,
+                mdColorHistory: false,
+                mdColorDefaultTab: 2,
+
+                $event: event
+
+            }).then(function (color) {
+                dataKey.color = color;
+                ngModelCtrl.$setDirty();
+            });
+        }
+
+        scope.editDataKey = function (event, dataKey, index) {
+
+            $mdDialog.show({
+                controller: 'DatakeyConfigDialogController',
+                controllerAs: 'vm',
+                templateUrl: datakeyConfigDialogTemplate,
+                locals: {
+                    dataKey: angular.copy(dataKey),
+                    dataKeySettingsSchema: scope.datakeySettingsSchema,
+                    deviceAlias: null,
+                    deviceAliases: null
+                },
+                parent: angular.element($document[0].body),
+                fullscreen: true,
+                targetEvent: event,
+                skipHide: true,
+                onComplete: function () {
+                    var w = angular.element($window);
+                    w.triggerHandler('resize');
+                }
+            }).then(function (dataKey) {
+                scope.funcDataKeys[index] = dataKey;
+                ngModelCtrl.$setDirty();
+            }, function () {
+            });
+        };
+
+        scope.textIsNotEmpty = function(text) {
+            return (text && text != null && text.length > 0) ? true : false;
+        }
+
+        scope.dataKeysSearch = function (dataKeySearchText) {
+            var dataKeys = dataKeySearchText ? scope.functionTypes.filter(
+                scope.createFilterForDataKey(dataKeySearchText)) : scope.functionTypes;
+            return dataKeys;
+        };
+
+        scope.createFilterForDataKey = function (query) {
+            var lowercaseQuery = angular.lowercase(query);
+            return function filterFn(dataKey) {
+                return (angular.lowercase(dataKey).indexOf(lowercaseQuery) === 0);
+            };
+        };
+
+        scope.createKey = function (event, chipsId) {
+            var chipsChild = $(chipsId, element)[0].firstElementChild;
+            var el = angular.element(chipsChild);
+            var chipBuffer = el.scope().$mdChipsCtrl.getChipBuffer();
+            event.preventDefault();
+            event.stopPropagation();
+            el.scope().$mdChipsCtrl.appendChip(chipBuffer.trim());
+            el.scope().$mdChipsCtrl.resetChipBuffer();
+        }
+
+        $compile(element.contents())(scope);
+    }
+
+    return {
+        restrict: "E",
+        require: "^ngModel",
+        scope: {
+            generateDataKey: '&',
+            datakeySettingsSchema: '='
+        },
+        link: linker
+    };
+}
+
+/* eslint-enable angular/angularelement */
diff --git a/ui/src/app/components/datasource-func.scss b/ui/src/app/components/datasource-func.scss
new file mode 100644
index 0000000..08dbd4e
--- /dev/null
+++ b/ui/src/app/components/datasource-func.scss
@@ -0,0 +1,29 @@
+/**
+ * Copyright © 2016 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-func-datakey-autocomplete {
+  .tb-not-found {
+    display: block;
+    line-height: 1.5;
+    height: 48px;
+    .tb-no-entries {
+      line-height: 48px;
+    }
+  }
+  li {
+    height: auto !important;
+    white-space: normal !important;
+  }
+}
diff --git a/ui/src/app/components/datasource-func.tpl.html b/ui/src/app/components/datasource-func.tpl.html
new file mode 100644
index 0000000..2b509f5
--- /dev/null
+++ b/ui/src/app/components/datasource-func.tpl.html
@@ -0,0 +1,68 @@
+<!--
+
+    Copyright © 2016 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<section flex layout='column'>
+   <md-chips flex style="padding-left: 4px;"
+			     id="function_datakey_chips"
+	   			 ng-required="true"
+	             ng-model="funcDataKeys" md-autocomplete-snap
+	             md-transform-chip="transformDataKeyChip($chip)"
+	             md-require-match="false">    
+		      <md-autocomplete 
+		    		md-no-cache="false"
+		    		id="dataKey"
+					md-selected-item="selectedDataKey"
+					md-search-text="dataKeySearchText"
+					md-items="item in dataKeysSearch(dataKeySearchText)"
+					md-item-text="item.name"
+					md-min-length="0"
+		            placeholder="{{ 'datakey.function-types' | translate }}"
+					md-menu-class="tb-func-datakey-autocomplete">
+		            <span md-highlight-text="dataKeySearchText" md-highlight-flags="^i">{{item}}</span>
+				    <md-not-found>
+						<div class="tb-not-found">
+							<div class="tb-no-entries" ng-if="!textIsNotEmpty(dataKeySearchText)">
+								<span translate>device.no-keys-found</span>
+							</div>
+							<div ng-if="textIsNotEmpty(dataKeySearchText)">
+								<span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>device.no-key-matching</span>
+								<span>
+		          					<a translate ng-click="createKey($event, '#function_datakey_chips')">device.create-new-key</a>
+						  		</span>
+							</div>
+						</div>
+				    </md-not-found>
+			  </md-autocomplete>
+		      <md-chip-template>
+		      	<div layout="row" layout-align="start center">
+					<div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
+						<div class="tb-color-result" ng-style="{background: $chip.color}"></div>
+					</div>
+			        <div>
+			          {{$chip.label}}: 
+			          <strong>{{$chip.name}}</strong>
+			        </div>
+				    <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">
+		        		<md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>
+		      		</md-button>
+		      	</div>
+		      </md-chip-template>
+	</md-chips>
+	<div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">
+		<div translate ng-message="funcTypes" class="tb-error-message">datakey.function-types-required</div>
+	</div>
+</section>
diff --git a/ui/src/app/components/datetime-period.directive.js b/ui/src/app/components/datetime-period.directive.js
new file mode 100644
index 0000000..00c6cf0
--- /dev/null
+++ b/ui/src/app/components/datetime-period.directive.js
@@ -0,0 +1,108 @@
+/*
+ * Copyright © 2016 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 './datetime-period.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import datetimePeriodTemplate from './datetime-period.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+export default angular.module('thingsboard.directives.datetimePeriod', [])
+    .directive('tbDatetimePeriod', DatetimePeriod)
+    .name;
+
+/*@ngInject*/
+function DatetimePeriod($compile, $templateCache) {
+
+    var linker = function (scope, element, attrs, ngModelCtrl) {
+
+        var template = $templateCache.get(datetimePeriodTemplate);
+        element.html(template);
+
+        ngModelCtrl.$render = function () {
+            var date = new Date();
+            scope.startDate = new Date(
+                date.getFullYear(),
+                date.getMonth(),
+                date.getDate() - 1);
+            scope.endDate = date;
+            if (ngModelCtrl.$viewValue) {
+                var value = ngModelCtrl.$viewValue;
+                scope.startDate = new Date(value.startTimeMs);
+                scope.endDate = new Date(value.endTimeMs);
+            }
+        }
+
+        scope.updateMinMaxDates = function () {
+            scope.maxStartDate = angular.copy(new Date(scope.endDate.getTime() - 1000));
+            scope.minEndDate = angular.copy(new Date(scope.startDate.getTime() + 1000));
+            scope.maxEndDate = new Date();
+        }
+
+        scope.updateView = function () {
+            var value = null;
+            if (scope.startDate && scope.endDate) {
+                value = {
+                    startTimeMs: scope.startDate.getTime(),
+                    endTimeMs: scope.endDate.getTime()
+                };
+                ngModelCtrl.$setValidity('datetimePeriod', true);
+            } else {
+                ngModelCtrl.$setValidity('datetimePeriod', !scope.required);
+            }
+            ngModelCtrl.$setViewValue(value);
+        }
+
+        scope.$watch('required', function () {
+            scope.updateView();
+        });
+
+        scope.$watch('startDate', function (newDate) {
+            if (newDate) {
+                if (newDate.getTime() > scope.maxStartDate) {
+                    scope.startDate = angular.copy(scope.maxStartDate);
+                }
+                scope.updateMinMaxDates();
+            }
+            scope.updateView();
+        });
+
+        scope.$watch('endDate', function (newDate) {
+            if (newDate) {
+                if (newDate.getTime() < scope.minEndDate) {
+                    scope.endDate = angular.copy(scope.minEndDate);
+                } else if (newDate.getTime() > scope.maxEndDate) {
+                    scope.endDate = angular.copy(scope.maxEndDate);
+                }
+                scope.updateMinMaxDates();
+            }
+            scope.updateView();
+        });
+
+        $compile(element.contents())(scope);
+
+    }
+
+    return {
+        restrict: "E",
+        require: "^ngModel",
+        scope: {
+            required: '=ngRequired'
+        },
+        link: linker
+    };
+}
diff --git a/ui/src/app/components/datetime-period.scss b/ui/src/app/components/datetime-period.scss
new file mode 100644
index 0000000..e4b3b45
--- /dev/null
+++ b/ui/src/app/components/datetime-period.scss
@@ -0,0 +1,28 @@
+/**
+ * Copyright © 2016 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-datetime-period {
+  md-input-container {
+    margin-bottom: 0px;
+    .md-errors-spacer {
+      min-height: 0px;
+    }
+  }
+  mdp-date-picker {
+    .md-input {
+      width: 150px !important;
+    }
+  }
+}
diff --git a/ui/src/app/components/datetime-period.tpl.html b/ui/src/app/components/datetime-period.tpl.html
new file mode 100644
index 0000000..39938d4
--- /dev/null
+++ b/ui/src/app/components/datetime-period.tpl.html
@@ -0,0 +1,31 @@
+<!--
+
+    Copyright © 2016 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<section layout="column" layout-align="start start">
+	<section layout="row" layout-align="start start">
+	    <mdp-date-picker ng-model="startDate" mdp-placeholder="{{ 'datetime.date-from' | translate }}"
+	       	mdp-max-date="maxStartDate"></mdp-date-picker>
+	    <mdp-time-picker ng-model="startDate" mdp-placeholder="{{ 'datetime.time-from' | translate }}"
+	    	mdp-max-date="maxStartDate" mdp-auto-switch="true"></mdp-time-picker>   	
+    </section>
+	<section layout="row" layout-align="start start">
+	    <mdp-date-picker ng-model="endDate" mdp-placeholder="{{ 'datetime.date-to' | translate }}"
+	       	mdp-min-date="minEndDate" mdp-max-date="maxEndDate"></mdp-date-picker>
+	    <mdp-time-picker ng-model="endDate" mdp-placeholder="{{ 'datetime.time-to' | translate }}"
+	    	mdp-min-date="minEndDate" mdp-max-date="maxEndDate" mdp-auto-switch="true"></mdp-time-picker>   	
+    </section>
+</section>
\ No newline at end of file
diff --git a/ui/src/app/components/details-sidenav.directive.js b/ui/src/app/components/details-sidenav.directive.js
new file mode 100644
index 0000000..71beab7
--- /dev/null
+++ b/ui/src/app/components/details-sidenav.directive.js
@@ -0,0 +1,94 @@
+/*
+ * Copyright © 2016 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 './details-sidenav.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import detailsSidenavTemplate from './details-sidenav.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+export default angular.module('thingsboard.directives.detailsSidenav', [])
+    .directive('tbDetailsSidenav', DetailsSidenav)
+    .name;
+
+/*@ngInject*/
+function DetailsSidenav($timeout) {
+
+    var linker = function (scope, element, attrs) {
+
+        if (angular.isUndefined(attrs.isReadOnly)) {
+            attrs.isReadOnly = false;
+        }
+
+        if (angular.isUndefined(scope.headerHeightPx)) {
+            scope.headerHeightPx = 100;
+        }
+
+        if (angular.isDefined(attrs.isAlwaysEdit) && attrs.isAlwaysEdit) {
+            scope.isEdit = true;
+        }
+
+        scope.toggleDetailsEditMode = function () {
+            if (!scope.isAlwaysEdit) {
+                if (!scope.isEdit) {
+                    scope.isEdit = true;
+                } else {
+                    scope.isEdit = false;
+                }
+            }
+            $timeout(function () {
+                scope.onToggleDetailsEditMode();
+            });
+        };
+
+        scope.detailsApply = function () {
+            $timeout(function () {
+                scope.onApplyDetails();
+            });
+        }
+
+        scope.closeDetails = function () {
+            scope.isOpen = false;
+            $timeout(function () {
+                scope.onCloseDetails();
+            });
+        };
+    }
+
+    return {
+        restrict: "E",
+        transclude: {
+            headerPane: '?headerPane',
+            detailsButtons: '?detailsButtons'
+        },
+        scope: {
+            headerTitle: '=',
+            headerSubtitle: '@',
+            headerHeightPx: '@',
+            isReadOnly: '=',
+            isOpen: '=',
+            isEdit: '=?',
+            isAlwaysEdit: '=?',
+            theForm: '=',
+            onCloseDetails: '&',
+            onToggleDetailsEditMode: '&',
+            onApplyDetails: '&'
+        },
+        link: linker,
+        templateUrl: detailsSidenavTemplate
+    };
+}
\ No newline at end of file
diff --git a/ui/src/app/components/details-sidenav.scss b/ui/src/app/components/details-sidenav.scss
new file mode 100644
index 0000000..2dc2b51
--- /dev/null
+++ b/ui/src/app/components/details-sidenav.scss
@@ -0,0 +1,49 @@
+/**
+ * Copyright © 2016 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 '../../scss/constants';
+
+.tb-details-title {
+  font-size: 1.600rem;
+  font-weight: 400;
+  text-transform: uppercase;
+  margin: 20px 8px 0 0;
+}
+
+.tb-details-subtitle {
+  font-size: 1.000rem;
+  margin: 10px 0;
+  opacity: 0.8;
+}
+
+md-sidenav.tb-sidenav-details {
+  width: 100% !important;
+  max-width: 100% !important;
+  z-index: 59 !important;
+  @media (min-width: $layout-breakpoint-gt-sm) {
+    width: 80% !important;
+  }
+  @media (min-width: $layout-breakpoint-gt-md) {
+    width: 65% !important;
+  }
+  @media (min-width: $layout-breakpoint-lg) {
+    width: 45% !important;
+  }
+  tb-dashboard {
+    md-content {
+      background-color: $primary-hue-3;
+    }
+  }
+}
diff --git a/ui/src/app/components/details-sidenav.tpl.html b/ui/src/app/components/details-sidenav.tpl.html
new file mode 100644
index 0000000..a5e84e0
--- /dev/null
+++ b/ui/src/app/components/details-sidenav.tpl.html
@@ -0,0 +1,62 @@
+<!--
+
+    Copyright © 2016 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-sidenav class="md-sidenav-right md-whiteframe-4dp tb-sidenav-details"
+      md-disable-backdrop="true"
+      md-is-open="isOpen"
+      md-component-id="right"
+      layout="column">
+      <header>
+	      <md-toolbar class="md-theme-light" ng-style="{'height':headerHeightPx+'px'}">
+	      	<div class="md-toolbar-tools">
+	      		<div class="md-toolbar-tools" layout="column" layout-align="start start">
+		        	<span class="tb-details-title">{{headerTitle}}</span>
+		        	<span class="tb-details-subtitle">{{headerSubtitle}}</span>
+					<span style="width: 100%;" ng-transclude="headerPane"></span>
+	        	</div>
+	        	<span flex></span>
+				<div ng-transclude="detailsButtons"></div>
+	        	<md-button class="md-icon-button" ng-click="closeDetails()">
+	        		<md-icon aria-label="close" class="material-icons">close</md-icon>
+	        	</md-button>	        	
+	        </div>
+		    <section ng-if="!isReadOnly" layout="row" layout-wrap
+				   class="tb-header-buttons md-fab">
+				<md-button ng-show="isEdit" ng-disabled="loading || theForm.$invalid || !theForm.$dirty"
+							 class="tb-btn-header md-accent md-hue-2 md-fab md-fab-bottom-right"
+							 aria-label="{{ 'action.apply' | translate }}"
+							 ng-click="detailsApply()">
+					  <md-tooltip md-direction="top">
+						  {{ 'action.apply-changes' | translate }}
+					  </md-tooltip>
+					  <ng-md-icon icon="done"></ng-md-icon>
+				</md-button>
+				<md-button ng-disabled="loading || (isAlwaysEdit && !theForm.$dirty)" class="tb-btn-header md-accent md-hue-2 md-fab md-fab-bottom-right"
+					 aria-label="{{ 'details.edit-mode' | translate }}"
+					 ng-click="toggleDetailsEditMode()">
+					<md-tooltip md-direction="top">
+						{{ (isAlwaysEdit ? 'action.decline-changes' : 'details.toggle-edit-mode') | translate }}
+					</md-tooltip>
+					<ng-md-icon icon="{{isEdit ? 'close' : 'edit'}}" options='{"easing": "circ-in-out", "duration": 375, "rotation": "none"}'></ng-md-icon>
+				</md-button>
+			</section>
+	      </md-toolbar>
+      </header>
+      <md-content flex>
+			<div ng-transclude></div>
+      </md-content>
+</md-sidenav>	
\ No newline at end of file
diff --git a/ui/src/app/components/device-alias-select.directive.js b/ui/src/app/components/device-alias-select.directive.js
new file mode 100644
index 0000000..52a31cc
--- /dev/null
+++ b/ui/src/app/components/device-alias-select.directive.js
@@ -0,0 +1,145 @@
+/*
+ * Copyright © 2016 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 $ from 'jquery';
+
+import './device-alias-select.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import deviceAliasSelectTemplate from './device-alias-select.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+
+/* eslint-disable angular/angularelement */
+
+export default angular.module('thingsboard.directives.deviceAliasSelect', [])
+    .directive('tbDeviceAliasSelect', DeviceAliasSelect)
+    .name;
+
+/*@ngInject*/
+function DeviceAliasSelect($compile, $templateCache, $mdConstant) {
+
+    var linker = function (scope, element, attrs, ngModelCtrl) {
+        var template = $templateCache.get(deviceAliasSelectTemplate);
+        element.html(template);
+
+        scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
+
+        scope.ngModelCtrl = ngModelCtrl;
+        scope.deviceAliasList = [];
+        scope.deviceAlias = null;
+
+        scope.updateValidity = function () {
+            var value = ngModelCtrl.$viewValue;
+            var valid = angular.isDefined(value) && value != null || !scope.tbRequired;
+            ngModelCtrl.$setValidity('deviceAlias', valid);
+        };
+
+        scope.$watch('deviceAliases', function () {
+            scope.deviceAliasList = [];
+            for (var aliasId in scope.deviceAliases) {
+                var deviceAlias = {id: aliasId, alias: scope.deviceAliases[aliasId].alias, deviceId: scope.deviceAliases[aliasId].deviceId};
+                scope.deviceAliasList.push(deviceAlias);
+            }
+        }, true);
+
+        scope.$watch('deviceAlias', function () {
+            scope.updateView();
+        });
+
+        scope.deviceAliasSearch = function (deviceAliasSearchText) {
+            return deviceAliasSearchText ? scope.deviceAliasList.filter(
+                scope.createFilterForDeviceAlias(deviceAliasSearchText)) : scope.deviceAliasList;
+        };
+
+        scope.createFilterForDeviceAlias = function (query) {
+            var lowercaseQuery = angular.lowercase(query);
+            return function filterFn(deviceAlias) {
+                return (angular.lowercase(deviceAlias.alias).indexOf(lowercaseQuery) === 0);
+            };
+        };
+
+        scope.updateView = function () {
+            ngModelCtrl.$setViewValue(scope.deviceAlias);
+            scope.updateValidity();
+        }
+
+        ngModelCtrl.$render = function () {
+            if (ngModelCtrl.$viewValue) {
+                scope.deviceAlias = ngModelCtrl.$viewValue;
+            }
+        }
+
+        scope.textIsNotEmpty = function(text) {
+            return (text && text != null && text.length > 0) ? true : false;
+        }
+
+        scope.deviceAliasEnter = function($event) {
+            if ($event.keyCode === $mdConstant.KEY_CODE.ENTER) {
+                $event.preventDefault();
+                if (!scope.deviceAlias) {
+                    var found = scope.deviceAliasSearch(scope.deviceAliasSearchText);
+                    found = found.length > 0;
+                    if (!found) {
+                        scope.createDeviceAlias($event, scope.deviceAliasSearchText);
+                    }
+                }
+            }
+        }
+
+        scope.createDeviceAlias = function (event, alias) {
+            var autoChild = $('#device-autocomplete', element)[0].firstElementChild;
+            var el = angular.element(autoChild);
+            el.scope().$mdAutocompleteCtrl.hidden = true;
+            el.scope().$mdAutocompleteCtrl.hasNotFound = false;
+            event.preventDefault();
+            var promise = scope.onCreateDeviceAlias({event: event, alias: alias});
+            if (promise) {
+                promise.then(
+                    function success(newAlias) {
+                        el.scope().$mdAutocompleteCtrl.hasNotFound = true;
+                        if (newAlias) {
+                            scope.deviceAliasList.push(newAlias);
+                            scope.deviceAlias = newAlias;
+                        }
+                    },
+                    function fail() {
+                        el.scope().$mdAutocompleteCtrl.hasNotFound = true;
+                    }
+                );
+            } else {
+                el.scope().$mdAutocompleteCtrl.hasNotFound = true;
+            }
+        };
+
+        $compile(element.contents())(scope);
+    }
+
+    return {
+        restrict: "E",
+        require: "^ngModel",
+        link: linker,
+        scope: {
+            tbRequired: '=?',
+            deviceAliases: '=',
+            onCreateDeviceAlias: '&'
+        }
+    };
+}
+
+/* eslint-enable angular/angularelement */
diff --git a/ui/src/app/components/device-alias-select.scss b/ui/src/app/components/device-alias-select.scss
new file mode 100644
index 0000000..2ce533c
--- /dev/null
+++ b/ui/src/app/components/device-alias-select.scss
@@ -0,0 +1,30 @@
+/**
+ * Copyright © 2016 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-device-alias-autocomplete {
+  .tb-not-found {
+    display: block;
+    line-height: 1.5;
+    height: 48px;
+    .tb-no-entries {
+      line-height: 48px;
+    }
+  }
+  li {
+    height: auto !important;
+    white-space: normal !important;
+  }
+}
diff --git a/ui/src/app/components/device-alias-select.tpl.html b/ui/src/app/components/device-alias-select.tpl.html
new file mode 100644
index 0000000..2fdd5d5
--- /dev/null
+++ b/ui/src/app/components/device-alias-select.tpl.html
@@ -0,0 +1,52 @@
+<!--
+
+    Copyright © 2016 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<section layout='column'>
+    <md-autocomplete id="device-autocomplete"
+                     md-input-name="device_alias"
+                     ng-required="tbRequired"
+                     ng-model="deviceAlias"
+                     md-selected-item="deviceAlias"
+                     md-search-text="deviceAliasSearchText"
+                     md-items="item in deviceAliasSearch(deviceAliasSearchText)"
+                     md-item-text="item.alias"
+                     tb-keydown="deviceAliasEnter($event)"
+                     tb-keypress="deviceAliasEnter($event)"
+                     md-min-length="0"
+                     placeholder="{{ 'device.device-alias' | translate }}"
+                     md-menu-class="tb-device-alias-autocomplete">
+        <md-item-template>
+            <span md-highlight-text="deviceAliasSearchText" md-highlight-flags="^i">{{item.alias}}</span>
+        </md-item-template>
+        <md-not-found>
+            <div class="tb-not-found">
+                <div class="tb-no-entries" ng-if="!textIsNotEmpty(deviceAliasSearchText)">
+                    <span translate>device.no-aliases-found</span>
+                </div>
+                <div ng-if="textIsNotEmpty(deviceAliasSearchText)">
+                    <span translate translate-values='{ alias: "{{deviceAliasSearchText | truncate:true:6:&apos;...&apos;}}" }'>device.no-alias-matching</span>
+                    <span>
+                        <a translate ng-click="createDeviceAlias($event, deviceAliasSearchText)">device.create-new-alias</a>
+                    </span>
+                </div>
+            </div>
+        </md-not-found>
+    </md-autocomplete>
+    <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">
+        <div translate ng-message="deviceAlias" class="tb-error-message">device.alias-required</div>
+    </div>
+</section>
diff --git a/ui/src/app/components/expand-fullscreen.directive.js b/ui/src/app/components/expand-fullscreen.directive.js
new file mode 100644
index 0000000..f5a078c
--- /dev/null
+++ b/ui/src/app/components/expand-fullscreen.directive.js
@@ -0,0 +1,140 @@
+/*
+ * Copyright © 2016 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 './expand-fullscreen.scss';
+
+import $ from 'jquery';
+
+export default angular.module('thingsboard.directives.expandFullscreen', [])
+    .directive('tbExpandFullscreen', ExpandFullscreen)
+    .name;
+
+/* eslint-disable angular/angularelement */
+
+/*@ngInject*/
+function ExpandFullscreen($compile, $document) {
+
+    var uniqueId = 1;
+    var linker = function (scope, element, attrs) {
+
+        scope.body = angular.element($document.find('body').eq(0));
+        scope.fullscreenParentId = 'fullscreen-parent' + uniqueId;
+        scope.fullscreenParent = $('#' + scope.fullscreenParentId, scope.body)[0];
+        if (!scope.fullscreenParent) {
+            uniqueId++;
+            var fullscreenParent = angular.element('<div id=\'' + scope.fullscreenParentId + '\' class=\'tb-fullscreen-parent\'></div>');
+            scope.body.append(fullscreenParent);
+            scope.fullscreenParent = $('#' + scope.fullscreenParentId, scope.body)[0];
+            scope.fullscreenParent = angular.element(scope.fullscreenParent);
+            scope.fullscreenParent.css('display', 'none');
+        } else {
+            scope.fullscreenParent = angular.element(scope.fullscreenParent);
+        }
+
+        scope.$on('$destroy', function () {
+            scope.fullscreenParent.remove();
+        });
+
+        scope.elementParent = null;
+        scope.expanded = false;
+        scope.fullscreenZindex = scope.fullscreenZindex();
+        if (!scope.fullscreenZindex) {
+            scope.fullscreenZindex = '70';
+        }
+
+        scope.$watch('expanded', function (newExpanded, prevExpanded) {
+            if (newExpanded != prevExpanded) {
+                if (scope.expanded) {
+                    scope.elementParent = element.parent();
+                    element.detach();
+                    scope.fullscreenParent.append(element);
+                    scope.fullscreenParent.css('display', '');
+                    scope.fullscreenParent.css('z-index', scope.fullscreenZindex);
+                    element.addClass('tb-fullscreen');
+                } else {
+                    if (scope.elementParent) {
+                        element.detach();
+                        scope.elementParent.append(element);
+                        scope.elementParent = null;
+                    }
+                    element.removeClass('tb-fullscreen');
+                    scope.fullscreenParent.css('display', 'none');
+                    scope.fullscreenParent.css('z-index', '');
+                }
+                if (scope.onFullscreenChanged) {
+                    scope.onFullscreenChanged({expanded: scope.expanded});
+                }
+            }
+        });
+
+        scope.$watch(function () {
+            return scope.expand();
+        }, function (newExpanded) {
+            scope.expanded = newExpanded;
+        });
+
+        scope.toggleExpand = function ($event) {
+            if ($event) {
+                $event.stopPropagation();
+            }
+            scope.expanded = !scope.expanded;
+        }
+
+        var expandButton = null;
+        if (attrs.expandButtonId) {
+            expandButton = $('#' + attrs.expandButtonId, element)[0];
+        }
+
+        var html = '<md-tooltip md-direction="{{expanded ? \'bottom\' : \'top\'}}">' +
+            '{{(expanded ? \'fullscreen.exit\' : \'fullscreen.expand\') | translate}}' +
+            '</md-tooltip>' +
+            '<ng-md-icon icon="{{expanded ? \'fullscreen_exit\' : \'fullscreen\'}}" ' +
+            'options=\'{"easing": "circ-in-out", "duration": 375, "rotation": "none"}\'>' +
+            '</ng-md-icon>';
+
+        if (expandButton) {
+            expandButton = angular.element(expandButton);
+            expandButton.attr('md-ink-ripple', 'false');
+            expandButton.append(html);
+
+            $compile(expandButton.contents())(scope);
+
+            expandButton.on("click", scope.toggleExpand);
+
+        } else if (!scope.hideExpandButton()) {
+            var button = angular.element('<md-button class="tb-fullscreen-button-style tb-fullscreen-button-pos md-icon-button" ' +
+                'md-ink-ripple="false" ng-click="toggleExpand($event)">' +
+                html +
+                '</md-button>');
+
+            $compile(button)(scope);
+
+            element.prepend(button);
+        }
+    }
+
+    return {
+        restrict: "A",
+        link: linker,
+        scope: {
+            expand: "&tbExpandFullscreen",
+            hideExpandButton: "&hideExpandButton",
+            onFullscreenChanged: "&onFullscreenChanged",
+            fullscreenZindex: "&fullscreenZindex"
+        }
+    };
+}
+
+/* eslint-enable angular/angularelement */
diff --git a/ui/src/app/components/expand-fullscreen.scss b/ui/src/app/components/expand-fullscreen.scss
new file mode 100644
index 0000000..1ff154f
--- /dev/null
+++ b/ui/src/app/components/expand-fullscreen.scss
@@ -0,0 +1,53 @@
+/**
+ * Copyright © 2016 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 "../../scss/constants";
+
+.tb-fullscreen {
+  section.header-buttons {
+    top: 25px;
+  }
+}
+
+.tb-fullscreen {
+  width: 100% !important;
+  height: 100% !important;
+  position: fixed !important;
+  top: 0;
+  left: 0;
+}
+
+.tb-fullscreen-parent {
+  background-color: $gray;
+  width: 100%;
+  height: 100%;
+  position: fixed;
+  top: 0;
+  left: 0;
+}
+
+.md-button.tb-fullscreen-button-style, .tb-fullscreen-button-style {
+  background: #ccc;
+  opacity: 0.85;
+  ng-md-icon {
+    color: #666;
+  }
+}
+
+.md-button.tb-fullscreen-button-pos, .tb-fullscreen-button-pos {
+  position: absolute;
+  top: 10px;
+  right: 10px;
+}
diff --git a/ui/src/app/components/grid.directive.js b/ui/src/app/components/grid.directive.js
new file mode 100644
index 0000000..ba8cdc1
--- /dev/null
+++ b/ui/src/app/components/grid.directive.js
@@ -0,0 +1,607 @@
+/*
+ * Copyright © 2016 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 './grid.scss';
+
+import thingsboardScopeElement from './scope-element.directive';
+import thingsboardDetailsSidenav from './details-sidenav.directive';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import gridTemplate from './grid.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+export default angular.module('thingsboard.directives.grid', [thingsboardScopeElement, thingsboardDetailsSidenav])
+    .directive('tbGrid', Grid)
+    .directive('tbGridCardContent', GridCardContent)
+    .filter('range', RangeFilter)
+    .name;
+
+/*@ngInject*/
+function RangeFilter() {
+    return function(input, total) {
+        total = parseInt(total);
+
+        for (var i=0; i<total; i++) {
+            input.push(i);
+        }
+
+        return input;
+    };
+}
+
+/*@ngInject*/
+function GridCardContent($compile) {
+    var linker = function(scope, element) {
+        scope.$watch('itemTemplate',
+            function(value) {
+                element.html(value);
+                $compile(element.contents())(scope);
+            }
+        );
+    };
+
+    return {
+        restrict: "E",
+        link: linker,
+        scope: {
+            parentCtl: "=parentCtl",
+            gridCtl: "=gridCtl",
+            itemTemplate: "=itemTemplate",
+            item: "=item"
+        }
+    };
+}
+
+/*@ngInject*/
+function Grid() {
+    return {
+        restrict: "E",
+        scope: true,
+        transclude: {
+            detailsButtons: '?detailsButtons'
+        },
+        bindToController: {
+            gridConfiguration: '&?'
+        },
+        controller: GridController,
+        controllerAs: 'vm',
+        templateUrl: gridTemplate
+    }
+}
+
+/*@ngInject*/
+function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $translate, $mdMedia, $templateCache) {
+
+    var vm = this;
+
+    var columns = 1;
+    if ($mdMedia('md')) {
+        columns = 2;
+    } else if ($mdMedia('lg')) {
+        columns = 3;
+    } else if ($mdMedia('gt-lg')) {
+        columns = 4;
+    }
+
+    var pageSize = 10 * columns;
+
+    vm.columns = columns;
+
+    vm.addItem = addItem;
+    vm.deleteItem = deleteItem;
+    vm.deleteItems = deleteItems;
+    vm.hasData = hasData;
+    vm.isCurrentItem = isCurrentItem;
+    vm.moveToTop = moveToTop;
+    vm.noData = noData;
+    vm.onCloseDetails = onCloseDetails;
+    vm.onToggleDetailsEditMode = onToggleDetailsEditMode;
+    vm.openItem = openItem;
+    vm.operatingItem = operatingItem;
+    vm.refreshList = refreshList;
+    vm.saveItem = saveItem;
+    vm.toggleItemSelection = toggleItemSelection;
+
+    $scope.$watch(function () {
+        return $mdMedia('sm');
+    }, function (sm) {
+        if (sm) {
+            columnsUpdated(1);
+        }
+    });
+    $scope.$watch(function () {
+        return $mdMedia('md');
+    }, function (md) {
+        if (md) {
+            columnsUpdated(2);
+        }
+    });
+    $scope.$watch(function () {
+        return $mdMedia('lg');
+    }, function (lg) {
+        if (lg) {
+            columnsUpdated(3);
+        }
+    });
+    $scope.$watch(function () {
+        return $mdMedia('gt-lg');
+    }, function (gtLg) {
+        if (gtLg) {
+            columnsUpdated(4);
+        }
+    });
+
+    initGridConfiguration();
+
+    vm.itemRows = {
+        getItemAtIndex: function (index) {
+            if (index >= vm.items.rowData.length) {
+                vm.itemRows.fetchMoreItems_(index);
+                return null;
+            }
+            return vm.items.rowData[index];
+        },
+
+        getLength: function () {
+            if (vm.items.hasNext) {
+                return vm.items.rowData.length + pageSize;
+            } else {
+                return vm.items.rowData.length;
+            }
+        },
+
+        fetchMoreItems_: function () {
+            if (vm.items.hasNext && !vm.items.pending) {
+                var promise = vm.fetchItemsFunc(vm.items.nextPageLink);
+                if (promise) {
+                    vm.items.pending = true;
+                    promise.then(
+                        function success(items) {
+                            vm.items.data = vm.items.data.concat(items.data);
+                            var startIndex = vm.items.data.length - items.data.length;
+                            var endIndex = vm.items.data.length;
+                            for (var i = startIndex; i < endIndex; i++) {
+                                var item = vm.items.data[i];
+                                item.index = i;
+                                var row = Math.floor(i / vm.columns);
+                                var itemRow = vm.items.rowData[row];
+                                if (!itemRow) {
+                                    itemRow = [];
+                                    vm.items.rowData.push(itemRow);
+                                }
+                                itemRow.push(item);
+                            }
+                            vm.items.nextPageLink = items.nextPageLink;
+                            vm.items.hasNext = items.hasNext;
+                            if (vm.items.hasNext) {
+                                vm.items.nextPageLink.limit = pageSize;
+                            }
+                            vm.items.pending = false;
+                        },
+                        function fail() {
+                            vm.items.hasNext = false;
+                            vm.items.pending = false;
+                        });
+                } else {
+                    vm.items.hasNext = false;
+                }
+            }
+        }
+    };
+
+    function columnsUpdated(newColumns) {
+        if (vm.columns !== newColumns) {
+            var newTopIndex = Math.ceil(vm.columns * vm.topIndex / newColumns);
+            pageSize = 10 * newColumns;
+            vm.items.rowData = [];
+            if (vm.items.nextPageLink) {
+                vm.items.nextPageLink.limit = pageSize;
+            }
+
+            for (var i = 0; i < vm.items.data.length; i++) {
+                var item = vm.items.data[i];
+                var row = Math.floor(i / newColumns);
+                var itemRow = vm.items.rowData[row];
+                if (!itemRow) {
+                    itemRow = [];
+                    vm.items.rowData.push(itemRow);
+                }
+                itemRow.push(item);
+            }
+
+            vm.columns = newColumns;
+            vm.topIndex = newTopIndex;
+            vm.itemRows.getItemAtIndex(newTopIndex+pageSize);
+            $timeout(function() {
+                moveToIndex(newTopIndex);
+            }, 500);
+        }
+    }
+
+    function initGridConfiguration() {
+        vm.gridConfiguration = vm.gridConfiguration || function () {
+                return {};
+            };
+
+        vm.config = vm.gridConfiguration();
+
+        vm.itemHeight = vm.config.itemHeight || 199;
+
+        vm.refreshParamsFunc = vm.config.refreshParamsFunc || function () {
+                return {"topIndex": vm.topIndex};
+            };
+
+        vm.deleteItemTitleFunc = vm.config.deleteItemTitleFunc || function () {
+                return $translate.instant('grid.delete-item-title');
+            };
+
+        vm.deleteItemContentFunc = vm.config.deleteItemContentFunc || function () {
+                return $translate.instant('grid.delete-item-text');
+            };
+
+        vm.deleteItemsTitleFunc = vm.config.deleteItemsTitleFunc || function () {
+                return $translate.instant('grid.delete-items-title', {count: vm.items.selectedCount}, 'messageformat');
+            };
+
+        vm.deleteItemsActionTitleFunc = vm.config.deleteItemsActionTitleFunc || function (selectedCount) {
+                return $translate.instant('grid.delete-items-action-title', {count: selectedCount}, 'messageformat');
+            };
+
+        vm.deleteItemsContentFunc = vm.config.deleteItemsContentFunc || function () {
+                return $translate.instant('grid.delete-items-text');
+            };
+
+        vm.fetchItemsFunc = vm.config.fetchItemsFunc || function () {
+                return $q.when([]);
+            };
+
+        vm.saveItemFunc = vm.config.saveItemFunc || function (item) {
+                return $q.when(item);
+            };
+
+        vm.deleteItemFunc = vm.config.deleteItemFunc || function () {
+                return $q.when();
+            };
+
+        vm.clickItemFunc = vm.config.clickItemFunc || function ($event, item) {
+                vm.openItem($event, item);
+            };
+
+        vm.itemCardTemplate = '<span></span>';
+        if (vm.config.itemCardTemplate) {
+            vm.itemCardTemplate = vm.config.itemCardTemplate;
+        } else if (vm.config.itemCardTemplateUrl) {
+            vm.itemCardTemplate = $templateCache.get(vm.config.itemCardTemplateUrl);
+        }
+
+        vm.parentCtl = vm.config.parentCtl || vm;
+
+        vm.getItemTitleFunc = vm.config.getItemTitleFunc || function () {
+                return '';
+            };
+
+        vm.actionsList = vm.config.actionsList || [];
+
+        for (var i = 0; i < vm.actionsList.length; i++) {
+            var action = vm.actionsList[i];
+            action.isEnabled = action.isEnabled || function() {
+                    return true;
+                };
+        }
+
+        vm.groupActionsList = vm.config.groupActionsList || [
+                {
+                    onAction: function ($event) {
+                        deleteItems($event);
+                    },
+                    name: function() { return $translate.instant('action.delete') },
+                    details: vm.deleteItemsActionTitleFunc,
+                    icon: "delete"
+                }
+            ];
+
+        vm.addItemText = vm.config.addItemText || function () {
+                return $translate.instant('grid.add-item-text');
+            };
+
+        vm.addItemAction = vm.config.addItemAction || {
+                onAction: function ($event) {
+                    addItem($event);
+                },
+                name: function() { return $translate.instant('action.add') },
+                details: function() { return vm.addItemText() },
+                icon: "add"
+            };
+
+        vm.onGridInited = vm.config.onGridInited || function () {
+            };
+
+        vm.addItemTemplateUrl = vm.config.addItemTemplateUrl;
+
+        vm.noItemsText = vm.config.noItemsText || function () {
+                return $translate.instant('grid.no-items-text');
+            };
+
+        vm.itemDetailsText = vm.config.itemDetailsText || function () {
+                return $translate.instant('grid.item-details');
+            };
+
+        vm.isDetailsReadOnly = vm.config.isDetailsReadOnly || function () {
+                return false;
+            };
+
+        vm.isSelectionEnabled = vm.config.isSelectionEnabled || function () {
+                return true;
+            };
+
+        vm.topIndex = vm.config.topIndex || 0;
+
+        vm.items = vm.config.items || {
+                data: [],
+                rowData: [],
+                nextPageLink: {
+                    limit: vm.topIndex + pageSize,
+                    textSearch: $scope.searchConfig.searchText
+                },
+                selections: {},
+                selectedCount: 0,
+                hasNext: true,
+                pending: false
+            };
+
+        vm.detailsConfig = {
+            isDetailsOpen: false,
+            isDetailsEditMode: false,
+            currentItem: null,
+            editingItem: null
+        };
+    }
+
+    $scope.$on('searchTextUpdated', function () {
+        vm.items = {
+            data: [],
+            rowData: [],
+            nextPageLink: {
+                limit: pageSize,
+                textSearch: $scope.searchConfig.searchText
+            },
+            selections: {},
+            selectedCount: 0,
+            hasNext: true,
+            pending: false
+        };
+        vm.detailsConfig.isDetailsOpen = false;
+        vm.itemRows.getItemAtIndex(pageSize);
+    });
+
+    vm.onGridInited(vm);
+
+    vm.itemRows.getItemAtIndex(pageSize);
+
+    function refreshList() {
+        $state.go($state.current, vm.refreshParamsFunc(), {reload: true});
+    }
+
+    function addItem($event) {
+        $mdDialog.show({
+            controller: AddItemController,
+            controllerAs: 'vm',
+            templateUrl: vm.addItemTemplateUrl,
+            parent: angular.element($document[0].body),
+            locals: {saveItemFunction: vm.saveItemFunc},
+            fullscreen: true,
+            targetEvent: $event
+        }).then(function () {
+            refreshList();
+        }, function () {
+        });
+    }
+
+    function openItem($event, item) {
+        $event.stopPropagation();
+        if (vm.detailsConfig.currentItem != null && vm.detailsConfig.currentItem.id.id === item.id.id) {
+            if (vm.detailsConfig.isDetailsOpen) {
+                vm.detailsConfig.isDetailsOpen = false;
+                return;
+            }
+        }
+        vm.detailsConfig.currentItem = item;
+        vm.detailsConfig.isDetailsEditMode = false;
+        vm.detailsConfig.isDetailsOpen = true;
+    }
+
+    function isCurrentItem(item) {
+        if (item != null && vm.detailsConfig.currentItem != null &&
+            vm.detailsConfig.currentItem.id.id === item.id.id) {
+            return vm.detailsConfig.isDetailsOpen;
+        } else {
+            return false;
+        }
+    }
+
+    function onToggleDetailsEditMode(theForm) {
+        if (!vm.detailsConfig.isDetailsEditMode) {
+            theForm.$setPristine();
+        }
+    }
+
+    function onCloseDetails() {
+        vm.detailsConfig.currentItem = null;
+    }
+
+    function operatingItem() {
+        if (!vm.detailsConfig.isDetailsEditMode) {
+            if (vm.detailsConfig.editingItem) {
+                vm.detailsConfig.editingItem = null;
+            }
+            return vm.detailsConfig.currentItem;
+        } else {
+            if (!vm.detailsConfig.editingItem) {
+                vm.detailsConfig.editingItem = angular.copy(vm.detailsConfig.currentItem);
+            }
+            return vm.detailsConfig.editingItem;
+        }
+    }
+
+    function saveItem(theForm) {
+        vm.saveItemFunc(vm.detailsConfig.editingItem).then(function success(item) {
+            theForm.$setPristine();
+            vm.detailsConfig.isDetailsEditMode = false;
+            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;
+        });
+    }
+
+    function deleteItem($event, item) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        var confirm = $mdDialog.confirm()
+            .targetEvent($event)
+            .title(vm.deleteItemTitleFunc(item))
+            .htmlContent(vm.deleteItemContentFunc(item))
+            .ariaLabel($translate.instant('grid.delete-item'))
+            .cancel($translate.instant('action.no'))
+            .ok($translate.instant('action.yes'));
+        $mdDialog.show(confirm).then(function () {
+                vm.deleteItemFunc(item.id.id).then(function success() {
+                    refreshList();
+                });
+            },
+            function () {
+            });
+
+    }
+
+    function deleteItems($event) {
+        var confirm = $mdDialog.confirm()
+            .targetEvent($event)
+            .title(vm.deleteItemsTitleFunc(vm.items.selectedCount))
+            .htmlContent(vm.deleteItemsContentFunc())
+            .ariaLabel($translate.instant('grid.delete-items'))
+            .cancel($translate.instant('action.no'))
+            .ok($translate.instant('action.yes'));
+        $mdDialog.show(confirm).then(function () {
+                var tasks = [];
+                for (var id in vm.items.selections) {
+                    tasks.push(vm.deleteItemFunc(id));
+                }
+                $q.all(tasks).then(function () {
+                    refreshList();
+                });
+            },
+            function () {
+            });
+    }
+
+
+    function toggleItemSelection($event, item) {
+        $event.stopPropagation();
+        var selected = angular.isDefined(item.selected) && item.selected;
+        item.selected = !selected;
+        if (item.selected) {
+            vm.items.selections[item.id.id] = true;
+            vm.items.selectedCount++;
+        } else {
+            delete vm.items.selections[item.id.id];
+            vm.items.selectedCount--;
+        }
+    }
+
+    function moveToTop() {
+        moveToIndex(0, true);
+    }
+
+    function moveToIndex(index, animate) {
+        var repeatContainer = $scope.repeatContainer[0];
+        var scrollElement = repeatContainer.children[0];
+        var startY = scrollElement.scrollTop;
+        var stopY = index * vm.itemHeight;
+        if (stopY > 0) {
+            stopY+= 16;
+        }
+        var distance = Math.abs(startY - stopY);
+        if (distance < 100 || !animate) {
+            scrollElement.scrollTop = stopY;
+            return;
+        }
+        var upElseDown = stopY < startY;
+        var speed = Math.round(distance / 100);
+        if (speed >= 20) speed = 20;
+        var step = Math.round(distance / 25);
+
+        var leapY = upElseDown ? startY - step : startY + step;
+        var timer = 0;
+        for (var i = startY; upElseDown ? (i > stopY) : (i < stopY); upElseDown ? (i -= step) : (i += step)) {
+            $timeout(function (topY) {
+                scrollElement.scrollTop = topY;
+            }, timer * speed, true, leapY);
+            if (upElseDown) {
+                leapY -= step;
+                if (leapY < stopY) {
+                    leapY = stopY;
+                }
+            } else {
+                leapY += step;
+                if (leapY > stopY) {
+                    leapY = stopY;
+                }
+            }
+            timer++;
+        }
+
+    }
+
+    function noData() {
+        return vm.items.data.length == 0 && !vm.items.hasNext;
+    }
+
+    function hasData() {
+        return vm.items.data.length > 0;
+    }
+
+}
+
+/*@ngInject*/
+function AddItemController($scope, $mdDialog, saveItemFunction, helpLinks) {
+
+    var vm = this;
+
+    vm.helpLinks = helpLinks;
+    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();
+        });
+    }
+}
\ No newline at end of file
diff --git a/ui/src/app/components/grid.scss b/ui/src/app/components/grid.scss
new file mode 100644
index 0000000..549b8eb
--- /dev/null
+++ b/ui/src/app/components/grid.scss
@@ -0,0 +1,37 @@
+/**
+ * Copyright © 2016 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 "~compass-sass-mixins/lib/animate";
+
+.tb-uppercase {
+  text-transform: uppercase;
+}
+
+.tb-card-item {
+  @include transition(all .2s ease-in-out);
+}
+
+.tb-current-item {
+  opacity: 0.5;
+  @include transform(scale(1.05));
+}
+
+#tb-vertical-container {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+}
diff --git a/ui/src/app/components/grid.tpl.html b/ui/src/app/components/grid.tpl.html
new file mode 100644
index 0000000..16d38f3
--- /dev/null
+++ b/ui/src/app/components/grid.tpl.html
@@ -0,0 +1,102 @@
+<!--
+
+    Copyright © 2016 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.
+
+-->
+<span layout-align="center center"
+      style="text-transform: uppercase; display: flex;"
+      class="md-headline tb-absolute-fill"
+      ng-show="vm.noData()">{{vm.noItemsText()}}</span>
+<section layout="row" flex style="height: 100%;" flex layout-wrap>
+    <div layout="column" style="height: 100%;" flex layout-wrap>
+        <md-virtual-repeat-container ng-show="vm.hasData()" tb-scope-element="repeatContainer" id="tb-vertical-container" md-top-index="vm.topIndex" flex>
+            <div class="md-padding" layout="column">
+                <section layout="row"  md-virtual-repeat="rowItem in vm.itemRows" md-on-demand md-item-size="vm.itemHeight">
+                    <div flex ng-repeat="n in [] | range:vm.columns" ng-style="{'height':vm.itemHeight+'px'}">
+                        <md-card ng-if="rowItem[n]"
+                                 ng-class="{'tb-current-item': vm.isCurrentItem(rowItem[n])}"
+                                 class="repeated-item tb-card-item" ng-style="{'height':(vm.itemHeight-16)+'px','cursor':'pointer'}"
+                                 ng-click="vm.clickItemFunc($event, rowItem[n])">
+                            <section layout="row" layout-wrap>
+                                <md-card-title layout="row">
+                                    <section layout="column" layout-wrap>
+                                        <md-checkbox ng-if="vm.isSelectionEnabled(rowItem[n])" ng-click="vm.toggleItemSelection($event, rowItem[n])"
+                                                     layout-align="start start" aria-label="{{ 'item.selected' | translate }}" ng-checked="rowItem[n].selected">
+                                        </md-checkbox>
+                                        <span flex></span>
+                                    </section>
+                                    <md-card-title-text>
+                                        <span class="md-headline">{{vm.getItemTitleFunc(rowItem[n])}}</span>
+                                    </md-card-title-text>
+                                </md-card-title>
+                            </section>
+                            <md-card-content flex>
+                                <tb-grid-card-content grid-ctl="vm" parent-ctl="vm.parentCtl" item-template="vm.itemCardTemplate" item="rowItem[n]"></tb-grid-card-content>
+                            </md-card-content>
+                            <md-card-actions layout="row" layout-align="end end">
+                                <md-button ng-if="action.isEnabled(rowItem[n])" ng-disabled="loading" class="md-icon-button md-primary" ng-repeat="action in vm.actionsList"
+                                           ng-click="action.onAction($event, rowItem[n])" aria-label="{{ action.name() }}">
+                                    <md-tooltip md-direction="top">
+                                        {{ action.details() }}
+                                    </md-tooltip>
+                                    <ng-md-icon icon="{{action.icon}}"></ng-md-icon>
+                                </md-button>
+                            </md-card-actions>
+                        </md-card>
+                    </div>
+                </section>
+            </div>
+        </md-virtual-repeat-container>
+    </div>
+    <tb-details-sidenav
+            header-title="vm.getItemTitleFunc(vm.operatingItem())"
+            header-subtitle="{{vm.itemDetailsText()}}"
+            is-read-only="vm.isDetailsReadOnly(vm.operatingItem())"
+            is-open="vm.detailsConfig.isDetailsOpen"
+            is-edit="vm.detailsConfig.isDetailsEditMode"
+            on-close-details="vm.onCloseDetails()"
+            on-toggle-details-edit-mode="vm.onToggleDetailsEditMode(vm.detailsForm)"
+            on-apply-details="vm.saveItem(vm.detailsForm)"
+            the-form="vm.detailsForm">
+        <details-buttons>
+            <div ng-transclude="detailsButtons"></div>
+        </details-buttons>
+        <form name="vm.detailsForm">
+            <div ng-transclude></div>
+        </form>
+    </tb-details-sidenav>
+</section>
+
+<section layout="row" layout-wrap class="tb-footer-buttons md-fab ">
+    <md-button ng-disabled="loading" ng-show="vm.items.selectedCount > 0" class="tb-btn-footer md-accent md-hue-2 md-fab" ng-repeat="groupAction in vm.groupActionsList"
+               ng-click="groupAction.onAction($event, vm.items)" aria-label="{{ groupAction.name() }}">
+        <md-tooltip md-direction="top">
+            {{ groupAction.details(vm.items.selectedCount) }}
+        </md-tooltip>
+        <ng-md-icon icon="{{groupAction.icon}}"></ng-md-icon>
+    </md-button>
+    <md-button ng-disabled="loading" ng-show="vm.topIndex > 0" class="tb-btn-footer md-primary md-hue-1 md-fab" ng-click="vm.moveToTop()" aria-label="{{'grid.scroll-to-top' | translate}}" >
+        <md-tooltip md-direction="top">
+            {{'grid.scroll-to-top' | translate}}
+        </md-tooltip>
+        <ng-md-icon icon="arrow_drop_up"></ng-md-icon>
+    </md-button>
+    <md-button ng-disabled="loading" ng-if="vm.addItemAction.name()" class="tb-btn-footer md-accent md-hue-2 md-fab" ng-click="vm.addItemAction.onAction($event)" aria-label="{{ vm.addItemAction.name() }}" >
+        <md-tooltip md-direction="top">
+            {{ vm.addItemAction.details() }}
+        </md-tooltip>
+        <ng-md-icon icon="{{ vm.addItemAction.icon }}"></ng-md-icon>
+    </md-button>
+</section>
\ No newline at end of file
diff --git a/ui/src/app/components/js-func.directive.js b/ui/src/app/components/js-func.directive.js
new file mode 100644
index 0000000..8247b2b
--- /dev/null
+++ b/ui/src/app/components/js-func.directive.js
@@ -0,0 +1,203 @@
+/*
+ * Copyright © 2016 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 './js-func.scss';
+
+import ace from 'brace';
+import 'brace/ext/language_tools';
+import $ from 'jquery';
+import thingsboardToast from '../services/toast';
+import thingsboardUtils from '../common/utils.service';
+import thingsboardExpandFullscreen from './expand-fullscreen.directive';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import jsFuncTemplate from './js-func.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/* eslint-disable angular/angularelement */
+
+export default angular.module('thingsboard.directives.jsFunc', [thingsboardToast, thingsboardUtils, thingsboardExpandFullscreen])
+    .directive('tbJsFunc', JsFunc)
+    .name;
+
+/*@ngInject*/
+function JsFunc($compile, $templateCache, toast, utils, $translate) {
+
+    var linker = function (scope, element, attrs, ngModelCtrl) {
+        var template = $templateCache.get(jsFuncTemplate);
+        element.html(template);
+
+        scope.functionArgs = scope.$eval(attrs.functionArgs);
+        scope.validationArgs = scope.$eval(attrs.validationArgs);
+        scope.resultType = attrs.resultType;
+        if (!scope.resultType || scope.resultType.length === 0) {
+            scope.resultType = "nocheck";
+        }
+
+        scope.functionValid = true;
+
+        var Range = ace.acequire("ace/range").Range;
+        scope.js_editor;
+        scope.errorMarkers = [];
+
+
+        scope.functionArgsString = '';
+        for (var i in scope.functionArgs) {
+            if (scope.functionArgsString.length > 0) {
+                scope.functionArgsString += ', ';
+            }
+            scope.functionArgsString += scope.functionArgs[i];
+        }
+
+        scope.onFullscreenChanged = function () {
+            if (scope.js_editor) {
+                scope.js_editor.resize();
+                scope.js_editor.renderer.updateFull();
+            }
+        };
+
+        scope.jsEditorOptions = {
+            useWrapMode: true,
+            mode: 'javascript',
+            advanced: {
+                enableSnippets: true,
+                enableBasicAutocompletion: true,
+                enableLiveAutocompletion: true
+            },
+            onLoad: function (_ace) {
+                scope.js_editor = _ace;
+                scope.js_editor.session.on("change", function () {
+                    scope.cleanupJsErrors();
+                });
+            }
+        };
+
+        scope.cleanupJsErrors = function () {
+            toast.hide();
+            for (var i = 0; i < scope.errorMarkers.length; i++) {
+                scope.js_editor.session.removeMarker(scope.errorMarkers[i]);
+            }
+            scope.errorMarkers = [];
+            if (scope.errorAnnotationId && scope.errorAnnotationId > -1) {
+                var annotations = scope.js_editor.session.getAnnotations();
+                annotations.splice(scope.errorAnnotationId, 1);
+                scope.js_editor.session.setAnnotations(annotations);
+                scope.errorAnnotationId = -1;
+            }
+        }
+
+        scope.updateValidity = function () {
+            ngModelCtrl.$setValidity('functionBody', scope.functionValid);
+        };
+
+        scope.$watch('functionBody', function (newFunctionBody, oldFunctionBody) {
+            ngModelCtrl.$setViewValue(scope.functionBody);
+            if (!angular.equals(newFunctionBody, oldFunctionBody)) {
+                scope.functionValid = true;
+            }
+            scope.updateValidity();
+        });
+
+        ngModelCtrl.$render = function () {
+            scope.functionBody = ngModelCtrl.$viewValue;
+        };
+
+        scope.showError = function (error) {
+            var toastParent = $('#tb-javascript-panel', element);
+            var dialogContent = toastParent.closest('md-dialog-content');
+            if (dialogContent.length > 0) {
+                toastParent = dialogContent;
+            }
+            toast.showError(error, toastParent, 'bottom left');
+        }
+
+        scope.validate = function () {
+            try {
+                var toValidate = new Function(scope.functionArgsString, scope.functionBody);
+                var res = toValidate.apply(this, scope.validationArgs);
+                if (scope.resultType != 'nocheck') {
+                    if (scope.resultType === 'any') {
+                        if (angular.isUndefined(res)) {
+                            scope.showError($translate.instant('js-func.no-return-error'));
+                            return false;
+                        }
+                    } else {
+                        var resType = typeof res;
+                        if (resType != scope.resultType) {
+                            scope.showError($translate.instant('js-func.return-type-mismatch', {type: scope.resultType}));
+                            return false;
+                        }
+                    }
+                }
+                return true;
+            } catch (e) {
+                var details = utils.parseException(e);
+                var errorInfo = 'Error:';
+                if (details.name) {
+                    errorInfo += ' ' + details.name + ':';
+                }
+                if (details.message) {
+                    errorInfo += ' ' + details.message;
+                }
+                if (details.lineNumber) {
+                    errorInfo += '<br>Line ' + details.lineNumber;
+                    if (details.columnNumber) {
+                        errorInfo += ' column ' + details.columnNumber;
+                    }
+                    errorInfo += ' of script.';
+                }
+                scope.showError(errorInfo);
+                if (scope.js_editor && details.lineNumber) {
+                    var line = details.lineNumber - 1;
+                    var column = 0;
+                    if (details.columnNumber) {
+                        column = details.columnNumber;
+                    }
+
+                    var errorMarkerId = scope.js_editor.session.addMarker(new Range(line, 0, line, Infinity), "ace_active-line", "screenLine");
+                    scope.errorMarkers.push(errorMarkerId);
+                    var annotations = scope.js_editor.session.getAnnotations();
+                    var errorAnnotation = {
+                        row: line,
+                        column: column,
+                        text: details.message,
+                        type: "error"
+                    };
+                    scope.errorAnnotationId = annotations.push(errorAnnotation) - 1;
+                    scope.js_editor.session.setAnnotations(annotations);
+                }
+                return false;
+            }
+        };
+
+        scope.$on('form-submit', function () {
+            scope.functionValid = scope.validate();
+            scope.updateValidity();
+        });
+
+        $compile(element.contents())(scope);
+    }
+
+    return {
+        restrict: "E",
+        require: "^ngModel",
+        scope: {},
+        link: linker
+    };
+}
+
+/* eslint-enable angular/angularelement */
diff --git a/ui/src/app/components/js-func.scss b/ui/src/app/components/js-func.scss
new file mode 100644
index 0000000..b251e33
--- /dev/null
+++ b/ui/src/app/components/js-func.scss
@@ -0,0 +1,31 @@
+/**
+ * Copyright © 2016 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-js-func {
+  position: relative;
+}
+
+.tb-js-func-panel {
+  margin-left: 15px;
+  border: 1px solid #C0C0C0;
+  height: 100%;
+  #tb-javascript-input {
+    min-width: 400px;
+    min-height: 200px;
+    width: 100%;
+    height: 100%;
+  }
+}
diff --git a/ui/src/app/components/js-func.tpl.html b/ui/src/app/components/js-func.tpl.html
new file mode 100644
index 0000000..2c52232
--- /dev/null
+++ b/ui/src/app/components/js-func.tpl.html
@@ -0,0 +1,33 @@
+<!--
+
+    Copyright © 2016 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 style="background: #fff;" tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button" on-fullscreen-changed="onFullscreenChanged()" layout="column">
+	<div layout="row" layout-align="start center" style="height: 40px;">
+		<span style="font-style: italic;">function({{ functionArgsString }}) {</span>
+		<span flex></span>
+		<md-button id="expand-button" aria-label="Fullscreen" class="md-icon-button tb-md-32 tb-fullscreen-button-style"></md-button>
+	</div>
+	<div flex id="tb-javascript-panel" class="tb-js-func-panel" layout="column">
+		<div flex id="tb-javascript-input"
+			 ui-ace="jsEditorOptions" 
+			 ng-model="functionBody">
+		</div>
+	</div>
+	<div layout="row" layout-align="start center"  style="height: 40px;">
+		<span style="font-style: italic;">}</span>
+	</div>	   
+</div>
\ No newline at end of file
diff --git a/ui/src/app/components/json-form.directive.js b/ui/src/app/components/json-form.directive.js
new file mode 100644
index 0000000..a7f187d
--- /dev/null
+++ b/ui/src/app/components/json-form.directive.js
@@ -0,0 +1,243 @@
+/*
+ * Copyright © 2016 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 './json-form.scss';
+
+import tinycolor from 'tinycolor2';
+import ObjectPath from 'objectpath';
+import inspector from 'schema-inspector';
+import ReactSchemaForm from './react/json-form-react.jsx';
+import jsonFormTemplate from './json-form.tpl.html';
+import { utils } from 'react-schema-form';
+
+export default angular.module('thingsboard.directives.jsonForm', [])
+    .directive('tbJsonForm', JsonForm)
+    .value('ReactSchemaForm', ReactSchemaForm)
+    .name;
+
+/*@ngInject*/
+function JsonForm($compile, $templateCache, $mdColorPicker) {
+
+    var linker = function (scope, element) {
+
+        var template = $templateCache.get(jsonFormTemplate);
+
+        element.html(template);
+
+        var childScope;
+
+        var destroyModelChangeWatches = function() {
+            if (scope.modelWatchHandle) {
+                scope.modelWatchHandle();
+            }
+            if (scope.modelRefWatchHandle) {
+                scope.modelRefWatchHandle();
+            }
+        }
+
+        var initModelChangeWatches = function() {
+            scope.modelWatchHandle = scope.$watch('model',function(newValue, prevValue) {
+                if (newValue && prevValue && !angular.equals(newValue,prevValue)) {
+                    scope.validate();
+                    if (scope.formControl) {
+                        scope.formControl.$setDirty();
+                    }
+                }
+            }, true);
+            scope.modelRefWatchHandle = scope.$watch('model',function(newValue, prevValue) {
+                if (newValue && newValue != prevValue) {
+                    scope.updateValues();
+                }
+            });
+        };
+
+        var recompile = function() {
+            if (childScope) {
+                childScope.$destroy();
+            }
+            childScope = scope.$new();
+            $compile(element.contents())(childScope);
+        }
+
+        scope.formProps = {
+            option: {
+                formDefaults: {
+                    startEmpty: true
+                }
+            },
+            onModelChange: function(key, val) {
+                if (angular.isString(val) && val === '') {
+                    val = undefined;
+                }
+                selectOrSet(key, scope.model, val);
+            },
+            onColorClick: function(event, key, val) {
+                scope.showColorPicker(event, val);
+            }
+        };
+
+        scope.showColorPicker = function (event, color) {
+            $mdColorPicker.show({
+                value: tinycolor(color).toRgbString(),
+                defaultValue: '#fff',
+                random: tinycolor.random(),
+                clickOutsideToClose: false,
+                hasBackdrop: false,
+                skipHide: true,
+                preserveScope: false,
+
+                mdColorAlphaChannel: true,
+                mdColorSpectrum: true,
+                mdColorSliders: true,
+                mdColorGenericPalette: false,
+                mdColorMaterialPalette: true,
+                mdColorHistory: false,
+                mdColorDefaultTab: 2,
+
+                $event: event
+
+            }).then(function (color) {
+                if (event.data && event.data.onValueChanged) {
+                    event.data.onValueChanged(tinycolor(color).toRgb());
+                }
+            });
+        }
+
+        scope.validate = function(){
+            if (scope.schema && scope.model) {
+                var result = utils.validateBySchema(scope.schema, scope.model);
+                if (scope.formControl) {
+                    scope.formControl.$setValidity('jsonForm', result.valid);
+                }
+            }
+        }
+
+        scope.updateValues = function(skipRerender) {
+            destroyModelChangeWatches();
+            if (!skipRerender) {
+                element.html(template);
+            }
+            var readonly = (scope.readonly && scope.readonly === true) ? true : false;
+            var schema = scope.schema ? angular.copy(scope.schema) : {
+                    type: 'object'
+                };
+            schema.strict = true;
+            var form = scope.form ? angular.copy(scope.form) : [ "*" ];
+            var model = scope.model || {};
+            scope.model = inspector.sanitize(schema, model).data;
+            scope.formProps.option.formDefaults.readonly = readonly;
+            scope.formProps.schema = schema;
+            scope.formProps.form = form;
+            scope.formProps.model = angular.copy(scope.model);
+            if (!skipRerender) {
+                recompile();
+            }
+            initModelChangeWatches();
+        }
+
+        scope.updateValues(true);
+
+        scope.$watch('readonly',function() {
+            scope.updateValues();
+        });
+
+        scope.$watch('schema',function(newValue, prevValue) {
+            if (newValue && newValue != prevValue) {
+                scope.updateValues();
+                scope.validate();
+            }
+        });
+
+        scope.$watch('form',function(newValue, prevValue) {
+            if (newValue && newValue != prevValue) {
+                scope.updateValues();
+            }
+        });
+
+        scope.validate();
+
+        recompile();
+
+    }
+
+    return {
+        restrict: "E",
+        scope: {
+            schema: '=',
+            form: '=',
+            model: '=',
+            formControl: '=',
+            readonly: '='
+        },
+        link: linker
+    };
+
+}
+
+function setValue(obj, key, val) {
+    var changed = false;
+    if (obj) {
+        if (angular.isUndefined(val)) {
+            if (angular.isDefined(obj[key])) {
+                delete obj[key];
+                changed = true;
+            }
+        } else {
+            changed = !angular.equals(obj[key], val);
+            obj[key] = val;
+        }
+    }
+    return changed;
+}
+
+function selectOrSet(projection, obj, valueToSet) {
+    var numRe = /^\d+$/;
+
+    if (!obj) {
+        obj = this;
+    }
+
+    if (!obj) {
+        return false;
+    }
+
+    var parts = angular.isString(projection) ? ObjectPath.parse(projection) : projection;
+
+    if (parts.length === 1) {
+        return setValue(obj, parts[0], valueToSet);
+    }
+
+    if (angular.isUndefined(obj[parts[0]])) {
+        obj[parts[0]] = parts.length > 2 && numRe.test(parts[1]) ? [] : {};
+    }
+
+    var value = obj[parts[0]];
+    for (var i = 1; i < parts.length; i++) {
+        if (parts[i] === '') {
+            return false;
+        }
+        if (i === parts.length - 1) {
+            return setValue(value, parts[i], valueToSet);
+        } else {
+            var tmp = value[parts[i]];
+            if (angular.isUndefined(tmp) || tmp === null) {
+                tmp = numRe.test(parts[i + 1]) ? [] : {};
+                value[parts[i]] = tmp;
+            }
+            value = tmp;
+        }
+    }
+    return value;
+}
diff --git a/ui/src/app/components/json-form.scss b/ui/src/app/components/json-form.scss
new file mode 100644
index 0000000..79cdde9
--- /dev/null
+++ b/ui/src/app/components/json-form.scss
@@ -0,0 +1,19 @@
+/**
+ * Copyright © 2016 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-json-form {
+  overflow: auto;
+  padding-bottom: 14px !important;
+}
\ No newline at end of file
diff --git a/ui/src/app/components/json-form.tpl.html b/ui/src/app/components/json-form.tpl.html
new file mode 100644
index 0000000..fb79640
--- /dev/null
+++ b/ui/src/app/components/json-form.tpl.html
@@ -0,0 +1,18 @@
+<!--
+
+    Copyright © 2016 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.
+
+-->
+<react-component name="ReactSchemaForm" props="formProps" watch-depth="value"></react-component>
\ No newline at end of file
diff --git a/ui/src/app/components/led-light.directive.js b/ui/src/app/components/led-light.directive.js
new file mode 100644
index 0000000..edd430c
--- /dev/null
+++ b/ui/src/app/components/led-light.directive.js
@@ -0,0 +1,110 @@
+/*
+ * Copyright © 2016 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 Raphael from 'raphael';
+import tinycolor from 'tinycolor2';
+import $ from 'jquery';
+
+/* eslint-disable angular/angularelement */
+
+export default angular.module('thingsboard.directives.ledLight', [])
+    .directive('tbLedLight', LedLight).name;
+
+/*@ngInject*/
+function LedLight($compile) {
+
+    var linker = function (scope, element) {
+        scope.offOpacity = scope.offOpacity || "0.4";
+        scope.glowColor = tinycolor(scope.colorOn).lighten().toHexString();
+
+        scope.$watch('tbEnabled',function() {
+            scope.draw();
+        });
+
+        scope.$watch('size',function() {
+            scope.update();
+        });
+
+        scope.draw = function () {
+            if (scope.tbEnabled) {
+                scope.circleElement.attr("fill", scope.colorOn);
+                scope.circleElement.attr("stroke", scope.colorOn);
+                scope.circleElement.attr("opacity", "1");
+
+                if (scope.circleElement.theGlow) {
+                    scope.circleElement.theGlow.remove();
+                }
+
+                scope.circleElement.theGlow = scope.circleElement.glow(
+                    {
+                        color: scope.glowColor,
+                        width: scope.radius + scope.glowSize,
+                        opacity: 0.8,
+                        fill: true
+                    });
+            } else {
+                if (scope.circleElement.theGlow) {
+                    scope.circleElement.theGlow.remove();
+                }
+
+                /*scope.circleElement.theGlow = scope.circleElement.glow(
+                 {
+                 color: scope.glowColor,
+                 width: scope.radius + scope.glowSize,
+                 opacity: 0.4,
+                 fill: true
+                 });*/
+
+                scope.circleElement.attr("fill", scope.colorOff);
+                scope.circleElement.attr("stroke", scope.colorOff);
+                scope.circleElement.attr("opacity", scope.offOpacity);
+            }
+        }
+
+        scope.update = function() {
+            scope.size = scope.size || 50;
+            scope.canvasSize = scope.size;
+            scope.radius = scope.canvasSize / 4;
+            scope.glowSize = scope.radius / 5;
+
+            var template = '<div id="canvas_container" style="width: ' + scope.size + 'px; height: ' + scope.size + 'px;"></div>';
+            element.html(template);
+            $compile(element.contents())(scope);
+            scope.paper = new Raphael($('#canvas_container', element)[0], scope.canvasSize, scope.canvasSize);
+            var center = scope.canvasSize / 2;
+            scope.circleElement = scope.paper.circle(center, center, scope.radius);
+            scope.draw();
+        }
+
+        scope.update();
+    }
+
+
+    return {
+        restrict: "E",
+        link: linker,
+        scope: {
+            size: '=?',
+            colorOn: '=',
+            colorOff: '=',
+            offOpacity: '=?',
+            //glowColor: '=',
+            tbEnabled: '='
+        }
+    };
+
+}
+
+/* eslint-enable angular/angularelement */
diff --git a/ui/src/app/components/menu-link.directive.js b/ui/src/app/components/menu-link.directive.js
new file mode 100644
index 0000000..a8f20ff
--- /dev/null
+++ b/ui/src/app/components/menu-link.directive.js
@@ -0,0 +1,76 @@
+/*
+ * Copyright © 2016 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 './menu-link.scss';
+
+import thingsboardMenu from '../services/menu.service';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import menulinkTemplate from './menu-link.tpl.html';
+import menutoggleTemplate from './menu-toggle.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+export default angular.module('thingsboard.directives.menuLink', [thingsboardMenu])
+    .directive('tbMenuLink', MenuLink)
+    .filter('nospace', NoSpace)
+    .name;
+
+/*@ngInject*/
+function MenuLink($compile, $templateCache, menu) {
+
+    var linker = function (scope, element) {
+        var template;
+
+        if (scope.section.type === 'link') {
+            template = $templateCache.get(menulinkTemplate);
+        } else {
+            template = $templateCache.get(menutoggleTemplate);
+
+            var parentNode = element[0].parentNode.parentNode.parentNode;
+            if (parentNode.classList.contains('parent-list-item')) {
+                var heading = parentNode.querySelector('h2');
+                element[0].firstChild.setAttribute('aria-describedby', heading.id);
+            }
+
+            scope.sectionActive = function () {
+                return menu.sectionActive(scope.section);
+            };
+
+            scope.sectionHeight = function () {
+                return menu.sectionHeight(scope.section);
+            };
+        }
+
+        element.html(template);
+
+        $compile(element.contents())(scope);
+    }
+
+    return {
+        restrict: "E",
+        link: linker,
+        scope: {
+            section: '='
+        }
+    };
+}
+
+function NoSpace() {
+    return function (value) {
+        return (!value) ? '' : value.replace(/ /g, '');
+    }
+}
diff --git a/ui/src/app/components/menu-link.scss b/ui/src/app/components/menu-link.scss
new file mode 100644
index 0000000..5d04761
--- /dev/null
+++ b/ui/src/app/components/menu-link.scss
@@ -0,0 +1,32 @@
+/**
+ * Copyright © 2016 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 "~compass-sass-mixins/lib/compass";
+
+.md-button-toggle .md-toggle-icon.tb-toggled {
+  @include transform(rotateZ(180deg));
+}
+
+.tb-menu-toggle-list.ng-hide {
+  max-height: 0;
+}
+
+.tb-menu-toggle-list {
+  overflow: hidden;
+  position: relative;
+  z-index: 1;
+  @include transition(0.75s cubic-bezier(0.35, 0, 0.25, 1));
+  @include transition-property(height);
+}
diff --git a/ui/src/app/components/menu-link.tpl.html b/ui/src/app/components/menu-link.tpl.html
new file mode 100644
index 0000000..48a93af
--- /dev/null
+++ b/ui/src/app/components/menu-link.tpl.html
@@ -0,0 +1,22 @@
+<!--
+
+    Copyright © 2016 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-button 
+	ui-sref-active-eq="tb-active" ui-sref="{{section.state}}">
+	<md-icon ng-show="{{section.icon != null}}" aria-label="{{section.icon}}" class="material-icons">{{section.icon}}</md-icon>	
+	{{section.name | translate}}
+</md-button>
\ No newline at end of file
diff --git a/ui/src/app/components/menu-toggle.tpl.html b/ui/src/app/components/menu-toggle.tpl.html
new file mode 100644
index 0000000..3691521
--- /dev/null
+++ b/ui/src/app/components/menu-toggle.tpl.html
@@ -0,0 +1,33 @@
+<!--
+
+    Copyright © 2016 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-button 
+	ui-sref-active-eq="tb-active" ui-sref="{{section.state}}"
+    class="md-button-toggle"
+	aria-controls="docs-menu-{{section.name | nospace}}" layout="row"
+	aria-expanded="{{sectionActive()}}">
+	<md-icon ng-show="{{section.icon != null}}" aria-label="{{section.icon}}" class="material-icons">{{section.icon}}</md-icon>	
+	{{section.name | translate}}
+	<span aria-hidden="true" 
+	class=" pull-right fa fa-chevron-down md-toggle-icon"
+	ng-class="{'tb-toggled' : sectionActive()}"></span>
+</md-button>
+<ul id="docs-menu-{{section.name | nospace}}" class="tb-menu-toggle-list" style="height: {{sectionHeight()}};">
+	<li ng-repeat="page in section.pages">
+     	<tb-menu-link section="page"></tb-menu-link>
+	</li>
+</ul>
diff --git a/ui/src/app/components/no-animate.directive.js b/ui/src/app/components/no-animate.directive.js
new file mode 100644
index 0000000..deceaa8
--- /dev/null
+++ b/ui/src/app/components/no-animate.directive.js
@@ -0,0 +1,33 @@
+/*
+ * Copyright © 2016 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 ngAnimate from 'angular-animate';
+
+export default angular.module('thingsboard.directives.noAnimate', [ngAnimate])
+    .directive('tbNoAnimate', NoAnimate)
+    .name;
+
+/*@ngInject*/
+function NoAnimate($animate) {
+    return {
+        restrict: 'A',
+        link: function (scope, element) {
+            $animate.enabled(element, false)
+            scope.$watch(function () {
+                $animate.enabled(element, false)
+            })
+        }
+    };
+}
diff --git a/ui/src/app/components/plugin-select.directive.js b/ui/src/app/components/plugin-select.directive.js
new file mode 100644
index 0000000..5ec14dd
--- /dev/null
+++ b/ui/src/app/components/plugin-select.directive.js
@@ -0,0 +1,115 @@
+/*
+ * Copyright © 2016 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 './plugin-select.scss';
+
+import thingsboardApiPlugin from '../api/plugin.service';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import pluginSelectTemplate from './plugin-select.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+
+export default angular.module('thingsboard.directives.pluginSelect', [thingsboardApiPlugin])
+    .directive('tbPluginSelect', PluginSelect)
+    .name;
+
+/*@ngInject*/
+function PluginSelect($compile, $templateCache, $q, pluginService, types) {
+
+    var linker = function (scope, element, attrs, ngModelCtrl) {
+        var template = $templateCache.get(pluginSelectTemplate);
+        element.html(template);
+
+        scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
+        scope.plugin = null;
+        scope.pluginSearchText = '';
+        scope.searchTextChanged = false;
+
+        scope.pluginFetchFunction = pluginService.getAllPlugins;
+        if (angular.isDefined(scope.pluginsScope)) {
+            if (scope.pluginsScope === 'action') {
+                scope.pluginFetchFunction = pluginService.getAllActionPlugins;
+            } else if (scope.pluginsScope === 'system') {
+                scope.pluginFetchFunction = pluginService.getSystemPlugins;
+            } else if (scope.pluginsScope === 'tenant') {
+                scope.pluginFetchFunction = pluginService.getTenantPlugins;
+            }
+        }
+
+        scope.fetchPlugins = function(searchText) {
+            var pageLink = {limit: 10, textSearch: searchText};
+
+            var deferred = $q.defer();
+
+            scope.pluginFetchFunction(pageLink).then(function success(result) {
+                deferred.resolve(result.data);
+            }, function fail() {
+                deferred.reject();
+            });
+
+            return deferred.promise;
+        }
+
+        scope.pluginSearchTextChanged = function() {
+            scope.searchTextChanged = true;
+        }
+
+        scope.isSystem = function(item) {
+            return item && item.tenantId.id === types.id.nullUid;
+        }
+
+        scope.updateView = function () {
+            ngModelCtrl.$setViewValue(scope.plugin);
+        }
+
+        ngModelCtrl.$render = function () {
+            if (ngModelCtrl.$viewValue) {
+                scope.plugin = ngModelCtrl.$viewValue;
+            }
+        }
+
+        scope.$watch('plugin', function () {
+            scope.updateView();
+        })
+
+        if (scope.selectFirstPlugin) {
+            var pageLink = {limit: 1, textSearch: ''};
+            scope.pluginFetchFunction(pageLink).then(function success(result) {
+                var plugins = result.data;
+                if (plugins.length > 0) {
+                    scope.plugin = plugins[0];
+                }
+            }, function fail() {
+            });
+        }
+
+        $compile(element.contents())(scope);
+    }
+
+    return {
+        restrict: "E",
+        require: "^ngModel",
+        link: linker,
+        scope: {
+            pluginsScope: '@',
+            theForm: '=?',
+            tbRequired: '=?',
+            selectFirstPlugin: '='
+        }
+    };
+}
diff --git a/ui/src/app/components/plugin-select.scss b/ui/src/app/components/plugin-select.scss
new file mode 100644
index 0000000..6adcc82
--- /dev/null
+++ b/ui/src/app/components/plugin-select.scss
@@ -0,0 +1,37 @@
+/**
+ * Copyright © 2016 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 "../../scss/mixins";
+
+.tb-plugin-autocomplete {
+  .tb-not-found {
+    display: block;
+    line-height: 1.5;
+    height: 48px;
+  }
+  .tb-plugin-item {
+    display: block;
+    height: 48px;
+    .tb-plugin-system {
+      font-size: 0.8rem;
+      opacity: 0.8;
+      float: right;
+    }
+  }
+  li {
+    height: auto !important;
+    white-space: normal !important;
+  }
+}
diff --git a/ui/src/app/components/plugin-select.tpl.html b/ui/src/app/components/plugin-select.tpl.html
new file mode 100644
index 0000000..2beb2f6
--- /dev/null
+++ b/ui/src/app/components/plugin-select.tpl.html
@@ -0,0 +1,44 @@
+<!--
+
+    Copyright © 2016 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"
+                 md-input-name="plugin"
+                 ng-model="plugin"
+                 md-selected-item="plugin"
+                 md-search-text="pluginSearchText"
+                 md-search-text-change="pluginSearchTextChanged()"
+                 md-items="item in fetchPlugins(pluginSearchText)"
+                 md-item-text="item.name"
+                 md-min-length="0"
+                 placeholder="{{ 'plugin.select-plugin' | translate }}"
+                 md-menu-class="tb-plugin-autocomplete">
+    <md-item-template>
+        <div class="tb-plugin-item">
+            <span md-highlight-text="pluginSearchText" md-highlight-flags="^i">{{item.name}}</span>
+            <span translate class="tb-plugin-system" ng-if="isSystem(item)">plugin.system</span>
+        </div>
+    </md-item-template>
+    <md-not-found>
+        <div class="tb-not-found">
+            <span translate translate-values='{ plugin: pluginSearchText }'>plugin.no-plugins-matching</span>
+        </div>
+    </md-not-found>
+</md-autocomplete>
+<div ng-if="searchTextChanged" class="tb-error-messages" ng-messages="theForm.plugin.$error" role="alert">
+    <div translate ng-message="required" class="tb-error-message">plugin.plugin-required</div>
+    <div translate ng-message="md-require-match" class="tb-error-message">plugin.plugin-require-match</div>
+</div>
diff --git a/ui/src/app/components/react/json-form.scss b/ui/src/app/components/react/json-form.scss
new file mode 100644
index 0000000..889eca5
--- /dev/null
+++ b/ui/src/app/components/react/json-form.scss
@@ -0,0 +1,101 @@
+/**
+ * Copyright © 2016 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 "~compass-sass-mixins/lib/compass";
+
+$swift-ease-out-duration: 0.4s !default;
+$swift-ease-out-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !default;
+
+$input-label-float-offset: 6px !default;
+$input-label-float-scale: 0.75 !default;
+
+.json-form-error {
+    position: relative;
+    bottom: -5px;
+    font-size: 12px;
+    line-height: 12px;
+    color: rgb(244, 67, 54);
+    @include transition(all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms);
+}
+
+.tb-container {
+    position: relative;
+    margin-top: 32px;
+    padding: 10px 0;
+}
+
+.tb-field {
+    &.tb-required {
+        label:after {
+            content: ' *';
+            font-size: 13px;
+            vertical-align: top;
+            color: rgba(0,0,0,0.54);
+        }
+    }
+    &.tb-focused:not(.tb-readonly) {
+        label:after {
+            color: rgb(221,44,0);
+        }
+    }
+}
+
+.tb-date-field {
+    &.tb-required {
+        div>div:first-child:after {
+            content: ' *';
+            font-size: 13px;
+            vertical-align: top;
+            color: rgba(0,0,0,0.54);
+        }
+    }
+    &.tb-focused:not(.tb-readonly) {
+        div>div:first-child:after {
+            color: rgb(221,44,0);
+        }
+    }
+}
+
+label.tb-label {
+    color: rgba(0,0,0,0.54);
+    -webkit-font-smoothing: antialiased;
+    position: absolute;
+    bottom: 100%;
+    left: 0;
+    right: auto;
+    @include transform(translate3d(0, $input-label-float-offset, 0) scale($input-label-float-scale));
+    @include transition(transform $swift-ease-out-timing-function $swift-ease-out-duration,
+                          width $swift-ease-out-timing-function $swift-ease-out-duration);
+    transform-origin: left top;
+
+    &.tb-focused {
+        color: rgb(96,125,139);
+    }
+
+    &.tb-required:after {
+        content: ' *';
+        font-size: 13px;
+        vertical-align: top;
+        color: rgba(0,0,0,0.54);
+    }
+
+    &.tb-focused:not(.tb-readonly):after {
+        color: rgb(221,44,0);
+    }
+}
+
+.tb-head-label {
+    color: rgba(0,0,0,0.54);
+}
diff --git a/ui/src/app/components/react/json-form-ace-editor.jsx b/ui/src/app/components/react/json-form-ace-editor.jsx
new file mode 100644
index 0000000..0bfc77f
--- /dev/null
+++ b/ui/src/app/components/react/json-form-ace-editor.jsx
@@ -0,0 +1,136 @@
+/*
+ * Copyright © 2016 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 './json-form-ace-editor.scss';
+
+import React from 'react';
+import ThingsboardBaseComponent from './json-form-base-component.jsx';
+import reactCSS from 'reactcss';
+import AceEditor from 'react-ace';
+import FlatButton from 'material-ui/FlatButton';
+import 'brace/ext/language_tools';
+import 'brace/theme/github';
+
+class ThingsboardAceEditor extends React.Component {
+
+    constructor(props) {
+        super(props);
+        this.onValueChanged = this.onValueChanged.bind(this);
+        this.onBlur = this.onBlur.bind(this);
+        this.onFocus = this.onFocus.bind(this);
+        this.onTidy = this.onTidy.bind(this);
+        var value = props.value ? props.value + '' : '';
+        this.state = {
+            value: value,
+            focused: false
+        };
+    }
+
+    onValueChanged(value) {
+        this.setState({
+            value: value
+        });
+        this.props.onChangeValidate({
+            target: {
+                value: value
+            }
+        });
+    }
+
+    onBlur() {
+        this.setState({ focused: false })
+    }
+
+    onFocus() {
+        this.setState({ focused: true })
+    }
+
+    onTidy() {
+        if (!this.props.form.readonly) {
+            var value = this.state.value;
+            value = this.props.onTidy(value);
+            this.setState({
+                value: value
+            })
+            this.props.onChangeValidate({
+                target: {
+                    value: value
+                }
+            });
+        }
+    }
+
+    render() {
+
+        const styles = reactCSS({
+            'default': {
+                tidyButtonStyle: {
+                    color: '#7B7B7B',
+                    minWidth: '32px',
+                    minHeight: '15px',
+                    lineHeight: '15px',
+                    fontSize: '0.800rem',
+                    margin: '0',
+                    padding: '4px',
+                    height: '23px',
+                    borderRadius: '5px',
+                    marginLeft: '5px'
+                }
+            }
+        });
+
+        var labelClass = "tb-label";
+        if (this.props.form.required) {
+            labelClass += " tb-required";
+        }
+        if (this.props.form.readonly) {
+            labelClass += " tb-readonly";
+        }
+        if (this.state.focused) {
+            labelClass += " tb-focused";
+        }
+
+        return (
+            <div className="tb-container">
+                <label className={labelClass}>{this.props.form.title}</label>
+                <div className="json-form-ace-editor">
+                    <div className="title-panel">
+                        <label>{this.props.mode}</label>
+                        <FlatButton style={ styles.tidyButtonStyle } className="tidy-button" label={'Tidy'} onTouchTap={this.onTidy}/>
+                    </div>
+                    <AceEditor mode={this.props.mode}
+                               height="150px"
+                               width="300px"
+                               theme="github"
+                               onChange={this.onValueChanged}
+                               onFocus={this.onFocus}
+                               onBlur={this.onBlur}
+                               name={this.props.form.title}
+                               value={this.state.value}
+                               readOnly={this.props.form.readonly}
+                               editorProps={{$blockScrolling: true}}
+                               enableBasicAutocompletion={true}
+                               enableSnippets={true}
+                               enableLiveAutocompletion={true}
+                               style={this.props.form.style || {width: '100%'}}/>
+                </div>
+                <div className="json-form-error"
+                    style={{opacity: this.props.valid ? '0' : '1'}}>{this.props.error}</div>
+            </div>
+        );
+    }
+}
+
+export default ThingsboardBaseComponent(ThingsboardAceEditor);
diff --git a/ui/src/app/components/react/json-form-ace-editor.scss b/ui/src/app/components/react/json-form-ace-editor.scss
new file mode 100644
index 0000000..8ee48a3
--- /dev/null
+++ b/ui/src/app/components/react/json-form-ace-editor.scss
@@ -0,0 +1,42 @@
+/**
+ * Copyright © 2016 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.
+ */
+.json-form-ace-editor {
+    position: relative;
+    border: 1px solid #C0C0C0;
+    height: 100%;
+    .title-panel {
+        position: absolute;
+        font-size: 0.800rem;
+        font-weight: 500;
+        top: 10px;
+        right: 20px;
+        z-index: 5;
+        label {
+          color: #00acc1;
+          background: rgba(220, 220, 220, 0.35);
+          border-radius: 5px;
+          padding: 4px;
+          text-transform: uppercase;
+        }
+        button.tidy-button {
+          background: rgba(220, 220, 220, 0.35) !important;
+          span {
+             padding: 0px !important;
+             font-size: 12px !important;
+          }
+        }
+    }
+}
\ No newline at end of file
diff --git a/ui/src/app/components/react/json-form-array.jsx b/ui/src/app/components/react/json-form-array.jsx
new file mode 100644
index 0000000..6b380ef
--- /dev/null
+++ b/ui/src/app/components/react/json-form-array.jsx
@@ -0,0 +1,165 @@
+/*
+ * Copyright © 2016 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 React from 'react';
+import { utils } from 'react-schema-form';
+import ThingsboardBaseComponent from './json-form-base-component.jsx';
+import RaisedButton from 'material-ui/RaisedButton';
+import FontIcon from 'material-ui/FontIcon';
+import _ from 'lodash';
+import IconButton from 'material-ui/IconButton';
+
+class ThingsboardArray extends React.Component {
+
+    constructor(props) {
+        super(props);
+        this.onAppend = this.onAppend.bind(this);
+        this.onDelete = this.onDelete.bind(this);
+        var model = utils.selectOrSet(this.props.form.key, this.props.model) || [];
+        var keys = [];
+        for (var i=0;i<model.length;i++) {
+            keys.push(i);
+        }
+        this.state = {
+            model:  model,
+            keys: keys
+        };
+    }
+
+    componentDidMount() {
+        if(this.props.form.startEmpty !== true && this.state.model.length === 0) {
+            this.onAppend();
+        }
+    }
+
+    onAppend() {
+        var empty;
+        if(this.props.form && this.props.form.schema && this.props.form.schema.items) {
+            var items = this.props.form.schema.items;
+            if (items.type && items.type.indexOf('object') !== -1) {
+                empty = {};
+                if (!this.props.options || this.props.options.setSchemaDefaults !== false) {
+                    empty = typeof items['default'] !== 'undefined' ? items['default'] : empty;
+                    if (empty) {
+                        utils.traverseSchema(items, function(prop, path) {
+                            if (typeof prop['default'] !== 'undefined') {
+                                utils.selectOrSet(path, empty, prop['default']);
+                            }
+                        });
+                    }
+                }
+
+            } else if (items.type && items.type.indexOf('array') !== -1) {
+                empty = [];
+                if (!this.props.options || this.props.options.setSchemaDefaults !== false) {
+                    empty = items['default'] || empty;
+                }
+            } else {
+                if (!this.props.options || this.props.options.setSchemaDefaults !== false) {
+                    empty = items['default'] || empty;
+                }
+            }
+        }
+        var newModel = this.state.model;
+        newModel.push(empty);
+        var newKeys = this.state.keys;
+        var key = 0;
+        if (newKeys.length > 0) {
+            key = newKeys[newKeys.length-1]+1;
+        }
+        newKeys.push(key);
+        this.setState({
+                model: newModel,
+                keys: newKeys
+            }
+        );
+        this.props.onChangeValidate(this.state.model);
+    }
+
+    onDelete(index) {
+        var newModel = this.state.model;
+        newModel.splice(index, 1);
+        var newKeys = this.state.keys;
+        newKeys.splice(index, 1);
+        this.setState(
+            {
+                model: newModel,
+                keys: newKeys
+            }
+        );
+        this.props.onChangeValidate(this.state.model);
+    }
+
+    setIndex(index) {
+        return function(form) {
+            if (form.key) {
+                form.key[form.key.indexOf('')] = index;
+            }
+        };
+    };
+
+    copyWithIndex(form, index) {
+        var copy = _.cloneDeep(form);
+        copy.arrayIndex = index;
+        utils.traverseForm(copy, this.setIndex(index));
+        return copy;
+    };
+
+    render() {
+        var arrays = [];
+        var fields = [];
+        var model = this.state.model;
+        var keys = this.state.keys;
+        var items = this.props.form.items;
+        for(var i = 0; i < model.length; i++ ) {
+            let removeButton = '';
+            if (!this.props.form.readonly) {
+                let boundOnDelete = this.onDelete.bind(this, i)
+                removeButton = <IconButton iconClassName="material-icons" tooltip="Remove" onTouchTap={boundOnDelete}>clear</IconButton>
+            }
+            let forms = this.props.form.items.map(function(form, index){
+                var copy = this.copyWithIndex(form, i);
+                return this.props.builder(copy, this.props.model, index, this.props.onChange, this.props.onColorClick, this.props.mapper, this.props.builder);
+            }.bind(this));
+            arrays.push(
+            <li key={keys[i]} className="list-group-item">
+                {removeButton}
+                {forms}
+                </li>
+        );
+        }
+        let addButton = '';
+        if (!this.props.form.readonly) {
+            addButton = <RaisedButton label={this.props.form.add || 'New'}
+                                    primary={true}
+                                    icon={<FontIcon className="material-icons">add</FontIcon>}
+                                    onTouchTap={this.onAppend}></RaisedButton>;
+        }
+
+        return (
+            <div>
+                <div className="tb-container">
+                    <div className="tb-head-label">{this.props.form.title}</div>
+                        <ol className="list-group">
+                            {arrays}
+                        </ol>
+                </div>
+                {addButton}
+            </div>
+        );
+    }
+}
+
+export default ThingsboardBaseComponent(ThingsboardArray);
diff --git a/ui/src/app/components/react/json-form-base-component.jsx b/ui/src/app/components/react/json-form-base-component.jsx
new file mode 100644
index 0000000..8afbe68
--- /dev/null
+++ b/ui/src/app/components/react/json-form-base-component.jsx
@@ -0,0 +1,117 @@
+/*
+ * Copyright © 2016 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 React from 'react';
+import { utils } from 'react-schema-form';
+
+export default ThingsboardBaseComponent => class extends React.Component {
+
+    constructor(props) {
+        super(props);
+        this.onChangeValidate = this.onChangeValidate.bind(this);
+        let value = this.defaultValue();
+        let validationResult = utils.validate(this.props.form, value);
+        this.state = {
+            value: value,
+            valid: !!(validationResult.valid || !value),
+            error: !validationResult.valid && value ? validationResult.error.message : null
+        };
+    }
+
+    componentDidMount() {
+        if (typeof this.state.value !== 'undefined') {
+            this.props.onChange(this.props.form.key, this.state.value);
+        }
+    }
+
+    onChangeValidate(e) {
+        let value = null;
+        if (this.props.form.schema.type === 'integer' || this.props.form.schema.type === 'number') {
+            if (e.target.value === null || e.target.value === '') {
+                value = undefined;
+            } else if (e.target.value.indexOf('.') == -1) {
+                value = parseInt(e.target.value);
+            } else {
+                value = parseFloat(e.target.value);
+            }
+        } else if(this.props.form.schema.type === 'boolean') {
+            value = e.target.checked;
+        } else if(this.props.form.schema.type === 'date' || this.props.form.schema.type === 'array') {
+            value = e;
+        } else { // string
+            value = e.target.value;
+        }
+        let validationResult = utils.validate(this.props.form, value);
+        this.setState({
+            value: value,
+            valid: validationResult.valid,
+            error: validationResult.valid ? null : validationResult.error.message
+        });
+        this.props.onChange(this.props.form.key, value);
+    }
+
+    defaultValue() {
+        let value = utils.selectOrSet(this.props.form.key, this.props.model);
+        if (this.props.form.schema.type === 'boolean') {
+            if (typeof value !== 'boolean' && this.props.form['default']) {
+                value = this.props.form['default'];
+            }
+            if (typeof value !== 'boolean' && this.props.form.schema && this.props.form.schema['default']) {
+                value = this.props.form.schema['default'];
+            }
+            if (typeof value !== 'boolean' &&
+                this.props.form.schema &&
+                this.props.form.required) {
+                value = false;
+            }
+        } else if (this.props.form.schema.type === 'integer' || this.props.form.schema.type === 'number') {
+            if (typeof value !== 'number' && this.props.form['default']) {
+                value = this.props.form['default'];
+            }
+            if (typeof value !== 'number' && this.props.form.schema && this.props.form.schema['default']) {
+                value = this.props.form.schema['default'];
+            }
+            if (typeof value !== 'number' && this.props.form.titleMap && this.props.form.titleMap[0].value) {
+                value = this.props.form.titleMap[0].value;
+            }
+            if (value && typeof value === 'string') {
+                if (value.indexOf('.') == -1) {
+                    value = parseInt(value);
+                } else {
+                    value = parseFloat(value);
+                }
+            }
+        } else {
+            if(!value && this.props.form['default']) {
+                value = this.props.form['default'];
+            }
+            if(!value && this.props.form.schema && this.props.form.schema['default']) {
+                value = this.props.form.schema['default'];
+            }
+            if(!value && this.props.form.titleMap && this.props.form.titleMap[0].value) {
+                value = this.props.form.titleMap[0].value;
+            }
+        }
+        return value;
+    }
+
+    render() {
+        if (this.props.form && this.props.form.schema) {
+            return <ThingsboardBaseComponent {...this.props} {...this.state} onChangeValidate={this.onChangeValidate}/>;
+        } else {
+            return <div></div>;
+        }
+    }
+};
diff --git a/ui/src/app/components/react/json-form-checkbox.jsx b/ui/src/app/components/react/json-form-checkbox.jsx
new file mode 100644
index 0000000..748b0a2
--- /dev/null
+++ b/ui/src/app/components/react/json-form-checkbox.jsx
@@ -0,0 +1,36 @@
+/*
+ * Copyright © 2016 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 React from 'react';
+import ThingsboardBaseComponent from './json-form-base-component.jsx';
+import Checkbox from 'material-ui/Checkbox';
+
+class ThingsboardCheckbox extends React.Component {
+    render() {
+        return (
+            <Checkbox
+                name={this.props.form.key.slice(-1)[0]}
+                value={this.props.form.key.slice(-1)[0]}
+                defaultChecked={this.props.value || false}
+                label={this.props.form.title}
+                disabled={this.props.form.readonly}
+                onCheck={(e, checked) => {this.props.onChangeValidate(e)}}
+                style={{paddingTop: '20px'}}
+            />
+        );
+    }
+}
+
+export default ThingsboardBaseComponent(ThingsboardCheckbox);
\ No newline at end of file
diff --git a/ui/src/app/components/react/json-form-color.jsx b/ui/src/app/components/react/json-form-color.jsx
new file mode 100644
index 0000000..7980a54
--- /dev/null
+++ b/ui/src/app/components/react/json-form-color.jsx
@@ -0,0 +1,161 @@
+/*
+ * Copyright © 2016 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 './json-form-color.scss';
+
+import $ from 'jquery';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import ThingsboardBaseComponent from './json-form-base-component.jsx';
+import reactCSS from 'reactcss';
+import tinycolor from 'tinycolor2';
+import TextField from 'material-ui/TextField';
+import IconButton from 'material-ui/IconButton';
+
+class ThingsboardColor extends React.Component {
+
+    constructor(props) {
+        super(props);
+        this.onValueChanged = this.onValueChanged.bind(this);
+        this.onSwatchClick = this.onSwatchClick.bind(this);
+        this.onClear = this.onClear.bind(this);
+        var value = props.value ? props.value + '' : null;
+        var color = value != null ? tinycolor(value).toRgb() : null;
+        this.state = {
+            color: color
+        };
+    }
+
+    componentDidMount() {
+        var node = ReactDOM.findDOMNode(this);
+        var colContainer = $(node).children('#color-container');
+        colContainer.click(this, function(event) {
+            if (!event.data.props.form.readonly) {
+                event.data.onSwatchClick(event);
+            }
+        });
+    }
+
+    componentWillUnmount () {
+        var node = ReactDOM.findDOMNode(this);
+        var colContainer = $(node).children('#color-container');
+        colContainer.off( "click" );
+    }
+
+    onValueChanged(value) {
+        var color = null;
+        if (value != null) {
+            color = tinycolor(value);
+        }
+        this.setState({
+            color: value
+        })
+        var colorValue = '';
+        if (color != null && color.getAlpha() != 1) {
+            colorValue = color.toRgbString();
+        } else if (color != null) {
+            colorValue = color.toHexString();
+        }
+        this.props.onChangeValidate({
+            target: {
+                value: colorValue
+            }
+        });
+    }
+
+    onSwatchClick(event) {
+        this.props.onColorClick(event, this.props.form.key, this.state.color);
+    }
+
+    onClear(event) {
+        if (event) {
+            event.stopPropagation();
+        }
+        this.onValueChanged(null);
+    }
+
+    render() {
+
+        var background = 'rgba(0,0,0,0)';
+        if (this.state.color != null) {
+            background = `rgba(${ this.state.color.r }, ${ this.state.color.g }, ${ this.state.color.b }, ${ this.state.color.a })`;
+        }
+
+        const styles = reactCSS({
+            'default': {
+                color: {
+                    background: `${ background }`
+                },
+                swatch: {
+                    display: 'inline-block',
+                    marginRight: '10px',
+                    marginTop: 'auto',
+                    marginBottom: 'auto',
+                    cursor: 'pointer',
+                    opacity: `${ this.props.form.readonly ? '0.6' : '1' }`
+                },
+                swatchText: {
+                    display: 'inline-block',
+                    width: '100%'
+                },
+                container: {
+                    display: 'flex'
+                },
+                colorContainer: {
+                    display: 'flex',
+                    width: '100%'
+                }
+            },
+        });
+
+        var fieldClass = "tb-field";
+        if (this.props.form.required) {
+            fieldClass += " tb-required";
+        }
+        if (this.props.form.readonly) {
+            fieldClass += " tb-readonly";
+        }
+        if (this.state.focused) {
+            fieldClass += " tb-focused";
+        }
+
+        var stringColor = '';
+        if (this.state.color != null) {
+            var color = tinycolor(this.state.color);
+            stringColor = color.toRgbString();
+        }
+
+        return (
+            <div style={ styles.container }>
+                 <div id="color-container" style={ styles.colorContainer }>
+                    <div className="tb-color-preview" style={ styles.swatch }>
+                        <div className="tb-color-result" style={ styles.color }/>
+                    </div>
+                    <TextField
+                        className={fieldClass}
+                        floatingLabelText={this.props.form.title}
+                        hintText={this.props.form.placeholder}
+                        errorText={this.props.error}
+                        value={stringColor}
+                        disabled={this.props.form.readonly}
+                        style={ styles.swatchText } />
+                 </div>
+                <IconButton iconClassName="material-icons" tooltip="Clear" onTouchTap={this.onClear}>clear</IconButton>
+            </div>
+        );
+    }
+}
+
+export default ThingsboardBaseComponent(ThingsboardColor);
diff --git a/ui/src/app/components/react/json-form-color.scss b/ui/src/app/components/react/json-form-color.scss
new file mode 100644
index 0000000..60af66f
--- /dev/null
+++ b/ui/src/app/components/react/json-form-color.scss
@@ -0,0 +1,39 @@
+/**
+ * Copyright © 2016 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.
+ */
+@mixin tb-checkered-bg() {
+  background-color: #fff;
+  background-image: linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd),
+  linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd);
+  background-size: 8px 8px;
+  background-position: 0 0, 4px 4px;
+}
+
+.tb-color-preview {
+  content: '';
+  width: 24px;
+  height: 24px;
+  border: 2px solid #fff;
+  border-radius: 50%;
+  box-shadow: 0 3px 1px -2px rgba(0, 0, 0, .14), 0 2px 2px 0 rgba(0, 0, 0, .098), 0 1px 5px 0 rgba(0, 0, 0, .084);
+  position: relative;
+  overflow: hidden;
+  @include tb-checkered-bg();
+
+  .tb-color-result {
+    width: 100%;
+    height: 100%;
+  }
+}
diff --git a/ui/src/app/components/react/json-form-date.jsx b/ui/src/app/components/react/json-form-date.jsx
new file mode 100644
index 0000000..ff8fc70
--- /dev/null
+++ b/ui/src/app/components/react/json-form-date.jsx
@@ -0,0 +1,60 @@
+/*
+ * Copyright © 2016 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 React from 'react';
+import ThingsboardBaseComponent from './json-form-base-component.jsx';
+import DatePicker from 'material-ui/DatePicker/DatePicker';
+
+class ThingsboardDate extends React.Component {
+
+    constructor(props) {
+        super(props);
+        this.onDatePicked = this.onDatePicked.bind(this);
+    }
+
+
+    onDatePicked(empty, date) {
+        this.props.onChangeValidate(date);
+    }
+
+    render() {
+
+        var fieldClass = "tb-date-field";
+        if (this.props.form.required) {
+            fieldClass += " tb-required";
+        }
+        if (this.props.form.readonly) {
+            fieldClass += " tb-readonly";
+        }
+
+        return (
+            <div style={{width: '100%', display: 'block'}}>
+                <DatePicker
+                    className={fieldClass}
+                    mode={'landscape'}
+                    autoOk={true}
+                    hintText={this.props.form.title}
+                    onChange={this.onDatePicked}
+                    onShow={null}
+                    onDismiss={null}
+                    disabled={this.props.form.readonly}
+                    style={this.props.form.style || {width: '100%'}}/>
+
+            </div>
+        );
+    }
+}
+
+export default ThingsboardBaseComponent(ThingsboardDate);
\ No newline at end of file
diff --git a/ui/src/app/components/react/json-form-fieldset.jsx b/ui/src/app/components/react/json-form-fieldset.jsx
new file mode 100644
index 0000000..c12098c
--- /dev/null
+++ b/ui/src/app/components/react/json-form-fieldset.jsx
@@ -0,0 +1,38 @@
+/*
+ * Copyright © 2016 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 React from 'react';
+
+class ThingsboardFieldSet extends React.Component {
+
+    render() {
+        let forms = this.props.form.items.map(function(form, index){
+            return this.props.builder(form, this.props.model, index, this.props.onChange, this.props.onColorClick, this.props.mapper, this.props.builder);
+        }.bind(this));
+
+        return (
+            <div style={{paddingTop: '20px'}}>
+                <div className="tb-head-label">
+                    {this.props.form.title}
+                </div>
+                <div>
+                    {forms}
+                </div>
+            </div>
+        );
+    }
+}
+
+export default ThingsboardFieldSet;
diff --git a/ui/src/app/components/react/json-form-javascript.jsx b/ui/src/app/components/react/json-form-javascript.jsx
new file mode 100644
index 0000000..b808fc6
--- /dev/null
+++ b/ui/src/app/components/react/json-form-javascript.jsx
@@ -0,0 +1,41 @@
+/*
+ * Copyright © 2016 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 React from 'react';
+import ThingsboardAceEditor from './json-form-ace-editor.jsx';
+import 'brace/mode/javascript';
+import beautify from 'js-beautify';
+
+const js_beautify = beautify.js;
+
+class ThingsboardJavaScript extends React.Component {
+
+    constructor(props) {
+        super(props);
+        this.onTidyJavascript = this.onTidyJavascript.bind(this);
+    }
+
+    onTidyJavascript(javascript) {
+        return js_beautify(javascript, {indent_size: 4, wrap_line_length: 60});
+    }
+
+    render() {
+        return (
+                <ThingsboardAceEditor {...this.props} mode='javascript' onTidy={this.onTidyJavascript} {...this.state}></ThingsboardAceEditor>
+            );
+    }
+}
+
+export default ThingsboardJavaScript;
diff --git a/ui/src/app/components/react/json-form-json.jsx b/ui/src/app/components/react/json-form-json.jsx
new file mode 100644
index 0000000..91be72e
--- /dev/null
+++ b/ui/src/app/components/react/json-form-json.jsx
@@ -0,0 +1,41 @@
+/*
+ * Copyright © 2016 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 React from 'react';
+import ThingsboardAceEditor from './json-form-ace-editor.jsx';
+import 'brace/mode/json';
+import beautify from 'js-beautify';
+
+const js_beautify = beautify.js;
+
+class ThingsboardJson extends React.Component {
+
+    constructor(props) {
+        super(props);
+        this.onTidyJson = this.onTidyJson.bind(this);
+    }
+
+    onTidyJson(json) {
+        return js_beautify(json, {indent_size: 4});
+    }
+
+    render() {
+        return (
+            <ThingsboardAceEditor {...this.props} mode='json' onTidy={this.onTidyJson} {...this.state}></ThingsboardAceEditor>
+        );
+    }
+}
+
+export default ThingsboardJson;
diff --git a/ui/src/app/components/react/json-form-number.jsx b/ui/src/app/components/react/json-form-number.jsx
new file mode 100644
index 0000000..1922ca2
--- /dev/null
+++ b/ui/src/app/components/react/json-form-number.jsx
@@ -0,0 +1,85 @@
+/*
+ * Copyright © 2016 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 React from 'react';
+import ThingsboardBaseComponent from './json-form-base-component.jsx';
+import TextField from 'material-ui/TextField';
+
+class ThingsboardNumber extends React.Component {
+
+    constructor(props) {
+        super(props);
+        this.preValidationCheck = this.preValidationCheck.bind(this);
+        this.onBlur = this.onBlur.bind(this);
+        this.onFocus = this.onFocus.bind(this);
+        this.state = {
+            lastSuccessfulValue : this.props.value,
+            focused: false
+        }
+    }
+
+    isNumeric(n) {
+        return n === null || n === '' || !isNaN(n) && isFinite(n);
+    }
+
+    onBlur() {
+        this.setState({ focused: false })
+    }
+
+    onFocus() {
+        this.setState({ focused: true })
+    }
+
+    preValidationCheck(e) {
+        if (this.isNumeric(e.target.value)) {
+            this.setState({
+                lastSuccessfulValue: e.target.value
+            });
+            this.props.onChangeValidate(e);
+        }
+    }
+
+    render() {
+
+        var fieldClass = "tb-field";
+        if (this.props.form.required) {
+            fieldClass += " tb-required";
+        }
+        if (this.props.form.readonly) {
+            fieldClass += " tb-readonly";
+        }
+        if (this.state.focused) {
+            fieldClass += " tb-focused";
+        }
+
+        return (
+            <TextField
+                className={fieldClass}
+                type={this.props.form.type}
+                floatingLabelText={this.props.form.title}
+                hintText={this.props.form.placeholder}
+                errorText={this.props.error}
+                onChange={this.preValidationCheck}
+                defaultValue={this.state.lastSuccessfulValue}
+                ref="numberField"
+                disabled={this.props.form.readonly}
+                onFocus={this.onFocus}
+                onBlur={this.onBlur}
+                style={this.props.form.style || {width: '100%'}}/>
+        );
+    }
+}
+
+export default ThingsboardBaseComponent(ThingsboardNumber);
\ No newline at end of file
diff --git a/ui/src/app/components/react/json-form-rc-select.jsx b/ui/src/app/components/react/json-form-rc-select.jsx
new file mode 100644
index 0000000..d9eb09f
--- /dev/null
+++ b/ui/src/app/components/react/json-form-rc-select.jsx
@@ -0,0 +1,123 @@
+/*
+ * Copyright © 2016 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 'rc-select/assets/index.css';
+
+import React from 'react';
+import ThingsboardBaseComponent from './json-form-base-component.jsx';
+import Select, {Option} from 'rc-select';
+
+class ThingsboardRcSelect extends React.Component {
+
+    constructor(props) {
+        super(props);
+        this.onSelect = this.onSelect.bind(this);
+        this.onDeselect = this.onDeselect.bind(this);
+        this.onBlur = this.onBlur.bind(this);
+        this.onFocus = this.onFocus.bind(this);
+        let emptyValue = this.props.form.schema.type === 'array'? [] : null;
+        this.state = {
+            currentValue: this.props.value || emptyValue,
+            items: this.props.form.items,
+            focused: false
+        };
+    }
+
+    onSelect(value, option) {
+        if(this.props.form.schema.type === 'array') {
+            let v = this.state.currentValue;
+            v.push(value);
+            this.setState({
+                currentValue: v
+            });
+            this.props.onChangeValidate(v);
+        } else {
+            this.setState({currentValue: value});
+            this.props.onChangeValidate({target: {value: value}});
+        }
+    }
+
+    onDeselect(value, option) {
+        if (this.props.form.schema.type === 'array') {
+            let v = this.state.currentValue;
+            let index = v.indexOf(value);
+            if (index > -1) {
+                v.splice(index, 1);
+            }
+            this.setState({
+                currentValue: v
+            });
+            this.props.onChangeValidate(v);
+        }
+    }
+
+    onBlur() {
+        this.setState({ focused: false })
+    }
+
+    onFocus() {
+        this.setState({ focused: true })
+    }
+
+    render() {
+        let options = [];
+        if(this.state.items && this.state.items.length > 0) {
+            options = this.state.items.map((item, idx) => (
+                <Option key={idx} value={item.value}>{item.label}</Option>
+            ));
+        }
+
+        var labelClass = "tb-label";
+        if (this.props.form.required) {
+            labelClass += " tb-required";
+        }
+        if (this.props.form.readonly) {
+            labelClass += " tb-readonly";
+        }
+        if (this.state.focused) {
+            labelClass += " tb-focused";
+        }
+
+        return (
+            <div className="tb-container">
+                <label className={labelClass}>{this.props.form.title}</label>
+                <Select
+                    className={this.props.form.className}
+                    dropdownClassName={this.props.form.dropdownClassName}
+                    dropdownStyle={this.props.form.dropdownStyle}
+                    dropdownMenuStyle={this.props.form.dropdownMenuStyle}
+                    allowClear={this.props.form.allowClear}
+                    tags={this.props.form.tags}
+                    maxTagTextLength={this.props.form.maxTagTextLength}
+                    multiple={this.props.form.multiple}
+                    combobox={this.props.form.combobox}
+                    disabled={this.props.form.readonly}
+                    value={this.state.currentValue}
+                    onSelect={this.onSelect}
+                    onDeselect={this.onDeselect}
+                    onFocus={this.onFocus}
+                    onBlur={this.onBlur}
+                    style={this.props.form.style || {width: "100%"}}>
+                    {options}
+                </Select>
+                <div className="json-form-error"
+                     style={{opacity: this.props.valid ? '0' : '1',
+                             bottom: '-5px'}}>{this.props.error}</div>
+            </div>
+        );
+    }
+}
+
+export default ThingsboardBaseComponent(ThingsboardRcSelect);
diff --git a/ui/src/app/components/react/json-form-react.jsx b/ui/src/app/components/react/json-form-react.jsx
new file mode 100644
index 0000000..fbccd8e
--- /dev/null
+++ b/ui/src/app/components/react/json-form-react.jsx
@@ -0,0 +1,65 @@
+/*
+ * Copyright © 2016 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 './json-form.scss';
+
+import React from 'react';
+import getMuiTheme from 'material-ui/styles/getMuiTheme';
+import thingsboardTheme from './styles/thingsboardTheme';
+import ThingsboardSchemaForm from './json-form-schema-form.jsx';
+
+class ReactSchemaForm extends React.Component {
+
+    getChildContext() {
+        return {
+            muiTheme: this.state.muiTheme
+        };
+    }
+
+    constructor(props) {
+        super(props);
+        this.state = {
+            muiTheme: getMuiTheme(thingsboardTheme)
+        };
+    }
+
+    render () {
+        if (this.props.form.length > 0) {
+            return <ThingsboardSchemaForm {...this.props} />;
+        } else {
+            return <div></div>;
+        }
+    }
+}
+
+ReactSchemaForm.propTypes = {
+        schema: React.PropTypes.object,
+        form: React.PropTypes.array,
+        model: React.PropTypes.object,
+        option: React.PropTypes.object,
+        onModelChange: React.PropTypes.func,
+        onColorClick: React.PropTypes.func
+}
+
+ReactSchemaForm.defaultProps = {
+    schema: {},
+    form: [ "*" ]
+}
+
+ReactSchemaForm.childContextTypes = {
+        muiTheme: React.PropTypes.object
+}
+
+export default ReactSchemaForm;
diff --git a/ui/src/app/components/react/json-form-schema-form.jsx b/ui/src/app/components/react/json-form-schema-form.jsx
new file mode 100644
index 0000000..3e1c046
--- /dev/null
+++ b/ui/src/app/components/react/json-form-schema-form.jsx
@@ -0,0 +1,99 @@
+/*
+ * Copyright © 2016 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 React from 'react';
+import { utils } from 'react-schema-form';
+
+import ThingsboardArray from './json-form-array.jsx';
+import ThingsboardJavaScript from './json-form-javascript.jsx';
+import ThingsboardJson from './json-form-json.jsx';
+import ThingsboardColor from './json-form-color.jsx'
+import ThingsboardRcSelect from './json-form-rc-select.jsx';
+import ThingsboardNumber from './json-form-number.jsx';
+import ThingsboardText from './json-form-text.jsx';
+import Select from 'react-schema-form/lib/Select';
+import Radios from 'react-schema-form/lib/Radios';
+import ThingsboardDate from './json-form-date.jsx';
+import ThingsboardCheckbox from './json-form-checkbox.jsx';
+import Help from 'react-schema-form/lib/Help';
+import ThingsboardFieldSet from './json-form-fieldset.jsx';
+
+import _ from 'lodash';
+
+class ThingsboardSchemaForm extends React.Component {
+
+    constructor(props) {
+        super(props);
+
+        this.mapper = {
+            'number': ThingsboardNumber,
+            'text': ThingsboardText,
+            'password': ThingsboardText,
+            'textarea': ThingsboardText,
+            'select': Select,
+            'radios': Radios,
+            'date': ThingsboardDate,
+            'checkbox': ThingsboardCheckbox,
+            'help': Help,
+            'array': ThingsboardArray,
+            'javascript': ThingsboardJavaScript,
+            'json': ThingsboardJson,
+            'color': ThingsboardColor,
+            'rc-select': ThingsboardRcSelect,
+            'fieldset': ThingsboardFieldSet
+        };
+
+        this.onChange = this.onChange.bind(this);
+        this.onColorClick = this.onColorClick.bind(this);
+    }
+
+    onChange(key, val) {
+        //console.log('SchemaForm.onChange', key, val);
+        this.props.onModelChange(key, val);
+    }
+
+    onColorClick(event, key, val) {
+        this.props.onColorClick(event, key, val);
+    }
+
+    builder(form, model, index, onChange, onColorClick, mapper) {
+        var type = form.type;
+        let Field = this.mapper[type];
+        if(!Field) {
+            console.log('Invalid field: \"' + form.key[0] + '\"!');
+            return null;
+        }
+        if(form.condition && eval(form.condition) === false) {
+            return null;
+        }
+        return <Field model={model} form={form} key={index} onChange={onChange} onColorClick={onColorClick} mapper={mapper} builder={this.builder}/>
+    }
+
+    render() {
+        let merged = utils.merge(this.props.schema, this.props.form, this.props.ignore, this.props.option);
+        let mapper = this.mapper;
+        if(this.props.mapper) {
+            mapper = _.merge(this.mapper, this.props.mapper);
+        }
+        let forms = merged.map(function(form, index) {
+            return this.builder(form, this.props.model, index, this.onChange, this.onColorClick, mapper);
+        }.bind(this));
+
+        return (
+            <div style={{width: '100%'}} className='SchemaForm'>{forms}</div>
+        );
+    }
+}
+export default ThingsboardSchemaForm;
diff --git a/ui/src/app/components/react/json-form-text.jsx b/ui/src/app/components/react/json-form-text.jsx
new file mode 100644
index 0000000..6037dc2
--- /dev/null
+++ b/ui/src/app/components/react/json-form-text.jsx
@@ -0,0 +1,79 @@
+/*
+ * Copyright © 2016 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 React from 'react';
+import ThingsboardBaseComponent from './json-form-base-component.jsx';
+import TextField from 'material-ui/TextField';
+
+class ThingsboardText extends React.Component {
+
+    constructor(props) {
+        super(props);
+        this.onBlur = this.onBlur.bind(this);
+        this.onFocus = this.onFocus.bind(this);
+        this.state = {
+            focused: false
+        };
+    }
+
+    onBlur() {
+        this.setState({ focused: false })
+    }
+
+    onFocus() {
+        this.setState({ focused: true })
+    }
+
+    render() {
+
+        var fieldClass = "tb-field";
+        if (this.props.form.required) {
+            fieldClass += " tb-required";
+        }
+        if (this.props.form.readonly) {
+            fieldClass += " tb-readonly";
+        }
+        if (this.state.focused) {
+            fieldClass += " tb-focused";
+        }
+
+        var multiline = this.props.form.type === 'textarea';
+        var rows = multiline ? this.props.form.rows : 1;
+        var rowsMax = multiline ? this.props.form.rowsMax : 1;
+
+        return (
+            <div>
+                <TextField
+                    className={fieldClass}
+                    type={this.props.form.type}
+                    floatingLabelText={this.props.form.title}
+                    hintText={this.props.form.placeholder}
+                    errorText={this.props.error}
+                    onChange={this.props.onChangeValidate}
+                    defaultValue={this.props.value}
+                    disabled={this.props.form.readonly}
+                    multiLine={multiline}
+                    rows={rows}
+                    rowsMax={rowsMax}
+                    onFocus={this.onFocus}
+                    onBlur={this.onBlur}
+                    style={this.props.form.style || {width: '100%'}} />
+            </div>
+        );
+    }
+}
+
+export default ThingsboardBaseComponent(ThingsboardText);
\ No newline at end of file
diff --git a/ui/src/app/components/react/styles/thingsboardTheme.js b/ui/src/app/components/react/styles/thingsboardTheme.js
new file mode 100644
index 0000000..7ed06f4
--- /dev/null
+++ b/ui/src/app/components/react/styles/thingsboardTheme.js
@@ -0,0 +1,63 @@
+/*
+ * Copyright © 2016 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 { /*blueGrey500, blueGrey700, blueGrey100, orange500,*/
+         grey100, grey500, grey900, grey600, white, grey400, darkBlack, cyan500, fullBlack/*, indigo500*/, indigo700, indigo100, deepOrange500 } from 'material-ui/styles/colors';
+import {fade} from 'material-ui/utils/colorManipulator';
+import spacing from 'material-ui/styles/spacing';
+
+const PRIMARY_BACKGROUND_COLOR = "#305680";//"#3f51b5";
+
+/*var blueGrayPalette = {
+    primary1Color: blueGrey500,
+    primary2Color: blueGrey700,
+    primary3Color: blueGrey100,
+    accent1Color: orange500,
+    accent2Color: grey100,
+    accent3Color: grey500,
+    textColor: grey900,
+    secondaryTextColor: grey600,
+    alternateTextColor: white,
+    canvasColor: white,
+    borderColor: grey400,
+    disabledColor: fade(darkBlack, 0.3),
+    pickerHeaderColor: cyan500,
+    clockCircleColor: fade(darkBlack, 0.07),
+    shadowColor: fullBlack,
+};*/
+
+var indigoPalette = {
+    primary1Color: PRIMARY_BACKGROUND_COLOR,
+    primary2Color: indigo700,
+    primary3Color: indigo100,
+    accent1Color: deepOrange500,
+    accent2Color: grey100,
+    accent3Color: grey500,
+    textColor: grey900,
+    secondaryTextColor: grey600,
+    alternateTextColor: white,
+    canvasColor: white,
+    borderColor: grey400,
+    disabledColor: fade(darkBlack, 0.3),
+    pickerHeaderColor: cyan500,
+    clockCircleColor: fade(darkBlack, 0.07),
+    shadowColor: fullBlack,
+};
+
+export default {
+    spacing: spacing,
+    fontFamily: 'RobotoDraft, Roboto, \'Helvetica Neue\', sans-serif',
+    palette: indigoPalette,
+};
\ No newline at end of file
diff --git a/ui/src/app/components/scope-element.directive.js b/ui/src/app/components/scope-element.directive.js
new file mode 100644
index 0000000..73f11c3
--- /dev/null
+++ b/ui/src/app/components/scope-element.directive.js
@@ -0,0 +1,33 @@
+/*
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default angular.module('thingsboard.directives.scopeElement', [])
+    .directive('tbScopeElement', ScopeElement)
+    .name;
+
+/*@ngInject*/
+function ScopeElement() {
+    var directiveDefinitionObject = {
+        restrict: "A",
+        compile: function compile() {
+            return {
+                pre: function preLink(scope, iElement, iAttrs) {
+                    scope[iAttrs.tbScopeElement] = iElement;
+                }
+            };
+        }
+    };
+    return directiveDefinitionObject;
+}
diff --git a/ui/src/app/components/side-menu.directive.js b/ui/src/app/components/side-menu.directive.js
new file mode 100644
index 0000000..9127198
--- /dev/null
+++ b/ui/src/app/components/side-menu.directive.js
@@ -0,0 +1,50 @@
+/*
+ * Copyright © 2016 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 './side-menu.scss';
+
+import thingsboardMenu from '../services/menu.service';
+import thingsboardMenuLink from './menu-link.directive';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import sidemenuTemplate from './side-menu.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+export default angular.module('thingsboard.directives.sideMenu', [thingsboardMenu, thingsboardMenuLink])
+    .directive('tbSideMenu', SideMenu)
+    .name;
+
+/*@ngInject*/
+function SideMenu($compile, $templateCache, menu) {
+
+    var linker = function (scope, element) {
+
+        scope.sections = menu.getSections();
+
+        var template = $templateCache.get(sidemenuTemplate);
+
+        element.html(template);
+
+        $compile(element.contents())(scope);
+    }
+
+    return {
+        restrict: "E",
+        link: linker,
+        scope: {}
+    };
+}
diff --git a/ui/src/app/components/side-menu.scss b/ui/src/app/components/side-menu.scss
new file mode 100644
index 0000000..9705523
--- /dev/null
+++ b/ui/src/app/components/side-menu.scss
@@ -0,0 +1,84 @@
+/**
+ * Copyright © 2016 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 "~compass-sass-mixins/lib/compass";
+
+.tb-side-menu .md-button.tb-active {
+  background-color: rgba(255, 255, 255, 0.15);
+  font-weight: 500;
+}
+
+.tb-side-menu, .tb-side-menu ul {
+  list-style: none;
+  padding: 0;
+  margin-top: 0;
+}
+
+.tb-side-menu .tb-menu-toggle-list a.md-button {
+  padding: 0 16px 0 32px;
+  text-transform: none;
+  text-rendering: optimizeLegibility;
+  font-weight: 500;
+}
+
+.tb-side-menu .tb-menu-toggle-list .md-button {
+  padding: 0 16px 0 32px;
+  text-transform: none;
+}
+
+.tb-side-menu .tb-menu-toggle-list .tb-menu-toggle-list a.md-button {
+  padding: 0 16px 0 48px;
+}
+
+.tb-side-menu > li {
+  border-bottom: 1px solid rgba(0, 0, 0, 0.12);
+}
+
+.tb-side-menu .md-button-toggle .md-toggle-icon {
+  background-size: 100% auto;
+  display: inline-block;
+  margin: auto 0 auto auto;
+  width: 15px;
+  @include transition(transform .3s, ease-in-out);
+}
+
+.tb-side-menu .md-button {
+  display: block;
+  border-radius: 0;
+  color: inherit;
+  cursor: pointer;
+  line-height: 40px;
+  margin: 0;
+  max-height: 40px;
+  overflow: hidden;
+  padding: 0px 16px;
+  text-align: left;
+  text-decoration: none;
+  white-space: normal;
+  width: 100%;
+}
+
+.tb-side-menu tb-menu-link span.md-toggle-icon {
+  padding-top: 12px;
+  padding-bottom: 12px;
+}
+
+.tb-side-menu ng-md-icon {
+  margin-right: 8px;
+}
+
+.tb-side-menu md-icon {
+  margin-right: 8px;
+}
diff --git a/ui/src/app/components/side-menu.tpl.html b/ui/src/app/components/side-menu.tpl.html
new file mode 100644
index 0000000..200fe4e
--- /dev/null
+++ b/ui/src/app/components/side-menu.tpl.html
@@ -0,0 +1,23 @@
+<!--
+
+    Copyright © 2016 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.
+
+-->
+<ul flex layout="column" layout-align="start stretch" class="tb-side-menu">
+   	  <li ng-repeat="section in sections" class="parent-list-item" 
+	      				ng-class="{'parentActive' : isOpen(section)}"> 
+			<tb-menu-link section="section"></tb-menu-link>
+      </li>
+</ul>
\ No newline at end of file
diff --git a/ui/src/app/components/tb-event-directives.js b/ui/src/app/components/tb-event-directives.js
new file mode 100644
index 0000000..1fefd51
--- /dev/null
+++ b/ui/src/app/components/tb-event-directives.js
@@ -0,0 +1,59 @@
+/*
+ * Copyright © 2016 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.
+ */
+const SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
+const MOZ_HACK_REGEXP = /^moz([A-Z])/;
+const PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
+
+var tbEventDirectives = {};
+
+angular.forEach(
+    'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
+    function(eventName) {
+        var directiveName = directiveNormalize('tb-' + eventName);
+        tbEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse) {
+            return {
+                restrict: 'A',
+                compile: function($element, attr) {
+                    var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true);
+                    return function ngEventHandler(scope, element) {
+                        element.on(eventName, function(event) {
+                            var callback = function() {
+                                fn(scope, {$event:event});
+                            };
+                            callback();
+                        });
+                    };
+                }
+            };
+        }];
+    }
+);
+
+export default angular.module('thingsboard.directives.event', [])
+    .directive(tbEventDirectives)
+    .name;
+
+function camelCase(name) {
+    return name.
+    replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
+        return offset ? letter.toUpperCase() : letter;
+    }).
+    replace(MOZ_HACK_REGEXP, 'Moz$1');
+}
+
+function directiveNormalize(name) {
+    return camelCase(name.replace(PREFIX_REGEXP, ''));
+}
\ No newline at end of file
diff --git a/ui/src/app/components/timeinterval.directive.js b/ui/src/app/components/timeinterval.directive.js
new file mode 100644
index 0000000..1381b2c
--- /dev/null
+++ b/ui/src/app/components/timeinterval.directive.js
@@ -0,0 +1,213 @@
+/*
+ * Copyright © 2016 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 './timeinterval.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import timeintervalTemplate from './timeinterval.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+export default angular.module('thingsboard.directives.timeinterval', [])
+    .directive('tbTimeinterval', Timeinterval)
+    .name;
+
+/*@ngInject*/
+function Timeinterval($compile, $templateCache, $translate) {
+
+    var linker = function (scope, element, attrs, ngModelCtrl) {
+
+        var template = $templateCache.get(timeintervalTemplate);
+        element.html(template);
+
+        scope.rendered = false;
+        scope.days = 0;
+        scope.hours = 0;
+        scope.mins = 1;
+        scope.secs = 0;
+
+        scope.predefIntervals = [
+            {
+                name: $translate.instant('timeinterval.seconds-interval', {seconds: 10}, 'messageformat'),
+                value: 10 * 1000
+            },
+            {
+                name: $translate.instant('timeinterval.seconds-interval', {seconds: 30}, 'messageformat'),
+                value: 30 * 1000
+            },
+            {
+                name: $translate.instant('timeinterval.minutes-interval', {minutes: 1}, 'messageformat'),
+                value: 60 * 1000
+            },
+            {
+                name: $translate.instant('timeinterval.minutes-interval', {minutes: 2}, 'messageformat'),
+                value: 2 * 60 * 1000
+            },
+            {
+                name: $translate.instant('timeinterval.minutes-interval', {minutes: 5}, 'messageformat'),
+                value: 5 * 60 * 1000
+            },
+            {
+                name: $translate.instant('timeinterval.minutes-interval', {minutes: 10}, 'messageformat'),
+                value: 10 * 60 * 1000
+            },
+            {
+                name: $translate.instant('timeinterval.minutes-interval', {minutes: 30}, 'messageformat'),
+                value: 30 * 60 * 1000
+            },
+            {
+                name: $translate.instant('timeinterval.hours-interval', {hours: 1}, 'messageformat'),
+                value: 60 * 60 * 1000
+            },
+            {
+                name: $translate.instant('timeinterval.hours-interval', {hours: 2}, 'messageformat'),
+                value: 2 * 60 * 60 * 1000
+            },
+            {
+                name: $translate.instant('timeinterval.hours-interval', {hours: 10}, 'messageformat'),
+                value: 10 * 60 * 60 * 1000
+            },
+            {
+                name: $translate.instant('timeinterval.days-interval', {days: 1}, 'messageformat'),
+                value: 24 * 60 * 60 * 1000
+            },
+            {
+                name: $translate.instant('timeinterval.days-interval', {days: 7}, 'messageformat'),
+                value: 7 * 24 * 60 * 60 * 1000
+            },
+            {
+                name: $translate.instant('timeinterval.days-interval', {days: 30}, 'messageformat'),
+                value: 30 * 24 * 60 * 60 * 1000
+            }
+        ];
+
+        scope.setIntervalMs = function (intervalMs) {
+            var intervalSeconds = Math.floor(intervalMs / 1000);
+            scope.days = Math.floor(intervalSeconds / 86400);
+            scope.hours = Math.floor((intervalSeconds % 86400) / 3600);
+            scope.mins = Math.floor(((intervalSeconds % 86400) % 3600) / 60);
+            scope.secs = intervalSeconds % 60;
+        }
+
+        ngModelCtrl.$render = function () {
+            if (ngModelCtrl.$viewValue) {
+                var intervalMs = ngModelCtrl.$viewValue;
+                scope.setIntervalMs(intervalMs);
+            }
+            scope.rendered = true;
+        }
+
+        scope.updateView = function () {
+            if (!scope.rendered) {
+                return;
+            }
+            var value = null;
+            var intervalMs = (scope.days * 86400 +
+                scope.hours * 3600 +
+                scope.mins * 60 +
+                scope.secs) * 1000;
+            if (!isNaN(intervalMs) && intervalMs > 0) {
+                value = intervalMs;
+                ngModelCtrl.$setValidity('tb-timeinterval', true);
+            } else {
+                ngModelCtrl.$setValidity('tb-timeinterval', !scope.required);
+            }
+            ngModelCtrl.$setViewValue(value);
+        }
+
+        scope.$watch('required', function (newRequired, prevRequired) {
+            if (angular.isDefined(newRequired) && newRequired !== prevRequired) {
+                scope.updateView();
+            }
+        });
+
+        scope.$watch('secs', function (newSecs) {
+            if (angular.isUndefined(newSecs)) {
+                return;
+            }
+            if (newSecs < 0) {
+                if ((scope.days + scope.hours + scope.mins) > 0) {
+                    scope.secs = newSecs + 60;
+                    scope.mins--;
+                } else {
+                    scope.secs = 0;
+                }
+            } else if (newSecs >= 60) {
+                scope.secs = newSecs - 60;
+                scope.mins++;
+            }
+            scope.updateView();
+        });
+
+        scope.$watch('mins', function (newMins) {
+            if (angular.isUndefined(newMins)) {
+                return;
+            }
+            if (newMins < 0) {
+                if ((scope.days + scope.hours) > 0) {
+                    scope.mins = newMins + 60;
+                    scope.hours--;
+                } else {
+                    scope.mins = 0;
+                }
+            } else if (newMins >= 60) {
+                scope.mins = newMins - 60;
+                scope.hours++;
+            }
+            scope.updateView();
+        });
+
+        scope.$watch('hours', function (newHours) {
+            if (angular.isUndefined(newHours)) {
+                return;
+            }
+            if (newHours < 0) {
+                if (scope.days > 0) {
+                    scope.hours = newHours + 24;
+                    scope.days--;
+                } else {
+                    scope.hours = 0;
+                }
+            } else if (newHours >= 24) {
+                scope.hours = newHours - 24;
+                scope.days++;
+            }
+            scope.updateView();
+        });
+
+        scope.$watch('days', function (newDays) {
+            if (angular.isUndefined(newDays)) {
+                return;
+            }
+            if (newDays < 0) {
+                scope.days = 0;
+            }
+            scope.updateView();
+        });
+
+        $compile(element.contents())(scope);
+
+    }
+
+    return {
+        restrict: "E",
+        require: "^ngModel",
+        scope: {
+            required: '=ngRequired'
+        },
+        link: linker
+    };
+}
diff --git a/ui/src/app/components/timeinterval.scss b/ui/src/app/components/timeinterval.scss
new file mode 100644
index 0000000..015ca18
--- /dev/null
+++ b/ui/src/app/components/timeinterval.scss
@@ -0,0 +1,34 @@
+/**
+ * Copyright © 2016 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-timeinterval {
+  md-input-container {
+    margin-bottom: 0px;
+    .md-errors-spacer {
+      min-height: 0px;
+    }
+  }
+  mdp-date-picker {
+    .md-input {
+      width: 150px;
+    }
+  }
+}
+
+tb-timeinterval {
+  .md-input {
+    width: 70px !important;
+  }
+}
diff --git a/ui/src/app/components/timeinterval.tpl.html b/ui/src/app/components/timeinterval.tpl.html
new file mode 100644
index 0000000..89c1dcd
--- /dev/null
+++ b/ui/src/app/components/timeinterval.tpl.html
@@ -0,0 +1,47 @@
+<!--
+
+    Copyright © 2016 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<section layout="row" layout-align="start start">
+	<md-input-container>
+		 <label translate>timeinterval.days</label>
+	     <input type="number" ng-model="days" step="1" aria-label="{{ 'timeinterval.days' | translate }}">
+	</md-input-container>
+	<md-input-container>
+		 <label translate>timeinterval.hours</label>
+	     <input type="number" ng-model="hours" step="1" aria-label="{{ 'timeinterval.hours' | translate }}">
+	</md-input-container>
+	<md-input-container>
+		 <label translate>timeinterval.minutes</label>
+	     <input type="number" ng-model="mins" step="1" aria-label="{{ 'timeinterval.minutes' | translate }}">
+	</md-input-container>
+	<md-input-container>
+		 <label translate>timeinterval.seconds</label>
+	     <input type="number" ng-model="secs" step="1" aria-label="{{ 'timeinterval.seconds' | translate }}">
+	</md-input-container>
+     <md-menu md-position-mode="target-right target">
+      <md-button class="md-icon-button"  aria-label="Open intervals" ng-click="$mdOpenMenu($event)">
+      	<md-icon md-menu-origin aria-label="arrow_drop_down" class="material-icons">arrow_drop_down</md-icon>	
+      </md-button>
+      <md-menu-content width="4">
+        <md-menu-item ng-repeat="interval in predefIntervals" >
+          <md-button ng-click="setIntervalMs(interval.value)">
+			<span>{{interval.name}}</span>
+          </md-button>
+        </md-menu-item>
+      </md-menu-content>
+     </md-menu>		
+</section>
diff --git a/ui/src/app/components/timewindow.directive.js b/ui/src/app/components/timewindow.directive.js
new file mode 100644
index 0000000..b3da4c8
--- /dev/null
+++ b/ui/src/app/components/timewindow.directive.js
@@ -0,0 +1,242 @@
+/*
+ * Copyright © 2016 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 './timewindow.scss';
+
+import thingsboardTimeinterval from './timeinterval.directive';
+import thingsboardDatetimePeriod from './datetime-period.directive';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import timewindowButtonTemplate from './timewindow-button.tpl.html';
+import timewindowTemplate from './timewindow.tpl.html';
+import timewindowPanelTemplate from './timewindow-panel.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+import TimewindowPanelController from './timewindow-panel.controller';
+
+export default angular.module('thingsboard.directives.timewindow', [thingsboardTimeinterval, thingsboardDatetimePeriod])
+    .controller('TimewindowPanelController', TimewindowPanelController)
+    .directive('tbTimewindow', Timewindow)
+    .filter('milliSecondsToTimeString', MillisecondsToTimeString)
+    .name;
+
+/*@ngInject*/
+function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $translate) {
+
+    var linker = function (scope, element, attrs, ngModelCtrl) {
+
+        /* tbTimewindow (ng-model)
+         * {
+         * 	  realtime: {
+         * 			timewindowMs: 0
+         * 	  },
+         * 	  history: {
+         * 			timewindowMs: 0,
+         * 			fixedTimewindow: {
+         * 				startTimeMs: 0,
+         * 				endTimeMs: 0
+         * 			}
+         * 	  }
+         * }
+         */
+
+        scope.historyOnly = angular.isDefined(attrs.historyOnly);
+
+        var translationPending = false;
+
+        $translate.onReady(function() {
+            if (translationPending) {
+                scope.updateDisplayValue();
+                translationPending = false;
+            }
+        });
+
+        var template;
+        if (scope.asButton) {
+            template = $templateCache.get(timewindowButtonTemplate);
+        } else {
+            template = $templateCache.get(timewindowTemplate);
+        }
+        element.html(template);
+
+        scope.isHovered = false;
+
+        scope.onHoverIn = function () {
+            scope.isHovered = true;
+        }
+
+        scope.onHoverOut = function () {
+            scope.isHovered = false;
+        }
+
+        scope.openEditMode = function (event) {
+            var position = $mdPanel.newPanelPosition()
+                .relativeTo(element)
+                .addPanelPosition($mdPanel.xPosition.ALIGN_START, $mdPanel.yPosition.BELOW);
+            var config = {
+                attachTo: angular.element($document[0].body),
+                controller: 'TimewindowPanelController',
+                controllerAs: 'vm',
+                templateUrl: timewindowPanelTemplate,
+                panelClass: 'tb-timewindow-panel',
+                position: position,
+                locals: {
+                    'timewindow': angular.copy(scope.model),
+                    'historyOnly': scope.historyOnly,
+                    'onTimewindowUpdate': function (timewindow) {
+                        scope.model = timewindow;
+                        scope.updateView();
+                    }
+                },
+                openFrom: event,
+                clickOutsideToClose: true,
+                escapeToClose: true,
+                focusOnOpen: false
+            };
+            $mdPanel.open(config);
+        }
+
+        scope.updateView = function () {
+            var value = {};
+            var model = scope.model;
+            if (model.selectedTab === 0) {
+                value.realtime = {
+                    timewindowMs: model.realtime.timewindowMs
+                };
+            } else {
+                if (model.history.historyType === 0) {
+                    value.history = {
+                        timewindowMs: model.history.timewindowMs
+                    };
+                } else {
+                    value.history = {
+                        fixedTimewindow: {
+                            startTimeMs: model.history.fixedTimewindow.startTimeMs,
+                            endTimeMs: model.history.fixedTimewindow.endTimeMs
+                        }
+                    };
+                }
+            }
+
+            ngModelCtrl.$setViewValue(value);
+            scope.updateDisplayValue();
+        }
+
+        scope.updateDisplayValue = function () {
+            if ($translate.isReady()) {
+                if (scope.model.selectedTab === 0 && !scope.historyOnly) {
+                    scope.model.displayValue = $translate.instant('timewindow.realtime') + ' - ' +
+                        $translate.instant('timewindow.last-prefix') + ' ' +
+                        $filter('milliSecondsToTimeString')(scope.model.realtime.timewindowMs);
+                } else {
+                    scope.model.displayValue = !scope.historyOnly ? ($translate.instant('timewindow.history') + ' - ') : '';
+                    if (scope.model.history.historyType === 0) {
+                        scope.model.displayValue += $translate.instant('timewindow.last-prefix') + ' ' +
+                            $filter('milliSecondsToTimeString')(scope.model.history.timewindowMs);
+                    } else {
+                        var startString = $filter('date')(scope.model.history.fixedTimewindow.startTimeMs, 'yyyy-MM-dd HH:mm:ss');
+                        var endString = $filter('date')(scope.model.history.fixedTimewindow.endTimeMs, 'yyyy-MM-dd HH:mm:ss');
+                        scope.model.displayValue += $translate.instant('timewindow.period', {startTime: startString, endTime: endString});
+                    }
+                }
+            } else {
+                translationPending = true;
+            }
+        }
+
+        ngModelCtrl.$render = function () {
+            var currentTime = (new Date).getTime();
+            scope.model = {
+                displayValue: "",
+                selectedTab: 0,
+                realtime: {
+                    timewindowMs: 60000 // 1 min by default
+                },
+                history: {
+                    historyType: 0,
+                    timewindowMs: 60000, // 1 min by default
+                    fixedTimewindow: {
+                        startTimeMs: currentTime - 24 * 60 * 60 * 1000, // 1 day by default
+                        endTimeMs: currentTime
+                    }
+                }
+            };
+            if (ngModelCtrl.$viewValue) {
+                var value = ngModelCtrl.$viewValue;
+                var model = scope.model;
+                if (angular.isDefined(value.realtime)) {
+                    model.selectedTab = 0;
+                    model.realtime.timewindowMs = value.realtime.timewindowMs;
+                } else {
+                    model.selectedTab = 1;
+                    if (angular.isDefined(value.history.timewindowMs)) {
+                        model.history.historyType = 0;
+                        model.history.timewindowMs = value.history.timewindowMs;
+                    } else {
+                        model.history.historyType = 1;
+                        model.history.fixedTimewindow.startTimeMs = value.history.fixedTimewindow.startTimeMs;
+                        model.history.fixedTimewindow.endTimeMs = value.history.fixedTimewindow.endTimeMs;
+                    }
+                }
+            }
+            scope.updateDisplayValue();
+        };
+
+        $compile(element.contents())(scope);
+    }
+
+    return {
+        restrict: "E",
+        require: "^ngModel",
+        scope: {
+            asButton: '=asButton'
+        },
+        link: linker
+    };
+}
+
+/*@ngInject*/
+function MillisecondsToTimeString($translate) {
+    return function (millseconds) {
+        var seconds = Math.floor(millseconds / 1000);
+        var days = Math.floor(seconds / 86400);
+        var hours = Math.floor((seconds % 86400) / 3600);
+        var minutes = Math.floor(((seconds % 86400) % 3600) / 60);
+        seconds = seconds % 60;
+        var timeString = '';
+        if (days > 0) timeString += $translate.instant('timewindow.days', {days: days}, 'messageformat');
+        if (hours > 0) {
+            if (timeString.length === 0 && hours === 1) {
+                hours = 0;
+            }
+            timeString += $translate.instant('timewindow.hours', {hours: hours}, 'messageformat');
+        }
+        if (minutes > 0) {
+            if (timeString.length === 0 && minutes === 1) {
+                minutes = 0;
+            }
+            timeString += $translate.instant('timewindow.minutes', {minutes: minutes}, 'messageformat');
+        }
+        if (seconds > 0) {
+            if (timeString.length === 0 && seconds === 1) {
+                seconds = 0;
+            }
+            timeString += $translate.instant('timewindow.seconds', {seconds: seconds}, 'messageformat');
+        }
+        return timeString;
+    }
+}
\ No newline at end of file
diff --git a/ui/src/app/components/timewindow.scss b/ui/src/app/components/timewindow.scss
new file mode 100644
index 0000000..1d2c738
--- /dev/null
+++ b/ui/src/app/components/timewindow.scss
@@ -0,0 +1,27 @@
+/**
+ * Copyright © 2016 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-timewindow-panel {
+  position: absolute;
+  background: white;
+  border-radius: 4px;
+  box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2),
+  0 13px 19px 2px rgba(0, 0, 0, 0.14),
+  0 5px 24px 4px rgba(0, 0, 0, 0.12);
+  overflow: hidden;
+  md-content {
+    background-color: #fff;
+  }
+}
diff --git a/ui/src/app/components/timewindow.tpl.html b/ui/src/app/components/timewindow.tpl.html
new file mode 100644
index 0000000..d618e94
--- /dev/null
+++ b/ui/src/app/components/timewindow.tpl.html
@@ -0,0 +1,23 @@
+<!--
+
+    Copyright © 2016 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<section ng-mouseover="onHoverIn()" ng-mouseleave="onHoverOut()" layout='row' layout-align="start center" style="min-height: 32px;">
+	<span ng-click="openEditMode($event)">{{model.displayValue}}</span>
+    <md-button class="md-icon-button tb-md-32" aria-label="{{ 'timewindow.edit' | translate }}" ng-show="isHovered" ng-click="openEditMode($event)">
+     	  <md-icon aria-label="{{ 'timewindow.date-range' | translate }}" class="material-icons">date_range</md-icon>
+    </md-button>	
+</section>
\ No newline at end of file
diff --git a/ui/src/app/components/timewindow-button.tpl.html b/ui/src/app/components/timewindow-button.tpl.html
new file mode 100644
index 0000000..4bed410
--- /dev/null
+++ b/ui/src/app/components/timewindow-button.tpl.html
@@ -0,0 +1,21 @@
+<!--
+
+    Copyright © 2016 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-button class="md-raised md-primary" ng-click="openEditMode($event)">
+    <md-icon class="material-icons">date_range</md-icon>
+    <span>{{model.displayValue}}</span>
+</md-button>
\ No newline at end of file
diff --git a/ui/src/app/components/timewindow-panel.controller.js b/ui/src/app/components/timewindow-panel.controller.js
new file mode 100644
index 0000000..005c3c8
--- /dev/null
+++ b/ui/src/app/components/timewindow-panel.controller.js
@@ -0,0 +1,49 @@
+/*
+ * Copyright © 2016 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 TimewindowPanelController(mdPanelRef, $scope, timewindow, historyOnly, onTimewindowUpdate) {
+
+    var vm = this;
+
+    vm._mdPanelRef = mdPanelRef;
+    vm.timewindow = timewindow;
+    vm.historyOnly = historyOnly;
+    vm.onTimewindowUpdate = onTimewindowUpdate;
+
+    if (vm.historyOnly) {
+        vm.timewindow.selectedTab = 1;
+    }
+
+    vm._mdPanelRef.config.onOpenComplete = function () {
+        $scope.theForm.$setPristine();
+    }
+
+    $scope.$watch('vm.timewindow.selectedTab', function (newSelection, prevSelection) {
+        if (newSelection !== prevSelection) {
+            $scope.theForm.$setDirty();
+        }
+    });
+
+    vm.cancel = function () {
+        vm._mdPanelRef && vm._mdPanelRef.close();
+    };
+
+    vm.update = function () {
+        vm._mdPanelRef && vm._mdPanelRef.close().then(function () {
+            vm.onTimewindowUpdate && vm.onTimewindowUpdate(vm.timewindow);
+        });
+    };
+}
diff --git a/ui/src/app/components/timewindow-panel.tpl.html b/ui/src/app/components/timewindow-panel.tpl.html
new file mode 100644
index 0000000..fa039c0
--- /dev/null
+++ b/ui/src/app/components/timewindow-panel.tpl.html
@@ -0,0 +1,66 @@
+<!--
+
+    Copyright © 2016 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.
+
+-->
+<form name="theForm" ng-submit="vm.update()">
+	<fieldset ng-disabled="loading">
+		<section layout="column">
+			<md-tabs ng-class="{'tb-headless': vm.historyOnly}" flex md-dynamic-height md-selected="vm.timewindow.selectedTab" md-border-bottom>
+				<md-tab label="{{ 'timewindow.realtime' | translate }}">
+					<md-content class="md-padding" layout="column">
+						<span translate>timewindow.last</span>
+						<tb-timeinterval
+							ng-required="vm.timewindow.selectedTab === 0"
+							ng-model="vm.timewindow.realtime.timewindowMs" style="padding-top: 8px;"></tb-timeinterval>
+					</md-content>
+				</md-tab>
+				<md-tab label="{{ 'timewindow.history' | translate }}">
+					<md-content class="md-padding" layout="column">
+						<md-radio-group ng-model="vm.timewindow.history.historyType" class="md-primary">
+							<md-radio-button ng-value=0 class="md-primary md-align-top-left md-radio-interactive">
+								<section layout="column">
+									<span translate>timewindow.last</span>
+									<tb-timeinterval
+										ng-required="vm.timewindow.selectedTab === 1 && vm.timewindow.history.historyType === 0"
+										ng-show="vm.timewindow.history.historyType === 0"
+										ng-model="vm.timewindow.history.timewindowMs" style="padding-top: 8px;"></tb-timeinterval>
+								</section>	
+							</md-radio-button>
+							<md-radio-button ng-value=1 class="md-primary md-align-top-left md-radio-interactive">
+								<section layout="column">
+									<span translate>timewindow.time-period</span>
+									<tb-datetime-period
+											ng-required="vm.timewindow.selectedTab === 1 && vm.timewindow.history.historyType === 1"
+											ng-show="vm.timewindow.history.historyType === 1"
+											ng-model="vm.timewindow.history.fixedTimewindow" style="padding-top: 8px;"></tb-datetime-period>
+								</section>	
+							</md-radio-button>
+						</md-radio-group>
+					</md-content>
+				</md-tab>
+			</md-tabs>
+			<section layout="row" layout-alignment="start center">
+			      <span flex></span>
+				  <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit" class="md-raised md-primary">
+				  		{{ 'action.update' | translate }}
+				  </md-button>
+			      <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">
+					  {{ 'action.cancel' | translate }}
+			      </md-button>
+			</section> 
+		</section>
+	</fieldset>
+</form>
\ No newline at end of file
diff --git a/ui/src/app/components/truncate.filter.js b/ui/src/app/components/truncate.filter.js
new file mode 100644
index 0000000..d56db6d
--- /dev/null
+++ b/ui/src/app/components/truncate.filter.js
@@ -0,0 +1,43 @@
+/*
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default angular.module('thingsboard.filters.truncate', [])
+    .filter('truncate', Truncate)
+    .name;
+
+/*@ngInject*/
+function Truncate () {
+    return function (value, wordwise, max, tail) {
+        if (!value) return '';
+
+        max = parseInt(max, 10);
+        if (!max) return value;
+        if (value.length <= max) return value;
+
+        value = value.substr(0, max);
+        if (wordwise) {
+            var lastspace = value.lastIndexOf(' ');
+            if (lastspace != -1) {
+                //Also remove . and , so its gives a cleaner result.
+                if (value.charAt(lastspace - 1) == '.' || value.charAt(lastspace - 1) == ',') {
+                    lastspace = lastspace - 1;
+                }
+                value = value.substr(0, lastspace);
+            }
+        }
+
+        return value + (tail || ' …');
+    };
+}
diff --git a/ui/src/app/components/widget.controller.js b/ui/src/app/components/widget.controller.js
new file mode 100644
index 0000000..565c8a1
--- /dev/null
+++ b/ui/src/app/components/widget.controller.js
@@ -0,0 +1,478 @@
+/*
+ * Copyright © 2016 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 $ from 'jquery';
+
+/* eslint-disable angular/angularelement */
+
+/*@ngInject*/
+export default function WidgetController($scope, $timeout, $window, $element, $q, $log, types, visibleRect,
+                                         datasourceService, deviceService, isPreview, widget, deviceAliasList, fns) {
+
+    var vm = this;
+
+    var timeWindow = {};
+    var subscriptionTimewindow = {
+        fixedWindow: null,
+        realtimeWindowMs: null
+    };
+
+    /*
+     *   data = array of datasourceData
+     *   datasourceData = {
+     *   			tbDatasource,
+     *   			dataKey,     { name, config }
+     *   			data = array of [time, value]
+     *   }
+     *
+     *
+     */
+    var data = [];
+    var datasourceListeners = [];
+    var targetDeviceAliasId = null;
+    var targetDeviceId = null;
+
+    var visible = false;
+
+    var bounds = {top: 0, left: 0, bottom: 0, right: 0};
+    var lastWidth, lastHeight;
+    var containerParent = $($element);
+    var container = $('#container', $element);
+    var containerElement = container[0];
+    var inited = false;
+
+    var gridsterItemElement;
+    var timer;
+
+    var init = fns.init || function () {
+        };
+
+    var redraw = fns.redraw || function () {
+        };
+
+    var destroy = fns.destroy || function () {
+        };
+
+    $scope.$timeout = $timeout;
+    $scope.$q = $q;
+
+    $scope.rpcRejection = null;
+    $scope.rpcErrorText = null;
+    $scope.rpcEnabled = false;
+    $scope.executingRpcRequest = false;
+    $scope.executingPromises = [];
+
+    function sendCommand(oneWayElseTwoWay, method, params, timeout) {
+        if (!$scope.rpcEnabled) {
+            return $q.reject();
+        }
+
+        if ($scope.rpcRejection && $scope.rpcRejection.status !== 408) {
+            $scope.rpcRejection = null;
+            $scope.rpcErrorText = null;
+        }
+
+        var requestBody = {
+            method: method,
+            params: params
+        };
+
+        if (timeout && timeout > 0) {
+            requestBody.timeout = timeout;
+        }
+
+        var deferred = $q.defer();
+        $scope.executingRpcRequest = true;
+        if ($scope.widgetEditMode) {
+            $timeout(function() {
+                $scope.executingRpcRequest = false;
+                if (oneWayElseTwoWay) {
+                    deferred.resolve();
+                } else {
+                    deferred.resolve(requestBody);
+                }
+            }, 500);
+        } else {
+            $scope.executingPromises.push(deferred.promise);
+            var targetSendFunction = oneWayElseTwoWay ? deviceService.sendOneWayRpcCommand : deviceService.sendTwoWayRpcCommand;
+            targetSendFunction(targetDeviceId, requestBody).then(
+                function success(responseBody) {
+                    $scope.rpcRejection = null;
+                    $scope.rpcErrorText = null;
+                    var index = $scope.executingPromises.indexOf(deferred.promise);
+                    if (index >= 0) {
+                        $scope.executingPromises.splice( index, 1 );
+                    }
+                    $scope.executingRpcRequest = $scope.executingPromises.length > 0;
+                    deferred.resolve(responseBody);
+                },
+                function fail(rejection) {
+                    var index = $scope.executingPromises.indexOf(deferred.promise);
+                    if (index >= 0) {
+                        $scope.executingPromises.splice( index, 1 );
+                    }
+                    $scope.executingRpcRequest = $scope.executingPromises.length > 0;
+                    if (!$scope.executingRpcRequest || rejection.status === 408) {
+                        $scope.rpcRejection = rejection;
+                        if (rejection.status === 408) {
+                            $scope.rpcErrorText = 'Device is offline.';
+                        } else {
+                            $scope.rpcErrorText =  'Error : ' + rejection.status + ' - ' + rejection.statusText;
+                            if (rejection.data && rejection.data.length > 0) {
+                                $scope.rpcErrorText += '</br>';
+                                $scope.rpcErrorText += rejection.data;
+                            }
+                        }
+                    }
+                    deferred.reject(rejection);
+                }
+            );
+        }
+        return deferred.promise;
+    }
+
+    $scope.clearRpcError = function() {
+        $scope.rpcRejection = null;
+        $scope.rpcErrorText = null;
+    }
+
+    var controlApi = {};
+
+    controlApi.sendOneWayCommand = function(method, params, timeout) {
+        return sendCommand(true, method, params, timeout);
+    };
+
+    controlApi.sendTwoWayCommand = function(method, params, timeout) {
+        return sendCommand(false, method, params, timeout);
+    };
+
+    vm.gridsterItemInitialized = gridsterItemInitialized;
+
+    function gridsterItemInitialized(item) {
+        if (item) {
+            gridsterItemElement = $(item.$element);
+            updateVisibility();
+        }
+    }
+
+    initWidget();
+
+    function initWidget() {
+        if (widget.type !== types.widgetType.rpc.value) {
+            for (var i in widget.config.datasources) {
+                var datasource = angular.copy(widget.config.datasources[i]);
+                for (var a in datasource.dataKeys) {
+                    var dataKey = datasource.dataKeys[a];
+                    var datasourceData = {
+                        datasource: datasource,
+                        dataKey: dataKey,
+                        data: []
+                    };
+                    data.push(datasourceData);
+                }
+            }
+        } else {
+            if (widget.config.targetDeviceAliasIds && widget.config.targetDeviceAliasIds.length > 0) {
+                targetDeviceAliasId = widget.config.targetDeviceAliasIds[0];
+                if (deviceAliasList[targetDeviceAliasId]) {
+                    targetDeviceId = deviceAliasList[targetDeviceAliasId].deviceId;
+                }
+            }
+            if (targetDeviceId) {
+                $scope.rpcEnabled = true;
+            } else {
+                $scope.rpcEnabled = $scope.widgetEditMode ? true : false;
+            }
+        }
+
+        $scope.$on('toggleDashboardEditMode', function (event, isEdit) {
+            isPreview = isEdit;
+            onRedraw();
+        });
+
+        $scope.$on('gridster-item-resized', function (event, item) {
+            if (item) {
+                updateBounds();
+            }
+        });
+
+        $scope.$on('gridster-item-transition-end', function (event, item) {
+            if (item) {
+                updateBounds();
+            }
+        });
+
+        $scope.$watch(function () {
+            return widget.row + ',' + widget.col;
+        }, function () {
+            updateBounds();
+            $scope.$emit("widgetPositionChanged", widget);
+        });
+
+        $scope.$on('visibleRectChanged', function (event, newVisibleRect) {
+            visibleRect = newVisibleRect;
+            updateVisibility();
+        });
+
+        $scope.$on('onWidgetFullscreenChanged', function(event, isWidgetExpanded, fullscreenWidget) {
+            if (widget === fullscreenWidget) {
+                onRedraw(0);
+            }
+        });
+
+        $scope.$on('deviceAliasListChanged', function (event, newDeviceAliasList) {
+            deviceAliasList = newDeviceAliasList;
+            if (widget.type === types.widgetType.rpc.value) {
+                if (targetDeviceAliasId) {
+                    var deviceId = null;
+                    if (deviceAliasList[targetDeviceAliasId]) {
+                        deviceId = deviceAliasList[targetDeviceAliasId].deviceId;
+                    }
+                    if (!angular.equals(deviceId, targetDeviceId)) {
+                        targetDeviceId = deviceId;
+                        if (targetDeviceId) {
+                            $scope.rpcEnabled = true;
+                        } else {
+                            $scope.rpcEnabled = $scope.widgetEditMode ? true : false;
+                        }
+                        inited = false;
+                        onRedraw();
+                    }
+                }
+            } else {
+                checkSubscriptions();
+            }
+        });
+
+        $scope.$on("$destroy", function () {
+            unsubscribe();
+            destroy();
+        });
+
+        subscribe();
+
+        if (widget.type === types.widgetType.timeseries.value) {
+            $scope.$watch(function () {
+                return widget.config.timewindow;
+            }, function (newTimewindow, prevTimewindow) {
+                if (!angular.equals(newTimewindow, prevTimewindow)) {
+                    unsubscribe();
+                    subscribe();
+                }
+            });
+        } else if (widget.type === types.widgetType.rpc.value) {
+            if (!inited) {
+                init(containerElement, widget.config.settings, widget.config.datasources, data, $scope, controlApi);
+                inited = true;
+            }
+        }
+    }
+
+    function updateVisibility(forceRedraw) {
+        if (visibleRect) {
+            forceRedraw = forceRedraw || visibleRect.containerResized;
+            var newVisible = false;
+            if (visibleRect.isMobile && gridsterItemElement) {
+                var topPx = gridsterItemElement.position().top;
+                var bottomPx = topPx + widget.sizeY * visibleRect.curRowHeight;
+                newVisible = !(topPx > visibleRect.bottomPx ||
+                bottomPx < visibleRect.topPx);
+            } else {
+                newVisible = !(bounds.left > visibleRect.right ||
+                bounds.right < visibleRect.left ||
+                bounds.top > visibleRect.bottom ||
+                bounds.bottom < visibleRect.top);
+            }
+            if (visible != newVisible) {
+                visible = newVisible;
+                if (visible) {
+                    onRedraw(50);
+                }
+            } else if (forceRedraw && visible) {
+                onRedraw(50);
+            }
+        }
+    }
+
+    function updateBounds() {
+        bounds = {
+            top: widget.row,
+            left: widget.col,
+            bottom: widget.row + widget.sizeY,
+            right: widget.col + widget.sizeX
+        };
+        updateVisibility(true);
+    }
+
+
+    function onRedraw(delay, dataUpdate) {
+        if (!visible) {
+            return;
+        }
+        if (angular.isUndefined(delay)) {
+            delay = 0;
+        }
+        $timeout(function () {
+            var width = containerParent.width();
+            var height = containerParent.height();
+            var sizeChanged = false;
+
+            if (!lastWidth || lastWidth != width || !lastHeight || lastHeight != height) {
+                if (width > 0 && height > 0) {
+                    container.css('height', height + 'px');
+                    container.css('width', width + 'px');
+                    lastWidth = width;
+                    lastHeight = height;
+                    sizeChanged = true;
+                }
+            }
+
+            if (width > 20 && height > 20) {
+                if (!inited) {
+                    init(containerElement, widget.config.settings, widget.config.datasources, data, $scope, controlApi);
+                    inited = true;
+                }
+                if (widget.type === types.widgetType.timeseries.value) {
+                    if (dataUpdate && timer) {
+                        $timeout.cancel(timer);
+                        timer = $timeout(onTick, 1500, false);
+                    }
+                    if (subscriptionTimewindow.realtimeWindowMs) {
+                        timeWindow.maxTime = (new Date).getTime();
+                        timeWindow.minTime = timeWindow.maxTime - subscriptionTimewindow.realtimeWindowMs;
+                    } else if (subscriptionTimewindow.fixedWindow) {
+                        timeWindow.maxTime = subscriptionTimewindow.fixedWindow.endTimeMs;
+                        timeWindow.minTime = subscriptionTimewindow.fixedWindow.startTimeMs;
+                    }
+                }
+                redraw(containerElement, width, height, data, timeWindow, sizeChanged, $scope);
+            }
+        }, delay, false);
+    }
+
+    function onDataUpdated(sourceData, datasourceIndex, dataKeyIndex) {
+        data[datasourceIndex + dataKeyIndex].data = sourceData;
+        onRedraw(0, true);
+    }
+
+    function checkSubscriptions() {
+        if (widget.type !== types.widgetType.rpc.value) {
+            var subscriptionsChanged = false;
+            for (var i in datasourceListeners) {
+                var listener = datasourceListeners[i];
+                var deviceId = null;
+                var aliasName = null;
+                if (listener.datasource.type === types.datasourceType.device) {
+                    if (deviceAliasList[listener.datasource.deviceAliasId]) {
+                        deviceId = deviceAliasList[listener.datasource.deviceAliasId].deviceId;
+                        aliasName = deviceAliasList[listener.datasource.deviceAliasId].alias;
+                    }
+                    if (!angular.equals(deviceId, listener.deviceId) ||
+                        !angular.equals(aliasName, listener.datasource.name)) {
+                        subscriptionsChanged = true;
+                        break;
+                    }
+                }
+            }
+            if (subscriptionsChanged) {
+                unsubscribe();
+                subscribe();
+            }
+        }
+    }
+
+    function unsubscribe() {
+        if (widget.type !== types.widgetType.rpc.value) {
+            if (timer) {
+                $timeout.cancel(timer);
+                timer = null;
+            }
+            for (var i in datasourceListeners) {
+                var listener = datasourceListeners[i];
+                datasourceService.unsubscribeFromDatasource(listener);
+            }
+        }
+    }
+
+    function onTick() {
+        onRedraw();
+        timer = $timeout(onTick, 1000, false);
+    }
+
+    function subscribe() {
+        if (widget.type !== types.widgetType.rpc.value) {
+            var index = 0;
+            subscriptionTimewindow.fixedWindow = null;
+            subscriptionTimewindow.realtimeWindowMs = null;
+            if (widget.type === types.widgetType.timeseries.value &&
+                angular.isDefined(widget.config.timewindow)) {
+                if (angular.isDefined(widget.config.timewindow.realtime)) {
+                    subscriptionTimewindow.realtimeWindowMs = widget.config.timewindow.realtime.timewindowMs;
+                } else if (angular.isDefined(widget.config.timewindow.history)) {
+                    if (angular.isDefined(widget.config.timewindow.history.timewindowMs)) {
+                        var currentTime = (new Date).getTime();
+                        subscriptionTimewindow.fixedWindow = {
+                            startTimeMs: currentTime - widget.config.timewindow.history.timewindowMs,
+                            endTimeMs: currentTime
+                        }
+                    } else {
+                        subscriptionTimewindow.fixedWindow = {
+                            startTimeMs: widget.config.timewindow.history.fixedTimewindow.startTimeMs,
+                            endTimeMs: widget.config.timewindow.history.fixedTimewindow.endTimeMs
+                        }
+                    }
+                }
+            }
+            for (var i in widget.config.datasources) {
+                var datasource = widget.config.datasources[i];
+                var deviceId = null;
+                if (datasource.type === types.datasourceType.device && datasource.deviceAliasId) {
+                    if (deviceAliasList[datasource.deviceAliasId]) {
+                        deviceId = deviceAliasList[datasource.deviceAliasId].deviceId;
+                        datasource.name = deviceAliasList[datasource.deviceAliasId].alias;
+                    }
+                } else {
+                    datasource.name = types.datasourceType.function;
+                }
+                var listener = {
+                    widget: widget,
+                    subscriptionTimewindow: subscriptionTimewindow,
+                    datasource: datasource,
+                    deviceId: deviceId,
+                    dataUpdated: function (data, datasourceIndex, dataKeyIndex) {
+                        onDataUpdated(data, datasourceIndex, dataKeyIndex);
+                    },
+                    datasourceIndex: index
+                };
+
+                for (var a = 0; a < datasource.dataKeys.length; a++) {
+                    data[index + a].data = [];
+                }
+
+                index += datasource.dataKeys.length;
+
+                datasourceListeners.push(listener);
+                datasourceService.subscribeToDatasource(listener);
+            }
+
+            if (subscriptionTimewindow.realtimeWindowMs) {
+                timer = $timeout(onTick, 0, false);
+            }
+        }
+    }
+
+}
+
+/* eslint-enable angular/angularelement */
\ No newline at end of file
diff --git a/ui/src/app/components/widget.directive.js b/ui/src/app/components/widget.directive.js
new file mode 100644
index 0000000..6ba25c3
--- /dev/null
+++ b/ui/src/app/components/widget.directive.js
@@ -0,0 +1,127 @@
+/*
+ * Copyright © 2016 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 thingsboardTypes from '../common/types.constant';
+import thingsboardApiDatasource from '../api/datasource.service';
+
+import WidgetController from './widget.controller';
+
+export default angular.module('thingsboard.directives.widget', [thingsboardTypes, thingsboardApiDatasource])
+    .controller('WidgetController', WidgetController)
+    .directive('tbWidget', Widget)
+    .name;
+
+/*@ngInject*/
+function Widget($controller, $compile, widgetService) {
+    return {
+        scope: true,
+        link: function (scope, elem, attrs) {
+
+            var widgetController;
+            var locals = scope.$eval(attrs.locals);
+            var widget = locals.widget;
+            var gridsterItem;
+
+            scope.$on('gridster-item-initialized', function (event, item) {
+                gridsterItem = item;
+                if (widgetController) {
+                    widgetController.gridsterItemInitialized(gridsterItem);
+                }
+            })
+
+            elem.html('<div flex layout="column" layout-align="center center" style="height: 100%;">' +
+                      '     <md-progress-circular md-mode="indeterminate" class="md-accent md-hue-2" md-diameter="120"></md-progress-circular>' +
+                      '</div>');
+            $compile(elem.contents())(scope);
+
+            widgetService.getWidgetInfo(widget.bundleAlias, widget.typeAlias, widget.isSystemType).then(
+                function(widgetInfo) {
+                    loadFromWidgetInfo(widgetInfo);
+                }
+            );
+
+            function loadFromWidgetInfo(widgetInfo) {
+
+                elem.addClass("tb-widget");
+
+                var widgetNamespace = "widget-type-" + (widget.isSystemType ? 'sys-' : '')
+                    + widget.bundleAlias + '-'
+                    + widget.typeAlias;
+
+                elem.addClass(widgetNamespace);
+                elem.html('<div id="container">' + widgetInfo.templateHtml + '</div>');
+
+                $compile(elem.contents())(scope);
+
+                angular.extend(locals, {$scope: scope, $element: elem});
+
+                var controllerFunctionBody = 'var fns = { init: null, redraw: null, destroy: null };';
+                controllerFunctionBody += widgetInfo.controllerScript;
+                controllerFunctionBody += '' +
+                    'angular.extend(this, $controller(\'WidgetController\',' +
+                    '{' +
+                    '$scope: $scope,' +
+                    '$timeout: $timeout,' +
+                    '$window: $window,' +
+                    '$element: $element,' +
+                    '$log: $log,' +
+                    'types: types,' +
+                    'visibleRect: visibleRect,' +
+                    'datasourceService: datasourceService,' +
+                    'deviceService: deviceService,' +
+                    'isPreview: isPreview,' +
+                    'widget: widget,' +
+                    'deviceAliasList: deviceAliasList,' +
+                    'fns: fns' +
+                    '}));' +
+                    '';
+
+                var controllerFunction = new Function("$scope",
+                    "$timeout",
+                    "$window",
+                    "$element",
+                    "$log",
+                    'types',
+                    "visibleRect",
+                    "datasourceService",
+                    "deviceService",
+                    "$controller",
+                    "isPreview",
+                    "widget",
+                    "deviceAliasList",
+                    controllerFunctionBody);
+
+                controllerFunction.$inject = ["$scope",
+                    "$timeout",
+                    "$window",
+                    "$element",
+                    "$log",
+                    'types',
+                    "visibleRect",
+                    "datasourceService",
+                    "deviceService",
+                    "$controller",
+                    "isPreview",
+                    "widget",
+                    "deviceAliasList"];
+
+                widgetController = $controller(controllerFunction, locals);
+                if (gridsterItem) {
+                    widgetController.gridsterItemInitialized(gridsterItem);
+                }
+            }
+        }
+    };
+}
diff --git a/ui/src/app/components/widget-config.directive.js b/ui/src/app/components/widget-config.directive.js
new file mode 100644
index 0000000..ee6675d
--- /dev/null
+++ b/ui/src/app/components/widget-config.directive.js
@@ -0,0 +1,340 @@
+/*
+ * Copyright © 2016 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 jsonSchemaDefaults from 'json-schema-defaults';
+import thingsboardTypes from '../common/types.constant';
+import thingsboardUtils from '../common/utils.service';
+import thingsboardDeviceAliasSelect from './device-alias-select.directive';
+import thingsboardDatasource from './datasource.directive';
+import thingsboardTimewindow from './timewindow.directive';
+import thingsboardJsonForm from "./json-form.directive";
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import widgetConfigTemplate from './widget-config.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/* eslint-disable angular/angularelement */
+
+export default angular.module('thingsboard.directives.widgetConfig', [thingsboardTypes,
+    thingsboardUtils,
+    thingsboardJsonForm,
+    thingsboardDeviceAliasSelect,
+    thingsboardDatasource,
+    thingsboardTimewindow])
+    .directive('tbWidgetConfig', WidgetConfig)
+    .name;
+
+/*@ngInject*/
+function WidgetConfig($compile, $templateCache, $rootScope, types, utils) {
+
+    var linker = function (scope, element, attrs, ngModelCtrl) {
+
+        var template = $templateCache.get(widgetConfigTemplate);
+
+        element.html(template);
+
+        scope.types = types;
+        scope.widgetEditMode = $rootScope.widgetEditMode;
+
+        scope.emptySettingsSchema = {
+            type: "object",
+            properties: {}
+        };
+        scope.defaultSettingsForm = [
+            '*'
+        ];
+
+        if (angular.isUndefined(scope.forceExpandDatasources)) {
+            scope.forceExpandDatasources = false;
+        }
+
+        scope.currentSettingsSchema = {};
+        scope.currentSettings = angular.copy(scope.emptySettingsSchema);
+
+        scope.targetDeviceAlias = {
+            value: null
+        }
+
+        ngModelCtrl.$render = function () {
+            if (ngModelCtrl.$viewValue) {
+                scope.selectedTab = 0;
+                scope.title = ngModelCtrl.$viewValue.title;
+                scope.showTitle = ngModelCtrl.$viewValue.showTitle;
+                scope.backgroundColor = ngModelCtrl.$viewValue.backgroundColor;
+                scope.color = ngModelCtrl.$viewValue.color;
+                scope.padding = ngModelCtrl.$viewValue.padding;
+                scope.timewindow = ngModelCtrl.$viewValue.timewindow;
+                if (scope.widgetType !== types.widgetType.rpc.value) {
+                    if (scope.datasources) {
+                        scope.datasources.splice(0, scope.datasources.length);
+                    } else {
+                        scope.datasources = [];
+                    }
+                    if (ngModelCtrl.$viewValue.datasources) {
+                        for (var i in ngModelCtrl.$viewValue.datasources) {
+                            scope.datasources.push({value: ngModelCtrl.$viewValue.datasources[i]});
+                        }
+                    }
+                } else {
+                    if (ngModelCtrl.$viewValue.targetDeviceAliasIds && ngModelCtrl.$viewValue.targetDeviceAliasIds.length > 0) {
+                        var aliasId = ngModelCtrl.$viewValue.targetDeviceAliasIds[0];
+                        if (scope.deviceAliases[aliasId]) {
+                            scope.targetDeviceAlias.value = {id: aliasId, alias: scope.deviceAliases[aliasId].alias,
+                                deviceId: scope.deviceAliases[aliasId].deviceId};
+                        } else {
+                            scope.targetDeviceAlias.value = null;
+                        }
+                    } else {
+                        scope.targetDeviceAlias.value = null;
+                    }
+                }
+
+                scope.settings = ngModelCtrl.$viewValue.settings;
+
+                scope.updateSchemaForm();
+
+                scope.updateDatasourcesAccordionState();
+            }
+        };
+
+        scope.displayAdvanced = function() {
+            return scope.widgetSettingsSchema && scope.widgetSettingsSchema.schema;
+        }
+
+        scope.updateSchemaForm = function() {
+            if (scope.widgetSettingsSchema && scope.widgetSettingsSchema.schema) {
+                scope.currentSettingsSchema = scope.widgetSettingsSchema.schema;
+                scope.currentSettingsForm = scope.widgetSettingsSchema.form || angular.copy(scope.defaultSettingsForm);
+                scope.currentSettings = scope.settings;
+            } else {
+                scope.currentSettingsForm = angular.copy(scope.defaultSettingsForm);
+                scope.currentSettingsSchema = angular.copy(scope.emptySettingsSchema);
+                scope.currentSettings = {};
+            }
+        }
+
+        scope.$on('datasources-accordion:onReady', function () {
+            if (scope.updateDatasourcesAccordionStatePending) {
+                scope.updateDatasourcesAccordionState();
+            }
+        });
+
+        scope.updateValidity = function () {
+            if (ngModelCtrl.$viewValue) {
+                var value = ngModelCtrl.$viewValue;
+                var valid;
+                if (scope.widgetType === types.widgetType.rpc.value) {
+                    valid = value && value.targetDeviceAliasIds && value.targetDeviceAliasIds.length > 0;
+                    ngModelCtrl.$setValidity('targetDeviceAliasIds', valid);
+                } else {
+                    valid = value && value.datasources && value.datasources.length > 0;
+                    ngModelCtrl.$setValidity('datasources', valid);
+                }
+            }
+        };
+
+        scope.$watch('title + showTitle + backgroundColor + color + padding + intervalSec', function () {
+            if (ngModelCtrl.$viewValue) {
+                var value = ngModelCtrl.$viewValue;
+                value.title = scope.title;
+                value.showTitle = scope.showTitle;
+                value.backgroundColor = scope.backgroundColor;
+                value.color = scope.color;
+                value.padding = scope.padding;
+                value.intervalSec = scope.intervalSec;
+                ngModelCtrl.$setViewValue(value);
+            }
+        });
+
+        scope.$watch('currentSettings', function () {
+            if (ngModelCtrl.$viewValue) {
+                var value = ngModelCtrl.$viewValue;
+                value.settings = scope.currentSettings;
+                ngModelCtrl.$setViewValue(value);
+            }
+        }, true);
+
+        scope.$watch('timewindow', function () {
+            if (ngModelCtrl.$viewValue) {
+                var value = ngModelCtrl.$viewValue;
+                value.timewindow = scope.timewindow;
+                ngModelCtrl.$setViewValue(value);
+            }
+        }, true);
+
+        scope.$watch('datasources', function () {
+            if (ngModelCtrl.$viewValue && scope.widgetType !== types.widgetType.rpc.value) {
+                var value = ngModelCtrl.$viewValue;
+                if (value.datasources) {
+                    value.datasources.splice(0, value.datasources.length);
+                } else {
+                    value.datasources = [];
+                }
+                if (scope.datasources) {
+                    for (var i in scope.datasources) {
+                        value.datasources.push(scope.datasources[i].value);
+                    }
+                }
+                ngModelCtrl.$setViewValue(value);
+                scope.updateValidity();
+            }
+        }, true);
+
+        scope.$watch('targetDeviceAlias.value', function () {
+            if (ngModelCtrl.$viewValue && scope.widgetType === types.widgetType.rpc.value) {
+                var value = ngModelCtrl.$viewValue;
+                if (scope.targetDeviceAlias.value) {
+                    value.targetDeviceAliasIds = [scope.targetDeviceAlias.value.id];
+                } else {
+                    value.targetDeviceAliasIds = [];
+                }
+                ngModelCtrl.$setViewValue(value);
+                scope.updateValidity();
+            }
+        });
+
+        scope.addDatasource = function () {
+            var newDatasource;
+            if (scope.functionsOnly) {
+                newDatasource = angular.copy(utils.getDefaultDatasource(scope.datakeySettingsSchema.schema));
+                newDatasource.dataKeys = [scope.generateDataKey('Sin', types.dataKeyType.function)];
+            } else {
+                newDatasource = { type: types.datasourceType.device,
+                    dataKeys: []
+                };
+            }
+            var datasource = {value: newDatasource};
+            scope.datasources.push(datasource);
+            if (scope.theForm) {
+                scope.theForm.$setDirty();
+            }
+        }
+
+        scope.removeDatasource = function ($event, datasource) {
+            var index = scope.datasources.indexOf(datasource);
+            if (index > -1) {
+                scope.datasources.splice(index, 1);
+                if (scope.theForm) {
+                    scope.theForm.$setDirty();
+                }
+            }
+        };
+
+        scope.updateDatasourcesAccordionState = function () {
+            if (scope.widgetType !== types.widgetType.rpc.value) {
+                if (scope.datasourcesAccordion) {
+                    scope.updateDatasourcesAccordionStatePending = false;
+                    var expand = scope.datasources && scope.datasources.length < 4;
+                    if (expand) {
+                        scope.datasourcesAccordion.expand('datasources-pane');
+                    } else {
+                        scope.datasourcesAccordion.collapse('datasources-pane');
+                    }
+                } else {
+                    scope.updateDatasourcesAccordionStatePending = true;
+                }
+            }
+        }
+
+        scope.generateDataKey = function (chip, type) {
+
+            if (angular.isObject(chip)) {
+                chip._hash = Math.random();
+                return chip;
+            }
+
+            var result = {
+                name: chip,
+                type: type,
+                label: scope.genNextLabel(chip),
+                color: scope.genNextColor(),
+                settings: {},
+                _hash: Math.random()
+            };
+
+            if (type === types.dataKeyType.function) {
+                result.name = 'f(x)';
+                result.funcBody = utils.getPredefinedFunctionBody(chip);
+                if (!result.funcBody) {
+                    result.funcBody = "return prevValue + 1;";
+                }
+            }
+
+            if (angular.isDefined(scope.datakeySettingsSchema.schema)) {
+                result.settings = jsonSchemaDefaults(scope.datakeySettingsSchema.schema);
+            }
+
+            return result;
+        };
+
+        scope.genNextLabel = function (name) {
+            var label = name;
+            var value = ngModelCtrl.$viewValue;
+            var i = 1;
+            var matches = false;
+            do {
+                matches = false;
+                if (value.datasources) {
+                    for (var d in value.datasources) {
+                        var datasource = value.datasources[d];
+                        for (var k in datasource.dataKeys) {
+                            var dataKey = datasource.dataKeys[k];
+                            if (dataKey.label === label) {
+                                i++;
+                                label = name + ' ' + i;
+                                matches = true;
+                            }
+                        }
+                    }
+                }
+            } while (matches);
+            return label;
+        }
+
+        scope.genNextColor = function () {
+            var i = 0;
+            var value = ngModelCtrl.$viewValue;
+            if (value.datasources) {
+                for (var d in value.datasources) {
+                    var datasource = value.datasources[d];
+                    i += datasource.dataKeys.length;
+                }
+            }
+            return utils.getMaterialColor(i);
+        }
+
+        $compile(element.contents())(scope);
+    }
+    return {
+        restrict: "E",
+        require: "^ngModel",
+        scope: {
+            forceExpandDatasources: '=?',
+            widgetType: '=',
+            widgetSettingsSchema: '=',
+            datakeySettingsSchema: '=',
+            deviceAliases: '=',
+            functionsOnly: '=',
+            fetchDeviceKeys: '&',
+            onCreateDeviceAlias: '&',
+            theForm: '='
+        },
+        link: linker
+    };
+}
+
+/* eslint-enable angular/angularelement */
diff --git a/ui/src/app/components/widget-config.tpl.html b/ui/src/app/components/widget-config.tpl.html
new file mode 100644
index 0000000..896e1e8
--- /dev/null
+++ b/ui/src/app/components/widget-config.tpl.html
@@ -0,0 +1,163 @@
+<!--
+
+    Copyright © 2016 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-tabs ng-class="{'tb-headless': !displayAdvanced()}" id="tabs" md-border-bottom flex class="tb-absolute-fill"
+         md-selected="selectedTab">
+    <md-tab label="{{ 'widget-config.settings' | translate }}">
+        <div id="settings-tab">
+            <md-content class="md-padding" layout="column">
+                <md-input-container class="md-block">
+                    <label translate>widget-config.title</label>
+                    <input name="title" ng-model="title">
+                </md-input-container>
+                <span translate>widget-config.general-settings</span>
+                <div layout="row" layout-align="start center">
+                    <div layout="row" layout-padding>
+                        <md-checkbox flex aria-label="{{ 'widget-config.display-title' | translate }}"
+                                     ng-model="showTitle">{{ 'widget-config.display-title' | translate }}
+                        </md-checkbox>
+                    </div>
+                    <div flex
+                         md-color-picker
+                         ng-model="backgroundColor"
+                         label="{{ 'widget-config.background-color' | translate }}"
+                         icon="format_color_fill"
+                         default="#fff"
+                         md-color-clear-button="false"
+                         open-on-input="true"
+                         md-color-generic-palette="false"
+                         md-color-history="false"
+                    ></div>
+                    <div flex
+                         md-color-picker
+                         ng-model="color"
+                         label="{{ 'widget-config.text-color' | translate }}"
+                         icon="format_color_fill"
+                         default="rgba(0, 0, 0, 0.87)"
+                         md-color-clear-button="false"
+                         open-on-input="true"
+                         md-color-generic-palette="false"
+                         md-color-history="false"
+                    ></div>
+                    <md-input-container flex>
+                        <label translate>widget-config.padding</label>
+                        <input ng-model="padding">
+                    </md-input-container>
+                </div>
+                <div ng-show="widgetType === types.widgetType.timeseries.value" layout="row"
+                     layout-align="center center">
+                    <span translate style="padding-right: 8px;">widget-config.timewindow</span>
+                    <tb-timewindow as-button="true" flex ng-model="timewindow"></tb-timewindow>
+                </div>
+                <v-accordion id="datasources-accordion" control="datasourcesAccordion" class="vAccordion--default"
+                             ng-show="widgetType !== types.widgetType.rpc.value">
+                    <v-pane id="datasources-pane" expanded="forceExpandDatasources">
+                        <v-pane-header>
+                            {{ 'widget-config.datasources' | translate }}
+                        </v-pane-header>
+                        <v-pane-content>
+                            <div ng-if="datasources.length === 0">
+                                <span translate layout-align="center center"
+                                      class="tb-prompt">datasource.add-datasource-prompt</span>
+                            </div>
+                            <div ng-if="datasources.length > 0">
+                                <div flex layout="row" layout-align="start center">
+                                    <span flex="5"></span>
+                                    <div flex layout="row" layout-align="start center"
+                                         style="padding: 0 0 0 10px; margin: 5px;">
+                                        <span translate style="min-width: 110px;">widget-config.datasource-type</span>
+                                        <span translate flex
+                                              style="padding-left: 10px;">widget-config.datasource-parameters</span>
+                                        <span style="min-width: 40px;"></span>
+                                    </div>
+                                </div>
+                                <div style="max-height: 300px; overflow: auto; padding-bottom: 15px;">
+                                    <div flex layout="row" layout-align="start center"
+                                         ng-repeat="datasource in datasources">
+                                        <span flex="5">{{$index + 1}}.</span>
+                                        <div class="md-whiteframe-4dp" flex layout="row" layout-align="start center"
+                                             style="padding: 0 0 0 10px; margin: 5px;">
+                                            <tb-datasource flex ng-model="datasource.value"
+                                                           widget-type="widgetType"
+                                                           device-aliases="deviceAliases"
+                                                           functions-only="functionsOnly"
+                                                           datakey-settings-schema="datakeySettingsSchema"
+                                                           generate-data-key="generateDataKey(chip,type)"
+                                                           fetch-device-keys="fetchDeviceKeys({deviceAliasId: deviceAliasId, query: query, type: type})"
+                                                           on-create-device-alias="onCreateDeviceAlias({event: event, alias: alias})"></tb-datasource>
+                                            <md-button ng-disabled="loading" class="md-icon-button md-primary"
+                                                       style="min-width: 40px;"
+                                                       ng-click="removeDatasource($event, datasource)"
+                                                       aria-label="{{ 'action.remove' | translate }}">
+                                                <md-tooltip md-direction="top">
+                                                    {{ 'widget-config.remove-datasource' | translate }}
+                                                </md-tooltip>
+                                                <md-icon aria-label="{{ 'action.delete' | translate }}"
+                                                         class="material-icons">
+                                                    close
+                                                </md-icon>
+                                            </md-button>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                            <div flex layout="row" layout-align="start center">
+                                <md-button ng-disabled="loading" class="md-primary md-raised"
+                                           ng-click="addDatasource($event)" aria-label="{{ 'action.add' | translate }}">
+                                    <md-tooltip md-direction="top">
+                                        {{ 'widget-config.add-datasource' | translate }}
+                                    </md-tooltip>
+                                    <md-icon class="material-icons">add</md-icon>
+                                    <span translate>action.add</span>
+                                </md-button>
+                            </div>
+                        </v-pane-content>
+                    </v-pane>
+                </v-accordion>
+                <v-accordion id="target-devices-accordion" control="targetDevicesAccordion" class="vAccordion--default"
+                             ng-show="widgetType === types.widgetType.rpc.value">
+                    <v-pane id="target-devices-pane" expanded="true">
+                        <v-pane-header>
+                            {{ 'widget-config.target-device' | translate }}
+                        </v-pane-header>
+                        <v-pane-content style="padding: 0 5px;">
+                            <tb-device-alias-select flex
+                                                    tb-required="widgetType === types.widgetType.rpc.value && !widgetEditMode"
+                                                    device-aliases="deviceAliases"
+                                                    ng-model="targetDeviceAlias.value"
+                                                    on-create-device-alias="onCreateDeviceAlias({event: event, alias: alias})">
+                            </tb-device-alias-select>
+                        </v-pane-content>
+                    </v-pane>
+                </v-accordion>
+            </md-content>
+        </div>
+    </md-tab>
+    <md-tab ng-if="displayAdvanced()" label="{{ 'widget-config.advanced' | translate }}">
+        <md-content class="md-padding" layout="column">
+            <ng-form name="ngform"
+                     layout="column"
+                     layout-padding>
+                <tb-json-form schema="currentSettingsSchema"
+                              form="currentSettingsForm"
+                              model="currentSettings"
+                              form-control="ngform">
+                </tb-json-form>
+            </ng-form>
+        </md-content>
+    </md-tab>
+</md-tabs>
diff --git a/ui/src/app/components/widgets-bundle-select.directive.js b/ui/src/app/components/widgets-bundle-select.directive.js
new file mode 100644
index 0000000..fcf5def
--- /dev/null
+++ b/ui/src/app/components/widgets-bundle-select.directive.js
@@ -0,0 +1,96 @@
+/*
+ * Copyright © 2016 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 './widgets-bundle-select.scss';
+
+import thingsboardApiWidget from '../api/widget.service';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import widgetsBundleSelectTemplate from './widgets-bundle-select.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+
+export default angular.module('thingsboard.directives.widgetsBundleSelect', [thingsboardApiWidget])
+    .directive('tbWidgetsBundleSelect', WidgetsBundleSelect)
+    .name;
+
+/*@ngInject*/
+function WidgetsBundleSelect($compile, $templateCache, widgetService, types) {
+
+    var linker = function (scope, element, attrs, ngModelCtrl) {
+        var template = $templateCache.get(widgetsBundleSelectTemplate);
+        element.html(template);
+
+        scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
+        scope.widgetsBundle = null;
+        scope.widgetsBundles = [];
+
+        var widgetsBundleFetchFunction = widgetService.getAllWidgetsBundles;
+        if (angular.isDefined(scope.bundlesScope)) {
+            if (scope.bundlesScope === 'system') {
+                widgetsBundleFetchFunction = widgetService.getSystemWidgetsBundles;
+            } else if (scope.bundlesScope === 'tenant') {
+                widgetsBundleFetchFunction = widgetService.getTenantWidgetsBundles;
+            }
+        }
+
+        widgetsBundleFetchFunction().then(
+            function success(widgetsBundles) {
+                scope.widgetsBundles = widgetsBundles;
+                if (scope.selectFirstBundle) {
+                    if (widgetsBundles.length > 0) {
+                        scope.widgetsBundle = widgetsBundles[0];
+                    }
+                }
+            },
+            function fail() {
+            }
+        );
+
+        scope.isSystem = function(item) {
+            return item && item.tenantId.id === types.id.nullUid;
+        }
+
+        scope.updateView = function () {
+            ngModelCtrl.$setViewValue(scope.widgetsBundle);
+        }
+
+        ngModelCtrl.$render = function () {
+            if (ngModelCtrl.$viewValue) {
+                scope.widgetsBundle = ngModelCtrl.$viewValue;
+            }
+        }
+
+        scope.$watch('widgetsBundle', function () {
+            scope.updateView();
+        });
+
+        $compile(element.contents())(scope);
+    }
+
+    return {
+        restrict: "E",
+        require: "^ngModel",
+        link: linker,
+        scope: {
+            bundlesScope: '@',
+            theForm: '=?',
+            tbRequired: '=?',
+            selectFirstBundle: '='
+        }
+    };
+}
\ No newline at end of file
diff --git a/ui/src/app/components/widgets-bundle-select.scss b/ui/src/app/components/widgets-bundle-select.scss
new file mode 100644
index 0000000..7b573f2
--- /dev/null
+++ b/ui/src/app/components/widgets-bundle-select.scss
@@ -0,0 +1,80 @@
+/**
+ * Copyright © 2016 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 "../../scss/mixins";
+
+tb-widgets-bundle-select {
+  md-select {
+    margin: 0;
+    padding: 5px 20px;
+  }
+  .tb-bundle-item {
+    height: 24px;
+    line-height: 24px;
+  }
+}
+
+.tb-widgets-bundle-select {
+  .tb-bundle-item {
+    height: 48px;
+    line-height: 48px;
+  }
+}
+
+tb-widgets-bundle-select, .tb-widgets-bundle-select {
+  .md-text {
+    width: 100%;
+  }
+  .tb-bundle-item {
+    display: block;
+    span {
+      display: inline-block;
+      vertical-align: middle;
+    }
+    .tb-bundle-system {
+      font-size: 0.8rem;
+      opacity: 0.8;
+      float: right;
+    }
+  }
+  md-option {
+    height: auto !important;
+    white-space: normal !important;
+  }
+}
+
+md-toolbar {
+  tb-widgets-bundle-select {
+    md-select {
+      background: rgba(255,255,255,0.2);
+      .md-select-value {
+        color: #fff;
+        font-size: 1.2rem;
+        span:first-child:after {
+            color: #fff;
+        }
+      }
+      .md-select-value.md-select-placeholder {
+        color: #fff;
+        opacity: 0.8;
+      }
+    }
+    md-select.ng-invalid.ng-touched {
+      .md-select-value {
+        color: #fff !important;
+      }
+    }
+  }
+}
diff --git a/ui/src/app/components/widgets-bundle-select.tpl.html b/ui/src/app/components/widgets-bundle-select.tpl.html
new file mode 100644
index 0000000..7ba7a95
--- /dev/null
+++ b/ui/src/app/components/widgets-bundle-select.tpl.html
@@ -0,0 +1,29 @@
+<!--
+
+    Copyright © 2016 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-required="tbRequired"
+           ng-model="widgetsBundle"
+           md-container-class="tb-widgets-bundle-select"
+           placeholder="{{ 'widget.select-widgets-bundle' | translate }}"
+           class="md-no-underline">
+    <md-option ng-repeat="item in widgetsBundles" ng-value="item">
+        <div class="tb-bundle-item">
+            <span>{{item.title}}</span>
+            <span translate class="tb-bundle-system" ng-if="isSystem(item)">widgets-bundle.system</span>
+        </div>
+    </md-option>
+</md-select>
diff --git a/ui/src/app/customer/add-customer.tpl.html b/ui/src/app/customer/add-customer.tpl.html
new file mode 100644
index 0000000..026342d
--- /dev/null
+++ b/ui/src/app/customer/add-customer.tpl.html
@@ -0,0 +1,46 @@
+<!--
+
+    Copyright © 2016 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="{{ 'customer.add' | translate }}" tb-help="'customers'" help-container-id="help-container">
+	<form name="theForm" ng-submit="vm.add()">
+	    <md-toolbar>
+	      <div class="md-toolbar-tools">
+	        <h2 translate>customer.add</h2>
+	        <span flex></span>
+			<div id="help-container"></div>
+	        <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-show="loading"></md-progress-linear>
+  	    <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
+	    <md-dialog-content>
+	      <div class="md-dialog-content">
+  	        	<tb-customer customer="vm.item" is-edit="true" the-form="theForm"></tb-customer>
+	      </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">
+		  		{{ 'action.add' | 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>
+
diff --git a/ui/src/app/customer/customer.controller.js b/ui/src/app/customer/customer.controller.js
new file mode 100644
index 0000000..68a9e0f
--- /dev/null
+++ b/ui/src/app/customer/customer.controller.js
@@ -0,0 +1,164 @@
+/*
+ * Copyright © 2016 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 addCustomerTemplate from './add-customer.tpl.html';
+import customerCard from './customer-card.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function CustomerController(customerService, $state, $stateParams, $translate) {
+
+    var customerActionsList = [
+        {
+            onAction: function ($event, item) {
+                openCustomerUsers($event, item);
+            },
+            name: function() { return $translate.instant('user.users') },
+            details: function() { return $translate.instant('customer.manage-customer-users') },
+            icon: "account_circle"
+        },
+        {
+            onAction: function ($event, item) {
+                openCustomerDevices($event, item);
+            },
+            name: function() { return $translate.instant('device.devices') },
+            details: function() { return $translate.instant('customer.manage-customer-devices') },
+            icon: "devices_other"
+        },
+        {
+            onAction: function ($event, item) {
+                openCustomerDashboards($event, item);
+            },
+            name: function() { return $translate.instant('dashboard.dashboards') },
+            details: function() { return $translate.instant('customer.manage-customer-dashboards') },
+            icon: "dashboard"
+        },
+        {
+            onAction: function ($event, item) {
+                vm.grid.deleteItem($event, item);
+            },
+            name: function() { return $translate.instant('action.delete') },
+            details: function() { return $translate.instant('customer.delete') },
+            icon: "delete"
+        }
+    ];
+
+    var vm = this;
+
+    vm.customerGridConfig = {
+
+        refreshParamsFunc: null,
+
+        deleteItemTitleFunc: deleteCustomerTitle,
+        deleteItemContentFunc: deleteCustomerText,
+        deleteItemsTitleFunc: deleteCustomersTitle,
+        deleteItemsActionTitleFunc: deleteCustomersActionTitle,
+        deleteItemsContentFunc: deleteCustomersText,
+
+        fetchItemsFunc: fetchCustomers,
+        saveItemFunc: saveCustomer,
+        deleteItemFunc: deleteCustomer,
+
+        getItemTitleFunc: getCustomerTitle,
+
+        itemCardTemplateUrl: customerCard,
+
+        actionsList: customerActionsList,
+
+        onGridInited: gridInited,
+
+        addItemTemplateUrl: addCustomerTemplate,
+
+        addItemText: function() { return $translate.instant('customer.add-customer-text') },
+        noItemsText: function() { return $translate.instant('customer.no-customers-text') },
+        itemDetailsText: function() { return $translate.instant('customer.customer-details') }
+    };
+
+    if (angular.isDefined($stateParams.items) && $stateParams.items !== null) {
+        vm.customerGridConfig.items = $stateParams.items;
+    }
+
+    if (angular.isDefined($stateParams.topIndex) && $stateParams.topIndex > 0) {
+        vm.customerGridConfig.topIndex = $stateParams.topIndex;
+    }
+
+    vm.openCustomerUsers = openCustomerUsers;
+    vm.openCustomerDevices = openCustomerDevices;
+    vm.openCustomerDashboards = openCustomerDashboards;
+
+    function deleteCustomerTitle(customer) {
+        return $translate.instant('customer.delete-customer-title', {customerTitle: customer.title});
+    }
+
+    function deleteCustomerText() {
+        return $translate.instant('customer.delete-customer-text');
+    }
+
+    function deleteCustomersTitle(selectedCount) {
+        return $translate.instant('customer.delete-customers-title', {count: selectedCount}, 'messageformat');
+    }
+
+    function deleteCustomersActionTitle(selectedCount) {
+        return $translate.instant('customer.delete-customers-action-title', {count: selectedCount}, 'messageformat');
+    }
+
+    function deleteCustomersText() {
+        return $translate.instant('customer.delete-customers-text');
+    }
+
+    function gridInited(grid) {
+        vm.grid = grid;
+    }
+
+    function fetchCustomers(pageLink) {
+        return customerService.getCustomers(pageLink);
+    }
+
+    function saveCustomer(customer) {
+        return customerService.saveCustomer(customer);
+    }
+
+    function deleteCustomer(customerId) {
+        return customerService.deleteCustomer(customerId);
+    }
+
+    function getCustomerTitle(customer) {
+        return customer ? customer.title : '';
+    }
+
+    function openCustomerUsers($event, customer) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        $state.go('home.customers.users', {customerId: customer.id.id});
+    }
+
+    function openCustomerDevices($event, customer) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        $state.go('home.customers.devices', {customerId: customer.id.id});
+    }
+
+    function openCustomerDashboards($event, customer) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        $state.go('home.customers.dashboards', {customerId: customer.id.id});
+    }
+}
diff --git a/ui/src/app/customer/customer.directive.js b/ui/src/app/customer/customer.directive.js
new file mode 100644
index 0000000..21ca44c
--- /dev/null
+++ b/ui/src/app/customer/customer.directive.js
@@ -0,0 +1,42 @@
+/*
+ * Copyright © 2016 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 customerFieldsetTemplate from './customer-fieldset.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function CustomerDirective($compile, $templateCache) {
+    var linker = function (scope, element) {
+        var template = $templateCache.get(customerFieldsetTemplate);
+        element.html(template);
+        $compile(element.contents())(scope);
+    }
+    return {
+        restrict: "E",
+        link: linker,
+        scope: {
+            customer: '=',
+            isEdit: '=',
+            theForm: '=',
+            onManageUsers: '&',
+            onManageDevices: '&',
+            onManageDashboards: '&',
+            onDeleteCustomer: '&'
+        }
+    };
+}
diff --git a/ui/src/app/customer/customer.routes.js b/ui/src/app/customer/customer.routes.js
new file mode 100644
index 0000000..bf0f27d
--- /dev/null
+++ b/ui/src/app/customer/customer.routes.js
@@ -0,0 +1,47 @@
+/*
+ * Copyright © 2016 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 customersTemplate from './customers.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function CustomerRoutes($stateProvider) {
+
+    $stateProvider
+        .state('home.customers', {
+            url: '/customers',
+            params: {'topIndex': 0},
+            module: 'private',
+            auth: ['TENANT_ADMIN'],
+            views: {
+                "content@home": {
+                    templateUrl: customersTemplate,
+                    controllerAs: 'vm',
+                    controller: 'CustomerController'
+                }
+            },
+            data: {
+                searchEnabled: true,
+                pageTitle: 'customer.customers'
+            },
+            ncyBreadcrumb: {
+                label: '{"icon": "supervisor_account", "label": "customer.customers"}'
+            }
+        });
+
+}
diff --git a/ui/src/app/customer/customer-card.tpl.html b/ui/src/app/customer/customer-card.tpl.html
new file mode 100644
index 0000000..99071bd
--- /dev/null
+++ b/ui/src/app/customer/customer-card.tpl.html
@@ -0,0 +1,18 @@
+<!--
+
+    Copyright © 2016 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<div class="tb-uppercase">{{ item | contactShort }}</div>
\ No newline at end of file
diff --git a/ui/src/app/customer/customer-fieldset.tpl.html b/ui/src/app/customer/customer-fieldset.tpl.html
new file mode 100644
index 0000000..dcecc2b
--- /dev/null
+++ b/ui/src/app/customer/customer-fieldset.tpl.html
@@ -0,0 +1,38 @@
+<!--
+
+    Copyright © 2016 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-button ng-click="onManageUsers({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{ 'customer.manage-users' | translate }}</md-button>
+<md-button ng-click="onManageDevices({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{ 'customer.manage-devices' | translate }}</md-button>
+<md-button ng-click="onManageDashboards({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{ 'customer.manage-dashboards' | translate }}</md-button>
+<md-button ng-click="onDeleteCustomer({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{ 'customer.delete' | translate }}</md-button>
+
+<md-content class="md-padding" layout="column">
+	<fieldset ng-disabled="loading || !isEdit">
+		<md-input-container class="md-block">
+			<label translate>customer.title</label>
+			<input required name="title" ng-model="customer.title">	
+			<div ng-messages="theForm.title.$error">
+	      		<div translate ng-message="required">customer.title-required</div>
+	    	</div>				
+		</md-input-container>
+		<md-input-container class="md-block">
+			<label translate>customer.description</label>
+			<textarea ng-model="customer.additionalInfo.description" rows="2"></textarea>
+		</md-input-container>
+		<tb-contact contact="customer" the-form="theForm" is-edit="isEdit"></tb-contact>
+	</fieldset>
+</md-content>
diff --git a/ui/src/app/customer/customers.tpl.html b/ui/src/app/customer/customers.tpl.html
new file mode 100644
index 0000000..a8b8aac
--- /dev/null
+++ b/ui/src/app/customer/customers.tpl.html
@@ -0,0 +1,29 @@
+<!--
+
+    Copyright © 2016 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-grid grid-configuration="vm.customerGridConfig">
+	<details-buttons tb-help="'customers'" help-container-id="help-container">
+		<div id="help-container"></div>
+	</details-buttons>
+	<tb-customer customer="vm.grid.operatingItem()"
+			is-edit="vm.grid.detailsConfig.isDetailsEditMode"
+			the-form="vm.grid.detailsForm"
+			on-manage-users="vm.openCustomerUsers(event, vm.grid.detailsConfig.currentItem)"
+			on-manage-devices="vm.openCustomerDevices(event, vm.grid.detailsConfig.currentItem)"
+			on-manage-dashboards="vm.openCustomerDashboards(event, vm.grid.detailsConfig.currentItem)"
+			on-delete-customer="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-customer>
+</tb-grid>
diff --git a/ui/src/app/customer/index.js b/ui/src/app/customer/index.js
new file mode 100644
index 0000000..08a1c19
--- /dev/null
+++ b/ui/src/app/customer/index.js
@@ -0,0 +1,36 @@
+/*
+ * Copyright © 2016 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 uiRouter from 'angular-ui-router';
+import thingsboardApiCustomer from '../api/customer.service';
+import thingsboardGrid from '../components/grid.directive';
+import thingsboardContact from '../components/contact.directive';
+import thingsboardContactShort from '../components/contact-short.filter';
+
+import CustomerRoutes from './customer.routes';
+import CustomerController from './customer.controller';
+import CustomerDirective from './customer.directive';
+
+export default angular.module('thingsboard.customer', [
+    uiRouter,
+    thingsboardApiCustomer,
+    thingsboardGrid,
+    thingsboardContact,
+    thingsboardContactShort
+])
+    .config(CustomerRoutes)
+    .controller('CustomerController', CustomerController)
+    .directive('tbCustomer', CustomerDirective)
+    .name;
diff --git a/ui/src/app/dashboard/add-dashboard.tpl.html b/ui/src/app/dashboard/add-dashboard.tpl.html
new file mode 100644
index 0000000..516f396
--- /dev/null
+++ b/ui/src/app/dashboard/add-dashboard.tpl.html
@@ -0,0 +1,45 @@
+<!--
+
+    Copyright © 2016 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.add' | translate }}" tb-help="'dashboards'" help-container-id="help-container">
+	<form name="theForm" ng-submit="vm.add()">
+	    <md-toolbar>
+	      <div class="md-toolbar-tools">
+	        <h2 translate>dashboard.add</h2>
+	        <span flex></span>
+			<div id="help-container"></div>
+	        <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-show="loading"></md-progress-linear>
+  	    <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
+	    <md-dialog-content>
+	      <div class="md-dialog-content">
+  	        	<tb-dashboard-details dashboard="vm.item" is-edit="true" the-form="theForm"></tb-dashboard-details>
+	      </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">
+			  {{ 'action.add' | 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>
\ No newline at end of file
diff --git a/ui/src/app/dashboard/add-dashboards-to-customer.controller.js b/ui/src/app/dashboard/add-dashboards-to-customer.controller.js
new file mode 100644
index 0000000..4a360b7
--- /dev/null
+++ b/ui/src/app/dashboard/add-dashboards-to-customer.controller.js
@@ -0,0 +1,122 @@
+/*
+ * Copyright © 2016 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 AddDashboardsToCustomerController(dashboardService, $mdDialog, $q, customerId, dashboards) {
+
+    var vm = this;
+
+    vm.dashboards = dashboards;
+    vm.searchText = '';
+
+    vm.assign = assign;
+    vm.cancel = cancel;
+    vm.hasData = hasData;
+    vm.noData = noData;
+    vm.searchDashboardTextUpdated = searchDashboardTextUpdated;
+    vm.toggleDashboardSelection = toggleDashboardSelection;
+
+    vm.theDashboards = {
+        getItemAtIndex: function (index) {
+            if (index > vm.dashboards.data.length) {
+                vm.theDashboards.fetchMoreItems_(index);
+                return null;
+            }
+            var item = vm.dashboards.data[index];
+            if (item) {
+                item.indexNumber = index + 1;
+            }
+            return item;
+        },
+
+        getLength: function () {
+            if (vm.dashboards.hasNext) {
+                return vm.dashboards.data.length + vm.dashboards.nextPageLink.limit;
+            } else {
+                return vm.dashboards.data.length;
+            }
+        },
+
+        fetchMoreItems_: function () {
+            if (vm.dashboards.hasNext && !vm.dashboards.pending) {
+                vm.dashboards.pending = true;
+                dashboardService.getTenantDashboards(vm.dashboards.nextPageLink).then(
+                    function success(dashboards) {
+                        vm.dashboards.data = vm.dashboards.data.concat(dashboards.data);
+                        vm.dashboards.nextPageLink = dashboards.nextPageLink;
+                        vm.dashboards.hasNext = dashboards.hasNext;
+                        if (vm.dashboards.hasNext) {
+                            vm.dashboards.nextPageLink.limit = vm.dashboards.pageSize;
+                        }
+                        vm.dashboards.pending = false;
+                    },
+                    function fail() {
+                        vm.dashboards.hasNext = false;
+                        vm.dashboards.pending = false;
+                    });
+            }
+        }
+    }
+
+    function cancel () {
+        $mdDialog.cancel();
+    }
+
+    function assign () {
+        var tasks = [];
+        for (var dashboardId in vm.dashboards.selections) {
+            tasks.push(dashboardService.assignDashboardToCustomer(customerId, dashboardId));
+        }
+        $q.all(tasks).then(function () {
+            $mdDialog.hide();
+        });
+    }
+
+    function noData () {
+        return vm.dashboards.data.length == 0 && !vm.dashboards.hasNext;
+    }
+
+    function hasData () {
+        return vm.dashboards.data.length > 0;
+    }
+
+    function toggleDashboardSelection ($event, dashboard) {
+        $event.stopPropagation();
+        var selected = angular.isDefined(dashboard.selected) && dashboard.selected;
+        dashboard.selected = !selected;
+        if (dashboard.selected) {
+            vm.dashboards.selections[dashboard.id.id] = true;
+            vm.dashboards.selectedCount++;
+        } else {
+            delete vm.dashboards.selections[dashboard.id.id];
+            vm.dashboards.selectedCount--;
+        }
+    }
+
+    function searchDashboardTextUpdated () {
+        vm.dashboards = {
+            pageSize: vm.dashboards.pageSize,
+            data: [],
+            nextPageLink: {
+                limit: vm.dashboards.pageSize,
+                textSearch: vm.searchText
+            },
+            selections: {},
+            selectedCount: 0,
+            hasNext: true,
+            pending: false
+        };
+    }
+}
\ No newline at end of file
diff --git a/ui/src/app/dashboard/add-dashboards-to-customer.tpl.html b/ui/src/app/dashboard/add-dashboards-to-customer.tpl.html
new file mode 100644
index 0000000..f258a9b
--- /dev/null
+++ b/ui/src/app/dashboard/add-dashboards-to-customer.tpl.html
@@ -0,0 +1,77 @@
+<!--
+
+    Copyright © 2016 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.assign-dashboard-to-customer' | translate }}">
+    <form name="theForm" ng-submit="vm.assign()">
+        <md-toolbar>
+            <div class="md-toolbar-tools">
+                <h2 translate>dashboard.assign-dashboard-to-customer</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-show="loading"></md-progress-linear>
+        <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
+        <md-dialog-content>
+            <div class="md-dialog-content">
+                <fieldset>
+                    <span translate>dashboard.assign-dashboard-to-customer-text</span>
+                    <md-input-container class="md-block" style='margin-bottom: 0px;'>
+                        <label>&nbsp;</label>
+                        <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">
+                            search
+                        </md-icon>
+                        <input id="dashboard-search" autofocus ng-model="vm.searchText"
+                               ng-change="vm.searchDashboardTextUpdated()"
+                               placeholder="{{ 'common.enter-search' | translate }}"/>
+                    </md-input-container>
+                    <div style='min-height: 150px;'>
+					<span translate layout-align="center center"
+                          style="text-transform: uppercase; display: flex; height: 150px;"
+                          class="md-subhead"
+                          ng-show="vm.noData()">dashboard.no-dashboards-text</span>
+                        <md-virtual-repeat-container ng-show="vm.hasData()"
+                                                     tb-scope-element="repeatContainer" md-top-index="vm.topIndex" flex
+                                                     style='min-height: 150px; width: 100%;'>
+                            <md-list>
+                                <md-list-item md-virtual-repeat="dashboard in vm.theDashboards" md-on-demand
+                                              class="repeated-item" flex>
+                                    <md-checkbox ng-click="vm.toggleDashboardSelection($event, dashboard)"
+                                                 aria-label="{{ 'item.selected' | translate }}"
+                                                 ng-checked="dashboard.selected"></md-checkbox>
+                                    <span> {{ dashboard.title }} </span>
+                                </md-list-item>
+                            </md-list>
+                        </md-virtual-repeat-container>
+                    </div>
+                </fieldset>
+            </div>
+        </md-dialog-content>
+        <md-dialog-actions layout="row">
+            <span flex></span>
+            <md-button ng-disabled="loading || vm.dashboards.selectedCount == 0" type="submit"
+                       class="md-raised md-primary">
+                {{ 'action.assign' | 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>
\ No newline at end of file
diff --git a/ui/src/app/dashboard/add-widget.controller.js b/ui/src/app/dashboard/add-widget.controller.js
new file mode 100644
index 0000000..ec77404
--- /dev/null
+++ b/ui/src/app/dashboard/add-widget.controller.js
@@ -0,0 +1,124 @@
+/*
+ * Copyright © 2016 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 deviceAliasesTemplate from './device-aliases.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function AddWidgetController($scope, widgetService, deviceService, $mdDialog, $q, $document, types, dashboard, widget, widgetInfo) {
+
+    var vm = this;
+
+    vm.dashboard = dashboard;
+    vm.widget = widget;
+    vm.widgetInfo = widgetInfo;
+
+    vm.functionsOnly = false;
+
+    vm.helpLinkIdForWidgetType = helpLinkIdForWidgetType;
+    vm.add = add;
+    vm.cancel = cancel;
+    vm.fetchDeviceKeys = fetchDeviceKeys;
+    vm.createDeviceAlias = createDeviceAlias;
+
+    vm.widgetConfig = vm.widget.config;
+    var settingsSchema = vm.widgetInfo.settingsSchema;
+    var dataKeySettingsSchema = vm.widgetInfo.dataKeySettingsSchema;
+    if (!settingsSchema || settingsSchema === '') {
+        vm.settingsSchema = {};
+    } else {
+        vm.settingsSchema = angular.fromJson(settingsSchema);
+    }
+    if (!dataKeySettingsSchema || dataKeySettingsSchema === '') {
+        vm.dataKeySettingsSchema = {};
+    } else {
+        vm.dataKeySettingsSchema = angular.fromJson(dataKeySettingsSchema);
+    }
+
+    function helpLinkIdForWidgetType() {
+        var link = 'widgetsConfig';
+        if (vm.widget && vm.widget.type) {
+            switch (vm.widget.type) {
+                case types.widgetType.timeseries.value: {
+                    link = 'widgetsConfigTimeseries';
+                    break;
+                }
+                case types.widgetType.latest.value: {
+                    link = 'widgetsConfigLatest';
+                    break;
+                }
+                case types.widgetType.rpc.value: {
+                    link = 'widgetsConfigRpc';
+                    break;
+                }
+            }
+        }
+        return link;
+    }
+
+    function cancel () {
+        $mdDialog.cancel();
+    }
+
+    function add () {
+        if ($scope.theForm.$valid) {
+            $scope.theForm.$setPristine();
+            vm.widget.config = vm.widgetConfig;
+            $mdDialog.hide(vm.widget);
+        }
+    }
+
+    function fetchDeviceKeys (deviceAliasId, query, type) {
+        var deviceAlias = vm.dashboard.configuration.deviceAliases[deviceAliasId];
+        if (deviceAlias && deviceAlias.deviceId) {
+            return deviceService.getDeviceKeys(deviceAlias.deviceId, query, type);
+        } else {
+            return $q.when([]);
+        }
+    }
+
+    function createDeviceAlias (event, alias) {
+
+        var deferred = $q.defer();
+        var singleDeviceAlias = {id: null, alias: alias, deviceId: null};
+
+        $mdDialog.show({
+            controller: 'DeviceAliasesController',
+            controllerAs: 'vm',
+            templateUrl: deviceAliasesTemplate,
+            locals: {
+                deviceAliases: angular.copy(vm.dashboard.configuration.deviceAliases),
+                aliasToWidgetsMap: null,
+                isSingleDevice: true,
+                singleDeviceAlias: singleDeviceAlias
+            },
+            parent: angular.element($document[0].body),
+            fullscreen: true,
+            skipHide: true,
+            targetEvent: event
+        }).then(function (singleDeviceAlias) {
+            vm.dashboard.configuration.deviceAliases[singleDeviceAlias.id] =
+                { alias: singleDeviceAlias.alias, deviceId: singleDeviceAlias.deviceId };
+            deferred.resolve(singleDeviceAlias);
+        }, function () {
+            deferred.reject();
+        });
+
+        return deferred.promise;
+    }
+}
diff --git a/ui/src/app/dashboard/add-widget.tpl.html b/ui/src/app/dashboard/add-widget.tpl.html
new file mode 100644
index 0000000..aefcdaf
--- /dev/null
+++ b/ui/src/app/dashboard/add-widget.tpl.html
@@ -0,0 +1,59 @@
+<!--
+
+    Copyright © 2016 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="{{ 'widget.add' | translate }}" style="width: 900px;" tb-help="vm.helpLinkIdForWidgetType()" help-container-id="help-container">
+    <form name="theForm" ng-submit="vm.add()">
+        <md-toolbar>
+            <div class="md-toolbar-tools">
+                <h2 translate>widget.add</h2>
+                <span flex></span>
+                <div id="help-container"></div>
+                <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-show="loading"></md-progress-linear>
+        <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
+        <md-dialog-content>
+            <div class="md-dialog-content" style="padding-top: 0px;">
+                <fieldset ng-disabled="loading" style="position: relative; height: 600px;">
+                    <tb-widget-config widget-type="vm.widget.type"
+                                      force-expand-datasources="true"
+                                      ng-model="vm.widgetConfig"
+                                      widget-settings-schema="vm.settingsSchema"
+                                      datakey-settings-schema="vm.dataKeySettingsSchema"
+                                      device-aliases="vm.dashboard.configuration.deviceAliases"
+                                      functions-only="vm.functionsOnly"
+                                      fetch-device-keys="vm.fetchDeviceKeys(deviceAliasId, query, type)"
+                                      on-create-device-alias="vm.createDeviceAlias(event, alias)"
+                                      the-form="theForm"></tb-widget-config>
+                </fieldset>
+            </div>
+        </md-dialog-content>
+        <md-dialog-actions layout="row">
+            <span flex></span>
+            <md-button ng-disabled="loading || theForm.$invalid" type="submit"
+                       class="md-raised md-primary">
+                {{ 'action.add' | 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>
diff --git a/ui/src/app/dashboard/assign-to-customer.controller.js b/ui/src/app/dashboard/assign-to-customer.controller.js
new file mode 100644
index 0000000..a5ca3cb
--- /dev/null
+++ b/ui/src/app/dashboard/assign-to-customer.controller.js
@@ -0,0 +1,123 @@
+/*
+ * Copyright © 2016 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 AssignDashboardToCustomerController(customerService, dashboardService, $mdDialog, $q, dashboardIds, customers) {
+
+    var vm = this;
+
+    vm.customers = customers;
+    vm.searchText = '';
+
+    vm.assign = assign;
+    vm.cancel = cancel;
+    vm.isCustomerSelected = isCustomerSelected;
+    vm.hasData = hasData;
+    vm.noData = noData;
+    vm.searchCustomerTextUpdated = searchCustomerTextUpdated;
+    vm.toggleCustomerSelection = toggleCustomerSelection;
+
+    vm.theCustomers = {
+        getItemAtIndex: function (index) {
+            if (index > vm.customers.data.length) {
+                vm.theCustomers.fetchMoreItems_(index);
+                return null;
+            }
+            var item = vm.customers.data[index];
+            if (item) {
+                item.indexNumber = index + 1;
+            }
+            return item;
+        },
+
+        getLength: function () {
+            if (vm.customers.hasNext) {
+                return vm.customers.data.length + vm.customers.nextPageLink.limit;
+            } else {
+                return vm.customers.data.length;
+            }
+        },
+
+        fetchMoreItems_: function () {
+            if (vm.customers.hasNext && !vm.customers.pending) {
+                vm.customers.pending = true;
+                customerService.getCustomers(vm.customers.nextPageLink).then(
+                    function success(customers) {
+                        vm.customers.data = vm.customers.data.concat(customers.data);
+                        vm.customers.nextPageLink = customers.nextPageLink;
+                        vm.customers.hasNext = customers.hasNext;
+                        if (vm.customers.hasNext) {
+                            vm.customers.nextPageLink.limit = vm.customers.pageSize;
+                        }
+                        vm.customers.pending = false;
+                    },
+                    function fail() {
+                        vm.customers.hasNext = false;
+                        vm.customers.pending = false;
+                    });
+            }
+        }
+    };
+
+    function cancel () {
+        $mdDialog.cancel();
+    }
+
+    function assign () {
+        var tasks = [];
+        for (var dashboardId in dashboardIds) {
+            tasks.push(dashboardService.assignDashboardToCustomer(vm.customers.selection.id.id, dashboardIds[dashboardId]));
+        }
+        $q.all(tasks).then(function () {
+            $mdDialog.hide();
+        });
+    }
+
+    function noData () {
+        return vm.customers.data.length == 0 && !vm.customers.hasNext;
+    }
+
+    function hasData () {
+        return vm.customers.data.length > 0;
+    }
+
+    function toggleCustomerSelection ($event, customer) {
+        $event.stopPropagation();
+        if (vm.isCustomerSelected(customer)) {
+            vm.customers.selection = null;
+        } else {
+            vm.customers.selection = customer;
+        }
+    }
+
+    function isCustomerSelected (customer) {
+        return vm.customers.selection != null && customer &&
+            customer.id.id === vm.customers.selection.id.id;
+    }
+
+    function searchCustomerTextUpdated () {
+        vm.customers = {
+            pageSize: vm.customers.pageSize,
+            data: [],
+            nextPageLink: {
+                limit: vm.customers.pageSize,
+                textSearch: vm.searchText
+            },
+            selection: null,
+            hasNext: true,
+            pending: false
+        };
+    }
+}
diff --git a/ui/src/app/dashboard/assign-to-customer.tpl.html b/ui/src/app/dashboard/assign-to-customer.tpl.html
new file mode 100644
index 0000000..dd539fa
--- /dev/null
+++ b/ui/src/app/dashboard/assign-to-customer.tpl.html
@@ -0,0 +1,76 @@
+<!--
+
+    Copyright © 2016 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.assign-dashboard-to-customer' | translate }}">
+    <form name="theForm" ng-submit="vm.assign()">
+        <md-toolbar>
+            <div class="md-toolbar-tools">
+                <h2 translate>dashboard.assign-dashboard-to-customer</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-show="loading"></md-progress-linear>
+        <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
+        <md-dialog-content>
+            <div class="md-dialog-content">
+                <fieldset>
+                    <span translate>dashboard.assign-to-customer-text</span>
+                    <md-input-container class="md-block" style='margin-bottom: 0px;'>
+                        <label>&nbsp;</label>
+                        <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">
+                            search
+                        </md-icon>
+                        <input id="customer-search" autofocus ng-model="vm.searchText"
+                               ng-change="vm.searchCustomerTextUpdated()"
+                               placeholder="{{ 'common.enter-search' | translate }}"/>
+                    </md-input-container>
+                    <div style='min-height: 150px;'>
+					<span translate layout-align="center center"
+                          style="text-transform: uppercase; display: flex; height: 150px;"
+                          class="md-subhead"
+                          ng-show="vm.noData()">customer.no-customers-text</span>
+                        <md-virtual-repeat-container ng-show="vm.hasData()"
+                                                     tb-scope-element="repeatContainer" md-top-index="vm.topIndex" flex
+                                                     style='min-height: 150px; width: 100%;'>
+                            <md-list>
+                                <md-list-item md-virtual-repeat="customer in vm.theCustomers" md-on-demand
+                                              class="repeated-item" flex>
+                                    <md-checkbox ng-click="vm.toggleCustomerSelection($event, customer)"
+                                                 aria-label="{{ 'item.selected' | translate }}"
+                                                 ng-checked="vm.isCustomerSelected(customer)"></md-checkbox>
+                                    <span> {{ customer.title }} </span>
+                                </md-list-item>
+                            </md-list>
+                        </md-virtual-repeat-container>
+                    </div>
+                </fieldset>
+            </div>
+        </md-dialog-content>
+        <md-dialog-actions layout="row">
+            <span flex></span>
+            <md-button ng-disabled="loading || vm.customers.selection==null" type="submit" class="md-raised md-primary">
+                {{ 'action.assign' | 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>
\ No newline at end of file
diff --git a/ui/src/app/dashboard/dashboard.controller.js b/ui/src/app/dashboard/dashboard.controller.js
new file mode 100644
index 0000000..18c2b5f
--- /dev/null
+++ b/ui/src/app/dashboard/dashboard.controller.js
@@ -0,0 +1,424 @@
+/*
+ * Copyright © 2016 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 deviceAliasesTemplate from './device-aliases.tpl.html';
+import addWidgetTemplate from './add-widget.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function DashboardController(types, widgetService, userService,
+                                            dashboardService, $window, $rootScope,
+                                            $scope, $state, $stateParams, $mdDialog, $timeout, $document, $q, $translate, $filter) {
+
+    var user = userService.getCurrentUser();
+
+    var vm = this;
+
+    vm.dashboard = null;
+    vm.editingWidget = null;
+    vm.editingWidgetIndex = null;
+    vm.editingWidgetSubtitle = null;
+    vm.forceDashboardMobileMode = false;
+    vm.isAddingWidget = false;
+    vm.isEdit = false;
+    vm.isEditingWidget = false;
+    vm.latestWidgetTypes = [];
+    vm.timeseriesWidgetTypes = [];
+    vm.rpcWidgetTypes = [];
+    vm.widgetEditMode = $state.$current.data.widgetEditMode;
+    vm.widgets = [];
+
+    vm.addWidget = addWidget;
+    vm.addWidgetFromType = addWidgetFromType;
+    vm.dashboardInited = dashboardInited;
+    vm.dashboardInitFailed = dashboardInitFailed;
+    vm.widgetClicked = widgetClicked;
+    vm.editWidget = editWidget;
+    vm.isTenantAdmin = isTenantAdmin;
+    vm.loadDashboard = loadDashboard;
+    vm.noData = noData;
+    vm.onAddWidgetClosed = onAddWidgetClosed;
+    vm.onEditWidgetClosed = onEditWidgetClosed;
+    vm.openDeviceAliases = openDeviceAliases;
+    vm.removeWidget = removeWidget;
+    vm.saveDashboard = saveDashboard;
+    vm.saveWidget = saveWidget;
+    vm.toggleDashboardEditMode = toggleDashboardEditMode;
+    vm.onRevertWidgetEdit = onRevertWidgetEdit;
+    vm.helpLinkIdForWidgetType = helpLinkIdForWidgetType;
+
+    vm.widgetsBundle;
+
+    $scope.$watch('vm.widgetsBundle', function (newVal, prevVal) {
+        if (newVal !== prevVal && !vm.widgetEditMode) {
+            loadWidgetLibrary();
+        }
+    });
+
+    function loadWidgetLibrary() {
+        vm.latestWidgetTypes = [];
+        vm.timeseriesWidgetTypes = [];
+        vm.rpcWidgetTypes = [];
+        if (vm.widgetsBundle) {
+            var bundleAlias = vm.widgetsBundle.alias;
+            var isSystem = vm.widgetsBundle.tenantId.id === types.id.nullUid;
+
+            widgetService.getBundleWidgetTypes(bundleAlias, isSystem).then(
+                function (widgetTypes) {
+
+                    widgetTypes = $filter('orderBy')(widgetTypes, ['-name']);
+
+                    var top = 0;
+                    var sizeY = 0;
+
+                    if (widgetTypes.length > 0) {
+                        loadNext(0);
+                    }
+
+                    function loadNextOrComplete(i) {
+                        i++;
+                        if (i < widgetTypes.length) {
+                            loadNext(i);
+                        }
+                    }
+
+                    function loadNext(i) {
+                        var widgetType = widgetTypes[i];
+                        var widgetTypeInfo = widgetService.toWidgetInfo(widgetType);
+                        var widget = {
+                            isSystemType: isSystem,
+                            bundleAlias: bundleAlias,
+                            typeAlias: widgetTypeInfo.alias,
+                            type: widgetTypeInfo.type,
+                            title: widgetTypeInfo.widgetName,
+                            sizeX: widgetTypeInfo.sizeX,
+                            sizeY: widgetTypeInfo.sizeY,
+                            row: top,
+                            col: 0,
+                            config: angular.fromJson(widgetTypeInfo.defaultConfig)
+                        };
+                        widget.config.title = widgetTypeInfo.widgetName;
+                        if (widgetTypeInfo.type === types.widgetType.timeseries.value) {
+                            vm.timeseriesWidgetTypes.push(widget);
+                        } else if (widgetTypeInfo.type === types.widgetType.latest.value) {
+                            vm.latestWidgetTypes.push(widget);
+                        } else if (widgetTypeInfo.type === types.widgetType.rpc.value) {
+                            vm.rpcWidgetTypes.push(widget);
+                        }
+                        top += sizeY;
+                        loadNextOrComplete(i);
+
+                    }
+                }
+            );
+        }
+    }
+
+    function loadDashboard() {
+
+        var deferred = $q.defer();
+
+        if (vm.widgetEditMode) {
+            $timeout(function () {
+                vm.widgets = [{
+                    isSystemType: true,
+                    bundleAlias: 'customWidgetBundle',
+                    typeAlias: 'customWidget',
+                    type: $rootScope.editWidgetInfo.type,
+                    title: 'My widget',
+                    sizeX: $rootScope.editWidgetInfo.sizeX * 2,
+                    sizeY: $rootScope.editWidgetInfo.sizeY * 2,
+                    row: 2,
+                    col: 4,
+                    config: angular.fromJson($rootScope.editWidgetInfo.defaultConfig)
+                }];
+                vm.widgets[0].config.title = vm.widgets[0].config.title || $rootScope.editWidgetInfo.widgetName;
+                deferred.resolve();
+                var parentScope = $window.parent.angular.element($window.frameElement).scope();
+                parentScope.$root.$broadcast('widgetEditModeInited');
+                parentScope.$root.$apply();
+
+                $scope.$watch('vm.widgets', function () {
+                    var widget = vm.widgets[0];
+                    parentScope.$root.$broadcast('widgetEditUpdated', widget);
+                    parentScope.$root.$apply();
+                }, true);
+
+            });
+        } else {
+
+            dashboardService.getDashboard($stateParams.dashboardId)
+                .then(function success(dashboard) {
+                    vm.dashboard = dashboard;
+                    if (vm.dashboard.configuration == null) {
+                        vm.dashboard.configuration = {widgets: [], deviceAliases: {}};
+                    }
+                    if (angular.isUndefined(vm.dashboard.configuration.widgets)) {
+                        vm.dashboard.configuration.widgets = [];
+                    }
+                    if (angular.isUndefined(vm.dashboard.configuration.deviceAliases)) {
+                        vm.dashboard.configuration.deviceAliases = {};
+                    }
+                    vm.widgets = vm.dashboard.configuration.widgets;
+                    deferred.resolve();
+                }, function fail(e) {
+                    deferred.reject(e);
+                });
+
+        }
+        return deferred.promise;
+    }
+
+    function dashboardInitFailed() {
+        var parentScope = $window.parent.angular.element($window.frameElement).scope();
+        parentScope.$emit('widgetEditModeInited');
+        parentScope.$apply();
+    }
+
+    function dashboardInited(dashboard) {
+        vm.dashboardContainer = dashboard;
+    }
+
+    function isTenantAdmin() {
+        return user.authority === 'TENANT_ADMIN';
+    }
+
+    function noData() {
+        return vm.widgets.length == 0;
+    }
+
+    function openDeviceAliases($event) {
+        var aliasToWidgetsMap = {};
+        var widgetsTitleList;
+        for (var w in vm.widgets) {
+            var widget = vm.widgets[w];
+            if (widget.type === types.widgetType.rpc.value) {
+                if (widget.config.targetDeviceAliasIds && widget.config.targetDeviceAliasIds.length > 0) {
+                    var targetDeviceAliasId = widget.config.targetDeviceAliasIds[0];
+                    widgetsTitleList = aliasToWidgetsMap[targetDeviceAliasId];
+                    if (!widgetsTitleList) {
+                        widgetsTitleList = [];
+                        aliasToWidgetsMap[targetDeviceAliasId] = widgetsTitleList;
+                    }
+                    widgetsTitleList.push(widget.config.title);
+                }
+            } else {
+                for (var i in widget.config.datasources) {
+                    var datasource = widget.config.datasources[i];
+                    if (datasource.type === types.datasourceType.device && datasource.deviceAliasId) {
+                        widgetsTitleList = aliasToWidgetsMap[datasource.deviceAliasId];
+                        if (!widgetsTitleList) {
+                            widgetsTitleList = [];
+                            aliasToWidgetsMap[datasource.deviceAliasId] = widgetsTitleList;
+                        }
+                        widgetsTitleList.push(widget.config.title);
+                    }
+                }
+            }
+        }
+
+        $mdDialog.show({
+            controller: 'DeviceAliasesController',
+            controllerAs: 'vm',
+            templateUrl: deviceAliasesTemplate,
+            locals: {
+                deviceAliases: angular.copy(vm.dashboard.configuration.deviceAliases),
+                aliasToWidgetsMap: aliasToWidgetsMap,
+                isSingleDevice: false,
+                singleDeviceAlias: null
+            },
+            parent: angular.element($document[0].body),
+            skipHide: true,
+            fullscreen: true,
+            targetEvent: $event
+        }).then(function (deviceAliases) {
+            vm.dashboard.configuration.deviceAliases = deviceAliases;
+        }, function () {
+        });
+    }
+
+    function editWidget($event, widget) {
+        $event.stopPropagation();
+        var newEditingIndex = vm.widgets.indexOf(widget);
+        if (vm.editingWidgetIndex === newEditingIndex) {
+            $timeout(onEditWidgetClosed());
+        } else {
+            var transition = !vm.forceDashboardMobileMode;
+            vm.editingWidgetIndex = vm.widgets.indexOf(widget);
+            vm.editingWidget = angular.copy(widget);
+            vm.editingWidgetSubtitle = widgetService.getInstantWidgetInfo(vm.editingWidget).widgetName;
+            vm.forceDashboardMobileMode = true;
+            vm.isEditingWidget = true;
+
+            if (vm.dashboardContainer) {
+                var delayOffset = transition ? 350 : 0;
+                var delay = transition ? 400 : 300;
+                $timeout(function () {
+                    vm.dashboardContainer.highlightWidget(vm.editingWidgetIndex, delay);
+                }, delayOffset, false);
+            }
+        }
+    }
+
+    function widgetClicked($event, widget) {
+        if (vm.isEditingWidget) {
+            editWidget($event, widget);
+        }
+    }
+
+    function helpLinkIdForWidgetType() {
+        var link = 'widgetsConfig';
+        if (vm.editingWidget && vm.editingWidget.type) {
+            switch (vm.editingWidget.type) {
+                case types.widgetType.timeseries.value: {
+                    link = 'widgetsConfigTimeseries';
+                    break;
+                }
+                case types.widgetType.latest.value: {
+                    link = 'widgetsConfigLatest';
+                    break;
+                }
+                case types.widgetType.rpc.value: {
+                    link = 'widgetsConfigRpc';
+                    break;
+                }
+            }
+        }
+        return link;
+    }
+
+    function onRevertWidgetEdit(widgetForm) {
+        if (widgetForm.$dirty) {
+            widgetForm.$setPristine();
+            vm.editingWidget = angular.copy(vm.widgets[vm.editingWidgetIndex]);
+        }
+    }
+
+    function saveWidget(widgetForm) {
+        widgetForm.$setPristine();
+        vm.widgets[vm.editingWidgetIndex] = angular.copy(vm.editingWidget);
+    }
+
+    function onEditWidgetClosed() {
+        vm.editingWidgetIndex = null;
+        vm.editingWidget = null;
+        vm.editingWidgetSubtitle = null;
+        vm.isEditingWidget = false;
+        if (vm.dashboardContainer) {
+            vm.dashboardContainer.resetHighlight();
+        }
+        vm.forceDashboardMobileMode = false;
+    }
+
+    function addWidget() {
+        loadWidgetLibrary();
+        vm.isAddingWidget = true;
+    }
+
+    function onAddWidgetClosed() {
+        vm.timeseriesWidgetTypes = [];
+        vm.latestWidgetTypes = [];
+        vm.rpcWidgetTypes = [];
+    }
+
+    function addWidgetFromType(event, widget) {
+        vm.onAddWidgetClosed();
+        vm.isAddingWidget = false;
+        widgetService.getWidgetInfo(widget.bundleAlias, widget.typeAlias, widget.isSystemType).then(
+            function (widgetTypeInfo) {
+                var config = angular.fromJson(widgetTypeInfo.defaultConfig);
+                config.title = 'New ' + widgetTypeInfo.widgetName;
+                config.datasources = [];
+                var newWidget = {
+                    isSystemType: widget.isSystemType,
+                    bundleAlias: widget.bundleAlias,
+                    typeAlias: widgetTypeInfo.alias,
+                    type: widgetTypeInfo.type,
+                    title: 'New widget',
+                    sizeX: widgetTypeInfo.sizeX,
+                    sizeY: widgetTypeInfo.sizeY,
+                    config: config
+                };
+                $mdDialog.show({
+                    controller: 'AddWidgetController',
+                    controllerAs: 'vm',
+                    templateUrl: addWidgetTemplate,
+                    locals: {dashboard: vm.dashboard, widget: newWidget, widgetInfo: widgetTypeInfo},
+                    parent: angular.element($document[0].body),
+                    fullscreen: true,
+                    skipHide: true,
+                    targetEvent: event,
+                    onComplete: function () {
+                        var w = angular.element($window);
+                        w.triggerHandler('resize');
+                    }
+                }).then(function (widget) {
+                    vm.widgets.push(widget);
+                }, function () {
+                });
+            }
+        );
+    }
+
+    function removeWidget(event, widget) {
+        var title = widget.config.title;
+        if (!title || title.length === 0) {
+            title = widgetService.getInstantWidgetInfo(widget).widgetName;
+        }
+        var confirm = $mdDialog.confirm()
+            .targetEvent(event)
+            .title($translate.instant('widget.remove-widget-title', {widgetTitle: title}))
+            .htmlContent($translate.instant('widget.remove-widget-text'))
+            .ariaLabel($translate.instant('widget.remove'))
+            .cancel($translate.instant('action.no'))
+            .ok($translate.instant('action.yes'));
+        $mdDialog.show(confirm).then(function () {
+            vm.widgets.splice(vm.widgets.indexOf(widget), 1);
+        });
+    }
+
+    function toggleDashboardEditMode() {
+        vm.isEdit = !vm.isEdit;
+        if (vm.isEdit) {
+            if (vm.widgetEditMode) {
+                vm.prevWidgets = angular.copy(vm.widgets);
+            } else {
+                vm.prevDashboard = angular.copy(vm.dashboard);
+            }
+        } else {
+            if (vm.widgetEditMode) {
+                vm.widgets = vm.prevWidgets;
+            } else {
+                vm.dashboard = vm.prevDashboard;
+                vm.widgets = vm.dashboard.configuration.widgets;
+            }
+        }
+    }
+
+    function saveDashboard() {
+        vm.isEdit = false;
+        notifyDashboardUpdated();
+    }
+
+    function notifyDashboardUpdated() {
+        if (!vm.widgetEditMode) {
+            dashboardService.saveDashboard(vm.dashboard);
+        }
+    }
+
+}
diff --git a/ui/src/app/dashboard/dashboard.directive.js b/ui/src/app/dashboard/dashboard.directive.js
new file mode 100644
index 0000000..cd9243f
--- /dev/null
+++ b/ui/src/app/dashboard/dashboard.directive.js
@@ -0,0 +1,42 @@
+/*
+ * Copyright © 2016 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 dashboardFieldsetTemplate from './dashboard-fieldset.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function DashboardDirective($compile, $templateCache) {
+    var linker = function (scope, element) {
+        var template = $templateCache.get(dashboardFieldsetTemplate);
+        element.html(template);
+        $compile(element.contents())(scope);
+    }
+    return {
+        restrict: "E",
+        link: linker,
+        scope: {
+            dashboard: '=',
+            isEdit: '=',
+            dashboardScope: '=',
+            theForm: '=',
+            onAssignToCustomer: '&',
+            onUnassignFromCustomer: '&',
+            onDeleteDashboard: '&'
+        }
+    };
+}
diff --git a/ui/src/app/dashboard/dashboard.routes.js b/ui/src/app/dashboard/dashboard.routes.js
new file mode 100644
index 0000000..6c3012c
--- /dev/null
+++ b/ui/src/app/dashboard/dashboard.routes.js
@@ -0,0 +1,107 @@
+/*
+ * Copyright © 2016 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 dashboardsTemplate from './dashboards.tpl.html';
+import dashboardTemplate from './dashboard.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function DashboardRoutes($stateProvider) {
+    $stateProvider
+        .state('home.dashboards', {
+            url: '/dashboards',
+            params: {'topIndex': 0},
+            module: 'private',
+            auth: ['TENANT_ADMIN', 'CUSTOMER_USER'],
+            views: {
+                "content@home": {
+                    templateUrl: dashboardsTemplate,
+                    controller: 'DashboardsController',
+                    controllerAs: 'vm'
+                }
+            },
+            data: {
+                dashboardsType: 'tenant',
+                searchEnabled: true,
+                pageTitle: 'dashboard.dashboards'
+            },
+            ncyBreadcrumb: {
+                label: '{"icon": "dashboard", "label": "dashboard.dashboards"}'
+            }
+        })
+        .state('home.customers.dashboards', {
+            url: '/:customerId/dashboards',
+            params: {'topIndex': 0},
+            module: 'private',
+            auth: ['TENANT_ADMIN'],
+            views: {
+                "content@home": {
+                    templateUrl: dashboardsTemplate,
+                    controllerAs: 'vm',
+                    controller: 'DashboardsController'
+                }
+            },
+            data: {
+                dashboardsType: 'customer',
+                searchEnabled: true,
+                pageTitle: 'customer.dashboards'
+            },
+            ncyBreadcrumb: {
+                label: '{"icon": "dashboard", "label": "customer.dashboards"}'
+            }
+        })
+        .state('home.dashboards.dashboard', {
+            url: '/:dashboardId',
+            module: 'private',
+            auth: ['TENANT_ADMIN', 'CUSTOMER_USER'],
+            views: {
+                "content@home": {
+                    templateUrl: dashboardTemplate,
+                    controller: 'DashboardController',
+                    controllerAs: 'vm'
+                }
+            },
+            data: {
+                widgetEditMode: false,
+                searchEnabled: false,
+                pageTitle: 'dashboard.dashboard'
+            },
+            ncyBreadcrumb: {
+                label: '{"icon": "dashboard", "label": "{{ vm.dashboard.title }}", "translate": "false"}'
+            }
+        })
+        .state('home.customers.dashboards.dashboard', {
+            url: '/:dashboardId',
+            module: 'private',
+            auth: ['TENANT_ADMIN', 'CUSTOMER_USER'],
+            views: {
+                "content@home": {
+                    templateUrl: dashboardTemplate,
+                    controller: 'DashboardController',
+                    controllerAs: 'vm'
+                }
+            },
+            data: {
+                searchEnabled: false,
+                pageTitle: 'customer.dashboard'
+            },
+            ncyBreadcrumb: {
+                label: '{"icon": "dashboard", "label": "customer.dashboard"}'
+            }
+        })
+}
diff --git a/ui/src/app/dashboard/dashboard.scss b/ui/src/app/dashboard/dashboard.scss
new file mode 100644
index 0000000..11efbec
--- /dev/null
+++ b/ui/src/app/dashboard/dashboard.scss
@@ -0,0 +1,55 @@
+/**
+ * Copyright © 2016 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 '../../scss/constants';
+
+section.tb-dashboard-title {
+  position: absolute;
+  top: 0;
+  left: 20px;
+}
+
+input.tb-dashboard-title {
+  font-size: 2.000rem;
+  font-weight: 500;
+  letter-spacing: 0.005em;
+  height: 38px;
+}
+
+div.tb-padded {
+  top: 60px;
+}
+
+section.tb-padded {
+  top: 60px;
+}
+
+div.tb-shrinked {
+  width: 40%;
+}
+
+tb-details-sidenav.tb-widget-details-sidenav {
+  md-sidenav.tb-sidenav-details {
+    @media (min-width: $layout-breakpoint-gt-sm) {
+      width: 85% !important;
+    }
+    @media (min-width: $layout-breakpoint-gt-md) {
+      width: 75% !important;
+    }
+    @media (min-width: $layout-breakpoint-lg) {
+      width: 60% !important;
+    }
+  }
+}
diff --git a/ui/src/app/dashboard/dashboard.tpl.html b/ui/src/app/dashboard/dashboard.tpl.html
new file mode 100644
index 0000000..b18c392
--- /dev/null
+++ b/ui/src/app/dashboard/dashboard.tpl.html
@@ -0,0 +1,177 @@
+<!--
+
+    Copyright © 2016 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 flex tb-expand-fullscreen="vm.widgetEditMode" hide-expand-button="vm.widgetEditMode">
+    <section ng-show="!vm.isAddingWidget && !loading && !vm.widgetEditMode" layout="row" layout-wrap
+             class="tb-header-buttons tb-top-header-buttons md-fab" ng-style="{'right': '50px'}">
+        <md-button ng-if="vm.isTenantAdmin()" ng-show="vm.isEdit" ng-disabled="loading"
+                   class="tb-btn-header md-accent md-hue-2 md-fab md-fab-bottom-right"
+                   aria-label="{{ 'action.apply' | translate }}"
+                   ng-click="vm.saveDashboard()">
+            <md-tooltip md-direction="top">
+                {{ 'action.apply-changes' | translate }}
+            </md-tooltip>
+            <ng-md-icon icon="done"></ng-md-icon>
+        </md-button>
+        <md-button ng-if="vm.isTenantAdmin()" ng-disabled="loading"
+                   class="tb-btn-header md-accent md-hue-2 md-fab md-fab-bottom-right"
+                   aria-label="{{ 'action.edit-mode' | translate }}"
+                   ng-click="vm.toggleDashboardEditMode()">
+            <md-tooltip md-direction="top">
+                {{ (vm.isEdit ? 'action.decline-changes' : 'action.enter-edit-mode') | translate }}
+            </md-tooltip>
+            <ng-md-icon icon="{{vm.isEdit ? 'close' : 'edit'}}"
+                        options='{"easing": "circ-in-out", "duration": 375, "rotation": "none"}'></ng-md-icon>
+        </md-button>
+    </section>
+    <section ng-show="!loading && vm.noData()" layout-align="center center"
+             ng-class="{'tb-padded' : !vm.widgetEditMode}"
+             style="text-transform: uppercase; display: flex; z-index: 1;"
+             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)">
+            <md-icon aria-label="{{ 'action.add' | translate }}" class="material-icons tb-md-96">add</md-icon>
+            {{ 'dashboard.add-widget' | translate }}
+        </md-button>
+    </section>
+    <section ng-if="!vm.widgetEditMode" class="tb-dashboard-title" layout="row" layout-align="center center">
+        <h3 ng-show="!vm.isEdit">{{ vm.dashboard.title }}</h3>
+        <md-input-container ng-show="vm.isEdit" class="md-block" style="height: 30px;">
+            <label translate>dashboard.title</label>
+            <input class="tb-dashboard-title" required name="title" ng-model="vm.dashboard.title">
+        </md-input-container>
+        <md-button class="md-raised" flex="none" ng-show="vm.isEdit" ng-click="vm.openDeviceAliases($event)">
+            {{ 'device.aliases' | translate }}
+        </md-button>
+    </section>
+    <div class="tb-absolute-fill" ng-class="{ 'tb-padded' : !vm.widgetEditMode, 'tb-shrinked' : vm.isEditingWidget }">
+        <tb-dashboard
+                widgets="vm.widgets"
+                device-alias-list="vm.dashboard.configuration.deviceAliases"
+                is-edit="vm.isEdit || vm.widgetEditMode"
+                is-mobile="vm.forceDashboardMobileMode"
+                is-mobile-disabled="vm.widgetEditMode"
+                is-edit-action-enabled="vm.isEdit || vm.widgetEditMode"
+                is-remove-action-enabled="vm.isEdit && !vm.widgetEditMode"
+                on-edit-widget="vm.editWidget(event, widget)"
+                on-widget-clicked="vm.widgetClicked(event, widget)"
+                on-remove-widget="vm.removeWidget(event, widget)"
+                load-widgets="vm.loadDashboard()"
+                on-init="vm.dashboardInited(dashboard)"
+                on-init-failed="vm.dashboardInitFailed(e)">
+        </tb-dashboard>
+    </div>
+    <tb-details-sidenav class="tb-widget-details-sidenav"
+                        header-title="vm.editingWidget.config.title"
+                        header-subtitle="{{vm.editingWidgetSubtitle}}"
+                        is-read-only="false"
+                        is-open="vm.isEditingWidget"
+                        is-always-edit="true"
+                        on-close-details="vm.onEditWidgetClosed()"
+                        on-toggle-details-edit-mode="vm.onRevertWidgetEdit(vm.widgetForm)"
+                        on-apply-details="vm.saveWidget(vm.widgetForm)"
+                        the-form="vm.widgetForm">
+        <details-buttons tb-help="vm.helpLinkIdForWidgetType()" help-container-id="help-container">
+            <div id="help-container"></div>
+        </details-buttons>
+        <form name="vm.widgetForm">
+            <tb-edit-widget
+                    dashboard="vm.dashboard"
+                    widget="vm.editingWidget"
+                    the-form="vm.widgetForm">
+            </tb-edit-widget>
+        </form>
+    </tb-details-sidenav>
+    <tb-details-sidenav ng-if="!vm.widgetEditMode" class="tb-select-widget-sidenav"
+                        header-title="'dashboard.select-widget-title' | translate"
+                        header-height-px="120"
+                        is-read-only="true"
+                        is-open="vm.isAddingWidget"
+                        is-edit="false"
+                        on-close-details="vm.onAddWidgetClosed()">
+        <header-pane>
+            <div layout="row">
+                <span class="tb-details-subtitle">{{ 'widgets-bundle.current' | translate }}</span>
+                <tb-widgets-bundle-select flex-offset="5"
+                                          flex
+                        ng-model="vm.widgetsBundle"
+                        tb-required="true"
+                        select-first-bundle="false">
+                </tb-widgets-bundle-select>
+            </div>
+        </header-pane>
+        <div>
+            <md-tabs ng-if="vm.timeseriesWidgetTypes.length > 0 || vm.latestWidgetTypes.length > 0 ||
+                            vm.rpcWidgetTypes.length > 0"
+                     flex
+                     class="tb-absolute-fill" md-border-bottom>
+                <md-tab ng-if="vm.timeseriesWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.timeseries' | translate }}">
+                    <tb-dashboard
+                            widgets="vm.timeseriesWidgetTypes"
+                            is-edit="false"
+                            is-mobile="true"
+                            is-edit-action-enabled="false"
+                            is-remove-action-enabled="false"
+                            on-widget-clicked="vm.addWidgetFromType(event, widget)">
+                    </tb-dashboard>
+                </md-tab>
+                <md-tab ng-if="vm.latestWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.latest-values' | translate }}">
+                    <tb-dashboard
+                            widgets="vm.latestWidgetTypes"
+                            is-edit="false"
+                            is-mobile="true"
+                            is-edit-action-enabled="false"
+                            is-remove-action-enabled="false"
+                            on-widget-clicked="vm.addWidgetFromType(event, widget)">
+                    </tb-dashboard>
+                </md-tab>
+                <md-tab ng-if="vm.rpcWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.rpc' | translate }}">
+                    <tb-dashboard
+                            widgets="vm.rpcWidgetTypes"
+                            is-edit="false"
+                            is-mobile="true"
+                            is-edit-action-enabled="false"
+                            is-remove-action-enabled="false"
+                            on-widget-clicked="vm.addWidgetFromType(event, widget)">
+                    </tb-dashboard>
+                </md-tab>
+            </md-tabs>
+            <span translate ng-if="vm.timeseriesWidgetTypes.length === 0 && vm.latestWidgetTypes.length === 0 &&
+                                   vm.rpcWidgetTypes.length === 0 && vm.widgetsBundle"
+                  layout-align="center center"
+                  style="text-transform: uppercase; display: flex;"
+                  class="md-headline tb-absolute-fill">widgets-bundle.empty</span>
+            <span translate ng-if="!vm.widgetsBundle"
+                  layout-align="center center"
+                  style="text-transform: uppercase; display: flex;"
+                  class="md-headline tb-absolute-fill">widget.select-widgets-bundle</span>
+        </div>
+    </tb-details-sidenav>
+    <!-- </section> -->
+    <section layout="row" layout-wrap class="tb-footer-buttons md-fab ">
+        <md-button ng-disabled="loading" ng-if="!vm.isAddingWidget && vm.isEdit && !vm.widgetEditMode"
+                   class="tb-btn-footer md-accent md-hue-2 md-fab" ng-click="vm.addWidget($event)"
+                   aria-label="{{ 'dashboard.add-widget' | translate }}">
+            <md-tooltip md-direction="top">
+                {{ 'dashboard.add-widget' | translate }}
+            </md-tooltip>
+            <ng-md-icon icon="add"></ng-md-icon>
+        </md-button>
+    </section>
+</md-content>
diff --git a/ui/src/app/dashboard/dashboard-card.tpl.html b/ui/src/app/dashboard/dashboard-card.tpl.html
new file mode 100644
index 0000000..8151b2f
--- /dev/null
+++ b/ui/src/app/dashboard/dashboard-card.tpl.html
@@ -0,0 +1,18 @@
+<!--
+
+    Copyright © 2016 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></div>
\ No newline at end of file
diff --git a/ui/src/app/dashboard/dashboard-fieldset.tpl.html b/ui/src/app/dashboard/dashboard-fieldset.tpl.html
new file mode 100644
index 0000000..46e15b4
--- /dev/null
+++ b/ui/src/app/dashboard/dashboard-fieldset.tpl.html
@@ -0,0 +1,36 @@
+<!--
+
+    Copyright © 2016 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-button ng-click="onAssignToCustomer({event: $event})" ng-show="!isEdit && dashboardScope === 'tenant'" class="md-raised md-primary">{{ 'dashboard.assign-to-customer' | translate }}</md-button>
+<md-button ng-click="onUnassignFromCustomer({event: $event})" ng-show="!isEdit && dashboardScope === 'customer'" class="md-raised md-primary">{{ 'dashboard.unassign-from-customer' | translate }}</md-button>
+<md-button ng-click="onDeleteDashboard({event: $event})" ng-show="!isEdit && dashboardScope === 'tenant'" class="md-raised md-primary">{{ 'dashboard.delete' | translate }}</md-button>
+
+<md-content class="md-padding" layout="column">
+	<fieldset ng-disabled="loading || !isEdit">
+		<md-input-container class="md-block">
+			<label translate>dashboard.title</label>
+			<input required name="title" ng-model="dashboard.title">	
+			<div ng-messages="theForm.title.$error">
+	      		<div translate ng-message="required">dashboard.title-required</div>
+	    	</div>				
+		</md-input-container>
+		<md-input-container class="md-block">
+			<label translate>dashboard.description</label>
+			<textarea ng-model="dashboard.configuration.description" rows="2"></textarea>
+		</md-input-container>
+	</fieldset>
+</md-content>
diff --git a/ui/src/app/dashboard/dashboards.controller.js b/ui/src/app/dashboard/dashboards.controller.js
new file mode 100644
index 0000000..5a93ad3
--- /dev/null
+++ b/ui/src/app/dashboard/dashboards.controller.js
@@ -0,0 +1,379 @@
+/*
+ * Copyright © 2016 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 addDashboardTemplate from './add-dashboard.tpl.html';
+import dashboardCard from './dashboard-card.tpl.html';
+import assignToCustomerTemplate from './assign-to-customer.tpl.html';
+import addDashboardsToCustomerTemplate from './add-dashboards-to-customer.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function DashboardsController(userService, dashboardService, customerService, $scope, $controller, $state, $stateParams, $mdDialog, $document, $q, $translate) {
+
+    var customerId = $stateParams.customerId;
+
+    var dashboardActionsList = [
+        {
+            onAction: function ($event, item) {
+                vm.grid.openItem($event, item);
+            },
+            name: function() { return $translate.instant('dashboard.details') },
+            details: function() { return $translate.instant('dashboard.dashboard-details') },
+            icon: "edit"
+        }
+    ];
+
+    var dashboardGroupActionsList = [];
+
+    var vm = this;
+
+    vm.dashboardGridConfig = {
+        deleteItemTitleFunc: deleteDashboardTitle,
+        deleteItemContentFunc: deleteDashboardText,
+        deleteItemsTitleFunc: deleteDashboardsTitle,
+        deleteItemsActionTitleFunc: deleteDashboardsActionTitle,
+        deleteItemsContentFunc: deleteDashboardsText,
+
+        saveItemFunc: saveDashboard,
+
+        clickItemFunc: openDashboard,
+
+        getItemTitleFunc: getDashboardTitle,
+        itemCardTemplateUrl: dashboardCard,
+
+        actionsList: dashboardActionsList,
+        groupActionsList: dashboardGroupActionsList,
+
+        onGridInited: gridInited,
+
+        addItemTemplateUrl: addDashboardTemplate,
+
+        addItemText: function() { return $translate.instant('dashboard.add-dashboard-text') },
+        noItemsText: function() { return $translate.instant('dashboard.no-dashboards-text') },
+        itemDetailsText: function() { return $translate.instant('dashboard.dashboard-details') },
+        isDetailsReadOnly: function () {
+            return vm.dashboardsScope === 'customer_user';
+        },
+        isSelectionEnabled: function () {
+            return !(vm.dashboardsScope === 'customer_user');
+        }
+    };
+
+    if (angular.isDefined($stateParams.items) && $stateParams.items !== null) {
+        vm.dashboardGridConfig.items = $stateParams.items;
+    }
+
+    if (angular.isDefined($stateParams.topIndex) && $stateParams.topIndex > 0) {
+        vm.dashboardGridConfig.topIndex = $stateParams.topIndex;
+    }
+
+    vm.dashboardsScope = $state.$current.data.dashboardsType;
+
+    vm.assignToCustomer = assignToCustomer;
+    vm.unassignFromCustomer = unassignFromCustomer;
+
+    initController();
+
+    function initController() {
+        var fetchDashboardsFunction = null;
+        var deleteDashboardFunction = null;
+        var refreshDashboardsParamsFunction = null;
+
+        var user = userService.getCurrentUser();
+
+        if (user.authority === 'CUSTOMER_USER') {
+            vm.dashboardsScope = 'customer_user';
+            customerId = user.customerId;
+        }
+
+        if (vm.dashboardsScope === 'tenant') {
+            fetchDashboardsFunction = function (pageLink) {
+                return dashboardService.getTenantDashboards(pageLink);
+            };
+            deleteDashboardFunction = function (dashboardId) {
+                return dashboardService.deleteDashboard(dashboardId);
+            };
+            refreshDashboardsParamsFunction = function () {
+                return {"topIndex": vm.topIndex};
+            };
+
+            dashboardActionsList.push(
+                {
+                    onAction: function ($event, item) {
+                        assignToCustomer($event, [ item.id.id ]);
+                    },
+                    name: function() { return $translate.instant('action.assign') },
+                    details: function() { return $translate.instant('dashboard.assign-to-customer') },
+                    icon: "assignment_ind"
+                }
+            );
+
+            dashboardActionsList.push(
+                {
+                    onAction: function ($event, item) {
+                        vm.grid.deleteItem($event, item);
+                    },
+                    name: function() { return $translate.instant('action.delete') },
+                    details: function() { return $translate.instant('dashboard.delete') },
+                    icon: "delete"
+                }
+            );
+
+            dashboardGroupActionsList.push(
+                    {
+                        onAction: function ($event, items) {
+                            assignDashboardsToCustomer($event, items);
+                        },
+                        name: function() { return $translate.instant('dashboard.assign-dashboards') },
+                        details: function(selectedCount) {
+                            return $translate.instant('dashboard.assign-dashboards-text', {count: selectedCount}, "messageformat");
+                        },
+                        icon: "assignment_ind"
+                    }
+            );
+
+            dashboardGroupActionsList.push(
+                {
+                    onAction: function ($event) {
+                        vm.grid.deleteItems($event);
+                    },
+                    name: function() { return $translate.instant('dashboard.delete-dashboards') },
+                    details: deleteDashboardsActionTitle,
+                    icon: "delete"
+                }
+            );
+
+
+        } else if (vm.dashboardsScope === 'customer' || vm.dashboardsScope === 'customer_user') {
+            fetchDashboardsFunction = function (pageLink) {
+                return dashboardService.getCustomerDashboards(customerId, pageLink);
+            };
+            deleteDashboardFunction = function (dashboardId) {
+                return dashboardService.unassignDashboardFromCustomer(dashboardId);
+            };
+            refreshDashboardsParamsFunction = function () {
+                return {"customerId": customerId, "topIndex": vm.topIndex};
+            };
+
+            if (vm.dashboardsScope === 'customer') {
+                dashboardActionsList.push(
+                    {
+                        onAction: function ($event, item) {
+                            unassignFromCustomer($event, item);
+                        },
+                        name: function() { return $translate.instant('action.unassign') },
+                        details: function() { return $translate.instant('dashboard.unassign-from-customer') },
+                        icon: "assignment_return"
+                    }
+                );
+
+                dashboardGroupActionsList.push(
+                    {
+                        onAction: function ($event, items) {
+                            unassignDashboardsFromCustomer($event, items);
+                        },
+                        name: function() { return $translate.instant('dashboard.unassign-dashboards') },
+                        details: function(selectedCount) {
+                            return $translate.instant('dashboard.unassign-dashboards-action-title', {count: selectedCount}, "messageformat");
+                        },
+                        icon: "assignment_return"
+                    }
+                );
+
+
+                vm.dashboardGridConfig.addItemAction = {
+                    onAction: function ($event) {
+                        addDashboardsToCustomer($event);
+                    },
+                    name: function() { return $translate.instant('dashboard.assign-dashboards') },
+                    details: function() { return $translate.instant('dashboard.assign-new-dashboard') },
+                    icon: "add"
+                };
+            } else if (vm.dashboardsScope === 'customer_user') {
+                vm.dashboardGridConfig.addItemAction = {};
+            }
+        }
+
+        vm.dashboardGridConfig.refreshParamsFunc = refreshDashboardsParamsFunction;
+        vm.dashboardGridConfig.fetchItemsFunc = fetchDashboardsFunction;
+        vm.dashboardGridConfig.deleteItemFunc = deleteDashboardFunction;
+
+    }
+
+    function deleteDashboardTitle (dashboard) {
+        return $translate.instant('dashboard.delete-dashboard-title', {dashboardTitle: dashboard.title});
+    }
+
+    function deleteDashboardText () {
+        return $translate.instant('dashboard.delete-dashboard-text');
+    }
+
+    function deleteDashboardsTitle (selectedCount) {
+        return $translate.instant('dashboard.delete-dashboards-title', {count: selectedCount}, 'messageformat');
+    }
+
+    function deleteDashboardsActionTitle(selectedCount) {
+        return $translate.instant('dashboard.delete-dashboards-action-title', {count: selectedCount}, 'messageformat');
+    }
+
+    function deleteDashboardsText () {
+        return $translate.instant('dashboard.delete-dashboards-text');
+    }
+
+    function gridInited(grid) {
+        vm.grid = grid;
+    }
+
+    function getDashboardTitle(dashboard) {
+        return dashboard ? dashboard.title : '';
+    }
+
+    function saveDashboard(dashboard) {
+        return dashboardService.saveDashboard(dashboard);
+    }
+
+    function assignToCustomer($event, dashboardIds) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        var pageSize = 10;
+        customerService.getCustomers({limit: pageSize, textSearch: ''}).then(
+            function success(_customers) {
+                var customers = {
+                    pageSize: pageSize,
+                    data: _customers.data,
+                    nextPageLink: _customers.nextPageLink,
+                    selection: null,
+                    hasNext: _customers.hasNext,
+                    pending: false
+                };
+                if (customers.hasNext) {
+                    customers.nextPageLink.limit = pageSize;
+                }
+                $mdDialog.show({
+                    controller: 'AssignDashboardToCustomerController',
+                    controllerAs: 'vm',
+                    templateUrl: assignToCustomerTemplate,
+                    locals: {dashboardIds: dashboardIds, customers: customers},
+                    parent: angular.element($document[0].body),
+                    fullscreen: true,
+                    targetEvent: $event
+                }).then(function () {
+                    vm.grid.refreshList();
+                }, function () {
+                });
+            },
+            function fail() {
+            });
+    }
+
+    function addDashboardsToCustomer($event) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        var pageSize = 10;
+        dashboardService.getTenantDashboards({limit: pageSize, textSearch: ''}).then(
+            function success(_dashboards) {
+                var dashboards = {
+                    pageSize: pageSize,
+                    data: _dashboards.data,
+                    nextPageLink: _dashboards.nextPageLink,
+                    selections: {},
+                    selectedCount: 0,
+                    hasNext: _dashboards.hasNext,
+                    pending: false
+                };
+                if (dashboards.hasNext) {
+                    dashboards.nextPageLink.limit = pageSize;
+                }
+                $mdDialog.show({
+                    controller: 'AddDashboardsToCustomerController',
+                    controllerAs: 'vm',
+                    templateUrl: addDashboardsToCustomerTemplate,
+                    locals: {customerId: customerId, dashboards: dashboards},
+                    parent: angular.element($document[0].body),
+                    fullscreen: true,
+                    targetEvent: $event
+                }).then(function () {
+                    vm.grid.refreshList();
+                }, function () {
+                });
+            },
+            function fail() {
+            });
+    }
+
+    function assignDashboardsToCustomer($event, items) {
+        var dashboardIds = [];
+        for (var id in items.selections) {
+            dashboardIds.push(id);
+        }
+        assignToCustomer($event, dashboardIds);
+    }
+
+    function unassignFromCustomer($event, dashboard) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        var confirm = $mdDialog.confirm()
+            .targetEvent($event)
+            .title($translate.instant('dashboard.unassign-dashboard-title', {dashboardTitle: dashboard.title}))
+            .htmlContent($translate.instant('dashboard.unassign-dashboard-text'))
+            .ariaLabel($translate.instant('dashboard.unassign-dashboard'))
+            .cancel($translate.instant('action.no'))
+            .ok($translate.instant('action.yes'));
+        $mdDialog.show(confirm).then(function () {
+            dashboardService.unassignDashboardFromCustomer(dashboard.id.id).then(function success() {
+                vm.grid.refreshList();
+            });
+        });
+    }
+
+    function unassignDashboardsFromCustomer($event, items) {
+        var confirm = $mdDialog.confirm()
+            .targetEvent($event)
+            .title($translate.instant('dashboard.unassign-dashboards-title', {count: items.selectedCount}, 'messageformat'))
+            .htmlContent($translate.instant('dashboard.unassign-dashboards-text'))
+            .ariaLabel($translate.instant('dashboard.unassign-dashboards'))
+            .cancel($translate.instant('action.no'))
+            .ok($translate.instant('action.yes'));
+        $mdDialog.show(confirm).then(function () {
+            var tasks = [];
+            for (var id in items.selections) {
+                tasks.push(dashboardService.unassignDashboardFromCustomer(id));
+            }
+            $q.all(tasks).then(function () {
+                vm.grid.refreshList();
+            });
+        });
+    }
+
+    function openDashboard($event, dashboard) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        if (vm.dashboardsScope === 'customer') {
+            $state.go('home.customers.dashboards.dashboard', {
+                customerId: customerId,
+                dashboardId: dashboard.id.id
+            });
+        } else {
+            $state.go('home.dashboards.dashboard', {dashboardId: dashboard.id.id});
+        }
+    }
+}
diff --git a/ui/src/app/dashboard/dashboards.tpl.html b/ui/src/app/dashboard/dashboards.tpl.html
new file mode 100644
index 0000000..ac8ccb2
--- /dev/null
+++ b/ui/src/app/dashboard/dashboards.tpl.html
@@ -0,0 +1,29 @@
+<!--
+
+    Copyright © 2016 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-grid grid-configuration="vm.dashboardGridConfig">
+	<details-buttons tb-help="'dashboards'" help-container-id="help-container">
+		<div id="help-container"></div>
+	</details-buttons>
+	<tb-dashboard-details dashboard="vm.grid.operatingItem()"
+						  is-edit="vm.grid.detailsConfig.isDetailsEditMode"
+						  dashboard-scope="vm.dashboardsScope"
+						  the-form="vm.grid.detailsForm"
+						  on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])"
+						  on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem)"
+						  on-delete-dashboard="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-dashboard-details>
+</tb-grid>
diff --git a/ui/src/app/dashboard/device-aliases.controller.js b/ui/src/app/dashboard/device-aliases.controller.js
new file mode 100644
index 0000000..ab2b6f4
--- /dev/null
+++ b/ui/src/app/dashboard/device-aliases.controller.js
@@ -0,0 +1,183 @@
+/*
+ * Copyright © 2016 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 './device-aliases.scss';
+
+/*@ngInject*/
+export default function DeviceAliasesController(deviceService, toast, $scope, $mdDialog, $document, $q, $translate,
+                                                deviceAliases, aliasToWidgetsMap, isSingleDevice, singleDeviceAlias) {
+
+    var vm = this;
+
+    vm.isSingleDevice = isSingleDevice;
+    vm.singleDeviceAlias = singleDeviceAlias;
+    vm.deviceAliases = [];
+    vm.aliasToWidgetsMap = aliasToWidgetsMap;
+    vm.singleDevice = null;
+    vm.singleDeviceSearchText = '';
+
+    vm.addAlias = addAlias;
+    vm.cancel = cancel;
+    vm.deviceSearchTextChanged = deviceSearchTextChanged;
+    vm.deviceChanged = deviceChanged;
+    vm.fetchDevices = fetchDevices;
+    vm.removeAlias = removeAlias;
+    vm.save = save;
+
+    initController();
+
+    function initController() {
+        for (var aliasId in deviceAliases) {
+            var alias = deviceAliases[aliasId].alias;
+            var deviceId = deviceAliases[aliasId].deviceId;
+            var deviceAlias = {id: aliasId, alias: alias, device: null, changed: false, searchText: ''};
+            if (deviceId) {
+                fetchAliasDevice(deviceAlias, deviceId);
+            }
+            vm.deviceAliases.push(deviceAlias);
+        }
+    }
+
+    function fetchDevices(searchText) {
+        var pageLink = {limit: 10, textSearch: searchText};
+
+        var deferred = $q.defer();
+
+        deviceService.getTenantDevices(pageLink).then(function success(result) {
+            deferred.resolve(result.data);
+        }, function fail() {
+            deferred.reject();
+        });
+
+        return deferred.promise;
+    }
+
+    function deviceSearchTextChanged() {
+    }
+
+    function deviceChanged(deviceAlias) {
+        if (deviceAlias && deviceAlias.device) {
+            if (angular.isDefined(deviceAlias.changed) && !deviceAlias.changed) {
+                deviceAlias.changed = true;
+            } else {
+                deviceAlias.alias = deviceAlias.device.name;
+            }
+        }
+    }
+
+    function addAlias() {
+        var aliasId = 0;
+        for (var a in vm.deviceAliases) {
+            aliasId = Math.max(vm.deviceAliases[a].id, aliasId);
+        }
+        aliasId++;
+        var deviceAlias = {id: aliasId, alias: '', device: null, searchText: ''};
+        vm.deviceAliases.push(deviceAlias);
+    }
+
+    function removeAlias($event, deviceAlias) {
+        var index = vm.deviceAliases.indexOf(deviceAlias);
+        if (index > -1) {
+            var widgetsTitleList = vm.aliasToWidgetsMap[deviceAlias.id];
+            if (widgetsTitleList) {
+                var widgetsListHtml = '';
+                for (var t in widgetsTitleList) {
+                    widgetsListHtml += '<br/>\'' + widgetsTitleList[t] + '\'';
+                }
+                var alert = $mdDialog.alert()
+                    .parent(angular.element($document[0].body))
+                    .clickOutsideToClose(true)
+                    .title($translate.instant('device.unable-delete-device-alias-title'))
+                    .htmlContent($translate.instant('device.unable-delete-device-alias-text', {deviceAlias: deviceAlias.alias, widgetsList: widgetsListHtml}))
+                    .ariaLabel($translate.instant('device.unable-delete-device-alias-title'))
+                    .ok($translate.instant('action.close'))
+                    .targetEvent($event);
+                alert._options.skipHide = true;
+                alert._options.fullscreen = true;
+
+                $mdDialog.show(alert);
+            } else {
+                for (var i = index + 1; i < vm.deviceAliases.length; i++) {
+                    vm.deviceAliases[i].changed = false;
+                }
+                vm.deviceAliases.splice(index, 1);
+                if ($scope.theForm) {
+                    $scope.theForm.$setDirty();
+                }
+            }
+        }
+    }
+
+    function cancel() {
+        $mdDialog.cancel();
+    }
+
+    function save() {
+
+        var deviceAliases = {};
+        var uniqueAliasList = {};
+
+        var valid = true;
+        var aliasId, maxAliasId;
+        var alias;
+        var i;
+
+        if (vm.isSingleDevice) {
+            maxAliasId = 0;
+            vm.singleDeviceAlias.deviceId = vm.singleDevice.id.id;
+            for (i in vm.deviceAliases) {
+                aliasId = vm.deviceAliases[i].id;
+                alias = vm.deviceAliases[i].alias;
+                if (alias === vm.singleDeviceAlias.alias) {
+                    valid = false;
+                    break;
+                }
+                maxAliasId = Math.max(aliasId, maxAliasId);
+            }
+            maxAliasId++;
+            vm.singleDeviceAlias.id = maxAliasId;
+        } else {
+            for (i in vm.deviceAliases) {
+                aliasId = vm.deviceAliases[i].id;
+                alias = vm.deviceAliases[i].alias;
+                if (!uniqueAliasList[alias]) {
+                    uniqueAliasList[alias] = alias;
+                    deviceAliases[aliasId] = {alias: alias, deviceId: vm.deviceAliases[i].device.id.id};
+                } else {
+                    valid = false;
+                    break;
+                }
+            }
+        }
+        if (valid) {
+            $scope.theForm.$setPristine();
+            if (vm.isSingleDevice) {
+                $mdDialog.hide(vm.singleDeviceAlias);
+            } else {
+                $mdDialog.hide(deviceAliases);
+            }
+        } else {
+            toast.showError($translate.instant('device.duplicate-alias-error', {alias: alias}));
+        }
+    }
+
+    function fetchAliasDevice(deviceAlias, deviceId) {
+        deviceService.getDevice(deviceId).then(function (device) {
+            deviceAlias.device = device;
+            deviceAlias.searchText = device.name;
+        });
+    }
+
+}
diff --git a/ui/src/app/dashboard/device-aliases.scss b/ui/src/app/dashboard/device-aliases.scss
new file mode 100644
index 0000000..d0bd4fb
--- /dev/null
+++ b/ui/src/app/dashboard/device-aliases.scss
@@ -0,0 +1,32 @@
+/**
+ * Copyright © 2016 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-alias {
+  padding: 10px 0 0 10px;
+  margin: 5px;
+
+  md-input-container {
+    margin: 0;
+  }
+  md-autocomplete {
+    height: 30px;
+    md-autocomplete-wrap {
+      height: 30px;
+    }
+    input, input:not(.md-input) {
+      height: 30px;
+    }
+  }
+}
diff --git a/ui/src/app/dashboard/device-aliases.tpl.html b/ui/src/app/dashboard/device-aliases.tpl.html
new file mode 100644
index 0000000..bf5bb61
--- /dev/null
+++ b/ui/src/app/dashboard/device-aliases.tpl.html
@@ -0,0 +1,131 @@
+<!--
+
+    Copyright © 2016 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 style="width: 700px;" aria-label="{{ 'device.aliases' | translate }}">
+	<form name="theForm" ng-submit="vm.save()">
+	    <md-toolbar>
+	      <div class="md-toolbar-tools">
+	        <h2>{{ vm.isSingleDevice ? ('device.select-device-for-alias' | translate:vm.singleDeviceAlias ) : ('device.aliases' | translate) }}</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-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 ng-show="vm.isSingleDevice">
+					  <md-autocomplete					  			
+					    		ng-required="vm.isSingleDevice"
+					    		md-input-name="device_id"
+					    		ng-model="vm.singleDevice"
+								md-selected-item="vm.singleDevice"
+								md-search-text="vm.singleDeviceSearchText"
+								md-search-text-change="vm.deviceSearchTextChanged(vm.singleDevice)"
+								md-items="item in vm.fetchDevices(vm.singleDeviceSearchText)"
+								md-item-text="item.name"
+								md-min-length="0"
+								placeholder="{{ 'device.device' | translate }}">
+								<md-item-template>
+					         	    <span md-highlight-text="vm.singleDeviceSearchText" md-highlight-flags="^i">{{item.name}}</span>
+					       		</md-item-template>
+						        <md-not-found>
+									<span translate translate-values='{ device: vm.singleDeviceSearchText }'>device.no-devices-matching</span>
+						        </md-not-found>
+						        <div ng-messages="theForm.device_id.$error">
+				      				<div translate ng-message="required">device.device-required</div>
+				    			</div>	
+					  </md-autocomplete>							
+				</div> 
+				<div ng-show="!vm.isSingleDevice" flex layout="row" layout-align="start center">
+					<span flex="5"></span>
+					<div flex layout="row" layout-align="start center" 
+						   style="padding: 0 0 0 10px; margin: 5px;">
+						   <span translate flex="40" style="min-width: 100px;">device.alias</span>
+						   <span translate flex="60" style="min-width: 190px; padding-left: 10px;">device.device</span>
+						   <span style="min-width: 40px;"></span>
+				    </div>
+				</div>
+				<div ng-show="!vm.isSingleDevice" style="max-height: 300px; overflow: auto; padding-bottom: 20px;">
+					<div ng-form name="aliasForm" flex layout="row" layout-align="start center" ng-repeat="deviceAlias in vm.deviceAliases track by $index">
+						<span flex="5">{{$index + 1}}.</span>
+						<div class="md-whiteframe-4dp tb-alias" flex layout="row" layout-align="start center">
+							  <md-input-container flex="40" style="min-width: 100px;" md-no-float class="md-block">
+								<input required name="alias" placeholder="{{ 'device.alias' | translate }}" ng-model="deviceAlias.alias">
+								  <div ng-messages="aliasForm.alias.$error">
+									  <div translate ng-message="required">device.alias-required</div>
+								  </div>
+							  </md-input-container>
+							  <section flex="60" layout="column">
+								  <md-autocomplete flex
+											ng-required="!vm.isSingleDevice"
+											md-input-name="device_id"
+											ng-model="deviceAlias.device"
+											md-selected-item="deviceAlias.device"
+											md-search-text="deviceAlias.searchText"
+											md-search-text-change="vm.deviceSearchTextChanged(deviceAlias)"
+											md-selected-item-change="vm.deviceChanged(deviceAlias)"
+											md-items="item in vm.fetchDevices(deviceAlias.searchText)"
+											md-item-text="item.name"
+											md-min-length="0"
+											placeholder="{{ 'device.device' | translate }}">
+											<md-item-template>
+												<span md-highlight-text="deviceAlias.searchText" md-highlight-flags="^i">{{item.name}}</span>
+											</md-item-template>
+											<md-not-found>
+												<span translate translate-values='{ device: deviceAlias.searchText }'>device.no-devices-matching</span>
+											</md-not-found>
+								  </md-autocomplete>
+								  <div class="tb-error-messages" ng-messages="aliasForm.device_id.$error">
+									<div translate ng-message="required" class="tb-error-message">device.device-required</div>
+								  </div>
+							  </section>
+							<md-button ng-disabled="loading" class="md-icon-button md-primary" style="min-width: 40px;"
+					                ng-click="vm.removeAlias($event, deviceAlias)" aria-label="{{ 'action.remove' | translate }}">
+					          		<md-tooltip md-direction="top">
+				        						{{ 'device.remove-alias' | translate }}
+				      					</md-tooltip>
+							        <md-icon aria-label="{{ 'action.delete' | translate }}" class="material-icons">
+							          close
+							        </md-icon>		        					
+					          </md-button>	
+				          </div>						
+					</div>					
+				</div>
+				<div ng-show="!vm.isSingleDevice" style="padding-bottom: 10px;">
+			         <md-button ng-disabled="loading" class="md-primary md-raised" ng-click="vm.addAlias($event)" aria-label="{{ 'action.add' | translate }}">
+			         		<md-tooltip md-direction="top">
+			      						{{ 'device.add-alias' | translate }}
+			    			</md-tooltip>
+				        	<span translate>action.add</span>
+			         </md-button>		
+			    </div>						
+			</fieldset>	      
+	      </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">
+		  		{{ '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>
\ No newline at end of file
diff --git a/ui/src/app/dashboard/edit-widget.directive.js b/ui/src/app/dashboard/edit-widget.directive.js
new file mode 100644
index 0000000..34b11f7
--- /dev/null
+++ b/ui/src/app/dashboard/edit-widget.directive.js
@@ -0,0 +1,111 @@
+/*
+ * Copyright © 2016 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 deviceAliasesTemplate from './device-aliases.tpl.html';
+import editWidgetTemplate from './edit-widget.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function EditWidgetDirective($compile, $templateCache, widgetService, deviceService, $q, $document, $mdDialog) {
+
+    var linker = function (scope, element) {
+        var template = $templateCache.get(editWidgetTemplate);
+        element.html(template);
+
+        scope.$watch('widget', function () {
+            if (scope.widget) {
+                widgetService.getWidgetInfo(scope.widget.bundleAlias,
+                    scope.widget.typeAlias,
+                    scope.widget.isSystemType).then(
+                    function(widgetInfo) {
+                        scope.$applyAsync(function(scope) {
+                            scope.widgetConfig = scope.widget.config;
+                            var settingsSchema = widgetInfo.settingsSchema;
+                            var dataKeySettingsSchema = widgetInfo.dataKeySettingsSchema;
+                            if (!settingsSchema || settingsSchema === '') {
+                                scope.settingsSchema = {};
+                            } else {
+                                scope.settingsSchema = angular.fromJson(settingsSchema);
+                            }
+                            if (!dataKeySettingsSchema || dataKeySettingsSchema === '') {
+                                scope.dataKeySettingsSchema = {};
+                            } else {
+                                scope.dataKeySettingsSchema = angular.fromJson(dataKeySettingsSchema);
+                            }
+
+                            scope.functionsOnly = scope.dashboard ? false : true;
+
+                            scope.theForm.$setPristine();
+                        });
+                    }
+                );
+            }
+        });
+
+        scope.fetchDeviceKeys = function (deviceAliasId, query, type) {
+            var deviceAlias = scope.dashboard.configuration.deviceAliases[deviceAliasId];
+            if (deviceAlias && deviceAlias.deviceId) {
+                return deviceService.getDeviceKeys(deviceAlias.deviceId, query, type);
+            } else {
+                return $q.when([]);
+            }
+        };
+
+        scope.createDeviceAlias = function (event, alias) {
+
+            var deferred = $q.defer();
+            var singleDeviceAlias = {id: null, alias: alias, deviceId: null};
+
+            $mdDialog.show({
+                controller: 'DeviceAliasesController',
+                controllerAs: 'vm',
+                templateUrl: deviceAliasesTemplate,
+                locals: {
+                    deviceAliases: angular.copy(scope.dashboard.configuration.deviceAliases),
+                    aliasToWidgetsMap: null,
+                    isSingleDevice: true,
+                    singleDeviceAlias: singleDeviceAlias
+                },
+                parent: angular.element($document[0].body),
+                fullscreen: true,
+                skipHide: true,
+                targetEvent: event
+            }).then(function (singleDeviceAlias) {
+                scope.dashboard.configuration.deviceAliases[singleDeviceAlias.id] =
+                            { alias: singleDeviceAlias.alias, deviceId: singleDeviceAlias.deviceId };
+                deferred.resolve(singleDeviceAlias);
+            }, function () {
+                deferred.reject();
+            });
+
+            return deferred.promise;
+        };
+
+        $compile(element.contents())(scope);
+    }
+
+    return {
+        restrict: "E",
+        link: linker,
+        scope: {
+            dashboard: '=',
+            widget: '=',
+            theForm: '='
+        }
+    };
+}
diff --git a/ui/src/app/dashboard/edit-widget.tpl.html b/ui/src/app/dashboard/edit-widget.tpl.html
new file mode 100644
index 0000000..5f03daf
--- /dev/null
+++ b/ui/src/app/dashboard/edit-widget.tpl.html
@@ -0,0 +1,28 @@
+<!--
+
+    Copyright © 2016 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.
+
+-->
+<fieldset ng-disabled="loading">
+	<tb-widget-config widget-type="widget.type"
+					  ng-model="widgetConfig"
+					  widget-settings-schema="settingsSchema"
+					  datakey-settings-schema="dataKeySettingsSchema"
+					  device-aliases="dashboard.configuration.deviceAliases"
+					  functions-only="functionsOnly"
+					  fetch-device-keys="fetchDeviceKeys(deviceAliasId, query, type)"
+					  on-create-device-alias="createDeviceAlias(event, alias)"
+					  the-form="theForm"></tb-widget-config>
+</fieldset>
diff --git a/ui/src/app/dashboard/index.js b/ui/src/app/dashboard/index.js
new file mode 100644
index 0000000..d740b10
--- /dev/null
+++ b/ui/src/app/dashboard/index.js
@@ -0,0 +1,67 @@
+/*
+ * Copyright © 2016 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 './dashboard.scss';
+
+import uiRouter from 'angular-ui-router';
+import gridster from 'angular-gridster';
+
+import thingsboardGrid from '../components/grid.directive';
+import thingsboardApiWidget from '../api/widget.service';
+import thingsboardApiUser from '../api/user.service';
+import thingsboardApiDashboard from '../api/dashboard.service';
+import thingsboardApiCustomer from '../api/customer.service';
+import thingsboardDetailsSidenav from '../components/details-sidenav.directive';
+import thingsboardWidgetConfig from '../components/widget-config.directive';
+import thingsboardDashboard from '../components/dashboard.directive';
+import thingsboardExpandFullscreen from '../components/expand-fullscreen.directive';
+import thingsboardWidgetsBundleSelect from '../components/widgets-bundle-select.directive';
+import thingsboardTypes from '../common/types.constant';
+
+import DashboardRoutes from './dashboard.routes';
+import DashboardsController from './dashboards.controller';
+import DashboardController from './dashboard.controller';
+import DeviceAliasesController from './device-aliases.controller';
+import AssignDashboardToCustomerController from './assign-to-customer.controller';
+import AddDashboardsToCustomerController from './add-dashboards-to-customer.controller';
+import AddWidgetController from './add-widget.controller';
+import DashboardDirective from './dashboard.directive';
+import EditWidgetDirective from './edit-widget.directive';
+
+export default angular.module('thingsboard.dashboard', [
+    uiRouter,
+    gridster.name,
+    thingsboardTypes,
+    thingsboardGrid,
+    thingsboardApiWidget,
+    thingsboardApiUser,
+    thingsboardApiDashboard,
+    thingsboardApiCustomer,
+    thingsboardDetailsSidenav,
+    thingsboardWidgetConfig,
+    thingsboardDashboard,
+    thingsboardExpandFullscreen,
+    thingsboardWidgetsBundleSelect
+])
+    .config(DashboardRoutes)
+    .controller('DashboardsController', DashboardsController)
+    .controller('DashboardController', DashboardController)
+    .controller('DeviceAliasesController', DeviceAliasesController)
+    .controller('AssignDashboardToCustomerController', AssignDashboardToCustomerController)
+    .controller('AddDashboardsToCustomerController', AddDashboardsToCustomerController)
+    .controller('AddWidgetController', AddWidgetController)
+    .directive('tbDashboardDetails', DashboardDirective)
+    .directive('tbEditWidget', EditWidgetDirective)
+    .name;
diff --git a/ui/src/app/device/add-device.tpl.html b/ui/src/app/device/add-device.tpl.html
new file mode 100644
index 0000000..171d631
--- /dev/null
+++ b/ui/src/app/device/add-device.tpl.html
@@ -0,0 +1,45 @@
+<!--
+
+    Copyright © 2016 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="{{ 'device.add' | translate }}" tb-help="'devices'" help-container-id="help-container">
+	<form name="theForm" ng-submit="vm.add()">
+	    <md-toolbar>
+	      <div class="md-toolbar-tools">
+	        <h2 translate>device.add</h2>
+	        <span flex></span>
+			<div id="help-container"></div>
+	        <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-show="loading"></md-progress-linear>
+  	    <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
+	    <md-dialog-content>
+	      <div class="md-dialog-content">
+  	        	<tb-device device="vm.item" is-edit="true" the-form="theForm"></tb-device>
+	      </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">
+		  		{{ 'action.add' | 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>
\ No newline at end of file
diff --git a/ui/src/app/device/add-devices-to-customer.controller.js b/ui/src/app/device/add-devices-to-customer.controller.js
new file mode 100644
index 0000000..ba4c6e1
--- /dev/null
+++ b/ui/src/app/device/add-devices-to-customer.controller.js
@@ -0,0 +1,123 @@
+/*
+ * Copyright © 2016 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 AddDevicesToCustomerController(deviceService, $mdDialog, $q, customerId, devices) {
+
+    var vm = this;
+
+    vm.devices = devices;
+    vm.searchText = '';
+
+    vm.assign = assign;
+    vm.cancel = cancel;
+    vm.hasData = hasData;
+    vm.noData = noData;
+    vm.searchDeviceTextUpdated = searchDeviceTextUpdated;
+    vm.toggleDeviceSelection = toggleDeviceSelection;
+
+    vm.theDevices = {
+        getItemAtIndex: function (index) {
+            if (index > vm.devices.data.length) {
+                vm.theDevices.fetchMoreItems_(index);
+                return null;
+            }
+            var item = vm.devices.data[index];
+            if (item) {
+                item.indexNumber = index + 1;
+            }
+            return item;
+        },
+
+        getLength: function () {
+            if (vm.devices.hasNext) {
+                return vm.devices.data.length + vm.devices.nextPageLink.limit;
+            } else {
+                return vm.devices.data.length;
+            }
+        },
+
+        fetchMoreItems_: function () {
+            if (vm.devices.hasNext && !vm.devices.pending) {
+                vm.devices.pending = true;
+                deviceService.getTenantDevices(vm.devices.nextPageLink).then(
+                    function success(devices) {
+                        vm.devices.data = vm.devices.data.concat(devices.data);
+                        vm.devices.nextPageLink = devices.nextPageLink;
+                        vm.devices.hasNext = devices.hasNext;
+                        if (vm.devices.hasNext) {
+                            vm.devices.nextPageLink.limit = vm.devices.pageSize;
+                        }
+                        vm.devices.pending = false;
+                    },
+                    function fail() {
+                        vm.devices.hasNext = false;
+                        vm.devices.pending = false;
+                    });
+            }
+        }
+    };
+
+    function cancel () {
+        $mdDialog.cancel();
+    }
+
+    function assign() {
+        var tasks = [];
+        for (var deviceId in vm.devices.selections) {
+            tasks.push(deviceService.assignDeviceToCustomer(customerId, deviceId));
+        }
+        $q.all(tasks).then(function () {
+            $mdDialog.hide();
+        });
+    }
+
+    function noData() {
+        return vm.devices.data.length == 0 && !vm.devices.hasNext;
+    }
+
+    function hasData() {
+        return vm.devices.data.length > 0;
+    }
+
+    function toggleDeviceSelection($event, device) {
+        $event.stopPropagation();
+        var selected = angular.isDefined(device.selected) && device.selected;
+        device.selected = !selected;
+        if (device.selected) {
+            vm.devices.selections[device.id.id] = true;
+            vm.devices.selectedCount++;
+        } else {
+            delete vm.devices.selections[device.id.id];
+            vm.devices.selectedCount--;
+        }
+    }
+
+    function searchDeviceTextUpdated() {
+        vm.devices = {
+            pageSize: vm.devices.pageSize,
+            data: [],
+            nextPageLink: {
+                limit: vm.devices.pageSize,
+                textSearch: vm.searchText
+            },
+            selections: {},
+            selectedCount: 0,
+            hasNext: true,
+            pending: false
+        };
+    }
+
+}
diff --git a/ui/src/app/device/add-devices-to-customer.tpl.html b/ui/src/app/device/add-devices-to-customer.tpl.html
new file mode 100644
index 0000000..ac855f7
--- /dev/null
+++ b/ui/src/app/device/add-devices-to-customer.tpl.html
@@ -0,0 +1,77 @@
+<!--
+
+    Copyright © 2016 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="{{ 'device.assign-to-customer' | translate }}">
+    <form name="theForm" ng-submit="vm.assign()">
+        <md-toolbar>
+            <div class="md-toolbar-tools">
+                <h2 translate>device.assign-device-to-customer</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-show="loading"></md-progress-linear>
+        <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
+        <md-dialog-content>
+            <div class="md-dialog-content">
+                <fieldset>
+                    <span translate>device.assign-device-to-customer-text</span>
+                    <md-input-container class="md-block" style='margin-bottom: 0px;'>
+                        <label>&nbsp;</label>
+                        <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">
+                            search
+                        </md-icon>
+                        <input id="device-search" autofocus ng-model="vm.searchText"
+                               ng-change="vm.searchDeviceTextUpdated()"
+                               placeholder="{{ 'common.enter-search' | translate }}"/>
+                    </md-input-container>
+                    <div style='min-height: 150px;'>
+					<span translate layout-align="center center"
+                          style="text-transform: uppercase; display: flex; height: 150px;"
+                          class="md-subhead"
+                          ng-show="vm.noData()">device.no-devices-text</span>
+                        <md-virtual-repeat-container ng-show="vm.hasData()"
+                                                     tb-scope-element="repeatContainer" md-top-index="vm.topIndex" flex
+                                                     style='min-height: 150px; width: 100%;'>
+                            <md-list>
+                                <md-list-item md-virtual-repeat="device in vm.theDevices" md-on-demand
+                                              class="repeated-item" flex>
+                                    <md-checkbox ng-click="vm.toggleDeviceSelection($event, device)"
+                                                 aria-label="{{ 'item.selected' | translate }}"
+                                                 ng-checked="device.selected"></md-checkbox>
+                                    <span> {{ device.name }} </span>
+                                </md-list-item>
+                            </md-list>
+                        </md-virtual-repeat-container>
+                    </div>
+                </fieldset>
+            </div>
+        </md-dialog-content>
+        <md-dialog-actions layout="row">
+            <span flex></span>
+            <md-button ng-disabled="loading || vm.devices.selectedCount == 0" type="submit"
+                       class="md-raised md-primary">
+                {{ 'action.assign' | 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>
\ No newline at end of file
diff --git a/ui/src/app/device/assign-to-customer.controller.js b/ui/src/app/device/assign-to-customer.controller.js
new file mode 100644
index 0000000..5c3083e
--- /dev/null
+++ b/ui/src/app/device/assign-to-customer.controller.js
@@ -0,0 +1,123 @@
+/*
+ * Copyright © 2016 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 AssignDeviceToCustomerController(customerService, deviceService, $mdDialog, $q, deviceIds, customers) {
+
+    var vm = this;
+
+    vm.customers = customers;
+    vm.searchText = '';
+
+    vm.assign = assign;
+    vm.cancel = cancel;
+    vm.isCustomerSelected = isCustomerSelected;
+    vm.hasData = hasData;
+    vm.noData = noData;
+    vm.searchCustomerTextUpdated = searchCustomerTextUpdated;
+    vm.toggleCustomerSelection = toggleCustomerSelection;
+
+    vm.theCustomers = {
+        getItemAtIndex: function (index) {
+            if (index > vm.customers.data.length) {
+                vm.theCustomers.fetchMoreItems_(index);
+                return null;
+            }
+            var item = vm.customers.data[index];
+            if (item) {
+                item.indexNumber = index + 1;
+            }
+            return item;
+        },
+
+        getLength: function () {
+            if (vm.customers.hasNext) {
+                return vm.customers.data.length + vm.customers.nextPageLink.limit;
+            } else {
+                return vm.customers.data.length;
+            }
+        },
+
+        fetchMoreItems_: function () {
+            if (vm.customers.hasNext && !vm.customers.pending) {
+                vm.customers.pending = true;
+                customerService.getCustomers(vm.customers.nextPageLink).then(
+                    function success(customers) {
+                        vm.customers.data = vm.customers.data.concat(customers.data);
+                        vm.customers.nextPageLink = customers.nextPageLink;
+                        vm.customers.hasNext = customers.hasNext;
+                        if (vm.customers.hasNext) {
+                            vm.customers.nextPageLink.limit = vm.customers.pageSize;
+                        }
+                        vm.customers.pending = false;
+                    },
+                    function fail() {
+                        vm.customers.hasNext = false;
+                        vm.customers.pending = false;
+                    });
+            }
+        }
+    };
+
+    function cancel() {
+        $mdDialog.cancel();
+    }
+
+    function assign() {
+        var tasks = [];
+        for (var deviceId in deviceIds) {
+            tasks.push(deviceService.assignDeviceToCustomer(vm.customers.selection.id.id, deviceIds[deviceId]));
+        }
+        $q.all(tasks).then(function () {
+            $mdDialog.hide();
+        });
+    }
+
+    function noData() {
+        return vm.customers.data.length == 0 && !vm.customers.hasNext;
+    }
+
+    function hasData() {
+        return vm.customers.data.length > 0;
+    }
+
+    function toggleCustomerSelection($event, customer) {
+        $event.stopPropagation();
+        if (vm.isCustomerSelected(customer)) {
+            vm.customers.selection = null;
+        } else {
+            vm.customers.selection = customer;
+        }
+    }
+
+    function isCustomerSelected(customer) {
+        return vm.customers.selection != null && customer &&
+            customer.id.id === vm.customers.selection.id.id;
+    }
+
+    function searchCustomerTextUpdated() {
+        vm.customers = {
+            pageSize: vm.customers.pageSize,
+            data: [],
+            nextPageLink: {
+                limit: vm.customers.pageSize,
+                textSearch: vm.searchText
+            },
+            selection: null,
+            hasNext: true,
+            pending: false
+        };
+    }
+}
diff --git a/ui/src/app/device/assign-to-customer.tpl.html b/ui/src/app/device/assign-to-customer.tpl.html
new file mode 100644
index 0000000..ed4c692
--- /dev/null
+++ b/ui/src/app/device/assign-to-customer.tpl.html
@@ -0,0 +1,76 @@
+<!--
+
+    Copyright © 2016 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="{{ 'device.assign-device-to-customer' | translate }}">
+    <form name="theForm" ng-submit="vm.assign()">
+        <md-toolbar>
+            <div class="md-toolbar-tools">
+                <h2 translate>device.assign-device-to-customer</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-show="loading"></md-progress-linear>
+        <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
+        <md-dialog-content>
+            <div class="md-dialog-content">
+                <fieldset>
+                    <span translate>device.assign-to-customer-text</span>
+                    <md-input-container class="md-block" style='margin-bottom: 0px;'>
+                        <label>&nbsp;</label>
+                        <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">
+                            search
+                        </md-icon>
+                        <input id="customer-search" autofocus ng-model="vm.searchText"
+                               ng-change="vm.searchCustomerTextUpdated()"
+                               placeholder="{{ 'common.enter-search' | translate }}"/>
+                    </md-input-container>
+                    <div style='min-height: 150px;'>
+					<span translate layout-align="center center"
+                          style="text-transform: uppercase; display: flex; height: 150px;"
+                          class="md-subhead"
+                          ng-show="vm.noData()">customer.no-customers-text</span>
+                        <md-virtual-repeat-container ng-show="vm.hasData()"
+                                                     tb-scope-element="repeatContainer" md-top-index="vm.topIndex" flex
+                                                     style='min-height: 150px; width: 100%;'>
+                            <md-list>
+                                <md-list-item md-virtual-repeat="customer in vm.theCustomers" md-on-demand
+                                              class="repeated-item" flex>
+                                    <md-checkbox ng-click="vm.toggleCustomerSelection($event, customer)"
+                                                 aria-label="{{ 'item.selected' | translate }}"
+                                                 ng-checked="vm.isCustomerSelected(customer)"></md-checkbox>
+                                    <span> {{ customer.title }} </span>
+                                </md-list-item>
+                            </md-list>
+                        </md-virtual-repeat-container>
+                    </div>
+                </fieldset>
+            </div>
+        </md-dialog-content>
+        <md-dialog-actions layout="row">
+            <span flex></span>
+            <md-button ng-disabled="loading || vm.customers.selection==null" type="submit" class="md-raised md-primary">
+                {{ 'action.assign' | 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>
\ No newline at end of file
diff --git a/ui/src/app/device/attribute/add-attribute-dialog.controller.js b/ui/src/app/device/attribute/add-attribute-dialog.controller.js
new file mode 100644
index 0000000..c17742d
--- /dev/null
+++ b/ui/src/app/device/attribute/add-attribute-dialog.controller.js
@@ -0,0 +1,50 @@
+/*
+ * Copyright © 2016 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 AddAttributeDialogController($scope, $mdDialog, types, deviceService, deviceId, attributeScope) {
+
+    var vm = this;
+
+    vm.attribute = {};
+
+    vm.valueTypes = types.valueType;
+
+    vm.valueType = types.valueType.string;
+
+    vm.add = add;
+    vm.cancel = cancel;
+
+    function cancel() {
+        $mdDialog.cancel();
+    }
+
+    function add() {
+        $scope.theForm.$setPristine();
+        deviceService.saveDeviceAttributes(deviceId, attributeScope, [vm.attribute]).then(
+            function success() {
+                $mdDialog.hide();
+            }
+        );
+    }
+
+    $scope.$watch('vm.valueType', function() {
+        if (vm.valueType === types.valueType.boolean) {
+            vm.attribute.value = false;
+        } else {
+            vm.attribute.value = null;
+        }
+    });
+}
diff --git a/ui/src/app/device/attribute/add-attribute-dialog.tpl.html b/ui/src/app/device/attribute/add-attribute-dialog.tpl.html
new file mode 100644
index 0000000..fc2bf57
--- /dev/null
+++ b/ui/src/app/device/attribute/add-attribute-dialog.tpl.html
@@ -0,0 +1,95 @@
+<!--
+
+    Copyright © 2016 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="{{ 'attribute.add' | translate }}" style="min-width: 400px;">
+    <form name="theForm" ng-submit="vm.add()">
+        <md-toolbar>
+            <div class="md-toolbar-tools">
+                <h2 translate>attribute.add</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-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>attribute.key</label>
+                            <input required name="key" ng-model="vm.attribute.key">
+                            <div ng-messages="theForm.key.$error">
+                                <div translate ng-message="required">attribute.key-required</div>
+                            </div>
+                        </md-input-container>
+                        <section layout="row">
+                            <md-input-container flex="40" class="md-block" style="width: 200px;">
+                                <label translate>value.type</label>
+                                <md-select ng-model="vm.valueType" ng-disabled="loading()">
+                                    <md-option ng-repeat="type in vm.valueTypes" ng-value="type">
+                                        <md-icon md-svg-icon="{{ type.icon }}"></md-icon>
+                                        <span>{{type.name | translate}}</span>
+                                    </md-option>
+                                </md-select>
+                            </md-input-container>
+                            <md-input-container ng-if="vm.valueType===vm.valueTypes.string" flex="60" class="md-block">
+                                <label translate>value.string-value</label>
+                                <input required name="value" ng-model="vm.attribute.value">
+                                <div ng-messages="theForm.value.$error">
+                                    <div translate ng-message="required">attribute.value-required</div>
+                                </div>
+                            </md-input-container>
+                            <md-input-container ng-if="vm.valueType===vm.valueTypes.integer" flex="60" class="md-block">
+                                <label translate>value.integer-value</label>
+                                <input required name="value" type="number" step="1" ng-pattern="/^-?[0-9]+$/" ng-model="vm.attribute.value">
+                                <div ng-messages="theForm.value.$error">
+                                    <div translate ng-message="required">attribute.value-required</div>
+                                    <div translate ng-message="pattern">value.invalid-integer-value</div>
+                                </div>
+                            </md-input-container>
+                            <md-input-container ng-if="vm.valueType===vm.valueTypes.double" flex="60" class="md-block">
+                                <label translate>value.double-value</label>
+                                <input required name="value" type="number" step="any" ng-model="vm.attribute.value">
+                                <div ng-messages="theForm.value.$error">
+                                    <div translate ng-message="required">attribute.value-required</div>
+                                </div>
+                            </md-input-container>
+                            <div layout="column" layout-align="center" flex="60" ng-if="vm.valueType===vm.valueTypes.boolean">
+                                <md-checkbox ng-model="vm.attribute.value" style="margin-bottom: 0px;">
+                                    {{ (vm.attribute.value ? 'value.true' : 'value.false') | translate }}
+                                </md-checkbox>
+                            </div>
+                        </section>
+                    </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">
+                {{ 'action.add' | 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>
diff --git a/ui/src/app/device/attribute/add-widget-to-dashboard-dialog.controller.js b/ui/src/app/device/attribute/add-widget-to-dashboard-dialog.controller.js
new file mode 100644
index 0000000..9149cf1
--- /dev/null
+++ b/ui/src/app/device/attribute/add-widget-to-dashboard-dialog.controller.js
@@ -0,0 +1,122 @@
+/*
+ * Copyright © 2016 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 AddWidgetToDashboardDialogController($scope, $mdDialog, $state, dashboardService, deviceId, deviceName, widget) {
+
+    var vm = this;
+
+    vm.widget = widget;
+    vm.dashboard = null;
+    vm.addToDashboardType = 0;
+    vm.newDashboard = {};
+    vm.openDashboard = false;
+
+    vm.add = add;
+    vm.cancel = cancel;
+
+    function cancel() {
+        $mdDialog.cancel();
+    }
+
+    function add() {
+        $scope.theForm.$setPristine();
+        var theDashboard;
+        var deviceAliases;
+        widget.col = 0;
+        widget.sizeX /= 2;
+        widget.sizeY /= 2;
+        if (vm.addToDashboardType === 0) {
+            theDashboard = vm.dashboard;
+            if (!theDashboard.configuration) {
+                theDashboard.configuration = {};
+            }
+            deviceAliases = theDashboard.configuration.deviceAliases;
+            if (!deviceAliases) {
+                deviceAliases = {};
+                theDashboard.configuration.deviceAliases = deviceAliases;
+            }
+            var newAliasId;
+            for (var aliasId in deviceAliases) {
+                if (deviceAliases[aliasId].deviceId === deviceId) {
+                    newAliasId = aliasId;
+                    break;
+                }
+            }
+            if (!newAliasId) {
+                var newAliasName = createDeviceAliasName(deviceAliases, deviceName);
+                newAliasId = 0;
+                for (aliasId in deviceAliases) {
+                    newAliasId = Math.max(newAliasId, aliasId);
+                }
+                newAliasId++;
+                deviceAliases[newAliasId] = {alias: newAliasName, deviceId: deviceId};
+            }
+            widget.config.datasources[0].deviceAliasId = newAliasId;
+
+            if (!theDashboard.configuration.widgets) {
+                theDashboard.configuration.widgets = [];
+            }
+
+            var row = 0;
+            for (var w in theDashboard.configuration.widgets) {
+                var existingWidget = theDashboard.configuration.widgets[w];
+                var wRow = existingWidget.row ? existingWidget.row : 0;
+                var wSizeY = existingWidget.sizeY ? existingWidget.sizeY : 1;
+                var bottom = wRow + wSizeY;
+                row = Math.max(row, bottom);
+            }
+            widget.row = row;
+            theDashboard.configuration.widgets.push(widget);
+        } else {
+            theDashboard = vm.newDashboard;
+            deviceAliases = {};
+            deviceAliases['1'] = {alias: deviceName, deviceId: deviceId};
+            theDashboard.configuration = {};
+            theDashboard.configuration.widgets = [];
+            widget.row = 0;
+            theDashboard.configuration.widgets.push(widget);
+            theDashboard.configuration.deviceAliases = deviceAliases;
+        }
+        dashboardService.saveDashboard(theDashboard).then(
+            function success(dashboard) {
+                $mdDialog.hide();
+                if (vm.openDashboard) {
+                    $state.go('home.dashboards.dashboard', {dashboardId: dashboard.id.id});
+                }
+            }
+        );
+
+    }
+
+    function createDeviceAliasName(deviceAliases, alias) {
+        var c = 0;
+        var newAlias = angular.copy(alias);
+        var unique = false;
+        while (!unique) {
+            unique = true;
+            for (var devAliasId in deviceAliases) {
+                var devAlias = deviceAliases[devAliasId];
+                if (newAlias === devAlias.alias) {
+                    c++;
+                    newAlias = alias + c;
+                    unique = false;
+                }
+            }
+        }
+        return newAlias;
+    }
+
+}
diff --git a/ui/src/app/device/attribute/add-widget-to-dashboard-dialog.tpl.html b/ui/src/app/device/attribute/add-widget-to-dashboard-dialog.tpl.html
new file mode 100644
index 0000000..dffc229
--- /dev/null
+++ b/ui/src/app/device/attribute/add-widget-to-dashboard-dialog.tpl.html
@@ -0,0 +1,80 @@
+<!--
+
+    Copyright © 2016 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="{{ 'attribute.add-widget-to-dashboard' | translate }}" style="min-width: 400px;">
+    <form name="theForm" ng-submit="vm.add()">
+        <md-toolbar>
+            <div class="md-toolbar-tools">
+                <h2 translate>attribute.add-widget-to-dashboard</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-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-radio-group ng-model="vm.addToDashboardType" class="md-primary">
+                            <md-radio-button flex ng-value=0 class="md-primary md-align-top-left md-radio-interactive">
+                                <section flex layout="column" style="width: 300px;">
+                                    <span translate style="padding-bottom: 10px;">dashboard.select-existing</span>
+                                    <tb-dashboard-select the-form="theForm"
+                                                         tb-required="vm.addToDashboardType === 0"
+                                                         ng-model="vm.dashboard"
+                                                         select-first-dashboard="false">
+                                    </tb-dashboard-select>
+                                </section>
+                            </md-radio-button>
+                            <md-radio-button flex ng-value=1 class="md-primary md-align-top-left md-radio-interactive">
+                                <section flex layout="column" style="width: 300px;">
+                                    <span translate>dashboard.create-new</span>
+                                    <md-input-container class="md-block">
+                                        <label translate>dashboard.new-dashboard-title</label>
+                                        <input ng-required="vm.addToDashboardType === 1" name="title" ng-model="vm.newDashboard.title">
+                                        <div ng-messages="theForm.title.$error">
+                                            <div translate ng-message="required">dashboard.title-required</div>
+                                        </div>
+                                    </md-input-container>
+                                </section>
+                            </md-radio-button>
+                        </md-radio-group>
+                    </fieldset>
+                </md-content>
+            </div>
+        </md-dialog-content>
+        <md-dialog-actions layout="row">
+            <span flex></span>
+            <md-checkbox
+                    ng-model="vm.openDashboard"
+                    aria-label="{{ 'dashboard.open-dashboard' | translate }}"
+                    style="margin-bottom: 0px; padding-right: 20px;">
+                {{ 'dashboard.open-dashboard' | translate }}
+            </md-checkbox>
+            <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit"
+                       class="md-raised md-primary">
+                {{ 'action.add' | 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>
diff --git a/ui/src/app/device/attribute/attribute-table.directive.js b/ui/src/app/device/attribute/attribute-table.directive.js
new file mode 100644
index 0000000..0a5e0cd
--- /dev/null
+++ b/ui/src/app/device/attribute/attribute-table.directive.js
@@ -0,0 +1,385 @@
+/*
+ * Copyright © 2016 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 'angular-material-data-table/dist/md-data-table.min.css';
+import './attribute-table.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import attributeTableTemplate from './attribute-table.tpl.html';
+import addAttributeDialogTemplate from './add-attribute-dialog.tpl.html';
+import addWidgetToDashboardDialogTemplate from './add-widget-to-dashboard-dialog.tpl.html';
+import editAttributeValueTemplate from './edit-attribute-value.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+import EditAttributeValueController from './edit-attribute-value.controller';
+
+/*@ngInject*/
+export default function AttributeTableDirective($compile, $templateCache, $rootScope, $q, $mdEditDialog, $mdDialog,
+                                                $document, $translate, utils, types, deviceService, widgetService) {
+
+    var linker = function (scope, element, attrs) {
+
+        var template = $templateCache.get(attributeTableTemplate);
+
+        element.html(template);
+
+        scope.types = types;
+        scope.attributeScopes = types.deviceAttributesScope;
+
+        var getAttributeScopeByValue = function(attributeScopeValue) {
+            if (scope.types.latestTelemetry.value === attributeScopeValue) {
+                return scope.types.latestTelemetry;
+            }
+            for (var attrScope in scope.attributeScopes) {
+                if (scope.attributeScopes[attrScope].value === attributeScopeValue) {
+                    return scope.attributeScopes[attrScope];
+                }
+            }
+        }
+
+        scope.attributeScope = getAttributeScopeByValue(attrs.defaultAttributeScope);
+
+        scope.attributes = {
+            count: 0,
+            data: []
+        };
+
+        scope.selectedAttributes = [];
+        scope.mode = 'default'; // 'widget'
+        scope.subscriptionId = null;
+
+        scope.query = {
+            order: 'key',
+            limit: 5,
+            page: 1,
+            search: null
+        };
+
+        scope.$watch("deviceId", function(newVal, prevVal) {
+            if (newVal && !angular.equals(newVal, prevVal)) {
+                scope.resetFilter();
+                scope.getDeviceAttributes();
+            }
+        });
+
+        scope.$watch("attributeScope", function(newVal, prevVal) {
+            if (newVal && !angular.equals(newVal, prevVal)) {
+                scope.mode = 'default';
+                scope.query.search = null;
+                scope.selectedAttributes = [];
+                scope.getDeviceAttributes();
+            }
+        });
+
+        scope.resetFilter = function() {
+            scope.mode = 'default';
+            scope.query.search = null;
+            scope.selectedAttributes = [];
+            scope.attributeScope = getAttributeScopeByValue(attrs.defaultAttributeScope);
+        }
+
+        scope.enterFilterMode = function() {
+            scope.query.search = '';
+        }
+
+        scope.exitFilterMode = function() {
+            scope.query.search = null;
+            scope.getDeviceAttributes();
+        }
+
+        scope.$watch("query.search", function(newVal, prevVal) {
+            if (!angular.equals(newVal, prevVal) && scope.query.search != null) {
+                scope.getDeviceAttributes();
+            }
+        });
+
+        function success(attributes, update) {
+            scope.attributes = attributes;
+            if (!update) {
+                scope.selectedAttributes = [];
+            }
+        }
+
+        scope.getDeviceAttributes = function(forceUpdate) {
+            if (scope.attributesDeferred) {
+                scope.attributesDeferred.resolve();
+            }
+            if (scope.deviceId && scope.attributeScope) {
+                scope.checkSubscription();
+                scope.attributesDeferred = deviceService.getDeviceAttributes(scope.deviceId, scope.attributeScope.value,
+                    scope.query, function(attributes, update) {
+                        success(attributes, update || forceUpdate);
+                    }
+                );
+            } else {
+                var deferred = $q.defer();
+                scope.attributesDeferred = deferred;
+                success({
+                    count: 0,
+                    data: []
+                });
+                deferred.resolve();
+            }
+        }
+
+        scope.checkSubscription = function() {
+            var newSubscriptionId = null;
+            if (scope.deviceId && scope.attributeScope.clientSide && scope.mode != 'widget') {
+                newSubscriptionId = deviceService.subscribeForDeviceAttributes(scope.deviceId, scope.attributeScope.value);
+            }
+            if (scope.subscriptionId && scope.subscriptionId != newSubscriptionId) {
+                deviceService.unsubscribeForDeviceAttributes(scope.subscriptionId);
+            }
+            scope.subscriptionId = newSubscriptionId;
+        }
+
+        scope.editAttribute = function($event, attribute) {
+            if (!scope.attributeScope.clientSide) {
+                $event.stopPropagation();
+                $mdEditDialog.show({
+                    controller: EditAttributeValueController,
+                    templateUrl: editAttributeValueTemplate,
+                    locals: {attributeValue: attribute.value,
+                             save: function (model) {
+                                var updatedAttribute = angular.copy(attribute);
+                                updatedAttribute.value = model.value;
+                                deviceService.saveDeviceAttributes(scope.deviceId, scope.attributeScope.value, [updatedAttribute]).then(
+                                    function success() {
+                                        scope.getDeviceAttributes();
+                                    }
+                                );
+                            }},
+                    targetEvent: $event
+                });
+            }
+        }
+
+        scope.addAttribute = function($event) {
+            if (!scope.attributeScope.clientSide) {
+                $event.stopPropagation();
+                $mdDialog.show({
+                    controller: 'AddAttributeDialogController',
+                    controllerAs: 'vm',
+                    templateUrl: addAttributeDialogTemplate,
+                    parent: angular.element($document[0].body),
+                    locals: {deviceId: scope.deviceId, attributeScope: scope.attributeScope.value},
+                    fullscreen: true,
+                    targetEvent: $event
+                }).then(function () {
+                    scope.getDeviceAttributes();
+                });
+            }
+        }
+
+        scope.deleteAttributes = function($event) {
+            if (!scope.attributeScope.clientSide) {
+                $event.stopPropagation();
+                var confirm = $mdDialog.confirm()
+                    .targetEvent($event)
+                    .title($translate.instant('attribute.delete-attributes-title', {count: scope.selectedAttributes.length}, 'messageformat'))
+                    .htmlContent($translate.instant('attribute.delete-attributes-text'))
+                    .ariaLabel($translate.instant('attribute.delete-attributes'))
+                    .cancel($translate.instant('action.no'))
+                    .ok($translate.instant('action.yes'));
+                $mdDialog.show(confirm).then(function () {
+                        deviceService.deleteDeviceAttributes(scope.deviceId, scope.attributeScope.value, scope.selectedAttributes).then(
+                            function success() {
+                                scope.selectedAttributes = [];
+                                scope.getDeviceAttributes();
+                            }
+                        )
+                });
+            }
+        }
+
+        scope.nextWidget = function() {
+            if (scope.widgetsCarousel.index < scope.widgetsList.length-1) {
+                scope.widgetsCarousel.index++;
+            }
+        }
+
+        scope.prevWidget = function() {
+            if (scope.widgetsCarousel.index > 0) {
+                scope.widgetsCarousel.index--;
+            }
+        }
+
+        scope.enterWidgetMode = function() {
+
+            if (scope.widgetsIndexWatch) {
+                scope.widgetsIndexWatch();
+                scope.widgetsIndexWatch = null;
+            }
+
+            if (scope.widgetsBundleWatch) {
+                scope.widgetsBundleWatch();
+                scope.widgetsBundleWatch = null;
+            }
+
+            scope.mode = 'widget';
+            scope.checkSubscription();
+            scope.widgetsList = [];
+            scope.widgetsListCache = [];
+            scope.widgetsLoaded = false;
+            scope.widgetsCarousel = {
+                index: 0
+            }
+            scope.widgetsBundle = null;
+
+            scope.deviceAliases = {};
+            scope.deviceAliases['1'] = {alias: scope.deviceName, deviceId: scope.deviceId};
+
+            var dataKeyType = scope.attributeScope === types.latestTelemetry ?
+                types.dataKeyType.timeseries : types.dataKeyType.attribute;
+
+            var datasource = {
+                type: types.datasourceType.device,
+                deviceAliasId: '1',
+                dataKeys: []
+            }
+            var i = 0;
+            for (var attr in scope.selectedAttributes) {
+                var attribute = scope.selectedAttributes[attr];
+                var dataKey = {
+                    name: attribute.key,
+                    label: attribute.key,
+                    type: dataKeyType,
+                    color: utils.getMaterialColor(i),
+                    settings: {},
+                    _hash: Math.random()
+                }
+                datasource.dataKeys.push(dataKey);
+                i++;
+            }
+
+            scope.widgetsIndexWatch = scope.$watch('widgetsCarousel.index', function(newVal, prevVal) {
+                if (scope.mode === 'widget' && (newVal != prevVal)) {
+                    var index = scope.widgetsCarousel.index;
+                    for (var i = 0; i < scope.widgetsList.length; i++) {
+                        scope.widgetsList[i].splice(0, scope.widgetsList[i].length);
+                        if (i === index) {
+                            scope.widgetsList[i].push(scope.widgetsListCache[i][0]);
+                        }
+                    }
+                }
+            });
+
+            scope.widgetsBundleWatch = scope.$watch('widgetsBundle', function(newVal, prevVal) {
+                if (scope.mode === 'widget' && (scope.firstBundle === true || newVal != prevVal)) {
+                    scope.widgetsList = [];
+                    scope.widgetsListCache = [];
+                    scope.widgetsCarousel.index = 0;
+                    scope.firstBundle = false;
+                    if (scope.widgetsBundle) {
+                        scope.widgetsLoaded = false;
+                        var bundleAlias = scope.widgetsBundle.alias;
+                        var isSystem = scope.widgetsBundle.tenantId.id === types.id.nullUid;
+                        widgetService.getBundleWidgetTypes(scope.widgetsBundle.alias, isSystem).then(
+                            function success(widgetTypes) {
+                                for (var i = 0; i < widgetTypes.length; i++) {
+                                    var widgetType = widgetTypes[i];
+                                    var widgetInfo = widgetService.toWidgetInfo(widgetType);
+                                    var sizeX = widgetInfo.sizeX*2;
+                                    var sizeY = widgetInfo.sizeY*2;
+                                    var col = Math.floor(Math.max(0, (20 - sizeX)/2));
+                                    var widget = {
+                                        isSystemType: isSystem,
+                                        bundleAlias: bundleAlias,
+                                        typeAlias: widgetInfo.alias,
+                                        type: widgetInfo.type,
+                                        title: widgetInfo.widgetName,
+                                        sizeX: sizeX,
+                                        sizeY: sizeY,
+                                        row: 0,
+                                        col: col,
+                                        config: angular.fromJson(widgetInfo.defaultConfig)
+                                    };
+
+                                    widget.config.title = widgetInfo.widgetName;
+                                    widget.config.datasources = [datasource];
+                                    var length;
+                                    if (scope.attributeScope === types.latestTelemetry && widgetInfo.type !== types.widgetType.rpc.value) {
+                                        length = scope.widgetsListCache.push([widget]);
+                                        scope.widgetsList.push(length === 1 ? [widget] : []);
+                                    } else if (widgetInfo.type === types.widgetType.latest.value) {
+                                        length = scope.widgetsListCache.push([widget]);
+                                        scope.widgetsList.push(length === 1 ? [widget] : []);
+                                    }
+                                }
+                                scope.widgetsLoaded = true;
+                            }
+                        );
+                    }
+                }
+            });
+
+            widgetService.getWidgetsBundleByAlias(types.systemBundleAlias.cards).then(
+                function success(widgetsBundle) {
+                    scope.firstBundle = true;
+                    scope.widgetsBundle = widgetsBundle;
+                }
+            );
+        }
+
+        scope.exitWidgetMode = function() {
+            if (scope.widgetsBundleWatch) {
+                scope.widgetsBundleWatch();
+                scope.widgetsBundleWatch = null;
+            }
+            if (scope.widgetsIndexWatch) {
+                scope.widgetsIndexWatch();
+                scope.widgetsIndexWatch = null;
+            }
+            scope.mode = 'default';
+            scope.getDeviceAttributes(true);
+        }
+
+        scope.addWidgetToDashboard = function($event) {
+            if (scope.mode === 'widget' && scope.widgetsListCache.length > 0) {
+                var widget = scope.widgetsListCache[scope.widgetsCarousel.index][0];
+                $event.stopPropagation();
+                $mdDialog.show({
+                    controller: 'AddWidgetToDashboardDialogController',
+                    controllerAs: 'vm',
+                    templateUrl: addWidgetToDashboardDialogTemplate,
+                    parent: angular.element($document[0].body),
+                    locals: {deviceId: scope.deviceId, deviceName: scope.deviceName, widget: angular.copy(widget)},
+                    fullscreen: true,
+                    targetEvent: $event
+                }).then(function () {
+
+                });
+            }
+        }
+
+        scope.loading = function() {
+            return $rootScope.loading;
+        }
+
+        $compile(element.contents())(scope);
+    }
+
+    return {
+        restrict: "E",
+        link: linker,
+        scope: {
+            deviceId: '=',
+            deviceName: '=',
+            disableAttributeScopeSelection: '@?'
+        }
+    };
+}
diff --git a/ui/src/app/device/attribute/attribute-table.scss b/ui/src/app/device/attribute/attribute-table.scss
new file mode 100644
index 0000000..bce0864
--- /dev/null
+++ b/ui/src/app/device/attribute/attribute-table.scss
@@ -0,0 +1,51 @@
+/**
+ * Copyright © 2016 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 '../../../scss/constants';
+
+$md-light: rgba(255, 255, 255, 100%);
+$md-edit-icon-fill: #757575;
+
+md-toolbar.md-table-toolbar.alternate {
+  .md-toolbar-tools {
+      md-icon {
+        color: $md-light;
+      }
+  }
+}
+
+.md-table {
+  .md-cell {
+    ng-md-icon {
+      fill: $md-edit-icon-fill;
+      float: right;
+      height: 16px;
+    }
+  }
+}
+
+.widgets-carousel {
+  position: relative;
+  margin: 0px;
+
+  min-height: 150px !important;
+
+  tb-dashboard {
+    #gridster-parent {
+      padding: 0 7px;
+    }
+  }
+}
\ No newline at end of file
diff --git a/ui/src/app/device/attribute/attribute-table.tpl.html b/ui/src/app/device/attribute/attribute-table.tpl.html
new file mode 100644
index 0000000..880d880
--- /dev/null
+++ b/ui/src/app/device/attribute/attribute-table.tpl.html
@@ -0,0 +1,208 @@
+<!--
+
+    Copyright © 2016 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 flex class="md-padding tb-absolute-fill" layout="column">
+    <section layout="row" ng-show="!disableAttributeScopeSelection">
+        <md-input-container class="md-block" style="width: 200px;">
+            <label translate>attribute.attributes-scope</label>
+            <md-select ng-model="attributeScope" ng-disabled="loading()">
+                <md-option ng-repeat="scope in attributeScopes" ng-value="scope">
+                    {{scope.name | translate}}
+                </md-option>
+            </md-select>
+        </md-input-container>
+    </section>
+    <div layout="column" class="md-whiteframe-z1" ng-class="{flex: mode==='widget'}">
+        <md-toolbar class="md-table-toolbar md-default" ng-show="mode==='default'
+                                                                 && !selectedAttributes.length
+                                                                 && query.search === null">
+            <div class="md-toolbar-tools">
+                <span translate>{{ attributeScope.name }}</span>
+                <span flex></span>
+                <md-button ng-show="!attributeScope.clientSide" class="md-icon-button" ng-click="addAttribute($event)">
+                    <md-icon>add</md-icon>
+                    <md-tooltip md-direction="top">
+                        {{ 'action.add' | translate }}
+                    </md-tooltip>
+                </md-button>
+                <md-button class="md-icon-button" ng-click="enterFilterMode()">
+                    <md-icon>search</md-icon>
+                    <md-tooltip md-direction="top">
+                        {{ 'action.search' | translate }}
+                    </md-tooltip>
+                </md-button>
+                <md-button ng-show="!attributeScope.clientSide" class="md-icon-button" ng-click="getDeviceAttributes()">
+                    <md-icon>refresh</md-icon>
+                    <md-tooltip md-direction="top">
+                        {{ 'action.refresh' | translate }}
+                    </md-tooltip>
+                </md-button>
+            </div>
+        </md-toolbar>
+        <md-toolbar class="md-table-toolbar md-default" ng-show="mode==='default'
+                                                                 && !selectedAttributes.length
+                                                                 && 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">
+                        {{ 'action.search' | translate }}
+                    </md-tooltip>
+                </md-button>
+                <md-input-container md-theme="tb-search-input" flex>
+                    <label>&nbsp;</label>
+                    <input ng-model="query.search" placeholder="{{ 'common.enter-search' | translate }}"/>
+                </md-input-container>
+                <md-button class="md-icon-button" aria-label="{{ 'action.back' | translate }}" ng-click="exitFilterMode()">
+                    <md-icon aria-label="{{ 'action.close' | translate }}" class="material-icons">close</md-icon>
+                    <md-tooltip md-direction="top">
+                        {{ 'action.close' | translate }}
+                    </md-tooltip>
+                </md-button>
+            </div>
+        </md-toolbar>
+        <md-toolbar class="md-table-toolbar alternate" ng-show="mode==='default' && selectedAttributes.length">
+            <div class="md-toolbar-tools">
+                <span translate="{{attributeScope === types.latestTelemetry
+                                    ? 'attribute.selected-telemetry'
+                                    : 'attribute.selected-attributes'}}"
+                      translate-values="{count: selectedAttributes.length}"
+                      translate-interpolation="messageformat"></span>
+                <span flex></span>
+                <md-button ng-show="!attributeScope.clientSide" class="md-icon-button" ng-click="deleteAttributes($event)">
+                    <md-icon>delete</md-icon>
+                    <md-tooltip md-direction="top">
+                        {{ 'action.delete' | translate }}
+                    </md-tooltip>
+                </md-button>
+                <md-button ng-show="attributeScope.clientSide" class="md-accent md-hue-2 md-raised" ng-click="enterWidgetMode()">
+                    <md-tooltip md-direction="top">
+                        {{ 'attribute.show-on-widget' | translate }}
+                    </md-tooltip>
+                    <md-icon>now_widgets</md-icon>
+                    <span translate>attribute.show-on-widget</span>
+                </md-button>
+            </div>
+        </md-toolbar>
+        <md-toolbar class="md-table-toolbar alternate" ng-show="mode==='widget'">
+            <div class="md-toolbar-tools">
+                <div flex layout="row" layout-align="start">
+                    <span class="tb-details-subtitle">{{ 'widgets-bundle.current' | translate }}</span>
+                    <tb-widgets-bundle-select flex-offset="5"
+                                              flex
+                                              ng-model="widgetsBundle"
+                                              select-first-bundle="false">
+                    </tb-widgets-bundle-select>
+                </div>
+                <md-button ng-show="widgetsList.length > 0" class="md-accent md-hue-2 md-raised" ng-click="addWidgetToDashboard($event)">
+                    <md-tooltip md-direction="top">
+                        {{ 'attribute.add-to-dashboard' | translate }}
+                    </md-tooltip>
+                    <md-icon>dashboard</md-icon>
+                    <span translate>attribute.add-to-dashboard</span>
+                </md-button>
+                <md-button class="md-icon-button" aria-label="{{ 'action.back' | translate }}" ng-click="exitWidgetMode()">
+                    <md-icon aria-label="{{ 'action.close' | translate }}" class="material-icons">close</md-icon>
+                    <md-tooltip md-direction="top">
+                        {{ 'action.close' | translate }}
+                    </md-tooltip>
+                </md-button>
+            </div>
+        </md-toolbar>
+        <md-table-container ng-show="mode!='widget'">
+            <table md-table md-row-select multiple="" ng-model="selectedAttributes" md-progress="attributesDeferred.promise">
+                <thead md-head md-order="query.order" md-on-reorder="getDeviceAttributes">
+                    <tr md-row>
+                        <th md-column md-order-by="lastUpdateTs"><span>Last update time</span></th>
+                        <th md-column md-order-by="key"><span>Key</span></th>
+                        <th md-column>Value</th>
+                    </tr>
+                </thead>
+                <tbody md-body>
+                    <tr md-row md-select="attribute" md-select-id="key" md-auto-select ng-repeat="attribute in attributes.data">
+                        <td md-cell>{{attribute.lastUpdateTs | date :  'yyyy-MM-dd HH:mm:ss'}}</td>
+                        <td md-cell>{{attribute.key}}</td>
+                        <td md-cell ng-click="editAttribute($event, attribute)">
+                            <span>{{attribute.value}}</span>
+                            <span ng-show="!attributeScope.clientSide"><ng-md-icon size="16" icon="edit"></ng-md-icon></span>
+                        </td>
+                    </tr>
+                </tbody>
+            </table>
+        </md-table-container>
+        <md-table-pagination ng-show="mode!='widget'" md-limit="query.limit" md-limit-options="[5, 10, 15]"
+                             md-page="query.page" md-total="{{attributes.count}}"
+                             md-on-paginate="getDeviceAttributes" md-page-select>
+        </md-table-pagination>
+        <ul flex rn-carousel ng-if="mode==='widget'" class="widgets-carousel"
+            rn-carousel-index="widgetsCarousel.index"
+            rn-carousel-buffered
+            rn-carousel-transition="fadeAndSlide"
+            rn-swipe-disabled="true">
+            <li ng-repeat="widgets in widgetsList">
+                <tb-dashboard
+                        device-alias-list="deviceAliases"
+                        widgets="widgets"
+                        columns="20"
+                        is-edit="true"
+                        is-mobile-disabled="true"
+                        is-edit-action-enabled="false"
+                        is-remove-action-enabled="false">
+                </tb-dashboard>
+            </li>
+            <span translate ng-if="widgetsLoaded &&
+                                   widgetsList.length === 0 &&
+                                   widgetsBundle"
+                  layout-align="center center"
+                  style="text-transform: uppercase; display: flex;"
+                  class="md-headline tb-absolute-fill">widgets-bundle.empty</span>
+            <span translate ng-if="!widgetsBundle"
+                  layout-align="center center"
+                  style="text-transform: uppercase; display: flex;"
+                  class="md-headline tb-absolute-fill">widget.select-widgets-bundle</span>
+            <div ng-show="widgetsList.length > 1"
+                 style="position: absolute; left: 0; height: 100%;" layout="column" layout-align="center">
+                <md-button ng-show="widgetsCarousel.index > 0"
+                           class="md-icon-button"
+                           ng-click="prevWidget()">
+                    <md-icon>navigate_before</md-icon>
+                    <md-tooltip md-direction="top">
+                        {{ 'attribute.prev-widget' | translate }}
+                    </md-tooltip>
+                </md-button>
+            </div>
+            <div ng-show="widgetsList.length > 1"
+                 style="position: absolute; right: 0; height: 100%;" layout="column" layout-align="center">
+                <md-button ng-show="widgetsCarousel.index < widgetsList.length-1"
+                           class="md-icon-button"
+                           ng-click="nextWidget()">
+                    <md-icon>navigate_next</md-icon>
+                    <md-tooltip md-direction="top">
+                        {{ 'attribute.next-widget' | translate }}
+                    </md-tooltip>
+                </md-button>
+            </div>
+            <div style="position: absolute; bottom: 0; width: 100%; font-size: 24px;" layout="row" layout-align="center">
+                <div rn-carousel-indicators
+                     ng-if="widgetsList.length > 1"
+                     slides="widgetsList"
+                     rn-carousel-index="widgetsCarousel.index">
+                </div>
+            </div>
+        </ul>
+    </div>
+</md-content>
diff --git a/ui/src/app/device/attribute/edit-attribute-value.controller.js b/ui/src/app/device/attribute/edit-attribute-value.controller.js
new file mode 100644
index 0000000..62022fb
--- /dev/null
+++ b/ui/src/app/device/attribute/edit-attribute-value.controller.js
@@ -0,0 +1,71 @@
+/*
+ * Copyright © 2016 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 EditAttributeValueController($scope, $q, $element, types, attributeValue, save) {
+
+    $scope.valueTypes = types.valueType;
+
+    $scope.model = {};
+
+    $scope.model.value = attributeValue;
+
+    if ($scope.model.value === true || $scope.model.value === false) {
+        $scope.valueType = types.valueType.boolean;
+    } else if (angular.isNumber($scope.model.value)) {
+        if ($scope.model.value.toString().indexOf('.') == -1) {
+            $scope.valueType = types.valueType.integer;
+        } else {
+            $scope.valueType = types.valueType.double;
+        }
+    } else {
+        $scope.valueType = types.valueType.string;
+    }
+
+    $scope.submit = submit;
+    $scope.dismiss = dismiss;
+
+    function dismiss() {
+        $element.remove();
+    }
+
+    function update() {
+        if($scope.editDialog.$invalid) {
+            return $q.reject();
+        }
+
+        if(angular.isFunction(save)) {
+            return $q.when(save($scope.model));
+        }
+
+        return $q.resolve();
+    }
+
+    function submit() {
+        update().then(function () {
+            $scope.dismiss();
+        });
+    }
+
+    $scope.$watch('valueType', function(newVal, prevVal) {
+        if (newVal != prevVal) {
+            if ($scope.valueType === types.valueType.boolean) {
+                $scope.model.value = false;
+            } else {
+                $scope.model.value = null;
+            }
+        }
+    });
+}
diff --git a/ui/src/app/device/attribute/edit-attribute-value.tpl.html b/ui/src/app/device/attribute/edit-attribute-value.tpl.html
new file mode 100644
index 0000000..bf1c6e1
--- /dev/null
+++ b/ui/src/app/device/attribute/edit-attribute-value.tpl.html
@@ -0,0 +1,72 @@
+<!--
+
+    Copyright © 2016 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-edit-dialog>
+    <form name="editDialog" ng-submit="submit()">
+        <div layout="column" class="md-content" style="width: 400px;">
+            <fieldset>
+                <section layout="row">
+                    <md-input-container flex="40" class="md-block">
+                        <label translate>value.type</label>
+                        <md-select ng-model="valueType">
+                            <md-option ng-repeat="type in valueTypes" ng-value="type">
+                                <md-icon md-svg-icon="{{ type.icon }}"></md-icon>
+                                <span>{{type.name | translate}}</span>
+                            </md-option>
+                        </md-select>
+                    </md-input-container>
+                    <md-input-container ng-if="valueType===valueTypes.string" flex="60" class="md-block">
+                        <label translate>value.string-value</label>
+                        <input required name="value" ng-model="model.value">
+                        <div ng-messages="editDialog.value.$error">
+                            <div translate ng-message="required">attribute.value-required</div>
+                        </div>
+                    </md-input-container>
+                    <md-input-container ng-if="valueType===valueTypes.integer" flex="60" class="md-block">
+                        <label translate>value.integer-value</label>
+                        <input required name="value" type="number" step="1" ng-pattern="/^-?[0-9]+$/" ng-model="model.value">
+                        <div ng-messages="editDialog.value.$error">
+                            <div translate ng-message="required">attribute.value-required</div>
+                            <div translate ng-message="pattern">value.invalid-integer-value</div>
+                        </div>
+                    </md-input-container>
+                    <md-input-container ng-if="valueType===valueTypes.double" flex="60" class="md-block">
+                        <label translate>value.double-value</label>
+                        <input required name="value" type="number" step="any" ng-model="model.value">
+                        <div ng-messages="editDialog.value.$error">
+                            <div translate ng-message="required">attribute.value-required</div>
+                        </div>
+                    </md-input-container>
+                    <div layout="column" layout-align="center" flex="60" ng-if="valueType===valueTypes.boolean">
+                        <md-checkbox ng-model="model.value" style="margin-bottom: 0px;">
+                            {{ (model.value ? 'value.true' : 'value.false') | translate }}
+                        </md-checkbox>
+                    </div>
+                </section>
+            </fieldset>
+        </div>
+        <div layout="row" layout-align="end" class="md-actions">
+            <md-button ng-click="dismiss()">{{ 'action.cancel' |
+                translate }}
+            </md-button>
+            <md-button ng-disabled="editDialog.$invalid || !editDialog.$dirty" type="submit"
+                       class="md-raised md-primary">
+                {{ 'action.update' | translate }}
+            </md-button>
+        </div>
+    </form>
+</md-edit-dialog>
\ No newline at end of file
diff --git a/ui/src/app/device/device.controller.js b/ui/src/app/device/device.controller.js
new file mode 100644
index 0000000..e031aa0
--- /dev/null
+++ b/ui/src/app/device/device.controller.js
@@ -0,0 +1,429 @@
+/*
+ * Copyright © 2016 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 addDeviceTemplate from './add-device.tpl.html';
+import deviceCard from './device-card.tpl.html';
+import assignToCustomerTemplate from './assign-to-customer.tpl.html';
+import addDevicesToCustomerTemplate from './add-devices-to-customer.tpl.html';
+import deviceCredentialsTemplate from './device-credentials.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function DeviceController(userService, deviceService, customerService, $scope, $controller, $state, $stateParams, $document, $mdDialog, $q, $translate, types) {
+
+    var customerId = $stateParams.customerId;
+
+    var deviceActionsList = [];
+
+    var deviceGroupActionsList = [];
+
+    var vm = this;
+
+    vm.types = types;
+
+    vm.deviceGridConfig = {
+        deleteItemTitleFunc: deleteDeviceTitle,
+        deleteItemContentFunc: deleteDeviceText,
+        deleteItemsTitleFunc: deleteDevicesTitle,
+        deleteItemsActionTitleFunc: deleteDevicesActionTitle,
+        deleteItemsContentFunc: deleteDevicesText,
+
+        saveItemFunc: saveDevice,
+
+        getItemTitleFunc: getDeviceTitle,
+
+        itemCardTemplateUrl: deviceCard,
+
+        actionsList: deviceActionsList,
+        groupActionsList: deviceGroupActionsList,
+
+        onGridInited: gridInited,
+
+        addItemTemplateUrl: addDeviceTemplate,
+
+        addItemText: function() { return $translate.instant('device.add-device-text') },
+        noItemsText: function() { return $translate.instant('device.no-devices-text') },
+        itemDetailsText: function() { return $translate.instant('device.device-details') },
+        isDetailsReadOnly: isCustomerUser,
+        isSelectionEnabled: function () {
+            return !isCustomerUser();
+        }
+    };
+
+    if (angular.isDefined($stateParams.items) && $stateParams.items !== null) {
+        vm.deviceGridConfig.items = $stateParams.items;
+    }
+
+    if (angular.isDefined($stateParams.topIndex) && $stateParams.topIndex > 0) {
+        vm.deviceGridConfig.topIndex = $stateParams.topIndex;
+    }
+
+    vm.devicesScope = $state.$current.data.devicesType;
+
+    vm.assignToCustomer = assignToCustomer;
+    vm.unassignFromCustomer = unassignFromCustomer;
+    vm.manageCredentials = manageCredentials;
+
+    initController();
+
+    function initController() {
+        var fetchDevicesFunction = null;
+        var deleteDeviceFunction = null;
+        var refreshDevicesParamsFunction = null;
+
+        var user = userService.getCurrentUser();
+
+        if (user.authority === 'CUSTOMER_USER') {
+            vm.devicesScope = 'customer_user';
+            customerId = user.customerId;
+        }
+
+        if (vm.devicesScope === 'tenant') {
+            fetchDevicesFunction = function (pageLink) {
+                return deviceService.getTenantDevices(pageLink);
+            };
+            deleteDeviceFunction = function (deviceId) {
+                return deviceService.deleteDevice(deviceId);
+            };
+            refreshDevicesParamsFunction = function() {
+                return {"topIndex": vm.topIndex};
+            };
+
+            deviceActionsList.push(
+                {
+                    onAction: function ($event, item) {
+                        assignToCustomer($event, [ item.id.id ]);
+                    },
+                    name: function() { return $translate.instant('action.assign') },
+                    details: function() { return $translate.instant('device.assign-to-customer') },
+                    icon: "assignment_ind",
+                    isEnabled: function(device) {
+                        return device && (!device.customerId || device.customerId.id === types.id.nullUid);
+                    }
+                }
+            );
+
+            deviceActionsList.push(
+                {
+                    onAction: function ($event, item) {
+                        unassignFromCustomer($event, item);
+                    },
+                    name: function() { return $translate.instant('action.unassign') },
+                    details: function() { return $translate.instant('device.unassign-from-customer') },
+                    icon: "assignment_return",
+                    isEnabled: function(device) {
+                        return device && device.customerId && device.customerId.id !== types.id.nullUid;
+                    }
+                }
+            );
+
+            deviceActionsList.push(
+                {
+                    onAction: function ($event, item) {
+                        manageCredentials($event, item);
+                    },
+                    name: function() { return $translate.instant('device.credentials') },
+                    details: function() { return $translate.instant('device.manage-credentials') },
+                    icon: "security"
+                }
+            );
+
+            deviceActionsList.push(
+                {
+                    onAction: function ($event, item) {
+                        vm.grid.deleteItem($event, item);
+                    },
+                    name: function() { return $translate.instant('action.delete') },
+                    details: function() { return $translate.instant('device.delete') },
+                    icon: "delete"
+                }
+            );
+
+            deviceGroupActionsList.push(
+                {
+                    onAction: function ($event, items) {
+                        assignDevicesToCustomer($event, items);
+                    },
+                    name: function() { return $translate.instant('device.assign-devices') },
+                    details: function(selectedCount) {
+                        return $translate.instant('device.assign-devices-text', {count: selectedCount}, "messageformat");
+                    },
+                    icon: "assignment_ind"
+                }
+            );
+
+            deviceGroupActionsList.push(
+                {
+                    onAction: function ($event) {
+                        vm.grid.deleteItems($event);
+                    },
+                    name: function() { return $translate.instant('device.delete-devices') },
+                    details: deleteDevicesActionTitle,
+                    icon: "delete"
+                }
+            );
+
+
+
+        } else if (vm.devicesScope === 'customer' || vm.devicesScope === 'customer_user') {
+            fetchDevicesFunction = function (pageLink) {
+                return deviceService.getCustomerDevices(customerId, pageLink);
+            };
+            deleteDeviceFunction = function (deviceId) {
+                return deviceService.unassignDeviceFromCustomer(deviceId);
+            };
+            refreshDevicesParamsFunction = function () {
+                return {"customerId": customerId, "topIndex": vm.topIndex};
+            };
+
+            if (vm.devicesScope === 'customer') {
+                deviceActionsList.push(
+                    {
+                        onAction: function ($event, item) {
+                            unassignFromCustomer($event, item);
+                        },
+                        name: function() { return $translate.instant('action.unassign') },
+                        details: function() { return $translate.instant('device.unassign-from-customer') },
+                        icon: "assignment_return"
+                    }
+                );
+                deviceActionsList.push(
+                    {
+                        onAction: function ($event, item) {
+                            manageCredentials($event, item);
+                        },
+                        name: function() { return $translate.instant('device.credentials') },
+                        details: function() { return $translate.instant('device.manage-credentials') },
+                        icon: "security"
+                    }
+                );
+
+                deviceGroupActionsList.push(
+                    {
+                        onAction: function ($event, items) {
+                            unassignDevicesFromCustomer($event, items);
+                        },
+                        name: function() { return $translate.instant('device.unassign-devices') },
+                        details: function(selectedCount) {
+                            return $translate.instant('device.unassign-devices-action-title', {count: selectedCount}, "messageformat");
+                        },
+                        icon: "assignment_return"
+                    }
+                );
+
+                vm.deviceGridConfig.addItemAction = {
+                    onAction: function ($event) {
+                        addDevicesToCustomer($event);
+                    },
+                    name: function() { return $translate.instant('device.assign-devices') },
+                    details: function() { return $translate.instant('device.assign-new-device') },
+                    icon: "add"
+                };
+
+
+            } else if (vm.devicesScope === 'customer_user') {
+                deviceActionsList.push(
+                    {
+                        onAction: function ($event, item) {
+                            manageCredentials($event, item);
+                        },
+                        name: function() { return $translate.instant('device.credentials') },
+                        details: function() { return $translate.instant('device.view-credentials') },
+                        icon: "security"
+                    }
+                );
+
+                vm.deviceGridConfig.addItemAction = {};
+            }
+        }
+
+        vm.deviceGridConfig.refreshParamsFunc = refreshDevicesParamsFunction;
+        vm.deviceGridConfig.fetchItemsFunc = fetchDevicesFunction;
+        vm.deviceGridConfig.deleteItemFunc = deleteDeviceFunction;
+
+    }
+
+    function deleteDeviceTitle(device) {
+        return $translate.instant('device.delete-device-title', {deviceName: device.name});
+    }
+
+    function deleteDeviceText() {
+        return $translate.instant('device.delete-device-text');
+    }
+
+    function deleteDevicesTitle(selectedCount) {
+        return $translate.instant('device.delete-devices-title', {count: selectedCount}, 'messageformat');
+    }
+
+    function deleteDevicesActionTitle(selectedCount) {
+        return $translate.instant('device.delete-devices-action-title', {count: selectedCount}, 'messageformat');
+    }
+
+    function deleteDevicesText () {
+        return $translate.instant('device.delete-devices-text');
+    }
+
+    function gridInited(grid) {
+        vm.grid = grid;
+    }
+
+    function getDeviceTitle(device) {
+        return device ? device.name : '';
+    }
+
+    function saveDevice (device) {
+        return deviceService.saveDevice(device);
+    }
+
+    function isCustomerUser() {
+        return vm.devicesScope === 'customer_user';
+    }
+
+    function assignToCustomer($event, deviceIds) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        var pageSize = 10;
+        customerService.getCustomers({limit: pageSize, textSearch: ''}).then(
+            function success(_customers) {
+                var customers = {
+                    pageSize: pageSize,
+                    data: _customers.data,
+                    nextPageLink: _customers.nextPageLink,
+                    selection: null,
+                    hasNext: _customers.hasNext,
+                    pending: false
+                };
+                if (customers.hasNext) {
+                    customers.nextPageLink.limit = pageSize;
+                }
+                $mdDialog.show({
+                    controller: 'AssignDeviceToCustomerController',
+                    controllerAs: 'vm',
+                    templateUrl: assignToCustomerTemplate,
+                    locals: {deviceIds: deviceIds, customers: customers},
+                    parent: angular.element($document[0].body),
+                    fullscreen: true,
+                    targetEvent: $event
+                }).then(function () {
+                    vm.grid.refreshList();
+                }, function () {
+                });
+            },
+            function fail() {
+            });
+    }
+
+    function addDevicesToCustomer($event) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        var pageSize = 10;
+        deviceService.getTenantDevices({limit: pageSize, textSearch: ''}).then(
+            function success(_devices) {
+                var devices = {
+                    pageSize: pageSize,
+                    data: _devices.data,
+                    nextPageLink: _devices.nextPageLink,
+                    selections: {},
+                    selectedCount: 0,
+                    hasNext: _devices.hasNext,
+                    pending: false
+                };
+                if (devices.hasNext) {
+                    devices.nextPageLink.limit = pageSize;
+                }
+                $mdDialog.show({
+                    controller: 'AddDevicesToCustomerController',
+                    controllerAs: 'vm',
+                    templateUrl: addDevicesToCustomerTemplate,
+                    locals: {customerId: customerId, devices: devices},
+                    parent: angular.element($document[0].body),
+                    fullscreen: true,
+                    targetEvent: $event
+                }).then(function () {
+                    vm.grid.refreshList();
+                }, function () {
+                });
+            },
+            function fail() {
+            });
+    }
+
+    function assignDevicesToCustomer($event, items) {
+        var deviceIds = [];
+        for (var id in items.selections) {
+            deviceIds.push(id);
+        }
+        assignToCustomer($event, deviceIds);
+    }
+
+    function unassignFromCustomer($event, device) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        var confirm = $mdDialog.confirm()
+            .targetEvent($event)
+            .title($translate.instant('device.unassign-device-title', {deviceName: device.name}))
+            .htmlContent($translate.instant('device.unassign-device-text'))
+            .ariaLabel($translate.instant('device.unassign-device'))
+            .cancel($translate.instant('action.no'))
+            .ok($translate.instant('action.yes'));
+        $mdDialog.show(confirm).then(function () {
+            deviceService.unassignDeviceFromCustomer(device.id.id).then(function success() {
+                vm.grid.refreshList();
+            });
+        });
+    }
+
+    function unassignDevicesFromCustomer($event, items) {
+        var confirm = $mdDialog.confirm()
+            .targetEvent($event)
+            .title($translate.instant('device.unassign-devices-title', {count: items.selectedCount}, 'messageformat'))
+            .htmlContent($translate.instant('device.unassign-devices-text'))
+            .ariaLabel($translate.instant('device.unassign-device'))
+            .cancel($translate.instant('action.no'))
+            .ok($translate.instant('action.yes'));
+        $mdDialog.show(confirm).then(function () {
+            var tasks = [];
+            for (var id in items.selections) {
+                tasks.push(deviceService.unassignDeviceFromCustomer(id));
+            }
+            $q.all(tasks).then(function () {
+                vm.grid.refreshList();
+            });
+        });
+    }
+
+    function manageCredentials($event, device) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        $mdDialog.show({
+            controller: 'ManageDeviceCredentialsController',
+            controllerAs: 'vm',
+            templateUrl: deviceCredentialsTemplate,
+            locals: {deviceId: device.id.id, isReadOnly: isCustomerUser()},
+            parent: angular.element($document[0].body),
+            fullscreen: true,
+            targetEvent: $event
+        }).then(function () {
+        }, function () {
+        });
+    }
+}
diff --git a/ui/src/app/device/device.directive.js b/ui/src/app/device/device.directive.js
new file mode 100644
index 0000000..918040b
--- /dev/null
+++ b/ui/src/app/device/device.directive.js
@@ -0,0 +1,69 @@
+/*
+ * Copyright © 2016 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 deviceFieldsetTemplate from './device-fieldset.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function DeviceDirective($compile, $templateCache, toast, $translate, types, customerService) {
+    var linker = function (scope, element) {
+        var template = $templateCache.get(deviceFieldsetTemplate);
+        element.html(template);
+
+        scope.isAssignedToCustomer = false;
+
+        scope.assignedCustomer = null;
+
+
+        scope.$watch('device', function(newVal) {
+            if (newVal) {
+                if (scope.device.customerId && scope.device.customerId.id !== types.id.nullUid) {
+                    scope.isAssignedToCustomer = true;
+                    customerService.getCustomer(scope.device.customerId.id).then(
+                        function success(customer) {
+                            scope.assignedCustomer = customer;
+                        }
+                    );
+                } else {
+                    scope.isAssignedToCustomer = false;
+                    scope.assignedCustomer = null;
+                }
+            }
+        });
+
+        scope.onDeviceIdCopied = function() {
+            toast.showSuccess($translate.instant('device.idCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left');
+        };
+
+        $compile(element.contents())(scope);
+    }
+    return {
+        restrict: "E",
+        link: linker,
+        scope: {
+            device: '=',
+            isEdit: '=',
+            deviceScope: '=',
+            theForm: '=',
+            onAssignToCustomer: '&',
+            onUnassignFromCustomer: '&',
+            onManageCredentials: '&',
+            onDeleteDevice: '&'
+        }
+    };
+}
diff --git a/ui/src/app/device/device.routes.js b/ui/src/app/device/device.routes.js
new file mode 100644
index 0000000..6a82d9d
--- /dev/null
+++ b/ui/src/app/device/device.routes.js
@@ -0,0 +1,68 @@
+/*
+ * Copyright © 2016 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 devicesTemplate from './devices.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function DeviceRoutes($stateProvider) {
+    $stateProvider
+        .state('home.devices', {
+            url: '/devices',
+            params: {'topIndex': 0},
+            module: 'private',
+            auth: ['TENANT_ADMIN', 'CUSTOMER_USER'],
+            views: {
+                "content@home": {
+                    templateUrl: devicesTemplate,
+                    controller: 'DeviceController',
+                    controllerAs: 'vm'
+                }
+            },
+            data: {
+                devicesType: 'tenant',
+                searchEnabled: true,
+                pageTitle: 'device.devices'
+            },
+            ncyBreadcrumb: {
+                label: '{"icon": "devices_other", "label": "device.devices"}'
+            }
+        })
+        .state('home.customers.devices', {
+            url: '/:customerId/devices',
+            params: {'topIndex': 0},
+            module: 'private',
+            auth: ['TENANT_ADMIN'],
+            views: {
+                "content@home": {
+                    templateUrl: devicesTemplate,
+                    controllerAs: 'vm',
+                    controller: 'DeviceController'
+                }
+            },
+            data: {
+                devicesType: 'customer',
+                searchEnabled: true,
+                pageTitle: 'customer.devices'
+            },
+            ncyBreadcrumb: {
+                label: '{"icon": "devices_other", "label": "customer.devices"}'
+            }
+        });
+
+}
diff --git a/ui/src/app/device/device-card.tpl.html b/ui/src/app/device/device-card.tpl.html
new file mode 100644
index 0000000..8151b2f
--- /dev/null
+++ b/ui/src/app/device/device-card.tpl.html
@@ -0,0 +1,18 @@
+<!--
+
+    Copyright © 2016 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></div>
\ No newline at end of file
diff --git a/ui/src/app/device/device-credentials.controller.js b/ui/src/app/device/device-credentials.controller.js
new file mode 100644
index 0000000..42b6696
--- /dev/null
+++ b/ui/src/app/device/device-credentials.controller.js
@@ -0,0 +1,64 @@
+/*
+ * Copyright © 2016 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 ManageDeviceCredentialsController(deviceService, $scope, $mdDialog, deviceId, isReadOnly) {
+
+    var vm = this;
+
+    vm.credentialsTypes = [
+        {
+            name: 'Access token',
+            value: 'ACCESS_TOKEN'
+        },
+        {
+            name: 'X.509 Certificate (Coming soon)',
+            value: 'X509_CERTIFICATE'
+        }
+    ];
+
+    vm.deviceCredentials = {};
+    vm.isReadOnly = isReadOnly;
+
+    vm.valid = valid;
+    vm.cancel = cancel;
+    vm.save = save;
+
+    loadDeviceCredentials();
+
+    function loadDeviceCredentials() {
+        deviceService.getDeviceCredentials(deviceId).then(function success(deviceCredentials) {
+            vm.deviceCredentials = deviceCredentials;
+        });
+    }
+
+    function cancel() {
+        $mdDialog.cancel();
+    }
+
+    function valid() {
+        return vm.deviceCredentials &&
+               vm.deviceCredentials.credentialsType === 'ACCESS_TOKEN' &&
+               vm.deviceCredentials.credentialsId && vm.deviceCredentials.credentialsId.length > 0;
+    }
+
+    function save() {
+        deviceService.saveDeviceCredentials(vm.deviceCredentials).then(function success(deviceCredentials) {
+            vm.deviceCredentials = deviceCredentials;
+            $scope.theForm.$setPristine();
+            $mdDialog.hide();
+        });
+    }
+}
diff --git a/ui/src/app/device/device-credentials.tpl.html b/ui/src/app/device/device-credentials.tpl.html
new file mode 100644
index 0000000..5bfc2c0
--- /dev/null
+++ b/ui/src/app/device/device-credentials.tpl.html
@@ -0,0 +1,62 @@
+<!--
+
+    Copyright © 2016 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="{{ 'device.device-credentials' | translate }}">
+	<form name="theForm" ng-submit="vm.save()">
+	    <md-toolbar>
+	      <div class="md-toolbar-tools">
+	        <h2 translate>device.device-credentials</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-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 || vm.isReadOnly">
+				<md-input-container class="md-block">
+					<label translate>device.credentials-type</label>
+		            <md-select ng-disabled="loading || vm.isReadOnly" ng-model="vm.deviceCredentials.credentialsType">
+		              	<md-option ng-repeat="credentialsType in vm.credentialsTypes" value="{{credentialsType.value}}">
+		                	{{credentialsType.name}}
+		             	</md-option>
+		            </md-select>
+				</md-input-container>
+				<md-input-container class="md-block" ng-if="vm.deviceCredentials.credentialsType === 'ACCESS_TOKEN'">
+					<label translate>device.access-token</label>
+					<input required name="accessToken" ng-model="vm.deviceCredentials.credentialsId"
+						   md-maxlength="20" ng-pattern="/^.{1,20}$/">
+					<div ng-messages="theForm.accessToken.$error">
+         					<div translate ng-message="required">device.access-token-required</div>
+							<div translate ng-message="pattern">device.access-token-invalid</div>
+       				</div>
+				</md-input-container>
+			</fieldset>
+	      </div>
+	    </md-dialog-content>
+	    <md-dialog-actions layout="row">
+	      <span flex></span>
+		  <md-button ng-if="!vm.isReadOnly" ng-disabled="loading || theForm.$invalid || !theForm.$dirty || !vm.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;">{{ (vm.isReadOnly ? 'action.close' : 'action.cancel') | translate }}</md-button>
+	    </md-dialog-actions>
+	</form>    
+</md-dialog>
\ No newline at end of file
diff --git a/ui/src/app/device/device-fieldset.tpl.html b/ui/src/app/device/device-fieldset.tpl.html
new file mode 100644
index 0000000..6d9892e
--- /dev/null
+++ b/ui/src/app/device/device-fieldset.tpl.html
@@ -0,0 +1,60 @@
+<!--
+
+    Copyright © 2016 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-button ng-click="onAssignToCustomer({event: $event})"
+           ng-show="!isEdit && deviceScope === 'tenant' && !isAssignedToCustomer"
+           class="md-raised md-primary">{{ 'device.assign-to-customer' | translate }}</md-button>
+<md-button ng-click="onUnassignFromCustomer({event: $event})"
+           ng-show="!isEdit && (deviceScope === 'customer' || deviceScope === 'tenant') && isAssignedToCustomer"
+           class="md-raised md-primary">{{ 'device.unassign-from-customer' | translate }}</md-button>
+<md-button ng-click="onManageCredentials({event: $event})"
+           ng-show="!isEdit"
+           class="md-raised md-primary">{{ (deviceScope === 'customer_user' ? 'device.view-credentials' : 'device.manage-credentials') | translate }}</md-button>
+<md-button ng-click="onDeleteDevice({event: $event})"
+           ng-show="!isEdit && deviceScope === 'tenant'"
+           class="md-raised md-primary">{{ 'device.delete' | translate }}</md-button>
+
+<div layout="row">
+	<md-button ngclipboard data-clipboard-action="copy"
+               ngclipboard-success="onDeviceIdCopied(e)"
+               data-clipboard-text="{{device.id.id}}" ng-show="!isEdit"
+			   class="md-raised">
+        <md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon>
+        <span translate>device.copyId</span>
+    </md-button>
+</div>
+
+<md-content class="md-padding" layout="column">
+    <md-input-container class="md-block"
+                        ng-show="isAssignedToCustomer && deviceScope === 'tenant'">
+        <label translate>device.assignedToCustomer</label>
+        <input ng-model="assignedCustomer.title" disabled>
+    </md-input-container>
+	<fieldset ng-disabled="loading || !isEdit">
+		<md-input-container class="md-block">
+			<label translate>device.name</label>
+			<input required name="name" ng-model="device.name">	
+			<div ng-messages="theForm.name.$error">
+	      		<div translate ng-message="required">device.name-required</div>
+	    	</div>				
+		</md-input-container>
+        <md-input-container class="md-block">
+            <label translate>device.description</label>
+            <textarea ng-model="device.additionalInfo.description" rows="2"></textarea>
+        </md-input-container>
+	</fieldset>
+</md-content>
diff --git a/ui/src/app/device/devices.tpl.html b/ui/src/app/device/devices.tpl.html
new file mode 100644
index 0000000..8aed648
--- /dev/null
+++ b/ui/src/app/device/devices.tpl.html
@@ -0,0 +1,55 @@
+<!--
+
+    Copyright © 2016 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-grid grid-configuration="vm.deviceGridConfig">
+    <details-buttons tb-help="'devices'" help-container-id="help-container">
+        <div id="help-container"></div>
+    </details-buttons>
+    <md-tabs ng-class="{'tb-headless': vm.grid.detailsConfig.isDetailsEditMode}"
+             id="tabs" md-border-bottom flex class="tb-absolute-fill">
+        <md-tab label="{{ 'device.details' | translate }}">
+            <tb-device device="vm.grid.operatingItem()"
+                       is-edit="vm.grid.detailsConfig.isDetailsEditMode"
+                       device-scope="vm.devicesScope"
+                       the-form="vm.grid.detailsForm"
+                       on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])"
+                       on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem)"
+                       on-manage-credentials="vm.manageCredentials(event, vm.grid.detailsConfig.currentItem)"
+                       on-delete-device="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-device>
+        </md-tab>
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'attribute.attributes' | translate }}">
+            <tb-attribute-table flex
+                            device-id="vm.grid.operatingItem().id.id"
+                            device-name="vm.grid.operatingItem().name"
+                            default-attribute-scope="{{vm.types.deviceAttributesScope.client.value}}">
+            </tb-attribute-table>
+        </md-tab>
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'attribute.latest-telemetry' | translate }}">
+            <tb-attribute-table flex
+                                device-id="vm.grid.operatingItem().id.id"
+                                default-attribute-scope="{{vm.types.latestTelemetry.value}}"
+                                disable-attribute-scope-selection="true">
+            </tb-attribute-table>
+        </md-tab>
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'device.events' | translate }}">
+            <tb-event-table flex entity-type="vm.types.entityType.device"
+                            entity-id="vm.grid.operatingItem().id.id"
+                            tenant-id="vm.grid.operatingItem().tenantId.id"
+                            default-event-type="{{vm.types.eventType.alarm.value}}">
+            </tb-event-table>
+        </md-tab>
+</tb-grid>
diff --git a/ui/src/app/device/index.js b/ui/src/app/device/index.js
new file mode 100644
index 0000000..43f290e
--- /dev/null
+++ b/ui/src/app/device/index.js
@@ -0,0 +1,52 @@
+/*
+ * Copyright © 2016 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 uiRouter from 'angular-ui-router';
+import thingsboardGrid from '../components/grid.directive';
+import thingsboardEvent from '../event';
+import thingsboardDashboardSelect from '../components/dashboard-select.directive';
+import thingsboardApiUser from '../api/user.service';
+import thingsboardApiDevice from '../api/device.service';
+import thingsboardApiCustomer from '../api/customer.service';
+
+import DeviceRoutes from './device.routes';
+import DeviceController from './device.controller';
+import AssignDeviceToCustomerController from './assign-to-customer.controller';
+import AddDevicesToCustomerController from './add-devices-to-customer.controller';
+import ManageDeviceCredentialsController from './device-credentials.controller';
+import AddAttributeDialogController from './attribute/add-attribute-dialog.controller';
+import AddWidgetToDashboardDialogController from './attribute/add-widget-to-dashboard-dialog.controller';
+import DeviceDirective from './device.directive';
+import AttributeTableDirective from './attribute/attribute-table.directive';
+
+export default angular.module('thingsboard.device', [
+    uiRouter,
+    thingsboardGrid,
+    thingsboardEvent,
+    thingsboardDashboardSelect,
+    thingsboardApiUser,
+    thingsboardApiDevice,
+    thingsboardApiCustomer
+])
+    .config(DeviceRoutes)
+    .controller('DeviceController', DeviceController)
+    .controller('AssignDeviceToCustomerController', AssignDeviceToCustomerController)
+    .controller('AddDevicesToCustomerController', AddDevicesToCustomerController)
+    .controller('ManageDeviceCredentialsController', ManageDeviceCredentialsController)
+    .controller('AddAttributeDialogController', AddAttributeDialogController)
+    .controller('AddWidgetToDashboardDialogController', AddWidgetToDashboardDialogController)
+    .directive('tbDevice', DeviceDirective)
+    .directive('tbAttributeTable', AttributeTableDirective)
+    .name;
diff --git a/ui/src/app/event/event.scss b/ui/src/app/event/event.scss
new file mode 100644
index 0000000..a43ea3e
--- /dev/null
+++ b/ui/src/app/event/event.scss
@@ -0,0 +1,72 @@
+/**
+ * Copyright © 2016 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-list.tb-table {
+    padding: 0px;
+
+    md-list-item {
+      padding: 0px;
+    }
+
+  .tb-row {
+    height: 48px;
+    padding: 0px;
+    overflow: hidden;
+  }
+
+  .tb-row:hover {
+    background-color: #EEEEEE;
+  }
+
+  .tb-header:hover {
+    background: none;
+  }
+
+  .tb-header {
+      .tb-cell {
+        color: rgba(0,0,0,.54);
+        font-size: 12px;
+        font-weight: 700;
+        white-space: nowrap;
+        background: none;
+      }
+  }
+
+  .tb-cell {
+      padding: 0 24px;
+      margin: auto 0;
+      color: rgba(0,0,0,.87);
+      font-size: 13px;
+      vertical-align: middle;
+      text-align: left;
+      overflow: hidden;
+      .md-button {
+        padding: 0;
+        margin: 0;
+      }
+  }
+
+  .tb-cell.tb-number {
+    text-align: right;
+  }
+
+}
+
+#tb-content {
+  min-width: 400px;
+  min-height: 50px;
+  width: 100%;
+  height: 100%;
+}
diff --git a/ui/src/app/event/event-content-dialog.controller.js b/ui/src/app/event/event-content-dialog.controller.js
new file mode 100644
index 0000000..80ea9c2
--- /dev/null
+++ b/ui/src/app/event/event-content-dialog.controller.js
@@ -0,0 +1,78 @@
+/*
+ * Copyright © 2016 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 $ from 'jquery';
+import 'brace/ext/language_tools';
+import 'brace/mode/java';
+import 'brace/theme/github';
+
+/* eslint-disable angular/angularelement */
+
+/*@ngInject*/
+export default function EventContentDialogController($mdDialog, content, title, showingCallback) {
+
+    var vm = this;
+
+    showingCallback.onShowing = function(scope, element) {
+        updateEditorSize(element);
+    }
+
+    vm.content = content;
+    vm.title = title;
+
+    vm.contentOptions = {
+        useWrapMode: false,
+        mode: 'java',
+        showGutter: false,
+        showPrintMargin: false,
+        theme: 'github',
+        advanced: {
+            enableSnippets: false,
+            enableBasicAutocompletion: false,
+            enableLiveAutocompletion: false
+        },
+        onLoad: function (_ace) {
+            vm.editor = _ace;
+        }
+    };
+
+    function updateEditorSize(element) {
+        var newHeight = 400;
+        var newWidth = 600;
+        if (vm.content && vm.content.length > 0) {
+            var lines = vm.content.split('\n');
+            newHeight = 16 * lines.length + 16;
+            var maxLineLength = 0;
+            for (var i in lines) {
+                var line = lines[i].replace(/\t/g, '    ').replace(/\n/g, '');
+                var lineLength = line.length;
+                maxLineLength = Math.max(maxLineLength, lineLength);
+            }
+            newWidth = 8 * maxLineLength + 16;
+        }
+        $('#tb-content', element).height(newHeight.toString() + "px")
+            .width(newWidth.toString() + "px");
+        vm.editor.resize();
+    }
+
+    vm.close = close;
+
+    function close () {
+        $mdDialog.hide();
+    }
+
+}
+
+/* eslint-enable angular/angularelement */
diff --git a/ui/src/app/event/event-content-dialog.tpl.html b/ui/src/app/event/event-content-dialog.tpl.html
new file mode 100644
index 0000000..52d66a6
--- /dev/null
+++ b/ui/src/app/event/event-content-dialog.tpl.html
@@ -0,0 +1,42 @@
+<!--
+
+    Copyright © 2016 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="{{ vm.title | translate }}">
+        <md-toolbar>
+            <div class="md-toolbar-tools">
+                <h2 translate>{{ vm.title }}</h2>
+                <span flex></span>
+                <md-button class="md-icon-button" ng-click="vm.close()">
+                    <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon>
+                </md-button>
+            </div>
+        </md-toolbar>
+        <md-dialog-content>
+            <div class="md-dialog-content">
+                <div flex id="tb-content" readonly
+                     ui-ace="vm.contentOptions"
+                     ng-model="vm.content">
+                </div>
+            </div>
+        </md-dialog-content>
+        <md-dialog-actions layout="row">
+            <span flex></span>
+            <md-button ng-disabled="loading" ng-click="vm.close()" style="margin-right:20px;">{{ 'action.close' |
+                translate }}
+            </md-button>
+        </md-dialog-actions>
+</md-dialog>
diff --git a/ui/src/app/event/event-header.directive.js b/ui/src/app/event/event-header.directive.js
new file mode 100644
index 0000000..e776980
--- /dev/null
+++ b/ui/src/app/event/event-header.directive.js
@@ -0,0 +1,66 @@
+/*
+ * Copyright © 2016 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 eventHeaderLcEventTemplate from './event-header-lc-event.tpl.html';
+import eventHeaderStatsTemplate from './event-header-stats.tpl.html';
+import eventHeaderErrorTemplate from './event-header-error.tpl.html';
+import eventHeaderAlarmTemplate from './event-header-alarm.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function EventHeaderDirective($compile, $templateCache, types) {
+
+    var linker = function (scope, element, attrs) {
+
+        var getTemplate = function(eventType) {
+            var template = '';
+            switch(eventType) {
+                case types.eventType.lcEvent.value:
+                    template = eventHeaderLcEventTemplate;
+                    break;
+                case types.eventType.stats.value:
+                    template = eventHeaderStatsTemplate;
+                    break;
+                case types.eventType.error.value:
+                    template = eventHeaderErrorTemplate;
+                    break;
+                case types.eventType.alarm.value:
+                    template = eventHeaderAlarmTemplate;
+                    break;
+            }
+            return $templateCache.get(template);
+        }
+
+        scope.loadTemplate = function() {
+            element.html(getTemplate(attrs.eventType));
+            $compile(element.contents())(scope);
+        }
+
+        attrs.$observe('eventType', function() {
+            scope.loadTemplate();
+        });
+
+    }
+
+    return {
+        restrict: "A",
+        replace: false,
+        link: linker,
+        scope: false
+    };
+}
diff --git a/ui/src/app/event/event-header-alarm.tpl.html b/ui/src/app/event/event-header-alarm.tpl.html
new file mode 100644
index 0000000..761c741
--- /dev/null
+++ b/ui/src/app/event/event-header-alarm.tpl.html
@@ -0,0 +1,20 @@
+<!--
+
+    Copyright © 2016 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 translate class="tb-cell" flex="30">event.event-time</div>
+<div translate class="tb-cell" flex="20">event.server</div>
+<div translate class="tb-cell" flex="20">event.alarm</div>
diff --git a/ui/src/app/event/event-header-error.tpl.html b/ui/src/app/event/event-header-error.tpl.html
new file mode 100644
index 0000000..38c832d
--- /dev/null
+++ b/ui/src/app/event/event-header-error.tpl.html
@@ -0,0 +1,21 @@
+<!--
+
+    Copyright © 2016 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 translate class="tb-cell" flex="30">event.event-time</div>
+<div translate class="tb-cell" flex="20">event.server</div>
+<div translate class="tb-cell" flex="20">event.method</div>
+<div translate class="tb-cell" flex="20">event.error</div>
diff --git a/ui/src/app/event/event-header-lc-event.tpl.html b/ui/src/app/event/event-header-lc-event.tpl.html
new file mode 100644
index 0000000..a67bcfe
--- /dev/null
+++ b/ui/src/app/event/event-header-lc-event.tpl.html
@@ -0,0 +1,22 @@
+<!--
+
+    Copyright © 2016 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 translate class="tb-cell" flex="30">event.event-time</div>
+<div translate class="tb-cell" flex="20">event.server</div>
+<div translate class="tb-cell" flex="20">event.event</div>
+<div translate class="tb-cell" flex="20">event.status</div>
+<div translate class="tb-cell" flex="20">event.error</div>
diff --git a/ui/src/app/event/event-header-stats.tpl.html b/ui/src/app/event/event-header-stats.tpl.html
new file mode 100644
index 0000000..cac3c78
--- /dev/null
+++ b/ui/src/app/event/event-header-stats.tpl.html
@@ -0,0 +1,21 @@
+<!--
+
+    Copyright © 2016 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 translate class="tb-cell" flex>event.event-time</div>
+<div translate class="tb-cell" flex>event.server</div>
+<div translate class="tb-cell tb-number" flex>event.messages-processed</div>
+<div translate class="tb-cell tb-number" flex>event.errors-occurred</div>
diff --git a/ui/src/app/event/event-row.directive.js b/ui/src/app/event/event-row.directive.js
new file mode 100644
index 0000000..b995905
--- /dev/null
+++ b/ui/src/app/event/event-row.directive.js
@@ -0,0 +1,90 @@
+/*
+ * Copyright © 2016 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 eventErrorDialogTemplate from './event-content-dialog.tpl.html';
+
+import eventRowLcEventTemplate from './event-row-lc-event.tpl.html';
+import eventRowStatsTemplate from './event-row-stats.tpl.html';
+import eventRowErrorTemplate from './event-row-error.tpl.html';
+import eventRowAlarmTemplate from './event-row-alarm.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function EventRowDirective($compile, $templateCache, $mdDialog, $document, types) {
+
+    var linker = function (scope, element, attrs) {
+
+        var getTemplate = function(eventType) {
+            var template = '';
+            switch(eventType) {
+                case types.eventType.lcEvent.value:
+                    template = eventRowLcEventTemplate;
+                    break;
+                case types.eventType.stats.value:
+                    template = eventRowStatsTemplate;
+                    break;
+                case types.eventType.error.value:
+                    template = eventRowErrorTemplate;
+                    break;
+                case types.eventType.alarm.value:
+                    template = eventRowAlarmTemplate;
+                    break;
+            }
+            return $templateCache.get(template);
+        }
+
+        scope.loadTemplate = function() {
+            element.html(getTemplate(attrs.eventType));
+            $compile(element.contents())(scope);
+        }
+
+        attrs.$observe('eventType', function() {
+            scope.loadTemplate();
+        });
+
+        scope.event = attrs.event;
+
+        scope.showContent = function($event, content, title) {
+            var onShowingCallback = {
+                onShowing: function(){}
+            }
+            $mdDialog.show({
+                controller: 'EventContentDialogController',
+                controllerAs: 'vm',
+                templateUrl: eventErrorDialogTemplate,
+                locals: {content: content, title: title, showingCallback: onShowingCallback},
+                parent: angular.element($document[0].body),
+                fullscreen: true,
+                targetEvent: $event,
+                skipHide: true,
+                onShowing: function(scope, element) {
+                    onShowingCallback.onShowing(scope, element);
+                }
+            });
+        }
+
+        $compile(element.contents())(scope);
+    }
+
+    return {
+        restrict: "A",
+        replace: false,
+        link: linker,
+        scope: false
+    };
+}
diff --git a/ui/src/app/event/event-row-alarm.tpl.html b/ui/src/app/event/event-row-alarm.tpl.html
new file mode 100644
index 0000000..c01f74a
--- /dev/null
+++ b/ui/src/app/event/event-row-alarm.tpl.html
@@ -0,0 +1,32 @@
+<!--
+
+    Copyright © 2016 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<div class="tb-cell" flex="30">{{event.createdTime | date :  'yyyy-MM-dd HH:mm:ss'}}</div>
+<div class="tb-cell" flex="20">{{event.body.server}}</div>
+<div class="tb-cell" flex="20">
+    <md-button ng-if="event.body.body" class="md-icon-button md-primary"
+               ng-click="showContent($event, event.body.body, 'event.alarm')"
+               aria-label="{{ 'action.view' | translate }}">
+        <md-tooltip md-direction="top">
+            {{ 'action.view' | translate }}
+        </md-tooltip>
+        <md-icon aria-label="{{ 'action.view' | translate }}"
+                 class="material-icons">
+            more_horiz
+        </md-icon>
+    </md-button>
+</div>
diff --git a/ui/src/app/event/event-row-error.tpl.html b/ui/src/app/event/event-row-error.tpl.html
new file mode 100644
index 0000000..8a35f4a
--- /dev/null
+++ b/ui/src/app/event/event-row-error.tpl.html
@@ -0,0 +1,33 @@
+<!--
+
+    Copyright © 2016 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<div class="tb-cell" flex="30">{{event.createdTime | date :  'yyyy-MM-dd HH:mm:ss'}}</div>
+<div class="tb-cell" flex="20">{{event.body.server}}</div>
+<div class="tb-cell" flex="20">{{event.body.method}}</div>
+<div class="tb-cell" flex="20">
+    <md-button ng-if="event.body.error" class="md-icon-button md-primary"
+               ng-click="showContent($event, event.body.error, 'event.error')"
+               aria-label="{{ 'action.view' | translate }}">
+        <md-tooltip md-direction="top">
+            {{ 'action.view' | translate }}
+        </md-tooltip>
+        <md-icon aria-label="{{ 'action.view' | translate }}"
+                 class="material-icons">
+            more_horiz
+        </md-icon>
+    </md-button>
+</div>
diff --git a/ui/src/app/event/event-row-lc-event.tpl.html b/ui/src/app/event/event-row-lc-event.tpl.html
new file mode 100644
index 0000000..2f21462
--- /dev/null
+++ b/ui/src/app/event/event-row-lc-event.tpl.html
@@ -0,0 +1,34 @@
+<!--
+
+    Copyright © 2016 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<div class="tb-cell" flex="30">{{event.createdTime | date :  'yyyy-MM-dd HH:mm:ss'}}</div>
+<div class="tb-cell" flex="20">{{event.body.server}}</div>
+<div class="tb-cell" flex="20">{{event.body.event}}</div>
+<div translate class="tb-cell" flex="20">{{event.body.success ? 'event.success' : 'event.failed'}}</div>
+<div class="tb-cell" flex="20">
+    <md-button ng-if="event.body.error" class="md-icon-button md-primary"
+               ng-click="showContent($event, event.body.error, 'event.error')"
+               aria-label="{{ 'action.view' | translate }}">
+        <md-tooltip md-direction="top">
+            {{ 'action.view' | translate }}
+        </md-tooltip>
+        <md-icon aria-label="{{ 'action.view' | translate }}"
+                 class="material-icons">
+            more_horiz
+        </md-icon>
+    </md-button>
+</div>
diff --git a/ui/src/app/event/event-row-stats.tpl.html b/ui/src/app/event/event-row-stats.tpl.html
new file mode 100644
index 0000000..a90196b
--- /dev/null
+++ b/ui/src/app/event/event-row-stats.tpl.html
@@ -0,0 +1,21 @@
+<!--
+
+    Copyright © 2016 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<div class="tb-cell" flex>{{event.createdTime | date :  'yyyy-MM-dd HH:mm:ss'}}</div>
+<div class="tb-cell" flex>{{event.body.server}}</div>
+<div class="tb-cell tb-number" flex>{{event.body.messagesProcessed}}</div>
+<div class="tb-cell tb-number" flex>{{event.body.errorsOccurred}}</div>
diff --git a/ui/src/app/event/event-table.directive.js b/ui/src/app/event/event-table.directive.js
new file mode 100644
index 0000000..e351244
--- /dev/null
+++ b/ui/src/app/event/event-table.directive.js
@@ -0,0 +1,212 @@
+/*
+ * Copyright © 2016 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 './event.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import eventTableTemplate from './event-table.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function EventTableDirective($compile, $templateCache, $rootScope, types, eventService) {
+
+    var linker = function (scope, element, attrs) {
+
+        var template = $templateCache.get(eventTableTemplate);
+
+        element.html(template);
+
+        if (attrs.disabledEventTypes) {
+            var disabledEventTypes = attrs.disabledEventTypes.split(',');
+            scope.eventTypes = {};
+            for (var type in types.eventType) {
+                var eventType = types.eventType[type];
+                var enabled = true;
+                for (var disabledType in disabledEventTypes) {
+                    if (eventType.value === disabledEventTypes[disabledType]) {
+                        enabled = false;
+                        break;
+                    }
+                }
+                if (enabled) {
+                    scope.eventTypes[type] = eventType;
+                }
+            }
+        } else {
+            scope.eventTypes = types.eventType;
+        }
+
+        scope.eventType = attrs.defaultEventType;
+
+        var pageSize = 20;
+        var startTime = 0;
+        var endTime = 0;
+
+        scope.timewindow = {
+            history: {
+                timewindowMs: 24 * 60 * 60 * 1000 // 1 day
+            }
+        }
+
+        scope.topIndex = 0;
+
+        scope.theEvents = {
+            getItemAtIndex: function (index) {
+                if (index > scope.events.data.length) {
+                    scope.theEvents.fetchMoreItems_(index);
+                    return null;
+                }
+                var item = scope.events.data[index];
+                if (item) {
+                    item.indexNumber = index + 1;
+                }
+                return item;
+            },
+
+            getLength: function () {
+                if (scope.events.hasNext) {
+                    return scope.events.data.length + scope.events.nextPageLink.limit;
+                } else {
+                    return scope.events.data.length;
+                }
+            },
+
+            fetchMoreItems_: function () {
+                if (scope.events.hasNext && !scope.events.pending) {
+                    if (scope.entityType && scope.entityId && scope.eventType && scope.tenantId) {
+                        var promise = eventService.getEvents(scope.entityType, scope.entityId,
+                            scope.eventType, scope.tenantId, scope.events.nextPageLink);
+                        if (promise) {
+                            scope.events.pending = true;
+                            promise.then(
+                                function success(events) {
+                                    scope.events.data = scope.events.data.concat(events.data);
+                                    scope.events.nextPageLink = events.nextPageLink;
+                                    scope.events.hasNext = events.hasNext;
+                                    if (scope.events.hasNext) {
+                                        scope.events.nextPageLink.limit = pageSize;
+                                    }
+                                    scope.events.pending = false;
+                                },
+                                function fail() {
+                                    scope.events.hasNext = false;
+                                    scope.events.pending = false;
+                                });
+                        } else {
+                            scope.events.hasNext = false;
+                        }
+                    } else {
+                        scope.events.hasNext = false;
+                    }
+                }
+            }
+        };
+
+        scope.$watch("entityId", function(newVal, prevVal) {
+            if (newVal && !angular.equals(newVal, prevVal)) {
+                scope.resetFilter();
+                scope.reload();
+            }
+        });
+
+        scope.$watch("eventType", function(newVal, prevVal) {
+            if (newVal && !angular.equals(newVal, prevVal)) {
+                scope.reload();
+            }
+        });
+
+        scope.$watch("timewindow", function(newVal, prevVal) {
+            if (newVal && !angular.equals(newVal, prevVal)) {
+                scope.reload();
+            }
+        }, true);
+
+        scope.resetFilter = function() {
+            scope.timewindow = {
+                history: {
+                    timewindowMs: 24 * 60 * 60 * 1000 // 1 day
+                }
+            };
+            scope.eventType = attrs.defaultEventType;
+        }
+
+        scope.updateTimeWindowRange = function() {
+            if (scope.timewindow.history.timewindowMs) {
+                var currentTime = (new Date).getTime();
+                startTime = currentTime - scope.timewindow.history.timewindowMs;
+                endTime = currentTime;
+            } else {
+                startTime = scope.timewindow.history.fixedTimewindow.startTimeMs;
+                endTime = scope.timewindow.history.fixedTimewindow.endTimeMs;
+            }
+        }
+
+        scope.reload = function() {
+            scope.topIndex = 0;
+            scope.selected = [];
+            scope.updateTimeWindowRange();
+            scope.events = {
+                data: [],
+                nextPageLink: {
+                    limit: pageSize,
+                    startTime: startTime,
+                    endTime: endTime
+                },
+                hasNext: true,
+                pending: false
+            };
+            scope.theEvents.getItemAtIndex(pageSize);
+        }
+
+        scope.noData = function() {
+            return scope.events.data.length == 0 && !scope.events.hasNext;
+        }
+
+        scope.hasData = function() {
+            return scope.events.data.length > 0;
+        }
+
+        scope.loading = function() {
+            return $rootScope.loading;
+        }
+
+        scope.hasScroll = function() {
+            var repeatContainer = scope.repeatContainer[0];
+            if (repeatContainer) {
+                var scrollElement = repeatContainer.children[0];
+                if (scrollElement) {
+                    return scrollElement.scrollHeight > scrollElement.clientHeight;
+                }
+            }
+            return false;
+        }
+
+        scope.reload();
+
+        $compile(element.contents())(scope);
+    }
+
+    return {
+        restrict: "E",
+        link: linker,
+        scope: {
+            entityType: '=',
+            entityId: '=',
+            tenantId: '='
+        }
+    };
+}
diff --git a/ui/src/app/event/event-table.tpl.html b/ui/src/app/event/event-table.tpl.html
new file mode 100644
index 0000000..7681f42
--- /dev/null
+++ b/ui/src/app/event/event-table.tpl.html
@@ -0,0 +1,47 @@
+<!--
+
+    Copyright © 2016 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 flex class="md-padding tb-absolute-fill" layout="column">
+    <section layout="row">
+        <md-input-container class="md-block" style="width: 200px;">
+            <label translate>event.event-type</label>
+            <md-select ng-model="eventType" ng-disabled="loading()">
+                <md-option ng-repeat="type in eventTypes" ng-value="type.value">
+                    {{type.name | translate}}
+                </md-option>
+            </md-select>
+        </md-input-container>
+        <tb-timewindow flex ng-model="timewindow" history-only as-button="true"></tb-timewindow>
+    </section>
+    <md-list flex layout="column" class="md-whiteframe-z1 tb-table">
+           <md-list class="tb-row tb-header" layout="row" tb-event-header event-type="{{eventType}}">
+           </md-list>
+        <md-progress-linear style="max-height: 0px;" md-mode="indeterminate"
+                            ng-show="loading()"></md-progress-linear>
+        <md-divider></md-divider>
+        <span translate layout-align="center center"
+              style="margin-top: 25px;"
+              class="tb-prompt" ng-show="noData()">event.no-events-prompt</span>
+        <md-virtual-repeat-container ng-show="hasData()" flex md-top-index="topIndex" tb-scope-element="repeatContainer">
+           <md-list-item md-virtual-repeat="event in theEvents" md-on-demand flex ng-style="hasScroll() ? {'margin-right':'-15px'} : {}">
+               <md-list class="tb-row" flex layout="row" tb-event-row event-type="{{eventType}}" event="{{event}}">
+               </md-list>
+               <md-divider flex></md-divider>
+           </md-list-item>
+       </md-virtual-repeat-container>
+    </md-list>
+</md-content>
diff --git a/ui/src/app/event/index.js b/ui/src/app/event/index.js
new file mode 100644
index 0000000..f443382
--- /dev/null
+++ b/ui/src/app/event/index.js
@@ -0,0 +1,30 @@
+/*
+ * Copyright © 2016 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 thingsboardApiEvent from '../api/event.service';
+
+import EventContentDialogController from './event-content-dialog.controller';
+import EventHeaderDirective from './event-header.directive';
+import EventRowDirective from './event-row.directive';
+import EventTableDirective from './event-table.directive';
+
+export default angular.module('thingsboard.event', [
+    thingsboardApiEvent
+])
+    .controller('EventContentDialogController', EventContentDialogController)
+    .directive('tbEventHeader', EventHeaderDirective)
+    .directive('tbEventRow', EventRowDirective)
+    .directive('tbEventTable', EventTableDirective)
+    .name;
diff --git a/ui/src/app/global-interceptor.service.js b/ui/src/app/global-interceptor.service.js
new file mode 100644
index 0000000..7061152
--- /dev/null
+++ b/ui/src/app/global-interceptor.service.js
@@ -0,0 +1,182 @@
+/*
+ * Copyright © 2016 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 GlobalInterceptor($rootScope, $q, $injector) {
+
+    var toast;
+    var translate;
+    var userService;
+    var types;
+    var http;
+
+    var internalUrlPrefixes = [
+        '/api/auth/token',
+        '/api/plugins/rpc'
+    ];
+
+    var service = {
+        request: request,
+        requestError: requestError,
+        response: response,
+        responseError: responseError
+    }
+
+    return service;
+
+    function getToast() {
+        if (!toast) {
+            toast = $injector.get("toast");
+        }
+        return toast;
+    }
+
+    function getTranslate() {
+        if (!translate) {
+            translate = $injector.get("$translate");
+        }
+        return translate;
+    }
+
+    function getUserService() {
+        if (!userService) {
+            userService = $injector.get("userService");
+        }
+        return userService;
+    }
+
+    function getTypes() {
+        if (!types) {
+            types = $injector.get("types");
+        }
+        return types;
+    }
+
+    function getHttp() {
+        if (!http) {
+            http = $injector.get("$http");
+        }
+        return http;
+    }
+
+    function rejectionErrorCode(rejection) {
+        if (rejection && rejection.data && rejection.data.errorCode) {
+            return rejection.data.errorCode;
+        } else {
+            return undefined;
+        }
+    }
+
+    function isTokenBasedAuthEntryPoint(url) {
+        return  url.startsWith('/api/') &&
+               !url.startsWith(getTypes().entryPoints.login) &&
+               !url.startsWith(getTypes().entryPoints.tokenRefresh) &&
+               !url.startsWith(getTypes().entryPoints.nonTokenBased);
+    }
+
+    function refreshTokenAndRetry(request) {
+        return getUserService().refreshJwtToken().then(function success() {
+            getUserService().updateAuthorizationHeader(request.config.headers);
+            return getHttp()(request.config);
+        }, function fail(message) {
+            $rootScope.$broadcast('unauthenticated');
+            request.status = 401;
+            request.data = {};
+            request.data.message = message || getTranslate().instant('access.unauthorized');
+            return $q.reject(request);
+        });
+    }
+
+    function isInternalUrlPrefix(url) {
+        for (var index in internalUrlPrefixes) {
+            if (url.startsWith(internalUrlPrefixes[index])) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    function request(config) {
+        var rejected = false;
+        if (config.url.startsWith('/api/')) {
+            $rootScope.loading = !isInternalUrlPrefix(config.url);
+            if (isTokenBasedAuthEntryPoint(config.url)) {
+                if (!getUserService().updateAuthorizationHeader(config.headers) &&
+                    !getUserService().refreshTokenPending()) {
+                    $rootScope.loading = false;
+                    rejected = true;
+                    getUserService().clearJwtToken(false);
+                    return $q.reject({ data: {message: getTranslate().instant('access.unauthorized')}, status: 401, config: config});
+                } else if (!getUserService().isJwtTokenValid()) {
+                    return $q.reject({ refreshTokenPending: true, config: config });
+                }
+            }
+        }
+        if (!rejected) {
+            return config;
+        }
+    }
+
+    function requestError(rejection) {
+        if (rejection.config.url.startsWith('/api/')) {
+            $rootScope.loading = false;
+        }
+        return $q.reject(rejection);
+    }
+
+    function response(response) {
+        if (response.config.url.startsWith('/api/')) {
+            $rootScope.loading = false;
+        }
+        return response;
+    }
+
+    function responseError(rejection) {
+        if (rejection.config.url.startsWith('/api/')) {
+            $rootScope.loading = false;
+        }
+        var unhandled = false;
+        if (rejection.refreshTokenPending || rejection.status === 401) {
+            var errorCode = rejectionErrorCode(rejection);
+            if (rejection.refreshTokenPending || (errorCode && errorCode === getTypes().serverErrorCode.jwtTokenExpired)) {
+                return refreshTokenAndRetry(rejection);
+            } else {
+                unhandled = true;
+            }
+        } else if (rejection.status === 403) {
+            $rootScope.$broadcast('forbidden');
+        } else if (rejection.status === 0 || rejection.status === -1) {
+            getToast().showError(getTranslate().instant('error.unable-to-connect'));
+        } else if (!rejection.config.url.startsWith('/api/plugins/rpc')) {
+            if (rejection.status === 404) {
+                getToast().showError(rejection.config.method + ": " + rejection.config.url + "<br/>" +
+                    rejection.status + ": " + rejection.statusText);
+            } else {
+                unhandled = true;
+            }
+        }
+
+        if (unhandled) {
+            if (rejection.data && !rejection.data.message) {
+                getToast().showError(rejection.data);
+            } else if (rejection.data && rejection.data.message) {
+                getToast().showError(rejection.data.message);
+            } else {
+                getToast().showError(getTranslate().instant('error.unhandled-error-code', {errorCode: rejection.status}));
+            }
+        }
+        return $q.reject(rejection);
+    }
+}
diff --git a/ui/src/app/help/help.directive.js b/ui/src/app/help/help.directive.js
new file mode 100644
index 0000000..5d74b1b
--- /dev/null
+++ b/ui/src/app/help/help.directive.js
@@ -0,0 +1,75 @@
+/*
+ * Copyright © 2016 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 './help.scss';
+
+import thingsboardHelpLinks from './help-links.constant';
+
+import $ from 'jquery';
+
+export default angular.module('thingsboard.directives.help', [thingsboardHelpLinks])
+    .directive('tbHelp', Help)
+    .name;
+
+/* eslint-disable angular/angularelement */
+
+/*@ngInject*/
+function Help($compile, $window, helpLinks) {
+
+    var linker = function (scope, element, attrs) {
+
+        scope.gotoHelpPage = function ($event) {
+            if ($event) {
+                $event.stopPropagation();
+            }
+            var helpUrl = helpLinks.linksMap[scope.helpLinkId];
+            if (helpUrl) {
+                $window.open(helpUrl, '_blank');
+            }
+        }
+
+        var html = '<md-tooltip md-direction="top">' +
+            '{{\'help.goto-help-page\' | translate}}' +
+            '</md-tooltip>' +
+            '<md-icon class="material-icons">' +
+                'help' +
+            '</md-icon>';
+
+        var helpButton = angular.element('<md-button class="tb-help-button-style tb-help-button-pos md-icon-button" ' +
+            'ng-click="gotoHelpPage($event)">' +
+            html +
+            '</md-button>');
+
+        if (attrs.helpContainerId) {
+            var helpContainer = $('#' + attrs.helpContainerId, element)[0];
+            helpContainer = angular.element(helpContainer);
+            helpContainer.append(helpButton);
+            $compile(helpContainer.contents())(scope);
+        } else {
+            $compile(helpButton)(scope);
+            element.append(helpButton);
+        }
+    }
+
+    return {
+        restrict: "A",
+        link: linker,
+        scope: {
+            helpLinkId: "=tbHelp"
+        }
+    };
+}
+
+/* eslint-enable angular/angularelement */
diff --git a/ui/src/app/help/help.scss b/ui/src/app/help/help.scss
new file mode 100644
index 0000000..44280cf
--- /dev/null
+++ b/ui/src/app/help/help.scss
@@ -0,0 +1,22 @@
+/**
+ * Copyright © 2016 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 "../../scss/constants";
+
+.md-button.tb-help-button-style, .tb-help-button-style {
+}
+
+.md-button.tb-help-button-pos, .tb-help-button-pos {
+}
diff --git a/ui/src/app/help/help-links.constant.js b/ui/src/app/help/help-links.constant.js
new file mode 100644
index 0000000..c0e2e89
--- /dev/null
+++ b/ui/src/app/help/help-links.constant.js
@@ -0,0 +1,129 @@
+/*
+ * Copyright © 2016 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.
+ */
+
+var pluginClazzHelpLinkMap = {
+    'org.thingsboard.server.extensions.core.plugin.messaging.DeviceMessagingPlugin': 'pluginDeviceMessaging',
+    'org.thingsboard.server.extensions.core.plugin.telemetry.TelemetryStoragePlugin': 'pluginTelemetryStorage',
+    'org.thingsboard.server.extensions.core.plugin.rpc.RpcPlugin': 'pluginRpcPlugin',
+    'org.thingsboard.server.extensions.core.plugin.mail.MailPlugin': 'pluginMailPlugin',
+    'org.thingsboard.server.extensions.rest.plugin.RestApiCallPlugin': 'pluginRestApiCallPlugin',
+    'org.thingsboard.server.extensions.core.plugin.time.TimePlugin': 'pluginTimePlugin',
+    'org.thingsboard.server.extensions.kafka.plugin.KafkaPlugin': 'pluginKafkaPlugin',
+    'org.thingsboard.server.extensions.rabbitmq.plugin.RabbitMqPlugin': 'pluginRabbitMqPlugin'
+
+};
+
+var filterClazzHelpLinkMap = {
+    'org.thingsboard.server.extensions.core.filter.MsgTypeFilter': 'filterMsgType',
+    'org.thingsboard.server.extensions.core.filter.DeviceTelemetryFilter': 'filterDeviceTelemetry',
+    'org.thingsboard.server.extensions.core.filter.MethodNameFilter': 'filterMethodName',
+    'org.thingsboard.server.extensions.core.filter.DeviceAttributesFilter': 'filterDeviceAttributes'
+};
+
+var processorClazzHelpLinkMap = {
+    'org.thingsboard.server.extensions.core.processor.AlarmDeduplicationProcessor': 'processorAlarmDeduplication'
+};
+
+var pluginActionsClazzHelpLinkMap = {
+    'org.thingsboard.server.extensions.core.action.rpc.RpcPluginAction': 'pluginActionRpc',
+    'org.thingsboard.server.extensions.core.action.mail.SendMailAction': 'pluginActionSendMail',
+    'org.thingsboard.server.extensions.core.action.telemetry.TelemetryPluginAction': 'pluginActionTelemetry',
+    'org.thingsboard.server.extensions.kafka.action.KafkaPluginAction': 'pluginActionKafka',
+    'org.thingsboard.server.extensions.rabbitmq.action.RabbitMqPluginAction': 'pluginActionRabbitMq',
+    'org.thingsboard.server.extensions.rest.action.RestApiCallPluginAction': 'pluginActionRestApiCall'
+};
+
+//var helpBaseUrl = "http://thingsboard.io";
+var helpBaseUrl = "http://localhost:4000";
+
+export default angular.module('thingsboard.help', [])
+    .constant('helpLinks',
+        {
+            linksMap: {
+                outgoingMailSettings: helpBaseUrl + "/docs/user-guide/ui/mail-settings",
+                plugins: helpBaseUrl + "/docs/user-guide/rule-engine/#plugins",
+                pluginDeviceMessaging: helpBaseUrl + "/docs/reference/plugins/messaging/",
+                pluginTelemetryStorage: helpBaseUrl + "/docs/reference/plugins/telemetry/",
+                pluginRpcPlugin: helpBaseUrl + "/docs/reference/plugins/rpc/",
+                pluginMailPlugin: helpBaseUrl + "/docs/reference/plugins/mail/",
+                pluginRestApiCallPlugin: helpBaseUrl + "/docs/reference/plugins/rest/",
+                pluginTimePlugin: helpBaseUrl + "/docs/reference/plugins/time/",
+                pluginKafkaPlugin: helpBaseUrl + "/docs/reference/plugins/kafka/",
+                pluginRabbitMqPlugin: helpBaseUrl + "/docs/reference/plugins/rabbitmq/",
+                rules: helpBaseUrl + "/docs/user-guide/rule-engine/#rules",
+                filters: helpBaseUrl + "/docs/user-guide/rule-engine/#filters",
+                filterMsgType: helpBaseUrl + "/docs/reference/filters/message-type-filter",
+                filterDeviceTelemetry: helpBaseUrl + "/docs/reference/filters/device-telemetry-filter",
+                filterMethodName: helpBaseUrl + "/docs/reference/filters/method-name-filter/",
+                filterDeviceAttributes: helpBaseUrl + "/docs/reference/filters/device-attributes-filter",
+                processors: helpBaseUrl + "/docs/user-guide/rule-engine/#processors",
+                processorAlarmDeduplication: "http://thingsboard.io/docs/#q=processorAlarmDeduplication",
+                pluginActions: helpBaseUrl + "/docs/user-guide/rule-engine/#actions",
+                pluginActionRpc: helpBaseUrl + "/docs/reference/actions/rpc-plugin-action",
+                pluginActionSendMail: helpBaseUrl + "/docs/reference/actions/send-mail-action",
+                pluginActionTelemetry: helpBaseUrl + "/docs/reference/actions/telemetry-plugin-action/",
+                pluginActionKafka: helpBaseUrl + "/docs/reference/actions/kafka-plugin-action",
+                pluginActionRabbitMq: helpBaseUrl + "/docs/reference/actions/rabbitmq-plugin-action",
+                pluginActionRestApiCall: helpBaseUrl + "/docs/reference/actions/rest-api-call-plugin-action",
+                tenants: helpBaseUrl + "/docs/user-guide/ui/tenants",
+                customers: helpBaseUrl + "/docs/user-guide/ui/customers",
+                devices: helpBaseUrl + "/docs/user-guide/ui/devices",
+                dashboards: helpBaseUrl + "/docs/user-guide/ui/dashboards",
+                users: helpBaseUrl + "/docs/user-guide/ui/users",
+                widgetsBundles: helpBaseUrl + "/docs/user-guide/ui/widget-library#bundles",
+                widgetsConfig:  helpBaseUrl + "/docs/user-guide/ui/dashboards#widget-configuration",
+                widgetsConfigTimeseries:  helpBaseUrl + "/docs/user-guide/ui/dashboards#timeseries",
+                widgetsConfigLatest: helpBaseUrl +  "/docs/user-guide/ui/dashboards#latest",
+                widgetsConfigRpc: helpBaseUrl +  "/docs/user-guide/ui/dashboards#rpc",
+            },
+            getPluginLink: function(plugin) {
+                var link = 'plugins';
+                if (plugin && plugin.clazz) {
+                    if (pluginClazzHelpLinkMap[plugin.clazz]) {
+                        link = pluginClazzHelpLinkMap[plugin.clazz];
+                    }
+                }
+                return link;
+            },
+            getFilterLink: function(filter) {
+                var link = 'filters';
+                if (filter && filter.clazz) {
+                    if (filterClazzHelpLinkMap[filter.clazz]) {
+                        link = filterClazzHelpLinkMap[filter.clazz];
+                    }
+                }
+                return link;
+            },
+            getProcessorLink: function(processor) {
+                var link = 'processors';
+                if (processor && processor.clazz) {
+                    if (processorClazzHelpLinkMap[processor.clazz]) {
+                        link = processorClazzHelpLinkMap[processor.clazz];
+                    }
+                }
+                return link;
+            },
+            getPluginActionLink: function(pluginAction) {
+                var link = 'pluginActions';
+                if (pluginAction && pluginAction.clazz) {
+                    if (pluginActionsClazzHelpLinkMap[pluginAction.clazz]) {
+                        link = pluginActionsClazzHelpLinkMap[pluginAction.clazz];
+                    }
+                }
+                return link;
+            }
+        }
+    ).name;
diff --git a/ui/src/app/home/home-links.controller.js b/ui/src/app/home/home-links.controller.js
new file mode 100644
index 0000000..c4fae6b
--- /dev/null
+++ b/ui/src/app/home/home-links.controller.js
@@ -0,0 +1,20 @@
+/*
+ * Copyright © 2016 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 HomeLinksController($scope, menu) {
+    var vm = this;
+    vm.model = menu.getHomeSections();
+}
diff --git a/ui/src/app/home/home-links.routes.js b/ui/src/app/home/home-links.routes.js
new file mode 100644
index 0000000..ab805a3
--- /dev/null
+++ b/ui/src/app/home/home-links.routes.js
@@ -0,0 +1,45 @@
+/*
+ * Copyright © 2016 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 homeLinksTemplate from './home-links.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function HomeLinksRoutes($stateProvider) {
+
+    $stateProvider
+        .state('home.links', {
+            url: '/home',
+            module: 'private',
+            auth: ['SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER'],
+            views: {
+                "content@home": {
+                    templateUrl: homeLinksTemplate,
+                    controllerAs: 'vm',
+                    controller: 'HomeLinksController'
+                }
+            },
+            data: {
+                pageTitle: 'home.home'
+            },
+            ncyBreadcrumb: {
+                label: '{"icon": "home", "label": "home.home"}',
+                icon: 'home'
+            }
+        });
+}
diff --git a/ui/src/app/home/home-links.tpl.html b/ui/src/app/home/home-links.tpl.html
new file mode 100644
index 0000000..89cb5b2
--- /dev/null
+++ b/ui/src/app/home/home-links.tpl.html
@@ -0,0 +1,38 @@
+<!--
+
+    Copyright © 2016 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-grid-list md-cols="2" md-cols-gt-xs="3" md-cols-gt-sm="4" md-row-height="280px">
+	<md-grid-tile md-colspan="{{section.places.length}}" ng-repeat="section in vm.model">
+		<md-card style='width: 100%;'>
+			<md-card-title>
+				<md-card-title-text>
+					<span translate class="md-headline">{{ section.name }}</span>
+				</md-card-title-text>
+			</md-card-title>
+			<md-card-content>
+				<md-grid-list md-row-height="170px" md-cols="{{section.places.length}}" md-cols-gt-md="{{section.places.length}}">
+					 <md-grid-tile class="card-tile" ng-repeat="place in section.places">
+			        	<md-button class="tb-card-button md-raised md-primary" layout="column" ui-sref="{{place.state}}">
+			        		<md-icon class="material-icons tb-md-96" aria-label="{{place.icon}}">{{place.icon}}</md-icon>
+			   				<span translate>{{place.name}}</span>
+			        	</md-button>
+					 </md-grid-tile>
+				</md-grid-list>
+			</md-card-content>
+		</md-card>
+	</md-grid-tile>
+</md-grid-list>
\ No newline at end of file
diff --git a/ui/src/app/home/index.js b/ui/src/app/home/index.js
new file mode 100644
index 0000000..13e4046
--- /dev/null
+++ b/ui/src/app/home/index.js
@@ -0,0 +1,26 @@
+/*
+ * Copyright © 2016 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 uiRouter from 'angular-ui-router';
+
+import HomeLinksRoutes from './home-links.routes';
+import HomeLinksController from './home-links.controller';
+
+export default angular.module('thingsboard.homeLinks', [
+    uiRouter
+])
+    .config(HomeLinksRoutes)
+    .controller('HomeLinksController', HomeLinksController)
+    .name;
diff --git a/ui/src/app/jsonform/index.js b/ui/src/app/jsonform/index.js
new file mode 100644
index 0000000..9666961
--- /dev/null
+++ b/ui/src/app/jsonform/index.js
@@ -0,0 +1,32 @@
+/*
+ * Copyright © 2016 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 uiRouter from 'angular-ui-router';
+import ngMaterial from 'angular-material';
+import ngMessages from 'angular-messages';
+import thingsboardJsonForm from "../components/json-form.directive";
+
+import JsonFormRoutes from './jsonform.routes';
+import JsonFormController from './jsonform.controller';
+
+export default angular.module('thingsboard.jsonform', [
+    uiRouter,
+    ngMaterial,
+    ngMessages,
+    thingsboardJsonForm
+])
+    .config(JsonFormRoutes)
+    .controller('JsonFormController', JsonFormController)
+    .name;
diff --git a/ui/src/app/jsonform/jsonform.controller.js b/ui/src/app/jsonform/jsonform.controller.js
new file mode 100644
index 0000000..5861b64
--- /dev/null
+++ b/ui/src/app/jsonform/jsonform.controller.js
@@ -0,0 +1,117 @@
+/*
+ * Copyright © 2016 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 './jsonform.scss';
+
+/*@ngInject*/
+export default function JsonFormController($scope/*, $rootScope, $log*/) {
+
+    var vm = this;
+
+    vm.pretty = pretty;
+    vm.resetModel = resetModel;
+    vm.itParses     = true;
+    vm.itParsesForm = true;
+
+    vm.formJson = "[   \n" +
+        "    {\n" +
+        "        \"key\": \"name\",\n" +
+        "\t\"type\": \"text\"        \n" +
+        "    },\n" +
+        "    {\n" +
+        "\t\"key\": \"name2\",\n" +
+        "\t\"type\": \"color\"\n" +
+        "    },\n" +
+        "    {\n" +
+        "\t\"key\": \"name3\",\n" +
+        "\t\"type\": \"javascript\"\n" +
+        "    },    \n" +
+            "\t\"name4\"\n" +
+        "]";
+    vm.schemaJson = "{\n" +
+        "    \"type\": \"object\",\n" +
+        "    \"title\": \"Comment\",\n" +
+        "    \"properties\": {\n" +
+        "        \"name\": {\n" +
+        "            \"title\": \"Name 1\",\n" +
+        "            \"type\": \"string\"\n" +
+        "         },\n" +
+        "        \"name2\": {\n" +
+        "            \"title\": \"Name 2\",\n" +
+        "            \"type\": \"string\"\n" +
+        "         },\n" +
+        "        \"name3\": {\n" +
+        "            \"title\": \"Name 3\",\n" +
+        "            \"type\": \"string\"\n" +
+        "         },\n" +
+        "        \"name4\": {\n" +
+        "            \"title\": \"Name 4\",\n" +
+        "            \"type\": \"number\"\n" +
+        "         }\n" +
+        "     },\n" +
+        "     \"required\": [\n" +
+        "         \"name1\", \"name2\", \"name3\", \"name4\"\n" +
+        "     ]\n" +
+        "}";
+/*        '{\n'+
+    '    "type": "object",\n'+
+    '    "title": "Comment",\n'+
+    '    "properties": {\n'+
+    '        "name": {\n'+
+    '            "title": "Name",\n'+
+    '            "type": "string"\n'+
+    '         }\n'+
+    '     },\n'+
+    '     "required": [\n'+
+    '         "name"\n'+
+    '     ]\n'+
+    '}';*/
+
+    vm.schema = angular.fromJson(vm.schemaJson);
+    vm.form = angular.fromJson(vm.formJson);
+    vm.model = { name: '#ccc' };
+
+    $scope.$watch('vm.schemaJson',function(val,old){
+        if (val && val !== old) {
+            try {
+                vm.schema = angular.fromJson(vm.schemaJson);
+                vm.itParses = true;
+            } catch (e){
+                vm.itParses = false;
+            }
+        }
+    });
+
+
+    $scope.$watch('vm.formJson',function(val,old){
+        if (val && val !== old) {
+            try {
+                vm.form = angular.fromJson(vm.formJson);
+                vm.itParsesForm = true;
+            } catch (e){
+                vm.itParsesForm = false;
+            }
+        }
+    });
+
+    function pretty (){
+        return angular.isString(vm.model) ? vm.model : angular.toJson(vm.model, true);
+    }
+
+    function resetModel () {
+        $scope.ngform.$setPristine();
+        vm.model = { name: 'New hello world!' };
+    }
+}
diff --git a/ui/src/app/jsonform/jsonform.routes.js b/ui/src/app/jsonform/jsonform.routes.js
new file mode 100644
index 0000000..323b4e0
--- /dev/null
+++ b/ui/src/app/jsonform/jsonform.routes.js
@@ -0,0 +1,44 @@
+/*
+ * Copyright © 2016 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 jsonFormTemplate from './jsonform.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function JsonFormRoutes($stateProvider) {
+    $stateProvider
+        .state('home.jsonform', {
+            url: '/jsonform',
+            module: 'private',
+            auth: ['SYS_ADMIN'],
+            views: {
+                "content@home": {
+                    templateUrl: jsonFormTemplate,
+                    controllerAs: 'vm',
+                    controller: 'JsonFormController'
+                }
+            },
+            data: {
+                key: 'general',
+                pageTitle: 'admin.general-settings'
+            },
+            ncyBreadcrumb: {
+                label: '{"icon": "settings", "label": "admin.system-settings"}'
+            }
+        });
+}
diff --git a/ui/src/app/jsonform/jsonform.scss b/ui/src/app/jsonform/jsonform.scss
new file mode 100644
index 0000000..fd1513e
--- /dev/null
+++ b/ui/src/app/jsonform/jsonform.scss
@@ -0,0 +1,17 @@
+/**
+ * Copyright © 2016 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.
+ */
+.form {  height: 400px;  }
+.schema {  height: 800px;  }
\ No newline at end of file
diff --git a/ui/src/app/jsonform/jsonform.tpl.html b/ui/src/app/jsonform/jsonform.tpl.html
new file mode 100644
index 0000000..7f28c8d
--- /dev/null
+++ b/ui/src/app/jsonform/jsonform.tpl.html
@@ -0,0 +1,54 @@
+<!--
+
+    Copyright © 2016 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 layout="row" layout-margin style="background-color: white;">
+
+    <div layout="column" flex="45">
+        <h3>The Generated Form</h3>
+        <form name="ngform"
+              layout="column"
+              layout-padding>
+            <tb-json-form schema="vm.schema"
+                          form="vm.form"
+                          model="vm.model"
+                          readonly="vm.isFormReadonly === 'true'"
+                          form-control="ngform">
+            </tb-json-form>
+        </form>
+        <md-checkbox ng-true-value="'true'" ng-false-value="'false'"
+                     ng-model="vm.isFormReadonly">Readonly</md-checkbox>
+        <div flex layout="column">
+            <div ng-show="ngform.$valid"><em>Form is valid</em></div>
+            <div ng-show="ngform.$invalid"><em>Form is not valid</em></div>
+            <div ng-show="ngform.$dirty"><em>Form is dirty</em></div>
+            <h3>Model</h3>
+            <md-button ng-click="vm.resetModel()">Reset model</md-button>
+            <pre>{{vm.pretty()}}</pre>
+        </div>
+    </div>
+
+    <div layout="column" flex>
+        <h3>Form</h3>
+        <div ui-ace="{ mode:'json'}"
+             ng-class="{red: !vm.itParsesForm}" ng-model="vm.formJson" class="form-control form"></div>
+        <h3>Schema</h3>
+        <div ui-ace="{ mode:'json'}"
+             ng-class="{red: !vm.itParses}" ng-model="vm.schemaJson" class="form-control schema"></div>
+
+    </div>
+
+</md-content>
\ No newline at end of file
diff --git a/ui/src/app/layout/breadcrumb.tpl.html b/ui/src/app/layout/breadcrumb.tpl.html
new file mode 100644
index 0000000..b5ae863
--- /dev/null
+++ b/ui/src/app/layout/breadcrumb.tpl.html
@@ -0,0 +1,38 @@
+<!--
+
+    Copyright © 2016 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<div class="tb-breadcrumb">
+	<h1 flex hide-gt-sm>{{ steps[steps.length-1].ncyBreadcrumbLabel | breadcrumbLabel }}</h1>
+	<span hide-xs hide-sm ng-repeat="step in steps" ng-switch="$last || !!step.abstract">
+	    <a ng-switch-when="false" href="{{step.ncyBreadcrumbLink}}">
+	    <md-icon ng-show="step.ncyBreadcrumbLabel | breadcrumbIcon" 
+	    class="material-icons"
+	    aria-label="{{step.ncyBreadcrumbLabel | breadcrumbIcon}}">
+	    	{{step.ncyBreadcrumbLabel | breadcrumbIcon}}
+	    </md-icon>
+	    {{step.ncyBreadcrumbLabel | breadcrumbLabel}}
+	    </a>
+	    <span ng-switch-when="true">
+	    	<md-icon ng-show="step.ncyBreadcrumbLabel | breadcrumbIcon" 
+	    	class="material-icons"
+	    	aria-label="{{step.ncyBreadcrumbLabel | breadcrumbIcon}}">
+	    	{{step.ncyBreadcrumbLabel | breadcrumbIcon}}
+	    	</md-icon>
+	    	{{step.ncyBreadcrumbLabel | breadcrumbLabel}}</span>
+	    <span class="divider" ng-hide="$last"> > </span>
+	</span>
+</div>
diff --git a/ui/src/app/layout/breadcrumb-icon.filter.js b/ui/src/app/layout/breadcrumb-icon.filter.js
new file mode 100644
index 0000000..ee33a70
--- /dev/null
+++ b/ui/src/app/layout/breadcrumb-icon.filter.js
@@ -0,0 +1,24 @@
+/*
+ * Copyright © 2016 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default function BreadcrumbIcon() {
+    return function (bLabel) {
+        var labelObj = angular.fromJson(bLabel);
+        if (angular.isDefined(labelObj.icon)) {
+            return labelObj.icon;
+        }
+        return null;
+    };
+}
diff --git a/ui/src/app/layout/breadcrumb-label.filter.js b/ui/src/app/layout/breadcrumb-label.filter.js
new file mode 100644
index 0000000..701847c
--- /dev/null
+++ b/ui/src/app/layout/breadcrumb-label.filter.js
@@ -0,0 +1,45 @@
+/*
+ * Copyright © 2016 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 BreadcrumbLabel($translate) {
+    var labels = {};
+
+    var breadcrumbLabel = function (bLabel) {
+
+        var labelObj;
+        labelObj = angular.fromJson(bLabel);
+        if (labelObj) {
+            if (!labels[labelObj.label]) {
+                labels[labelObj.label] = labelObj.label;
+                var translate = !(labelObj.translate && labelObj.translate === 'false');
+                if (translate) {
+                    $translate([labelObj.label]).then(
+                        function (translations) {
+                            labels[labelObj.label] = translations[labelObj.label];
+                        }
+                    )
+                }
+            }
+            return labels[labelObj.label];
+        } else {
+            return '';
+        }
+    };
+
+    breadcrumbLabel.$stateful = true;
+
+    return breadcrumbLabel;
+}
diff --git a/ui/src/app/layout/home.controller.js b/ui/src/app/layout/home.controller.js
new file mode 100644
index 0000000..462d469
--- /dev/null
+++ b/ui/src/app/layout/home.controller.js
@@ -0,0 +1,152 @@
+/*
+ * Copyright © 2016 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 logoSvg from '../../svg/logo_title_white.svg';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function HomeController(loginService, userService, deviceService, Fullscreen, $scope, $rootScope, $document, $state,
+                                       $log, $mdMedia, $translate) {
+
+    var isShowSidenav = false,
+        dashboardUser = userService.getCurrentUser();
+
+    var vm = this;
+
+    vm.Fullscreen = Fullscreen;
+    vm.logoSvg = logoSvg;
+
+    if (angular.isUndefined($rootScope.searchConfig)) {
+        $rootScope.searchConfig = {
+            searchEnabled: false,
+            showSearch: false,
+            searchText: ""
+        };
+    }
+
+    vm.authorityName = authorityName;
+    vm.displaySearchMode = displaySearchMode;
+    vm.lockSidenav = lockSidenav;
+    vm.logout = logout;
+    vm.openProfile = openProfile;
+    vm.openSidenav = openSidenav;
+    vm.showSidenav = showSidenav;
+    vm.searchTextUpdated = searchTextUpdated;
+    vm.sidenavClicked = sidenavClicked;
+    vm.toggleFullscreen = toggleFullscreen;
+    vm.userDisplayName = userDisplayName;
+
+    $scope.$on('$stateChangeSuccess', function (evt, to, toParams, from) {
+        if (angular.isDefined(to.data.searchEnabled)) {
+            $scope.searchConfig.searchEnabled = to.data.searchEnabled;
+            if ($scope.searchConfig.searchEnabled === false || to.name !== from.name) {
+                $scope.searchConfig.showSearch = false;
+                $scope.searchConfig.searchText = "";
+            }
+        } else {
+            $scope.searchConfig.searchEnabled = false;
+            $scope.searchConfig.showSearch = false;
+            $scope.searchConfig.searchText = "";
+        }
+    });
+
+    function displaySearchMode() {
+        return $scope.searchConfig.searchEnabled &&
+            $scope.searchConfig.showSearch;
+    }
+
+    function toggleFullscreen() {
+        if (Fullscreen.isEnabled()) {
+            Fullscreen.cancel();
+        } else {
+            Fullscreen.all();
+        }
+    }
+
+    function searchTextUpdated() {
+        $scope.$broadcast('searchTextUpdated');
+    }
+
+    function authorityName() {
+        var name = "user.anonymous";
+        if (dashboardUser) {
+            var authority = dashboardUser.authority;
+            if (authority === 'SYS_ADMIN') {
+                name = 'user.sys-admin';
+            } else if (authority === 'TENANT_ADMIN') {
+                name = 'user.tenant-admin';
+            } else if (authority === 'CUSTOMER_USER') {
+                name = 'user.customer';
+            }
+        }
+        return $translate.instant(name);
+    }
+
+    function userDisplayName() {
+        var name = "";
+        if (dashboardUser) {
+            if ((dashboardUser.firstName && dashboardUser.firstName.length > 0) ||
+                (dashboardUser.lastName && dashboardUser.lastName.length > 0)) {
+                if (dashboardUser.firstName) {
+                    name += dashboardUser.firstName;
+                }
+                if (dashboardUser.lastName) {
+                    if (name.length > 0) {
+                        name += " ";
+                    }
+                    name += dashboardUser.lastName;
+                }
+            } else {
+                name = dashboardUser.email;
+            }
+        }
+        return name;
+    }
+
+    function openProfile() {
+        $state.go('home.profile');
+    }
+
+    function logout() {
+        userService.logout();
+    }
+
+    function openSidenav() {
+        isShowSidenav = true;
+    }
+
+    function closeSidenav() {
+        isShowSidenav = false;
+    }
+
+    function lockSidenav() {
+        return $mdMedia('gt-sm');
+    }
+
+    function sidenavClicked() {
+        if (!$mdMedia('gt-sm')) {
+            closeSidenav();
+        }
+    }
+
+    function showSidenav() {
+        return isShowSidenav || $mdMedia('gt-sm');
+    }
+
+}
\ No newline at end of file
diff --git a/ui/src/app/layout/home.routes.js b/ui/src/app/layout/home.routes.js
new file mode 100644
index 0000000..4e6eb2c
--- /dev/null
+++ b/ui/src/app/layout/home.routes.js
@@ -0,0 +1,50 @@
+/*
+ * Copyright © 2016 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 breadcrumbTemplate from './breadcrumb.tpl.html';
+import homeTemplate from './home.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function HomeRoutes($stateProvider, $breadcrumbProvider) {
+
+    $breadcrumbProvider.setOptions({
+        prefixStateName: 'home',
+        templateUrl: breadcrumbTemplate
+    });
+
+    $stateProvider
+        .state('home', {
+            url: '',
+            module: 'private',
+            auth: ['SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER'],
+            views: {
+                "@": {
+                    controller: 'HomeController',
+                    controllerAs: 'vm',
+                    templateUrl: homeTemplate
+                }
+            },
+            data: {
+                pageTitle: 'home.home'
+            },
+            ncyBreadcrumb: {
+                skip: true
+            }
+        });
+}
diff --git a/ui/src/app/layout/home.scss b/ui/src/app/layout/home.scss
new file mode 100644
index 0000000..f2e4100
--- /dev/null
+++ b/ui/src/app/layout/home.scss
@@ -0,0 +1,93 @@
+/**
+ * Copyright © 2016 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 "~compass-sass-mixins/lib/animate";
+
+.tb-invisible {
+  display: none !important;
+}
+
+.tb-primary-toolbar {
+  h1 {
+    font-size: 24px !important;
+    font-weight: 400 !important;
+  }
+}
+
+.tb-breadcrumb {
+  font-size: 18px !important;
+  font-weight: 400 !important;
+  a {
+    border: none;
+    opacity: 0.75;
+    @include transition(opacity 0.35s);
+  }
+  a:hover, a:focus {
+    opacity: 1;
+    text-decoration: none !important;
+    border: none;
+  }
+  .divider {
+    padding: 0px 30px;
+  }
+}
+
+md-sidenav.tb-site-sidenav {
+  width: 250px;
+}
+
+md-icon.tb-mini-avatar {
+  margin: auto 8px;
+  font-size: 36px;
+  height: 36px;
+  width: 36px;
+}
+
+md-icon.tb-logo-title {
+  height: 36px;
+  width: 200px;
+}
+
+div.tb-user-info {
+  line-height: 1.5;
+  span {
+    text-transform: none;
+    text-align: left;
+  }
+  span.tb-user-display-name {
+    font-size: 0.800rem;
+    font-weight: 300;
+    letter-spacing: 0.008em;
+  }
+  span.tb-user-authority {
+    font-size: 0.800rem;
+    font-weight: 300;
+    letter-spacing: 0.005em;
+    opacity: 0.8;
+  }
+}
+
+.tb-nav-header {
+  flex-shrink: 0;
+  z-index: 2;
+  white-space: nowrap;
+}
+
+.tb-nav-header-toolbar {
+  border-bottom: 1px solid rgba(0, 0, 0, 0.12);
+  flex-shrink: 0;
+  z-index: 2;
+  white-space: nowrap;
+}
diff --git a/ui/src/app/layout/home.tpl.html b/ui/src/app/layout/home.tpl.html
new file mode 100644
index 0000000..e5a4c15
--- /dev/null
+++ b/ui/src/app/layout/home.tpl.html
@@ -0,0 +1,96 @@
+<!--
+
+    Copyright © 2016 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-sidenav class="tb-site-sidenav md-sidenav-left md-whiteframe-z2"
+      hide-print=""      
+      md-component-id="left"
+      aria-label="Toggle Nav"
+      ng-click="vm.sidenavClicked()"
+      md-is-open="vm.showSidenav()"
+      md-is-locked-open="vm.lockSidenav()"
+      layout="column">
+      <header class="tb-nav-header">
+          <md-toolbar md-scroll-shrink class="tb-nav-header-toolbar">
+          	 <div flex layout="row" layout-align="start center" class="md-toolbar-tools inset">
+				<md-icon md-svg-src="{{vm.logoSvg}}" aria-label="logo" class="tb-logo-title"></md-icon>
+          	 </div>
+          </md-toolbar>
+      </header>
+      <md-content flex layout="column" role="navigation">
+      	 <md-toolbar flex>
+         	<tb-side-menu></tb-side-menu>
+         </md-toolbar> 
+      </md-content>      	
+  </md-sidenav>
+
+  <div flex layout="column" tabIndex="-1" role="main">
+    <md-toolbar class="md-whiteframe-z1 tb-primary-toolbar" ng-class="{'md-hue-1': vm.displaySearchMode()}">
+    	<div flex class="md-toolbar-tools">
+		      <md-button id="main" hide-gt-sm
+		      		class="md-icon-button" ng-click="vm.openSidenav()" aria-label="{{ 'home.menu' | translate }}" ng-class="{'tb-invisible': vm.displaySearchMode()}">
+		      		<md-icon aria-label="{{ 'home.menu' | translate }}" class="material-icons">menu</md-icon>
+		      </md-button>
+	          <md-button class="md-icon-button" aria-label="{{ 'action.back' | translate }}" ng-click="searchConfig.showSearch = !searchConfig.showSearch" ng-class="{'tb-invisible': !vm.displaySearchMode()}" >
+		      	  <md-icon aria-label="{{ 'action.back' | translate }}" class="material-icons">arrow_back</md-icon>
+	          </md-button>		    
+			  <div flex ng-show="!vm.displaySearchMode()" tb-no-animate flex class="md-toolbar-tools">
+				  <span ng-cloak ncy-breadcrumb></span>
+			  </div>
+			  <md-input-container ng-show="vm.displaySearchMode()" md-theme="tb-search-input" flex>
+	              <label>&nbsp;</label>
+	              <input ng-model="searchConfig.searchText" ng-change="vm.searchTextUpdated()" placeholder="{{ 'common.enter-search' | translate }}"/>
+	          </md-input-container>		      
+		      <md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}" ng-show="searchConfig.searchEnabled" ng-click="searchConfig.showSearch = !searchConfig.showSearch">
+		      	  <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
+          	  </md-button>
+		      <md-button ng-show="!vm.displaySearchMode()" hide-xs hide-sm class="md-icon-button" ng-click="vm.toggleFullscreen()" aria-label="{{ 'fullscreen.toggle' | translate }}">
+	    			<ng-md-icon icon="{{vm.Fullscreen.isEnabled() ? 'fullscreen_exit' : 'fullscreen'}}" options='{"easing": "circ-in-out", "duration": 375, "rotation": "none"}'></ng-md-icon>
+		      </md-button>          	  
+	      	  <div hide-xs hide-sm ng-show="!vm.displaySearchMode()" class="tb-user-info" layout="row">
+  		        <md-icon aria-label="{{ 'home.avatar' | translate }}" class="material-icons tb-mini-avatar">account_circle</md-icon>
+  		        <div layout="column">
+	             	<span class="tb-user-display-name">{{vm.userDisplayName()}}</span>
+	             	<span class="tb-user-authority">{{vm.authorityName()}}</span>
+             	</div>
+              </div>
+ 		      <md-menu md-position-mode="target-right target">
+			      <md-button class="md-icon-button"  aria-label="{{ 'home.open-user-menu' | translate }}" ng-click="$mdOpenMenu($event)">
+			      	<md-icon md-menu-origin aria-label="{{ 'home.open-user-menu' | translate }}" class="material-icons">more_vert</md-icon>
+			      </md-button>
+			      <md-menu-content width="4">
+			        <md-menu-item>
+			          <md-button ng-click="vm.openProfile()">
+			          	<md-icon md-menu-align-target aria-label="{{ 'home.profile' | translate }}" class="material-icons">account_circle</md-icon>
+						<span translate>home.profile</span>
+			          </md-button>
+			        </md-menu-item>
+			        <md-menu-item>
+			          <md-button ng-click="vm.logout()">
+			          	<md-icon md-menu-align-target aria-label="{{ 'home.logout' | translate }}" class="material-icons">exit_to_app</md-icon>
+						<span translate>home.logout</span>
+			          </md-button>
+			        </md-menu-item>
+			      </md-menu-content>
+		      </md-menu>		      
+      	</div>
+    </md-toolbar>
+   	<md-progress-linear class="md-warn" style="z-index: 10; max-height: 0px; width: 100%;" md-mode="indeterminate" ng-show="loading"></md-progress-linear>
+    
+    <div flex layout="column" id="toast-parent" style="position: relative;">
+    	<md-content ng-cloak flex layout="column" class="page-content" ui-view name="content"></md-content>
+    </div>
+  </div>
diff --git a/ui/src/app/layout/index.js b/ui/src/app/layout/index.js
new file mode 100644
index 0000000..28c7258
--- /dev/null
+++ b/ui/src/app/layout/index.js
@@ -0,0 +1,78 @@
+/*
+ * Copyright © 2016 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 './home.scss';
+
+import uiRouter from 'angular-ui-router';
+import ngSanitize from 'angular-sanitize';
+import FBAngular from 'angular-fullscreen';
+import 'angular-breadcrumb';
+
+import thingsboardMenu from '../services/menu.service';
+import thingsboardApiDevice from '../api/device.service';
+import thingsboardApiLogin from '../api/login.service';
+import thingsboardApiUser from '../api/user.service';
+
+import thingsboardNoAnimate from '../components/no-animate.directive';
+import thingsboardSideMenu from '../components/side-menu.directive';
+
+import thingsboardTenant from '../tenant';
+import thingsboardCustomer from '../customer';
+import thingsboardUser from '../user';
+import thingsboardHomeLinks from '../home';
+import thingsboardAdmin from '../admin';
+import thingsboardProfile from '../profile';
+import thingsboardDevice from '../device';
+import thingsboardWidgetLibrary from '../widget';
+import thingsboardDashboard from '../dashboard';
+import thingsboardPlugin from '../plugin';
+import thingsboardRule from '../rule';
+
+import thingsboardJsonForm from '../jsonform';
+
+import HomeRoutes from './home.routes';
+import HomeController from './home.controller';
+import BreadcrumbLabel from './breadcrumb-label.filter';
+import BreadcrumbIcon from './breadcrumb-icon.filter';
+
+export default angular.module('thingsboard.home', [
+    uiRouter,
+    ngSanitize,
+    FBAngular.name,
+    'ncy-angular-breadcrumb',
+    thingsboardMenu,
+    thingsboardHomeLinks,
+    thingsboardTenant,
+    thingsboardCustomer,
+    thingsboardUser,
+    thingsboardAdmin,
+    thingsboardProfile,
+    thingsboardDevice,
+    thingsboardWidgetLibrary,
+    thingsboardDashboard,
+    thingsboardPlugin,
+    thingsboardRule,
+    thingsboardJsonForm,
+    thingsboardApiDevice,
+    thingsboardApiLogin,
+    thingsboardApiUser,
+    thingsboardNoAnimate,
+    thingsboardSideMenu
+])
+    .config(HomeRoutes)
+    .controller('HomeController', HomeController)
+    .filter('breadcrumbLabel', BreadcrumbLabel)
+    .filter('breadcrumbIcon', BreadcrumbIcon)
+    .name;
diff --git a/ui/src/app/login/create-password.controller.js b/ui/src/app/login/create-password.controller.js
new file mode 100644
index 0000000..fceced2
--- /dev/null
+++ b/ui/src/app/login/create-password.controller.js
@@ -0,0 +1,37 @@
+/*
+ * Copyright © 2016 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 CreatePasswordController($stateParams, $translate, toast, loginService, userService) {
+    var vm = this;
+
+    vm.password = '';
+    vm.password2 = '';
+
+    vm.createPassword = createPassword;
+
+    function createPassword() {
+        if (vm.password !== vm.password2) {
+            toast.showError($translate.instant('login.passwords-mismatch-error'));
+        } else {
+            loginService.activate($stateParams.activateToken, vm.password).then(function success(response) {
+                var token = response.data.token;
+                var refreshToken = response.data.refreshToken;
+                userService.setUserFromJwtToken(token, refreshToken, true);
+            }, function fail() {
+            });
+        }
+    }
+}
diff --git a/ui/src/app/login/create-password.tpl.html b/ui/src/app/login/create-password.tpl.html
new file mode 100644
index 0000000..ca951f3
--- /dev/null
+++ b/ui/src/app/login/create-password.tpl.html
@@ -0,0 +1,56 @@
+<!--
+
+    Copyright © 2016 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 layout="row" layout-align="center center" style="width: 100%;">
+    <md-card flex="initial" class="tb-login-card" md-theme="tb-dark">
+        <md-card-title>
+            <md-card-title-text>
+                <span translate class="md-headline">login.create-password</span>
+            </md-card-title-text>
+        </md-card-title>
+        <md-progress-linear class="md-warn" style="z-index: 1; max-height: 5px; width: inherit; position: absolute"
+                            md-mode="indeterminate" ng-show="loading"></md-progress-linear>
+        <md-card-content>
+            <form class="create-password-form" ng-submit="vm.createPassword()">
+                <div layout="column" layout-padding="" id="toast-parent">
+                    <span style="height: 50px;"></span>
+                    <md-input-container class="md-block">
+                        <label translate>common.password</label>
+                        <md-icon aria-label="{{ 'common.password' | translate }}" class="material-icons">
+                            lock
+                        </md-icon>
+                        <input id="password-input" type="password" ng-model="vm.password"/>
+                    </md-input-container>
+                    <md-input-container class="md-block">
+                        <label translate>login.password-again</label>
+                        <md-icon aria-label="{{ 'login.password-again' | translate }}" class="material-icons">
+                            lock
+                        </md-icon>
+                        <input id="password-input2" type="password" ng-model="vm.password2"/>
+                    </md-input-container>
+                    <div layout="column" layout-gt-sm="row" layout-padding=""
+                         layout-align="start center"
+                         layout-align-gt-sm="center start">
+                        <md-button class="md-raised md-accent" type="submit">{{ 'login.create-password' | translate }}
+                        </md-button>
+                        <md-button class="md-raised" ui-sref="login">{{ 'action.cancel' | translate }}</md-button>
+                    </div>
+                </div>
+            </form>
+        </md-card-content>
+    </md-card>
+</md-content>
diff --git a/ui/src/app/login/index.js b/ui/src/app/login/index.js
new file mode 100644
index 0000000..9620beb
--- /dev/null
+++ b/ui/src/app/login/index.js
@@ -0,0 +1,40 @@
+/*
+ * Copyright © 2016 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 './login.scss';
+
+import uiRouter from 'angular-ui-router';
+import thingsboardApiLogin from '../api/login.service';
+import thingsboardApiUser from '../api/user.service';
+import thingsboardToast from '../services/toast';
+
+import LoginRoutes from './login.routes';
+import LoginController from './login.controller';
+import ResetPasswordRequestController from './reset-password-request.controller';
+import ResetPasswordController from './reset-password.controller';
+import CreatePasswordController from './create-password.controller';
+
+export default angular.module('thingsboard.login', [
+    uiRouter,
+    thingsboardApiLogin,
+    thingsboardApiUser,
+    thingsboardToast
+])
+    .config(LoginRoutes)
+    .controller('LoginController', LoginController)
+    .controller('ResetPasswordRequestController', ResetPasswordRequestController)
+    .controller('ResetPasswordController', ResetPasswordController)
+    .controller('CreatePasswordController', CreatePasswordController)
+    .name;
diff --git a/ui/src/app/login/login.controller.js b/ui/src/app/login/login.controller.js
new file mode 100644
index 0000000..dcb2f44
--- /dev/null
+++ b/ui/src/app/login/login.controller.js
@@ -0,0 +1,46 @@
+/*
+ * Copyright © 2016 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 LoginController(toast, loginService, userService/*, $rootScope, $log, $translate*/) {
+    var vm = this;
+
+    vm.user = {
+        name: '',
+        password: ''
+    };
+
+    vm.login = login;
+
+    function doLogin() {
+        loginService.login(vm.user).then(function success(response) {
+            var token = response.data.token;
+            var refreshToken = response.data.refreshToken;
+            userService.setUserFromJwtToken(token, refreshToken, true);
+        }, function fail(/*response*/) {
+            /*if (response && response.data && response.data.message) {
+                toast.showError(response.data.message);
+            } else if (response && response.statusText) {
+                toast.showError(response.statusText);
+            } else {
+                toast.showError($translate.instant('error.unknown-error'));
+            }*/
+        });
+    }
+
+    function login() {
+        doLogin();
+    }
+}
diff --git a/ui/src/app/login/login.routes.js b/ui/src/app/login/login.routes.js
new file mode 100644
index 0000000..ba7291b
--- /dev/null
+++ b/ui/src/app/login/login.routes.js
@@ -0,0 +1,80 @@
+/*
+ * Copyright © 2016 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 loginTemplate from './login.tpl.html';
+import resetPasswordTemplate from './reset-password.tpl.html';
+import resetPasswordRequestTemplate from './reset-password-request.tpl.html';
+import createPasswordTemplate from './create-password.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function LoginRoutes($stateProvider) {
+    $stateProvider.state('login', {
+        url: '/login',
+        module: 'public',
+        views: {
+            "@": {
+                controller: 'LoginController',
+                controllerAs: 'vm',
+                templateUrl: loginTemplate
+            }
+        },
+        data: {
+            pageTitle: 'login.login'
+        }
+    }).state('login.resetPasswordRequest', {
+        url: '/resetPasswordRequest',
+        module: 'public',
+        views: {
+            "@": {
+                controller: 'ResetPasswordRequestController',
+                controllerAs: 'vm',
+                templateUrl: resetPasswordRequestTemplate
+            }
+        },
+        data: {
+            pageTitle: 'login.request-password-reset'
+        }
+    }).state('login.resetPassword', {
+        url: '/resetPassword?resetToken',
+        module: 'public',
+        views: {
+            "@": {
+                controller: 'ResetPasswordController',
+                controllerAs: 'vm',
+                templateUrl: resetPasswordTemplate
+            }
+        },
+        data: {
+            pageTitle: 'login.reset-password'
+        }
+    }).state('login.createPassword', {
+        url: '/createPassword?activateToken',
+        module: 'public',
+        views: {
+            "@": {
+                controller: 'CreatePasswordController',
+                controllerAs: 'vm',
+                templateUrl: createPasswordTemplate
+            }
+        },
+        data: {
+            pageTitle: 'login.create-password'
+        }
+    });
+}
diff --git a/ui/src/app/login/login.scss b/ui/src/app/login/login.scss
new file mode 100644
index 0000000..6a64eed
--- /dev/null
+++ b/ui/src/app/login/login.scss
@@ -0,0 +1,23 @@
+/**
+ * Copyright © 2016 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 '../../scss/constants';
+
+md-card.tb-login-card {
+  width: 330px !important;
+  @media (min-width: $layout-breakpoint-sm) {
+    width: 450px !important;
+  }
+}
diff --git a/ui/src/app/login/login.tpl.html b/ui/src/app/login/login.tpl.html
new file mode 100644
index 0000000..80036bb
--- /dev/null
+++ b/ui/src/app/login/login.tpl.html
@@ -0,0 +1,56 @@
+<!--
+
+    Copyright © 2016 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 layout="row" layout-align="center center" style="width: 100%;">
+    <md-card flex="initial" class="tb-login-card" md-theme="tb-dark">
+        <md-card-title>
+            <md-card-title-text>
+                <span translate class="md-headline">login.sign-in</span>
+            </md-card-title-text>
+        </md-card-title>
+        <md-progress-linear class="md-warn" style="z-index: 1; max-height: 5px; width: inherit; position: absolute"
+                            md-mode="indeterminate" ng-show="loading"></md-progress-linear>
+        <md-card-content>
+            <form class="login-form" ng-submit="vm.login()">
+                <div layout="column" layout-padding="" id="toast-parent">
+                    <span style="height: 50px;"></span>
+                    <md-input-container class="md-block">
+                        <label translate>login.username</label>
+                        <md-icon aria-label="{{ 'login.username' | translate }}" class="material-icons">
+                            email
+                        </md-icon>
+                        <input id="username-input" type="email" autofocus ng-model="vm.user.name"/>
+                    </md-input-container>
+                    <md-input-container class="md-block">
+                        <label translate>common.password</label>
+                        <md-icon aria-label="{{ 'common.password' | translate }}" class="material-icons">
+                            lock
+                        </md-icon>
+                        <input id="password-input" type="password" ng-model="vm.user.password"/>
+                    </md-input-container>
+                    <div layout-gt-sm="column" layout-align="space-between stretch">
+                        <div layout-gt-sm="column" layout-align="space-between end">
+                            <md-button ui-sref="login.resetPasswordRequest">{{ 'login.forgot-password' | translate }}
+                            </md-button>
+                        </div>
+                    </div>
+                    <md-button class="md-raised" type="submit">{{ 'login.login' | translate }}</md-button>
+                </div>
+            </form>
+        </md-card-content>
+    </md-card>
+</md-content>
diff --git a/ui/src/app/login/reset-password.controller.js b/ui/src/app/login/reset-password.controller.js
new file mode 100644
index 0000000..4855d6b
--- /dev/null
+++ b/ui/src/app/login/reset-password.controller.js
@@ -0,0 +1,37 @@
+/*
+ * Copyright © 2016 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 ResetPasswordController($stateParams, $translate, toast, loginService, userService) {
+    var vm = this;
+
+    vm.newPassword = '';
+    vm.newPassword2 = '';
+
+    vm.resetPassword = resetPassword;
+
+    function resetPassword() {
+        if (vm.newPassword !== vm.newPassword2) {
+            toast.showError($translate.instant('login.passwords-mismatch-error'));
+        } else {
+            loginService.resetPassword($stateParams.resetToken, vm.newPassword).then(function success(response) {
+                var token = response.data.token;
+                var refreshToken = response.data.refreshToken;
+                userService.setUserFromJwtToken(token, refreshToken, true);
+            }, function fail() {
+            });
+        }
+    }
+}
diff --git a/ui/src/app/login/reset-password.tpl.html b/ui/src/app/login/reset-password.tpl.html
new file mode 100644
index 0000000..4148c10
--- /dev/null
+++ b/ui/src/app/login/reset-password.tpl.html
@@ -0,0 +1,56 @@
+<!--
+
+    Copyright © 2016 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 layout="row" layout-align="center center" style="width: 100%;">
+    <md-card flex="initial" class="tb-login-card" md-theme="tb-dark">
+        <md-card-title>
+            <md-card-title-text>
+                <span translate class="md-headline">login.password-reset</span>
+            </md-card-title-text>
+        </md-card-title>
+        <md-progress-linear class="md-warn" style="z-index: 1; max-height: 5px; width: inherit; position: absolute"
+                            md-mode="indeterminate" ng-show="loading"></md-progress-linear>
+        <md-card-content>
+            <form class="password-reset-form" ng-submit="vm.resetPassword()">
+                <div layout="column" layout-padding="" id="toast-parent">
+                    <span style="height: 50px;"></span>
+                    <md-input-container class="md-block">
+                        <label translate>login.new-password</label>
+                        <md-icon aria-label="{{ 'login.new-password' | translate }}" class="material-icons">
+                            lock
+                        </md-icon>
+                        <input id="password-input" type="password" ng-model="vm.newPassword"/>
+                    </md-input-container>
+                    <md-input-container class="md-block">
+                        <label translate>login.new-password-again</label>
+                        <md-icon aria-label="{{ 'login.new-password-again' | translate }}" class="material-icons">
+                            lock
+                        </md-icon>
+                        <input id="password-input2" type="password" ng-model="vm.newPassword2"/>
+                    </md-input-container>
+                    <div layout="column" layout-gt-sm="row" layout-padding=""
+                         layout-align="start center"
+                         layout-align-gt-sm="center start">
+                        <md-button class="md-raised md-accent" type="submit">{{ 'login.reset-password' | translate }}
+                        </md-button>
+                        <md-button class="md-raised" ui-sref="login">{{ 'action.cancel' | translate }}</md-button>
+                    </div>
+                </div>
+            </form>
+        </md-card-content>
+    </md-card>
+</md-content>
diff --git a/ui/src/app/login/reset-password-request.controller.js b/ui/src/app/login/reset-password-request.controller.js
new file mode 100644
index 0000000..c4cb6c6
--- /dev/null
+++ b/ui/src/app/login/reset-password-request.controller.js
@@ -0,0 +1,30 @@
+/*
+ * Copyright © 2016 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 ResetPasswordRequestController($translate, toast, loginService) {
+    var vm = this;
+
+    vm.email = '';
+
+    vm.sendResetPasswordLink = sendResetPasswordLink;
+
+    function sendResetPasswordLink() {
+        loginService.sendResetPasswordLink(vm.email).then(function success() {
+            toast.showSuccess($translate.instant('login.password-link-sent-message'));
+        }, function fail() {
+        });
+    }
+}
diff --git a/ui/src/app/login/reset-password-request.tpl.html b/ui/src/app/login/reset-password-request.tpl.html
new file mode 100644
index 0000000..c541c59
--- /dev/null
+++ b/ui/src/app/login/reset-password-request.tpl.html
@@ -0,0 +1,50 @@
+<!--
+
+    Copyright © 2016 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 layout="row" layout-align="center center" style="width: 100%;">
+    <md-card flex="initial" class="tb-login-card" md-theme="tb-dark">
+        <md-card-title>
+            <md-card-title-text>
+                <span translate class="md-headline">login.request-password-reset</span>
+            </md-card-title-text>
+        </md-card-title>
+        <md-progress-linear class="md-warn" style="z-index: 1; max-height: 5px; width: inherit; position: absolute"
+                            md-mode="indeterminate" ng-show="loading"></md-progress-linear>
+        <md-card-content>
+            <form class="request-password-reset-form" ng-submit="vm.sendResetPasswordLink()">
+                <div layout="column" layout-padding="" id="toast-parent">
+                    <span style="height: 50px;"></span>
+                    <md-input-container class="md-block">
+                        <label translate>login.email</label>
+                        <md-icon aria-label="{{ 'login.email' | translate }}" class="material-icons">
+                            email
+                        </md-icon>
+                        <input id="email-input" type="email" autofocus ng-model="vm.email"/>
+                    </md-input-container>
+                    <div layout="column" layout-gt-sm="row" layout-padding=""
+                         layout-align="start center"
+                         layout-align-gt-sm="center start">
+                        <md-button class="md-raised md-accent" type="submit">{{ 'login.request-password-reset' |
+                            translate }}
+                        </md-button>
+                        <md-button class="md-raised" ui-sref="login">{{ 'action.cancel' | translate }}</md-button>
+                    </div>
+                </div>
+            </form>
+        </md-card-content>
+    </md-card>
+</md-content>
diff --git a/ui/src/app/plugin/add-plugin.tpl.html b/ui/src/app/plugin/add-plugin.tpl.html
new file mode 100644
index 0000000..d7525ab
--- /dev/null
+++ b/ui/src/app/plugin/add-plugin.tpl.html
@@ -0,0 +1,48 @@
+<!--
+
+    Copyright © 2016 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="{{ 'plugin.add' | translate }}" tb-help="vm.helpLinks.getPluginLink(vm.item)" help-container-id="help-container">
+    <form name="theForm" ng-submit="vm.add()">
+        <md-toolbar>
+            <div class="md-toolbar-tools">
+                <h2 translate>plugin.add</h2>
+                <span flex></span>
+                <div id="help-container"></div>
+                <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-show="loading"></md-progress-linear>
+        <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
+        <md-dialog-content>
+            <div class="md-dialog-content">
+                <tb-plugin plugin="vm.item" is-edit="true" the-form="theForm"></tb-plugin>
+            </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">
+                {{ 'action.add' | 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>
diff --git a/ui/src/app/plugin/index.js b/ui/src/app/plugin/index.js
new file mode 100644
index 0000000..c1262a9
--- /dev/null
+++ b/ui/src/app/plugin/index.js
@@ -0,0 +1,38 @@
+/*
+ * Copyright © 2016 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 uiRouter from 'angular-ui-router';
+import thingsboardGrid from '../components/grid.directive';
+import thingsboardJsonForm from '../components/json-form.directive';
+import thingsboardEvent from '../event';
+import thingsboardApiPlugin from '../api/plugin.service';
+import thingsboardApiComponentDescriptor from '../api/component-descriptor.service';
+
+import PluginRoutes from './plugin.routes';
+import PluginController from './plugin.controller';
+import PluginDirective from './plugin.directive';
+
+export default angular.module('thingsboard.plugin', [
+    uiRouter,
+    thingsboardGrid,
+    thingsboardJsonForm,
+    thingsboardEvent,
+    thingsboardApiPlugin,
+    thingsboardApiComponentDescriptor
+])
+    .config(PluginRoutes)
+    .controller('PluginController', PluginController)
+    .directive('tbPlugin', PluginDirective)
+    .name;
diff --git a/ui/src/app/plugin/plugin.controller.js b/ui/src/app/plugin/plugin.controller.js
new file mode 100644
index 0000000..9c0aac9
--- /dev/null
+++ b/ui/src/app/plugin/plugin.controller.js
@@ -0,0 +1,177 @@
+/*
+ * Copyright © 2016 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 addPluginTemplate from './add-plugin.tpl.html';
+import pluginCard from './plugin-card.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function PluginController(pluginService, userService, $state, $stateParams, $filter, $translate, types, helpLinks) {
+
+    var pluginActionsList = [
+        {
+            onAction: function ($event, item) {
+                activatePlugin($event, item);
+            },
+            name: function() { return $translate.instant('action.activate') },
+            details: function() { return $translate.instant('plugin.activate') },
+            icon: "play_arrow",
+            isEnabled: function(plugin) {
+                return isPluginEditable(plugin) && plugin && plugin.state === 'SUSPENDED';
+            }
+        },
+        {
+            onAction: function ($event, item) {
+                suspendPlugin($event, item);
+            },
+            name: function() { return $translate.instant('action.suspend') },
+            details: function() { return $translate.instant('plugin.suspend') },
+            icon: "pause",
+            isEnabled: function(plugin) {
+                return isPluginEditable(plugin) && plugin && plugin.state === 'ACTIVE';
+            }
+        },
+        {
+            onAction: function ($event, item) {
+                vm.grid.deleteItem($event, item);
+            },
+            name: function() { return $translate.instant('action.delete') },
+            details: function() { return $translate.instant('plugin.delete') },
+            icon: "delete",
+            isEnabled: isPluginEditable
+        }
+    ];
+
+    var vm = this;
+
+    vm.types = types;
+
+    vm.helpLinkIdForPlugin = helpLinkIdForPlugin;
+
+    vm.pluginGridConfig = {
+
+        refreshParamsFunc: null,
+
+        deleteItemTitleFunc: deletePluginTitle,
+        deleteItemContentFunc: deletePluginText,
+        deleteItemsTitleFunc: deletePluginsTitle,
+        deleteItemsActionTitleFunc: deletePluginsActionTitle,
+        deleteItemsContentFunc: deletePluginsText,
+
+        fetchItemsFunc: fetchPlugins,
+        saveItemFunc: savePlugin,
+        deleteItemFunc: deletePlugin,
+
+        getItemTitleFunc: getPluginTitle,
+        itemCardTemplateUrl: pluginCard,
+        parentCtl: vm,
+
+        actionsList: pluginActionsList,
+
+        onGridInited: gridInited,
+
+        addItemTemplateUrl: addPluginTemplate,
+
+        addItemText: function() { return $translate.instant('plugin.add-plugin-text') },
+        noItemsText: function() { return $translate.instant('plugin.no-plugins-text') },
+        itemDetailsText: function() { return $translate.instant('plugin.plugin-details') },
+        isSelectionEnabled: isPluginEditable,
+        isDetailsReadOnly: function(plugin) {
+            return !isPluginEditable(plugin);
+        }
+
+    };
+
+    if (angular.isDefined($stateParams.items) && $stateParams.items !== null) {
+        vm.pluginGridConfig.items = $stateParams.items;
+    }
+
+    if (angular.isDefined($stateParams.topIndex) && $stateParams.topIndex > 0) {
+        vm.pluginGridConfig.topIndex = $stateParams.topIndex;
+    }
+
+    vm.activatePlugin = activatePlugin;
+    vm.suspendPlugin = suspendPlugin;
+
+    function helpLinkIdForPlugin() {
+        return helpLinks.getPluginLink(vm.grid.operatingItem());
+    }
+
+    function deletePluginTitle(plugin) {
+        return $translate.instant('plugin.delete-plugin-title', {pluginName: plugin.name});
+    }
+
+    function deletePluginText() {
+        return $translate.instant('plugin.delete-plugin-text');
+    }
+
+    function deletePluginsTitle(selectedCount) {
+        return $translate.instant('plugin.delete-plugins-title', {count: selectedCount}, 'messageformat');
+    }
+
+    function deletePluginsActionTitle(selectedCount) {
+        return $translate.instant('plugin.delete-plugins-action-title', {count: selectedCount}, 'messageformat');
+    }
+
+    function deletePluginsText() {
+        return $translate.instant('plugin.delete-plugins-text');
+    }
+
+    function gridInited(grid) {
+        vm.grid = grid;
+    }
+
+    function fetchPlugins(pageLink) {
+        return pluginService.getAllPlugins(pageLink);
+    }
+
+    function savePlugin(plugin) {
+        return pluginService.savePlugin(plugin);
+    }
+
+    function deletePlugin(pluginId) {
+        return pluginService.deletePlugin(pluginId);
+    }
+
+    function getPluginTitle(plugin) {
+        return plugin ? plugin.name : '';
+    }
+
+    function isPluginEditable(plugin) {
+        if (userService.getAuthority() === 'TENANT_ADMIN') {
+            return plugin && plugin.tenantId.id != types.id.nullUid;
+        } else {
+            return userService.getAuthority() === 'SYS_ADMIN';
+        }
+    }
+
+    function activatePlugin(event, plugin) {
+        pluginService.activatePlugin(plugin.id.id).then(function () {
+            vm.grid.refreshList();
+        }, function () {
+        });
+    }
+
+    function suspendPlugin(event, plugin) {
+        pluginService.suspendPlugin(plugin.id.id).then(function () {
+            vm.grid.refreshList();
+        }, function () {
+        });
+    }
+
+}
diff --git a/ui/src/app/plugin/plugin.directive.js b/ui/src/app/plugin/plugin.directive.js
new file mode 100644
index 0000000..7799d3d
--- /dev/null
+++ b/ui/src/app/plugin/plugin.directive.js
@@ -0,0 +1,89 @@
+/*
+ * Copyright © 2016 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 './plugin.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import pluginFieldsetTemplate from './plugin-fieldset.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function PluginDirective($compile, $templateCache, types, utils, userService, componentDescriptorService) {
+    var linker = function (scope, element) {
+        var template = $templateCache.get(pluginFieldsetTemplate);
+        element.html(template);
+
+        scope.showPluginConfig = false;
+
+        scope.pluginConfiguration = {
+            data: null
+        };
+
+        if (scope.plugin && !scope.plugin.configuration) {
+            scope.plugin.configuration = {};
+        }
+
+        scope.$watch("plugin.clazz", function (newValue, prevValue) {
+            if (newValue != prevValue) {
+                scope.pluginConfiguration.data = null;
+                if (scope.plugin) {
+                    componentDescriptorService.getComponentDescriptorByClazz(scope.plugin.clazz).then(
+                        function success(component) {
+                            scope.pluginComponent = component;
+                            scope.showPluginConfig = !(userService.getAuthority() === 'TENANT_ADMIN'
+                                                        && scope.plugin.tenantId
+                                                        && scope.plugin.tenantId.id === types.id.nullUid)
+                                                      && utils.isDescriptorSchemaNotEmpty(scope.pluginComponent.configurationDescriptor);
+                            scope.pluginConfiguration.data = angular.copy(scope.plugin.configuration);
+                        },
+                        function fail() {
+                        }
+                    );
+                }
+            }
+        });
+
+        scope.$watch("pluginConfiguration.data", function (newValue, prevValue) {
+            if (newValue && !angular.equals(newValue, prevValue)) {
+                scope.plugin.configuration = angular.copy(scope.pluginConfiguration.data);
+            }
+        }, true);
+
+        componentDescriptorService.getComponentDescriptorsByType(types.componentType.plugin).then(
+            function success(components) {
+                scope.pluginComponents = components;
+            },
+            function fail() {
+            }
+        );
+
+        $compile(element.contents())(scope);
+    }
+    return {
+        restrict: "E",
+        link: linker,
+        scope: {
+            plugin: '=',
+            isEdit: '=',
+            isReadOnly: '=',
+            theForm: '=',
+            onActivatePlugin: '&',
+            onSuspendPlugin: '&',
+            onDeletePlugin: '&'
+        }
+    };
+}
diff --git a/ui/src/app/plugin/plugin.routes.js b/ui/src/app/plugin/plugin.routes.js
new file mode 100644
index 0000000..9b19f92
--- /dev/null
+++ b/ui/src/app/plugin/plugin.routes.js
@@ -0,0 +1,46 @@
+/*
+ * Copyright © 2016 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 pluginsTemplate from './plugins.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function PluginRoutes($stateProvider) {
+
+    $stateProvider
+        .state('home.plugins', {
+            url: '/plugins',
+            params: {'topIndex': 0},
+            module: 'private',
+            auth: ['SYS_ADMIN', 'TENANT_ADMIN'],
+            views: {
+                "content@home": {
+                    templateUrl: pluginsTemplate,
+                    controllerAs: 'vm',
+                    controller: 'PluginController'
+                }
+            },
+            data: {
+                searchEnabled: true,
+                pageTitle: 'plugin.plugins'
+            },
+            ncyBreadcrumb: {
+                label: '{"icon": "extension", "label": "plugin.plugins"}'
+            }
+        });
+}
diff --git a/ui/src/app/plugin/plugin.scss b/ui/src/app/plugin/plugin.scss
new file mode 100644
index 0000000..92b9cb9
--- /dev/null
+++ b/ui/src/app/plugin/plugin.scss
@@ -0,0 +1,18 @@
+/**
+ * Copyright © 2016 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.
+ */
+.plugin-config {
+    min-width: 500px;
+}
\ No newline at end of file
diff --git a/ui/src/app/plugin/plugin-card.tpl.html b/ui/src/app/plugin/plugin-card.tpl.html
new file mode 100644
index 0000000..f711239
--- /dev/null
+++ b/ui/src/app/plugin/plugin-card.tpl.html
@@ -0,0 +1,19 @@
+<!--
+
+    Copyright © 2016 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<div class="tb-uppercase" ng-if="item && parentCtl.types.id.nullUid === item.tenantId.id" translate>plugin.system</div>
+<div class="tb-uppercase" translate>{{item && item.state === 'ACTIVE' ? 'plugin.active' : 'plugin.suspended'}}</div>
diff --git a/ui/src/app/plugin/plugin-fieldset.tpl.html b/ui/src/app/plugin/plugin-fieldset.tpl.html
new file mode 100644
index 0000000..52a2578
--- /dev/null
+++ b/ui/src/app/plugin/plugin-fieldset.tpl.html
@@ -0,0 +1,71 @@
+<!--
+
+    Copyright © 2016 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-button ng-click="onActivatePlugin({event: $event})" ng-show="!isEdit && !isReadOnly && plugin.state === 'SUSPENDED'" class="md-raised md-primary">{{ 'plugin.activate' | translate }}</md-button>
+<md-button ng-click="onSuspendPlugin({event: $event})" ng-show="!isEdit && !isReadOnly && plugin.state === 'ACTIVE'" class="md-raised md-primary">{{ 'plugin.suspend' | translate }}</md-button>
+<md-button ng-click="onDeletePlugin({event: $event})" ng-show="!isEdit && !isReadOnly" class="md-raised md-primary">{{ 'plugin.delete' | translate }}</md-button>
+
+<md-content class="md-padding" layout="column" style="overflow-x: hidden">
+    <fieldset ng-disabled="loading || !isEdit || isReadOnly">
+        <md-input-container class="md-block">
+            <label translate>plugin.name</label>
+            <input required name="name" ng-model="plugin.name">
+            <div ng-messages="theForm.name.$error">
+                <div translate ng-message="required">plugin.name-required</div>
+            </div>
+        </md-input-container>
+        <md-input-container class="md-block">
+            <label translate>plugin.description</label>
+            <textarea ng-model="plugin.additionalInfo.description" rows="2"></textarea>
+        </md-input-container>
+        <section flex layout="row">
+            <md-input-container flex class="md-block">
+                <label translate>plugin.api-token</label>
+                <input required name="apiToken" ng-model="plugin.apiToken">
+                <div ng-messages="theForm.apiToken.$error">
+                    <div translate ng-message="required">plugin.api-token-required</div>
+                </div>
+            </md-input-container>
+            <md-input-container flex class="md-block">
+                <label translate>plugin.type</label>
+                <md-select required name="pluginType" ng-model="plugin.clazz" ng-disabled="loading || !isEdit">
+                    <md-option ng-repeat="component in pluginComponents" ng-value="component.clazz">
+                        {{component.name}}
+                    </md-option>
+                </md-select>
+                <div ng-messages="theForm.pluginType.$error">
+                    <div translate ng-message="required">plugin.type-required</div>
+                </div>
+            </md-input-container>
+        </section>
+        <md-card flex class="plugin-config" ng-if="showPluginConfig">
+            <md-card-title>
+                <md-card-title-text>
+                    <span translate class="md-headline" ng-class="{'tb-readonly-label' : (loading || !isEdit || isReadOnly)}">plugin.configuration</span>
+                </md-card-title-text>
+            </md-card-title>
+            <md-card-content>
+                <tb-json-form schema="pluginComponent.configurationDescriptor.schema"
+                              form="pluginComponent.configurationDescriptor.form"
+                              model="pluginConfiguration.data"
+                              readonly="loading || !isEdit || isReadOnly"
+                              form-control="theForm">
+                </tb-json-form>
+            </md-card-content>
+        </md-card>
+    </fieldset>
+</md-content>
diff --git a/ui/src/app/plugin/plugins.tpl.html b/ui/src/app/plugin/plugins.tpl.html
new file mode 100644
index 0000000..96c41dd
--- /dev/null
+++ b/ui/src/app/plugin/plugins.tpl.html
@@ -0,0 +1,42 @@
+<!--
+
+    Copyright © 2016 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-grid grid-configuration="vm.pluginGridConfig">
+    <details-buttons tb-help="vm.helpLinkIdForPlugin()" help-container-id="help-container">
+        <div id="help-container"></div>
+    </details-buttons>
+    <md-tabs ng-class="{'tb-headless': vm.grid.detailsConfig.isDetailsEditMode}"
+             id="tabs" md-border-bottom flex class="tb-absolute-fill">
+        <md-tab label="{{ 'plugin.details' | translate }}">
+            <tb-plugin plugin="vm.grid.operatingItem()"
+                 is-edit="vm.grid.detailsConfig.isDetailsEditMode"
+                 is-read-only="vm.grid.isDetailsReadOnly(vm.grid.operatingItem())"
+                 the-form="vm.grid.detailsForm"
+                 on-activate-plugin="vm.activatePlugin(event, vm.grid.detailsConfig.currentItem)"
+                 on-suspend-plugin="vm.suspendPlugin(event, vm.grid.detailsConfig.currentItem)"
+                 on-delete-plugin="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-plugin>
+        </md-tab>
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'plugin.events' | translate }}">
+            <tb-event-table flex entity-type="vm.types.entityType.plugin"
+                            entity-id="vm.grid.operatingItem().id.id"
+                            tenant-id="vm.grid.operatingItem().tenantId.id"
+                            default-event-type="{{vm.types.eventType.lcEvent.value}}"
+                            disabled-event-types="{{vm.types.eventType.alarm.value}}">
+            </tb-event-table>
+        </md-tab>
+    </md-tabs>
+</tb-grid>
diff --git a/ui/src/app/profile/change-password.controller.js b/ui/src/app/profile/change-password.controller.js
new file mode 100644
index 0000000..621683e
--- /dev/null
+++ b/ui/src/app/profile/change-password.controller.js
@@ -0,0 +1,41 @@
+/*
+ * Copyright © 2016 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 ChangePasswordController($scope, $translate, toast, $mdDialog, loginService) {
+    var vm = this;
+
+    vm.currentPassword = '';
+    vm.newPassword = '';
+    vm.newPassword2 = '';
+
+    vm.cancel = cancel;
+    vm.changePassword = changePassword;
+
+    function cancel() {
+        $mdDialog.cancel();
+    }
+
+    function changePassword() {
+        if (vm.newPassword !== vm.newPassword2) {
+            toast.showError($translate.instant('login.passwords-mismatch-error'));
+        } else {
+            loginService.changePassword(vm.currentPassword, vm.newPassword).then(function success() {
+                $scope.theForm.$setPristine();
+                $mdDialog.hide();
+            });
+        }
+    }
+}
diff --git a/ui/src/app/profile/change-password.tpl.html b/ui/src/app/profile/change-password.tpl.html
new file mode 100644
index 0000000..06082f7
--- /dev/null
+++ b/ui/src/app/profile/change-password.tpl.html
@@ -0,0 +1,64 @@
+<!--
+
+    Copyright © 2016 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="{{ 'profile.change-password' | translate }}">
+	<form name="theForm" ng-submit="vm.changePassword()">
+	    <md-toolbar>
+	      <div class="md-toolbar-tools">
+	        <h2 translate>profile.change-password</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-show="loading"></md-progress-linear>
+  	    <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
+	    <md-dialog-content>
+	      <div class="md-dialog-content">
+      		<md-input-container class="md-block">
+		    	<label translate>profile.current-password</label>
+		        <md-icon aria-label="{{ 'profile.current-password' | translate }}" class="material-icons">
+		          lock
+		        </md-icon>					                
+		        <input id="current-password-input" type="password" ng-model="vm.currentPassword"/>
+		    </md-input-container>
+      		<md-input-container class="md-block">
+		    	<label translate>login.new-password</label>
+		        <md-icon aria-label="{{ 'login.new-password' | translate }}" class="material-icons">
+		          lock
+		        </md-icon>					                
+		        <input id="password-input" type="password" ng-model="vm.newPassword"/>
+		    </md-input-container>
+		    <md-input-container class="md-block">
+		    	<label translate>login.new-password-again</label>
+		        <md-icon aria-label="{{ 'login.new-password-again' | translate }}" class="material-icons">
+		          lock
+		        </md-icon>					                
+		    	<input id="password-input2" type="password" ng-model="vm.newPassword2"/>
+		    </md-input-container>  	        
+	      </div>
+	    </md-dialog-content>
+	    <md-dialog-actions layout="row">
+	      <span flex></span>
+		  <md-button ng-disabled="loading || theForm.$invalid" type="submit" class="md-raised md-primary">
+		  		{{ 'profile.change-password' | 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>
diff --git a/ui/src/app/profile/index.js b/ui/src/app/profile/index.js
new file mode 100644
index 0000000..255ec2c
--- /dev/null
+++ b/ui/src/app/profile/index.js
@@ -0,0 +1,38 @@
+/*
+ * Copyright © 2016 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 uiRouter from 'angular-ui-router';
+import ngMaterial from 'angular-material';
+import ngMessages from 'angular-messages';
+import thingsboardApiUser from '../api/user.service';
+import thingsboardApiLogin from '../api/login.service';
+import thingsboardConfirmOnExit from '../components/confirm-on-exit.directive';
+
+import ProfileRoutes from './profile.routes';
+import ProfileController from './profile.controller';
+import ChangePasswordController from './change-password.controller';
+
+export default angular.module('thingsboard.profile', [
+    uiRouter,
+    ngMaterial,
+    ngMessages,
+    thingsboardApiUser,
+    thingsboardApiLogin,
+    thingsboardConfirmOnExit
+])
+    .config(ProfileRoutes)
+    .controller('ProfileController', ProfileController)
+    .controller('ChangePasswordController', ChangePasswordController)
+    .name;
diff --git a/ui/src/app/profile/profile.controller.js b/ui/src/app/profile/profile.controller.js
new file mode 100644
index 0000000..0144d62
--- /dev/null
+++ b/ui/src/app/profile/profile.controller.js
@@ -0,0 +1,58 @@
+/*
+ * Copyright © 2016 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 changePasswordTemplate from './change-password.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function ProfileController(userService, $scope, $document, $mdDialog) {
+    var vm = this;
+
+    vm.profileUser = {};
+
+    vm.save = save;
+    vm.changePassword = changePassword;
+
+    loadProfile();
+
+    function loadProfile() {
+        userService.getUser(userService.getCurrentUser().userId).then(function success(user) {
+            vm.profileUser = user;
+        });
+    }
+
+    function save() {
+        userService.saveUser(vm.profileUser).then(function success(user) {
+            vm.profileUser = user;
+            $scope.theForm.$setPristine();
+        });
+    }
+
+    function changePassword($event) {
+        $mdDialog.show({
+            controller: 'ChangePasswordController',
+            controllerAs: 'vm',
+            templateUrl: changePasswordTemplate,
+            parent: angular.element($document[0].body),
+            fullscreen: true,
+            targetEvent: $event
+        }).then(function () {
+        }, function () {
+        });
+    }
+}
diff --git a/ui/src/app/profile/profile.routes.js b/ui/src/app/profile/profile.routes.js
new file mode 100644
index 0000000..ec92fec
--- /dev/null
+++ b/ui/src/app/profile/profile.routes.js
@@ -0,0 +1,45 @@
+/*
+ * Copyright © 2016 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 profileTemplate from './profile.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function ProfileRoutes($stateProvider) {
+
+    $stateProvider
+        .state('home.profile', {
+            url: '/profile',
+            module: 'private',
+            auth: ['SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER'],
+            views: {
+                "content@home": {
+                    templateUrl: profileTemplate,
+                    controllerAs: 'vm',
+                    controller: 'ProfileController'
+                }
+            },
+            data: {
+                pageTitle: 'profile.profile'
+            },
+            ncyBreadcrumb: {
+                label: '{"icon": "account_circle", "label": "profile.profile"}'
+            }
+        });
+
+}
diff --git a/ui/src/app/profile/profile.tpl.html b/ui/src/app/profile/profile.tpl.html
new file mode 100644
index 0000000..4f24887
--- /dev/null
+++ b/ui/src/app/profile/profile.tpl.html
@@ -0,0 +1,56 @@
+<!--
+
+    Copyright © 2016 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 layout="row" width="100%" layout-wrap>
+    <md-card flex-gt-sm="60" flex="100">
+        <md-card-title>
+            <md-card-title-text>
+                <span translate class="md-headline">profile.profile</span>
+                <span style='opacity: 0.7;'>{{ vm.profileUser.email }}</span>
+            </md-card-title-text>
+        </md-card-title>
+        <md-progress-linear md-mode="indeterminate" ng-show="loading"></md-progress-linear>
+        <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
+        <md-card-content>
+            <form name="theForm" ng-submit="vm.save()" tb-confirm-on-exit confirm-form="theForm">
+                <fieldset ng-disabled="loading">
+                    <md-input-container class="md-block">
+                        <label translate>user.email</label>
+                        <input name="email" type="email" ng-model="vm.profileUser.email">
+                    </md-input-container>
+                    <md-input-container class="md-block">
+                        <label translate>user.first-name</label>
+                        <input name="firstName" ng-model="vm.profileUser.firstName">
+                    </md-input-container>
+                    <md-input-container class="md-block">
+                        <label translate>user.last-name</label>
+                        <input name="lastName" ng-model="vm.profileUser.lastName">
+                    </md-input-container>
+                    <md-button ng-disabled="loading" ng-click="vm.changePassword($event)"
+                               class="md-raised md-primary">{{ 'profile.change-password' | translate }}
+                    </md-button>
+                    <div layout="row" layout-align="end center" width="100%" layout-wrap>
+                        <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit"
+                                   class="md-raised md-primary">{{ 'action.save' | translate }}
+                        </md-button>
+                    </div>
+                </fieldset>
+            </form>
+        </md-card-content>
+    </md-card>
+</div>
+
diff --git a/ui/src/app/rule/add-rule.tpl.html b/ui/src/app/rule/add-rule.tpl.html
new file mode 100644
index 0000000..44f5b85
--- /dev/null
+++ b/ui/src/app/rule/add-rule.tpl.html
@@ -0,0 +1,48 @@
+<!--
+
+    Copyright © 2016 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="{{ 'rule.add' | translate }}" tb-help="'rules'" help-container-id="help-container">
+    <form name="theForm" ng-submit="vm.add()">
+        <md-toolbar>
+            <div class="md-toolbar-tools">
+                <h2 translate>rule.add</h2>
+                <span flex></span>
+                <div id="help-container"></div>
+                <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-show="loading"></md-progress-linear>
+        <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
+        <md-dialog-content>
+            <div class="md-dialog-content">
+                <tb-rule rule="vm.item" is-edit="true" the-form="theForm"></tb-rule>
+            </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">
+                {{ 'action.add' | 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>
diff --git a/ui/src/app/rule/index.js b/ui/src/app/rule/index.js
new file mode 100644
index 0000000..5af6ddc
--- /dev/null
+++ b/ui/src/app/rule/index.js
@@ -0,0 +1,42 @@
+/*
+ * Copyright © 2016 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 uiRouter from 'angular-ui-router';
+import thingsboardGrid from '../components/grid.directive';
+import thingsboardPluginSelect from '../components/plugin-select.directive';
+import thingsboardComponent from '../component';
+import thingsboardEvent from '../event';
+import thingsboardApiRule from '../api/rule.service';
+import thingsboardApiPlugin from '../api/plugin.service';
+import thingsboardApiComponentDescriptor from '../api/component-descriptor.service';
+
+import RuleRoutes from './rule.routes';
+import RuleController from './rule.controller';
+import RuleDirective from './rule.directive';
+
+export default angular.module('thingsboard.rule', [
+    uiRouter,
+    thingsboardGrid,
+    thingsboardPluginSelect,
+    thingsboardComponent,
+    thingsboardEvent,
+    thingsboardApiRule,
+    thingsboardApiPlugin,
+    thingsboardApiComponentDescriptor
+])
+    .config(RuleRoutes)
+    .controller('RuleController', RuleController)
+    .directive('tbRule', RuleDirective)
+    .name;
diff --git a/ui/src/app/rule/rule.controller.js b/ui/src/app/rule/rule.controller.js
new file mode 100644
index 0000000..00be85e
--- /dev/null
+++ b/ui/src/app/rule/rule.controller.js
@@ -0,0 +1,170 @@
+/*
+ * Copyright © 2016 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 addRuleTemplate from './add-rule.tpl.html';
+import ruleCard from './rule-card.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function RuleController(ruleService, userService, $state, $stateParams, $filter, $translate, types) {
+
+    var ruleActionsList = [
+        {
+            onAction: function ($event, item) {
+                activateRule($event, item);
+            },
+            name: function() { return $translate.instant('action.activate') },
+            details: function() { return $translate.instant('rule.activate') },
+            icon: "play_arrow",
+            isEnabled: function(rule) {
+                return isRuleEditable(rule) && rule && rule.state === 'SUSPENDED';
+            }
+        },
+        {
+            onAction: function ($event, item) {
+                suspendRule($event, item);
+            },
+            name: function() { return $translate.instant('action.suspend') },
+            details: function() { return $translate.instant('rule.suspend') },
+            icon: "pause",
+            isEnabled: function(rule) {
+                return isRuleEditable(rule) && rule.state === 'ACTIVE';
+            }
+        },
+        {
+            onAction: function ($event, item) {
+                vm.grid.deleteItem($event, item);
+            },
+            name: function() { return $translate.instant('action.delete') },
+            details: function() { return $translate.instant('rule.delete') },
+            icon: "delete",
+            isEnabled: isRuleEditable
+        }
+    ];
+
+    var vm = this;
+
+    vm.types = types;
+
+    vm.ruleGridConfig = {
+
+        refreshParamsFunc: null,
+
+        deleteItemTitleFunc: deleteRuleTitle,
+        deleteItemContentFunc: deleteRuleText,
+        deleteItemsTitleFunc: deleteRulesTitle,
+        deleteItemsActionTitleFunc: deleteRulesActionTitle,
+        deleteItemsContentFunc: deleteRulesText,
+
+        fetchItemsFunc: fetchRules,
+        saveItemFunc: saveRule,
+        deleteItemFunc: deleteRule,
+
+        getItemTitleFunc: getRuleTitle,
+        itemCardTemplateUrl: ruleCard,
+        parentCtl: vm,
+
+        actionsList: ruleActionsList,
+
+        onGridInited: gridInited,
+
+        addItemTemplateUrl: addRuleTemplate,
+
+        addItemText: function() { return $translate.instant('rule.add-rule-text') },
+        noItemsText: function() { return $translate.instant('rule.no-rules-text') },
+        itemDetailsText: function() { return $translate.instant('rule.rule-details') },
+        isSelectionEnabled: isRuleEditable,
+        isDetailsReadOnly: function(rule) {
+            return !isRuleEditable(rule);
+        }
+    };
+
+    if (angular.isDefined($stateParams.items) && $stateParams.items !== null) {
+        vm.ruleGridConfig.items = $stateParams.items;
+    }
+
+    if (angular.isDefined($stateParams.topIndex) && $stateParams.topIndex > 0) {
+        vm.ruleGridConfig.topIndex = $stateParams.topIndex;
+    }
+
+    vm.activateRule = activateRule;
+    vm.suspendRule = suspendRule;
+
+    function deleteRuleTitle(rule) {
+        return $translate.instant('rule.delete-rule-title', {ruleName: rule.name});
+    }
+
+    function deleteRuleText() {
+        return $translate.instant('rule.delete-rule-text');
+    }
+
+    function deleteRulesTitle(selectedCount) {
+        return $translate.instant('rule.delete-rules-title', {count: selectedCount}, 'messageformat');
+    }
+
+    function deleteRulesActionTitle(selectedCount) {
+        return $translate.instant('rule.delete-rules-action-title', {count: selectedCount}, 'messageformat');
+    }
+
+    function deleteRulesText() {
+        return $translate.instant('rule.delete-rules-text');
+    }
+
+    function gridInited(grid) {
+        vm.grid = grid;
+    }
+
+    function fetchRules(pageLink) {
+        return ruleService.getAllRules(pageLink);
+    }
+
+    function saveRule(rule) {
+        return ruleService.saveRule(rule);
+    }
+
+    function deleteRule(ruleId) {
+        return ruleService.deleteRule(ruleId);
+    }
+
+    function getRuleTitle(rule) {
+        return rule ? rule.name : '';
+    }
+
+    function isRuleEditable(rule) {
+        if (userService.getAuthority() === 'TENANT_ADMIN') {
+            return rule && rule.tenantId.id != types.id.nullUid;
+        } else {
+            return userService.getAuthority() === 'SYS_ADMIN';
+        }
+    }
+
+    function activateRule(event, rule) {
+        ruleService.activateRule(rule.id.id).then(function () {
+            vm.grid.refreshList();
+        }, function () {
+        });
+    }
+
+    function suspendRule(event, rule) {
+        ruleService.suspendRule(rule.id.id).then(function () {
+            vm.grid.refreshList();
+        }, function () {
+        });
+    }
+
+}
diff --git a/ui/src/app/rule/rule.directive.js b/ui/src/app/rule/rule.directive.js
new file mode 100644
index 0000000..362e1a1
--- /dev/null
+++ b/ui/src/app/rule/rule.directive.js
@@ -0,0 +1,183 @@
+/*
+ * Copyright © 2016 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 './rule.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import ruleFieldsetTemplate from './rule-fieldset.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function RuleDirective($compile, $templateCache, $mdDialog, $document, $q, pluginService, componentDialogService, componentDescriptorService, types) {
+    var linker = function (scope, element) {
+        var template = $templateCache.get(ruleFieldsetTemplate);
+        element.html(template);
+
+        scope.plugin = null;
+        scope.types = types;
+        scope.filters = [];
+
+        scope.addFilter = function($event) {
+            componentDialogService.openComponentDialog($event, true, false,
+                'rule.filter', types.componentType.filter).then(
+                function success(filter) {
+                    scope.filters.push({ value: filter });
+                },
+                function fail() {}
+            );
+        }
+
+        scope.removeFilter = function ($event, filter) {
+            var index = scope.filters.indexOf(filter);
+            if (index > -1) {
+                scope.filters.splice(index, 1);
+            }
+        };
+
+        scope.addProcessor = function($event) {
+            componentDialogService.openComponentDialog($event, true, false,
+                'rule.processor', types.componentType.processor).then(
+                function success(processor) {
+                    scope.rule.processor = processor;
+                },
+                function fail() {}
+            );
+        }
+
+        scope.removeProcessor = function() {
+            if (scope.rule.processor) {
+                scope.rule.processor = null;
+            }
+        }
+
+        scope.addAction = function($event) {
+            componentDialogService.openComponentDialog($event, true, false,
+                'rule.plugin-action', types.componentType.action, scope.plugin.clazz).then(
+                function success(action) {
+                    scope.rule.action = action;
+                },
+                function fail() {}
+            );
+        }
+
+        scope.removeAction = function() {
+            if (scope.rule.action) {
+                scope.rule.action = null;
+            }
+        }
+
+        scope.updateValidity = function () {
+            if (scope.rule) {
+                var valid = scope.rule.filters && scope.rule.filters.length > 0;
+                scope.theForm.$setValidity('filters', valid);
+                valid = angular.isDefined(scope.rule.pluginToken) && scope.rule.pluginToken != null;
+                scope.theForm.$setValidity('plugin', valid);
+                valid = angular.isDefined(scope.rule.action) && scope.rule.action != null;
+                scope.theForm.$setValidity('action', valid);
+            }
+        };
+
+        scope.$watch('rule', function(newVal, prevVal) {
+                if (newVal) {
+                    if (!scope.rule.filters) {
+                        scope.rule.filters = [];
+                    }
+                    if (!angular.equals(newVal, prevVal)) {
+                        if (scope.rule.pluginToken) {
+                            pluginService.getPluginByToken(scope.rule.pluginToken).then(
+                                function success(plugin) {
+                                    scope.plugin = plugin;
+                                },
+                                function fail() {}
+                            );
+                        } else {
+                            scope.plugin = null;
+                        }
+                        if (scope.filters) {
+                            scope.filters.splice(0, scope.filters.length);
+                        } else {
+                            scope.filters = [];
+                        }
+                        if (scope.rule.filters) {
+                            for (var i in scope.rule.filters) {
+                                scope.filters.push({value: scope.rule.filters[i]});
+                            }
+                        }
+                    }
+                    scope.updateValidity();
+                }
+            }
+        );
+
+        scope.$watch('filters', function (newVal, prevVal) {
+            if (scope.rule && scope.isEdit && !angular.equals(newVal, prevVal)) {
+                if (scope.rule.filters) {
+                    scope.rule.filters.splice(0, scope.rule.filters.length);
+                } else {
+                    scope.rule.filters = [];
+                }
+                if (scope.filters) {
+                    for (var i in scope.filters) {
+                        scope.rule.filters.push(scope.filters[i].value);
+                    }
+                }
+                scope.theForm.$setDirty();
+                scope.updateValidity();
+            }
+        }, true);
+
+        scope.$watch('plugin', function(newVal, prevVal) {
+            if (scope.rule && scope.isEdit && !angular.equals(newVal, prevVal)) {
+                if (newVal) {
+                    scope.rule.pluginToken = scope.plugin.apiToken;
+                } else {
+                    scope.rule.pluginToken = null;
+                }
+                scope.rule.action = null;
+                scope.updateValidity();
+            }
+        }, true);
+
+        scope.$watch('rule.processor', function(newVal, prevVal) {
+            if (scope.rule && scope.isEdit && !angular.equals(newVal, prevVal)) {
+                scope.theForm.$setDirty();
+            }
+        }, true);
+
+        scope.$watch('rule.action', function(newVal, prevVal) {
+            if (scope.rule && scope.isEdit && !angular.equals(newVal, prevVal)) {
+                scope.theForm.$setDirty();
+                scope.updateValidity();
+            }
+        }, true);
+
+        $compile(element.contents())(scope);
+    }
+    return {
+        restrict: "E",
+        link: linker,
+        scope: {
+            rule: '=',
+            isEdit: '=',
+            isReadOnly: '=',
+            theForm: '=',
+            onActivateRule: '&',
+            onSuspendRule: '&',
+            onDeleteRule: '&'
+        }
+    };
+}
diff --git a/ui/src/app/rule/rule.routes.js b/ui/src/app/rule/rule.routes.js
new file mode 100644
index 0000000..6b294c4
--- /dev/null
+++ b/ui/src/app/rule/rule.routes.js
@@ -0,0 +1,46 @@
+/*
+ * Copyright © 2016 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 rulesTemplate from './rules.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function RuleRoutes($stateProvider) {
+
+    $stateProvider
+        .state('home.rules', {
+            url: '/rules',
+            params: {'topIndex': 0},
+            module: 'private',
+            auth: ['SYS_ADMIN', 'TENANT_ADMIN'],
+            views: {
+                "content@home": {
+                    templateUrl: rulesTemplate,
+                    controllerAs: 'vm',
+                    controller: 'RuleController'
+                }
+            },
+            data: {
+                searchEnabled: true,
+                pageTitle: 'rule.rules'
+            },
+            ncyBreadcrumb: {
+                label: '{"icon": "settings_ethernet", "label": "rule.rules"}'
+            }
+        });
+}
diff --git a/ui/src/app/rule/rule.scss b/ui/src/app/rule/rule.scss
new file mode 100644
index 0000000..d448592
--- /dev/null
+++ b/ui/src/app/rule/rule.scss
@@ -0,0 +1,55 @@
+/**
+ * Copyright © 2016 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-rule {
+  min-width: 600px;
+  tb-plugin-select {
+    padding: 2px;
+    margin: 18px 0;
+  }
+}
+
+.tb-filter {
+  min-width: 500px;
+  min-height: 300px;
+}
+
+.tb-filters ul[dnd-list],
+.tb-filters ul[dnd-list] > li {
+  position: relative;
+}
+
+.tb-filters ul[dnd-list] {
+  min-height: 42px;
+  padding-left: 0px;
+}
+
+.tb-filters ul[dnd-list] .dndDraggingSource {
+  display: none;
+}
+
+.tb-filters ul[dnd-list] .dndPlaceholder {
+  display: block;
+  background-color: #ddd;
+  min-height: 42px;
+}
+
+.tb-filters ul[dnd-list] li {
+  display: block;
+}
+
+.tb-filters .handle {
+  cursor: move;
+}
diff --git a/ui/src/app/rule/rule-card.tpl.html b/ui/src/app/rule/rule-card.tpl.html
new file mode 100644
index 0000000..4fbc5d0
--- /dev/null
+++ b/ui/src/app/rule/rule-card.tpl.html
@@ -0,0 +1,19 @@
+<!--
+
+    Copyright © 2016 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<div class="tb-uppercase" ng-if="item && parentCtl.types.id.nullUid === item.tenantId.id" translate>rule.system</div>
+<div class="tb-uppercase" translate>{{item && item.state === 'ACTIVE' ? 'rule.active' : 'rule.suspended'}}</div>
diff --git a/ui/src/app/rule/rule-fieldset.tpl.html b/ui/src/app/rule/rule-fieldset.tpl.html
new file mode 100644
index 0000000..e5be2ff
--- /dev/null
+++ b/ui/src/app/rule/rule-fieldset.tpl.html
@@ -0,0 +1,200 @@
+<!--
+
+    Copyright © 2016 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-button ng-click="onActivateRule({event: $event})" ng-show="!isEdit && !isReadOnly && rule.state === 'SUSPENDED'" class="md-raised md-primary">{{ 'rule.activate' | translate }}</md-button>
+<md-button ng-click="onSuspendRule({event: $event})" ng-show="!isEdit && !isReadOnly && rule.state === 'ACTIVE'" class="md-raised md-primary">{{ 'rule.suspend' | translate }}</md-button>
+<md-button ng-click="onDeleteRule({event: $event})" ng-show="!isEdit && !isReadOnly" class="md-raised md-primary">{{ 'rule.delete' | translate }}</md-button>
+
+<md-content class="md-padding tb-rule" layout="column">
+    <fieldset ng-disabled="loading || !isEdit || isReadOnly">
+        <md-input-container class="md-block">
+            <label translate>rule.name</label>
+            <input required name="name" ng-model="rule.name">
+            <div ng-messages="theForm.name.$error">
+                <div translate ng-message="required">rule.name-required</div>
+            </div>
+        </md-input-container>
+        <md-input-container class="md-block">
+            <label translate>rule.description</label>
+            <textarea ng-model="rule.additionalInfo.description" rows="2"></textarea>
+        </md-input-container>
+    </fieldset>
+    <v-accordion id="filters-accordion" class="vAccordion--default"
+                 ng-class="{'tb-readonly-label' : (!isEdit || isReadOnly)}">
+            <v-pane id="filters-pane" expanded="true">
+                <v-pane-header>
+                    {{ 'rule.filters' | translate }}
+                </v-pane-header>
+                <v-pane-content>
+                    <div ng-if="rule.filters.length === 0">
+                                <span translate layout-align="center center"
+                                      class="tb-prompt">rule.add-filter-prompt</span>
+                    </div>
+                    <div ng-if="rule.filters.length > 0">
+                        <div flex layout="row" layout-align="start center">
+                            <span  ng-if="isEdit && !isReadOnly" style="min-width: 40px; margin: 0 6px;"></br></span>
+                            <span flex="5"></span>
+                            <div flex layout="row" layout-align="start center"
+                                 style="padding: 0 0 0 10px; margin: 5px;">
+                                <span translate flex="50">rule.filter-name</span>
+                                <span translate flex="50">rule.filter-type</span>
+                                <span style="min-width: 80px; margin: 0 12px;"></br></span>
+                            </div>
+                        </div>
+                        <div class="tb-filters" style="max-height: 300px; overflow: auto; padding-bottom: 15px;">
+                            <ul dnd-list="filters" dnd-disable-if="!isEdit || isReadOnly">
+                                <li ng-repeat="filter in filters"
+                                    dnd-draggable="filter"
+                                    dnd-moved="filters.splice($index, 1)"
+                                    dnd-disable-if="!isEdit || isReadOnly"
+                                    dnd-effect-allowed="move">
+                                        <div flex layout="row" layout-align="start center">
+                                            <md-button ng-if="isEdit && !isReadOnly" dnd-handle class="md-icon-button md-primary handle"
+                                                       style="min-width: 40px;"
+                                                       aria-label="{{ 'action.drag' | translate }}">
+                                                <md-tooltip md-direction="top">
+                                                    {{ 'action.drag' | translate }}
+                                                </md-tooltip>
+                                                <md-icon aria-label="{{ 'action.drag' | translate }}"
+                                                         class="material-icons">
+                                                    drag_handle
+                                                </md-icon>
+                                            </md-button>
+                                            <dnd-nodrag flex layout="row" layout-align="start center">
+                                                <span flex="5">{{$index + 1}}.</span>
+                                                <tb-component flex
+                                                              component="filter.value"
+                                                              type="types.componentType.filter"
+                                                              title="rule.filter"
+                                                              read-only="!isEdit || isReadOnly"
+                                                              on-remove-component="removeFilter(event, filter)">
+                                                </tb-component>
+                                            </dnd-nodrag>
+                                        </div>
+                                </li>
+                            </ul>
+                        </div>
+                    </div>
+                    <div ng-if="isEdit && !isReadOnly" flex layout="row" layout-align="start center">
+                        <md-button ng-disabled="loading" class="md-primary md-raised"
+                                   ng-click="addFilter($event)" aria-label="{{ 'action.add' | translate }}">
+                            <md-tooltip md-direction="top">
+                                {{ 'rule.add-filter' | translate }}
+                            </md-tooltip>
+                            <md-icon class="material-icons">add</md-icon>
+                            <span translate>action.add</span>
+                        </md-button>
+                    </div>
+                </v-pane-content>
+            </v-pane>
+        </v-accordion>
+        <v-accordion id="processor-accordion" class="vAccordion--default"
+                     ng-class="{'tb-readonly-label' : (!isEdit || isReadOnly)}">
+            <v-pane id="processor-pane" expanded="true">
+                <v-pane-header>
+                    {{ 'rule.processor' | translate }}
+                </v-pane-header>
+                <v-pane-content>
+                    <div ng-if="rule.processor && rule.processor != null">
+                        <div flex layout="row" layout-align="start center"
+                             style="padding: 0 0 0 10px; margin: 5px;">
+                            <span translate flex="50">rule.processor-name</span>
+                            <span translate flex="50">rule.processor-type</span>
+                            <span style="min-width: 80px; margin: 0 12px;"></br></span>
+                        </div>
+                        <div flex layout="row" layout-align="start center" style="padding-bottom: 15px;">
+                            <tb-component flex
+                                          component="rule.processor"
+                                          type="types.componentType.processor"
+                                          title="rule.processor"
+                                          read-only="!isEdit || isReadOnly"
+                                          on-remove-component="removeProcessor(event)">
+                            </tb-component>
+                        </div>
+                    </div>
+                    <div ng-if="!rule.processor || rule.processor == null">
+                                    <span ng-if="!isEdit || isReadOnly" translate layout-align="center center"
+                                          class="tb-prompt">rule.no-processor-configured</span>
+                        <div ng-if="isEdit && !isReadOnly" flex layout="row" layout-align="start center">
+                            <md-button ng-disabled="loading" class="md-primary md-raised"
+                                       ng-click="addProcessor($event)" aria-label="{{ 'action.create' | translate }}">
+                                <md-tooltip md-direction="top">
+                                    {{ 'rule.create-processor' | translate }}
+                                </md-tooltip>
+                                <md-icon class="material-icons">add</md-icon>
+                                <span translate>action.create</span>
+                            </md-button>
+                        </div>
+                    </div>
+                </v-pane-content>
+            </v-pane>
+        </v-accordion>
+        <fieldset ng-disabled="loading || !isEdit || isReadOnly">
+            <md-input-container ng-if="!isEdit || isReadOnly" flex class="md-block">
+                <label translate>plugin.plugin</label>
+                <input required name="name" ng-model="plugin.name">
+            </md-input-container>
+            <tb-plugin-select ng-show="isEdit && !isReadOnly" flex
+                      ng-model="plugin"
+                      tb-required="true"
+                      the-form="theForm"
+                      plugins-scope="action">
+            </tb-plugin-select>
+        </fieldset>
+        <v-accordion ng-if="plugin != null" id="plugin-action-accordion" class="vAccordion--default"
+                     ng-class="{'tb-readonly-label' : (!isEdit || isReadOnly)}">
+            <v-pane id="plugin-action-pane" expanded="true">
+                <v-pane-header>
+                    {{ 'rule.plugin-action' | translate }}
+                </v-pane-header>
+                <v-pane-content>
+                    <div ng-if="rule.action && rule.action != null">
+                        <div flex layout="row" layout-align="start center"
+                             style="padding: 0 0 0 10px; margin: 5px;">
+                            <span translate flex="50">rule.action-name</span>
+                            <span translate flex="50">rule.action-type</span>
+                            <span style="min-width: 80px; margin: 0 12px;"></br></span>
+                        </div>
+                        <div flex layout="row" layout-align="start center" style="padding-bottom: 15px;">
+                            <tb-component flex
+                                          component="rule.action"
+                                          type="types.componentType.action"
+                                          plugin-clazz="plugin.clazz"
+                                          title="rule.plugin-action"
+                                          read-only="!isEdit || isReadOnly"
+                                          on-remove-component="removeAction(event)">
+                            </tb-component>
+                        </div>
+                    </div>
+                    <div ng-if="!rule.action || rule.action == null">
+                        <span translate layout-align="center center"
+                              class="tb-prompt">rule.create-action-prompt</span>
+                        <div ng-if="isEdit && !isReadOnly" flex layout="row" layout-align="start center">
+                            <md-button ng-disabled="loading" class="md-primary md-raised"
+                                       ng-click="addAction($event)" aria-label="{{ 'action.create' | translate }}">
+                                <md-tooltip md-direction="top">
+                                    {{ 'rule.create-action' | translate }}
+                                </md-tooltip>
+                                <md-icon class="material-icons">add</md-icon>
+                                <span translate>action.create</span>
+                            </md-button>
+                        </div>
+                    </div>
+                </v-pane-content>
+            </v-pane>
+        </v-accordion>
+</md-content>
diff --git a/ui/src/app/rule/rules.tpl.html b/ui/src/app/rule/rules.tpl.html
new file mode 100644
index 0000000..2463540
--- /dev/null
+++ b/ui/src/app/rule/rules.tpl.html
@@ -0,0 +1,42 @@
+<!--
+
+    Copyright © 2016 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-grid grid-configuration="vm.ruleGridConfig">
+    <details-buttons tb-help="'rules'" help-container-id="help-container">
+        <div id="help-container"></div>
+    </details-buttons>
+    <md-tabs ng-class="{'tb-headless': vm.grid.detailsConfig.isDetailsEditMode}"
+             id="tabs" md-border-bottom flex class="tb-absolute-fill">
+        <md-tab label="{{ 'rule.details' | translate }}">
+            <tb-rule rule="vm.grid.operatingItem()"
+                               is-edit="vm.grid.detailsConfig.isDetailsEditMode"
+                               is-read-only="vm.grid.isDetailsReadOnly(vm.grid.operatingItem())"
+                               the-form="vm.grid.detailsForm"
+                               on-activate-rule="vm.activateRule(event, vm.grid.detailsConfig.currentItem)"
+                               on-suspend-rule="vm.suspendRule(event, vm.grid.detailsConfig.currentItem)"
+                               on-delete-rule="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-rule>
+        </md-tab>
+        <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'rule.events' | translate }}">
+            <tb-event-table flex entity-type="vm.types.entityType.rule"
+                            entity-id="vm.grid.operatingItem().id.id"
+                            tenant-id="vm.grid.operatingItem().tenantId.id"
+                            default-event-type="{{vm.types.eventType.lcEvent.value}}"
+                            disabled-event-types="{{vm.types.eventType.alarm.value}}">
+            </tb-event-table>
+        </md-tab>
+    </md-tabs>
+</tb-grid>
diff --git a/ui/src/app/services/error-toast.tpl.html b/ui/src/app/services/error-toast.tpl.html
new file mode 100644
index 0000000..4c99df3
--- /dev/null
+++ b/ui/src/app/services/error-toast.tpl.html
@@ -0,0 +1,25 @@
+<!--
+
+    Copyright © 2016 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-toast class="tb-error-toast">
+	<div class="md-toast-content">
+		  <div class="md-toast-text" ng-bind-html="vm.message"></div>
+		  <md-button ng-click="vm.closeToast()">
+		       {{ 'action.close' | translate }}
+		  </md-button>
+	</div>
+</md-toast>
diff --git a/ui/src/app/services/menu.service.js b/ui/src/app/services/menu.service.js
new file mode 100644
index 0000000..524602a
--- /dev/null
+++ b/ui/src/app/services/menu.service.js
@@ -0,0 +1,321 @@
+/*
+ * Copyright © 2016 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 thingsboardApiUser from '../api/user.service';
+
+export default angular.module('thingsboard.menu', [thingsboardApiUser])
+    .factory('menu', Menu)
+    .name;
+
+/*@ngInject*/
+function Menu(userService, $state, $rootScope) {
+
+    var authority = '';
+    var sections = [];
+    var homeSections = [];
+
+    if (userService.isUserLoaded() === true) {
+        buildMenu();
+    }
+
+    var authenticatedHandle = $rootScope.$on('authenticated', function () {
+        buildMenu();
+    });
+
+    var service = {
+        authenticatedHandle: authenticatedHandle,
+        getHomeSections: getHomeSections,
+        getSections: getSections,
+        sectionHeight: sectionHeight,
+        sectionActive: sectionActive
+    }
+
+    return service;
+
+    function getSections() {
+        return sections;
+    }
+
+    function getHomeSections() {
+        return homeSections;
+    }
+
+    function buildMenu() {
+        var user = userService.getCurrentUser();
+        if (user) {
+            if (authority !== user.authority) {
+                sections = [];
+                authority = user.authority;
+                if (authority === 'SYS_ADMIN') {
+                    sections = [
+                        {
+                            name: 'home.home',
+                            type: 'link',
+                            state: 'home',
+                            icon: 'home'
+                        },
+                        {
+                            name: 'plugin.plugins',
+                            type: 'link',
+                            state: 'home.plugins',
+                            icon: 'extension'
+                        },
+                        {
+                            name: 'rule.rules',
+                            type: 'link',
+                            state: 'home.rules',
+                            icon: 'settings_ethernet'
+                        },
+                        {
+                            name: 'tenant.tenants',
+                            type: 'link',
+                            state: 'home.tenants',
+                            icon: 'supervisor_account'
+                        },
+                        {
+                            name: 'widget.widget-library',
+                            type: 'link',
+                            state: 'home.widgets-bundles',
+                            icon: 'now_widgets'
+                        },
+                        {
+                            name: 'admin.system-settings',
+                            type: 'toggle',
+                            state: 'home.settings',
+                            height: '80px',
+                            icon: 'settings',
+                            pages: [
+                                {
+                                    name: 'admin.general',
+                                    type: 'link',
+                                    state: 'home.settings.general',
+                                    icon: 'settings_applications'
+                                },
+                                {
+                                    name: 'admin.outgoing-mail',
+                                    type: 'link',
+                                    state: 'home.settings.outgoing-mail',
+                                    icon: 'mail'
+                                }
+                            ]
+                        }];
+                    homeSections =
+                        [{
+                            name: 'rule-plugin.management',
+                            places: [
+                                {
+                                    name: 'plugin.plugins',
+                                    icon: 'extension',
+                                    state: 'home.plugins'
+                                },
+                                {
+                                    name: 'rule.rules',
+                                    icon: 'settings_ethernet',
+                                    state: 'home.rules'
+                                }
+                            ]
+                        },
+                        {
+                            name: 'tenant.management',
+                            places: [
+                                {
+                                    name: 'tenant.tenants',
+                                    icon: 'supervisor_account',
+                                    state: 'home.tenants'
+                                }
+                            ]
+                        },
+                            {
+                                name: 'widget.management',
+                                places: [
+                                    {
+                                        name: 'widget.widget-library',
+                                        icon: 'now_widgets',
+                                        state: 'home.widgets-bundles'
+                                    }
+                                ]
+                            },
+                            {
+                                name: 'admin.system-settings',
+                                places: [
+                                    {
+                                        name: 'admin.general',
+                                        icon: 'settings_applications',
+                                        state: 'home.settings.general'
+                                    },
+                                    {
+                                        name: 'admin.outgoing-mail',
+                                        icon: 'mail',
+                                        state: 'home.settings.outgoing-mail'
+                                    }
+                                ]
+                            }];
+                } else if (authority === 'TENANT_ADMIN') {
+                    sections = [
+                        {
+                            name: 'home.home',
+                            type: 'link',
+                            state: 'home',
+                            icon: 'home'
+                        },
+                        {
+                            name: 'plugin.plugins',
+                            type: 'link',
+                            state: 'home.plugins',
+                            icon: 'extension'
+                        },
+                        {
+                            name: 'rule.rules',
+                            type: 'link',
+                            state: 'home.rules',
+                            icon: 'settings_ethernet'
+                        },
+                        {
+                            name: 'customer.customers',
+                            type: 'link',
+                            state: 'home.customers',
+                            icon: 'supervisor_account'
+                        },
+                        {
+                            name: 'device.devices',
+                            type: 'link',
+                            state: 'home.devices',
+                            icon: 'devices_other'
+                        },
+                        {
+                            name: 'widget.widget-library',
+                            type: 'link',
+                            state: 'home.widgets-bundles',
+                            icon: 'now_widgets'
+                        },
+                        {
+                            name: 'dashboard.dashboards',
+                            type: 'link',
+                            state: 'home.dashboards',
+                            icon: 'dashboards'
+                        }];
+
+                    homeSections =
+                        [{
+                            name: 'rule-plugin.management',
+                            places: [
+                                {
+                                    name: 'plugin.plugins',
+                                    icon: 'extension',
+                                    state: 'home.plugins'
+                                },
+                                {
+                                    name: 'rule.rules',
+                                    icon: 'settings_ethernet',
+                                    state: 'home.rules'
+                                }
+                            ]
+                        },
+                        {
+                            name: 'customer.management',
+                            places: [
+                                {
+                                    name: 'customer.customers',
+                                    icon: 'supervisor_account',
+                                    state: 'home.customers'
+                                }
+                            ]
+                        },
+                            {
+                                name: 'device.management',
+                                places: [
+                                    {
+                                        name: 'device.devices',
+                                        icon: 'devices_other',
+                                        state: 'home.devices'
+                                    }
+                                ]
+                            },
+                            {
+                                name: 'dashboard.management',
+                                places: [
+                                    {
+                                        name: 'widget.widget-library',
+                                        icon: 'now_widgets',
+                                        state: 'home.widgets-bundles'
+                                    },
+                                    {
+                                        name: 'dashboard.dashboards',
+                                        icon: 'dashboard',
+                                        state: 'home.dashboards'
+                                    }
+                                ]
+                            }];
+
+                } else if (authority === 'CUSTOMER_USER') {
+                    sections = [
+                        {
+                            name: 'home.home',
+                            type: 'link',
+                            state: 'home',
+                            icon: 'home'
+                        },
+                        {
+                            name: 'device.devices',
+                            type: 'link',
+                            state: 'home.devices',
+                            icon: 'devices_other'
+                        },
+                        {
+                            name: 'dashboard.dashboards',
+                            type: 'link',
+                            state: 'home.dashboards',
+                            icon: 'dashboard'
+                        }];
+
+                    homeSections =
+                        [{
+                            name: 'device.view-devices',
+                            places: [
+                                {
+                                    name: 'device.devices',
+                                    icon: 'devices_other',
+                                    state: 'home.devices'
+                                }
+                            ]
+                        },
+                            {
+                                name: 'dashboard.view-dashboards',
+                                places: [
+                                    {
+                                        name: 'dashboard.dashboards',
+                                        icon: 'dashboard',
+                                        state: 'home.dashboards'
+                                    }
+                                ]
+                            }];
+                }
+            }
+        }
+    }
+
+    function sectionHeight(section) {
+        if ($state.includes(section.state)) {
+            return section.height;
+        } else {
+            return '0px';
+        }
+    }
+
+    function sectionActive(section) {
+        return $state.includes(section.state);
+    }
+
+}
\ No newline at end of file
diff --git a/ui/src/app/services/success-toast.tpl.html b/ui/src/app/services/success-toast.tpl.html
new file mode 100644
index 0000000..23c27a9
--- /dev/null
+++ b/ui/src/app/services/success-toast.tpl.html
@@ -0,0 +1,25 @@
+<!--
+
+    Copyright © 2016 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-toast class="tb-success-toast">
+	<div class="md-toast-content">
+		  <div class="md-toast-text" ng-bind-html="vm.message"></div>
+		  <md-button ng-click="vm.closeToast()">
+			  {{ 'action.close' | translate }}
+		  </md-button>
+	</div>
+</md-toast>
diff --git a/ui/src/app/services/toast.controller.js b/ui/src/app/services/toast.controller.js
new file mode 100644
index 0000000..72e2c14
--- /dev/null
+++ b/ui/src/app/services/toast.controller.js
@@ -0,0 +1,27 @@
+/*
+ * Copyright © 2016 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 ToastController($mdToast, message) {
+    var vm = this;
+    vm.message = message;
+
+    vm.closeToast = closeToast;
+
+    function closeToast() {
+        $mdToast.hide();
+    }
+
+}
diff --git a/ui/src/app/services/toast.js b/ui/src/app/services/toast.js
new file mode 100644
index 0000000..4e9153b
--- /dev/null
+++ b/ui/src/app/services/toast.js
@@ -0,0 +1,24 @@
+/*
+ * Copyright © 2016 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 './toast.scss';
+
+import Toast from './toast.service';
+import ToastController from './toast.controller';
+
+export default angular.module('thingsboard.toast', [])
+    .factory('toast', Toast)
+    .controller('ToastController', ToastController)
+    .name;
diff --git a/ui/src/app/services/toast.scss b/ui/src/app/services/toast.scss
new file mode 100644
index 0000000..a4c2160
--- /dev/null
+++ b/ui/src/app/services/toast.scss
@@ -0,0 +1,26 @@
+/**
+ * Copyright © 2016 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-toast.tb-success-toast .md-toast-content {
+  font-size: 18px !important;
+  background-color: green;
+  height: 100%;
+}
+
+md-toast.tb-error-toast .md-toast-content {
+  font-size: 18px !important;
+  background-color: maroon;
+  height: 100%;
+}
diff --git a/ui/src/app/services/toast.service.js b/ui/src/app/services/toast.service.js
new file mode 100644
index 0000000..7f6e4f8
--- /dev/null
+++ b/ui/src/app/services/toast.service.js
@@ -0,0 +1,85 @@
+/*
+ * Copyright © 2016 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 successToast from './success-toast.tpl.html';
+import errorToast from './error-toast.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function Toast($mdToast, $document) {
+
+    var showing = false;
+
+    var service = {
+        showSuccess: showSuccess,
+        showError: showError,
+        hide: hide
+    }
+
+    return service;
+
+    function showSuccess(successMessage, delay, toastParent, position) {
+        if (!toastParent) {
+            toastParent = angular.element($document[0].getElementById('toast-parent'));
+        }
+        if (!position) {
+            position = 'top left';
+        }
+        $mdToast.show({
+            hideDelay: delay || 0,
+            position: position,
+            controller: 'ToastController',
+            controllerAs: 'vm',
+            templateUrl: successToast,
+            locals: {message: successMessage},
+            parent: toastParent
+        });
+    }
+
+    function showError(errorMessage, toastParent, position) {
+        if (!showing) {
+            if (!toastParent) {
+                toastParent = angular.element($document[0].getElementById('toast-parent'));
+            }
+            if (!position) {
+                position = 'top left';
+            }
+            showing = true;
+            $mdToast.show({
+                hideDelay: 0,
+                position: position,
+                controller: 'ToastController',
+                controllerAs: 'vm',
+                templateUrl: errorToast,
+                locals: {message: errorMessage},
+                parent: toastParent
+            }).then(function hide() {
+                showing = false;
+            }, function cancel() {
+                showing = false;
+            });
+        }
+    }
+
+    function hide() {
+        if (showing) {
+            $mdToast.hide();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/ui/src/app/tenant/add-tenant.tpl.html b/ui/src/app/tenant/add-tenant.tpl.html
new file mode 100644
index 0000000..814a5b7
--- /dev/null
+++ b/ui/src/app/tenant/add-tenant.tpl.html
@@ -0,0 +1,48 @@
+<!--
+
+    Copyright © 2016 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="{{ 'tenant.add' | translate }}" tb-help="'tenants'" help-container-id="help-container">
+    <form name="theForm" ng-submit="vm.add()">
+        <md-toolbar>
+            <div class="md-toolbar-tools">
+                <h2 translate>tenant.add</h2>
+                <span flex></span>
+                <div id="help-container"></div>
+                <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-show="loading"></md-progress-linear>
+        <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
+        <md-dialog-content>
+            <div class="md-dialog-content">
+                <tb-tenant tenant="vm.item" is-edit="true" the-form="theForm"></tb-tenant>
+            </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">
+                {{ 'action.add' | 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>
diff --git a/ui/src/app/tenant/index.js b/ui/src/app/tenant/index.js
new file mode 100644
index 0000000..6c70c40
--- /dev/null
+++ b/ui/src/app/tenant/index.js
@@ -0,0 +1,36 @@
+/*
+ * Copyright © 2016 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 uiRouter from 'angular-ui-router';
+import thingsboardGrid from '../components/grid.directive';
+import thingsboardApiTenant from '../api/tenant.service';
+import thingsboardContact from '../components/contact.directive';
+import thingsboardContactShort from '../components/contact-short.filter';
+
+import TenantRoutes from './tenant.routes';
+import TenantController from './tenant.controller';
+import TenantDirective from './tenant.directive';
+
+export default angular.module('thingsboard.tenant', [
+    uiRouter,
+    thingsboardGrid,
+    thingsboardApiTenant,
+    thingsboardContact,
+    thingsboardContactShort
+])
+    .config(TenantRoutes)
+    .controller('TenantController', TenantController)
+    .directive('tbTenant', TenantDirective)
+    .name;
diff --git a/ui/src/app/tenant/tenant.controller.js b/ui/src/app/tenant/tenant.controller.js
new file mode 100644
index 0000000..07e737f
--- /dev/null
+++ b/ui/src/app/tenant/tenant.controller.js
@@ -0,0 +1,132 @@
+/*
+ * Copyright © 2016 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 addTenantTemplate from './add-tenant.tpl.html';
+import tenantCard from './tenant-card.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function TenantController(tenantService, $state, $stateParams, $translate) {
+
+    var tenantActionsList = [
+        {
+            onAction: function ($event, item) {
+                openTenantUsers($event, item);
+            },
+            name: function() { return $translate.instant('tenant.admins') },
+            details: function() { return $translate.instant('tenant.manage-tenant-admins') },
+            icon: "account_circle"
+        },
+        {
+            onAction: function ($event, item) {
+                vm.grid.deleteItem($event, item);
+            },
+            name: function() { return $translate.instant('action.delete') },
+            details: function() { return $translate.instant('tenant.delete') },
+            icon: "delete"
+        }
+    ];
+
+    var vm = this;
+
+    vm.tenantGridConfig = {
+
+        refreshParamsFunc: null,
+
+        deleteItemTitleFunc: deleteTenantTitle,
+        deleteItemContentFunc: deleteTenantText,
+        deleteItemsTitleFunc: deleteTenantsTitle,
+        deleteItemsActionTitleFunc: deleteTenantsActionTitle,
+        deleteItemsContentFunc: deleteTenantsText,
+
+        fetchItemsFunc: fetchTenants,
+        saveItemFunc: saveTenant,
+        deleteItemFunc: deleteTenant,
+
+        getItemTitleFunc: getTenantTitle,
+
+        itemCardTemplateUrl: tenantCard,
+
+        actionsList: tenantActionsList,
+
+        onGridInited: gridInited,
+
+        addItemTemplateUrl: addTenantTemplate,
+
+        addItemText: function() { return $translate.instant('tenant.add-tenant-text') },
+        noItemsText: function() { return $translate.instant('tenant.no-tenants-text') },
+        itemDetailsText: function() { return $translate.instant('tenant.tenant-details') }
+    };
+
+    if (angular.isDefined($stateParams.items) && $stateParams.items !== null) {
+        vm.tenantGridConfig.items = $stateParams.items;
+    }
+
+    if (angular.isDefined($stateParams.topIndex) && $stateParams.topIndex > 0) {
+        vm.tenantGridConfig.topIndex = $stateParams.topIndex;
+    }
+
+    vm.openTenantUsers = openTenantUsers;
+
+    function deleteTenantTitle(tenant) {
+        return $translate.instant('tenant.delete-tenant-title', {tenantTitle: tenant.title});
+    }
+
+    function deleteTenantText() {
+        return $translate.instant('tenant.delete-tenant-text');
+    }
+
+    function deleteTenantsTitle(selectedCount) {
+        return $translate.instant('tenant.delete-tenants-title', {count: selectedCount}, 'messageformat');
+    }
+
+    function deleteTenantsActionTitle(selectedCount) {
+        return $translate.instant('tenant.delete-tenants-action-title', {count: selectedCount}, 'messageformat');
+    }
+
+    function deleteTenantsText() {
+        return $translate.instant('tenant.delete-tenants-text');
+    }
+
+    function gridInited(grid) {
+        vm.grid = grid;
+    }
+
+    function fetchTenants(pageLink) {
+        return tenantService.getTenants(pageLink);
+    }
+
+    function saveTenant(tenant) {
+        return tenantService.saveTenant(tenant);
+    }
+
+    function deleteTenant(tenantId) {
+        return tenantService.deleteTenant(tenantId);
+    }
+
+    function getTenantTitle(tenant) {
+        return tenant ? tenant.title : '';
+    }
+
+    function openTenantUsers($event, tenant) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        $state.go('home.tenants.users', {tenantId: tenant.id.id});
+    }
+}
diff --git a/ui/src/app/tenant/tenant.directive.js b/ui/src/app/tenant/tenant.directive.js
new file mode 100644
index 0000000..ef8ce8f
--- /dev/null
+++ b/ui/src/app/tenant/tenant.directive.js
@@ -0,0 +1,40 @@
+/*
+ * Copyright © 2016 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 tenantFieldsetTemplate from './tenant-fieldset.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function TenantDirective($compile, $templateCache) {
+    var linker = function (scope, element) {
+        var template = $templateCache.get(tenantFieldsetTemplate);
+        element.html(template);
+        $compile(element.contents())(scope);
+    }
+    return {
+        restrict: "E",
+        link: linker,
+        scope: {
+            tenant: '=',
+            isEdit: '=',
+            theForm: '=',
+            onManageUsers: '&',
+            onDeleteTenant: '&'
+        }
+    };
+}
diff --git a/ui/src/app/tenant/tenant.routes.js b/ui/src/app/tenant/tenant.routes.js
new file mode 100644
index 0000000..4c56ac3
--- /dev/null
+++ b/ui/src/app/tenant/tenant.routes.js
@@ -0,0 +1,46 @@
+/*
+ * Copyright © 2016 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 tenantsTemplate from './tenants.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function TenantRoutes($stateProvider) {
+
+    $stateProvider
+        .state('home.tenants', {
+            url: '/tenants',
+            params: {'topIndex': 0},
+            module: 'private',
+            auth: ['SYS_ADMIN'],
+            views: {
+                "content@home": {
+                    templateUrl: tenantsTemplate,
+                    controllerAs: 'vm',
+                    controller: 'TenantController'
+                }
+            },
+            data: {
+                searchEnabled: true,
+                pageTitle: 'tenant.tenants'
+            },
+            ncyBreadcrumb: {
+                label: '{"icon": "supervisor_account", "label": "tenant.tenants"}'
+            }
+        });
+}
diff --git a/ui/src/app/tenant/tenant-card.tpl.html b/ui/src/app/tenant/tenant-card.tpl.html
new file mode 100644
index 0000000..99071bd
--- /dev/null
+++ b/ui/src/app/tenant/tenant-card.tpl.html
@@ -0,0 +1,18 @@
+<!--
+
+    Copyright © 2016 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<div class="tb-uppercase">{{ item | contactShort }}</div>
\ No newline at end of file
diff --git a/ui/src/app/tenant/tenant-fieldset.tpl.html b/ui/src/app/tenant/tenant-fieldset.tpl.html
new file mode 100644
index 0000000..45e7c97
--- /dev/null
+++ b/ui/src/app/tenant/tenant-fieldset.tpl.html
@@ -0,0 +1,36 @@
+<!--
+
+    Copyright © 2016 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-button ng-click="onManageUsers({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{ 'tenant.manage-tenant-admins' | translate }}</md-button>
+<md-button ng-click="onDeleteTenant({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{ 'tenant.delete' | translate }}</md-button>
+
+<md-content class="md-padding" layout="column">
+	<fieldset ng-disabled="loading || !isEdit">
+		<md-input-container class="md-block">
+			<label translate>tenant.title</label>
+			<input required name="title" ng-model="tenant.title">	
+			<div ng-messages="theForm.title.$error">
+	      		<div translate ng-message="required">tenant.title-required</div>
+	    	</div>				
+		</md-input-container>
+		<md-input-container class="md-block">
+			<label translate>tenant.description</label>
+			<textarea ng-model="tenant.additionalInfo.description" rows="2"></textarea>
+		</md-input-container>
+		<tb-contact contact="tenant" the-form="theForm" is-edit="isEdit"></tb-contact>
+	</fieldset>
+</md-content>
diff --git a/ui/src/app/tenant/tenants.tpl.html b/ui/src/app/tenant/tenants.tpl.html
new file mode 100644
index 0000000..77cb80f
--- /dev/null
+++ b/ui/src/app/tenant/tenants.tpl.html
@@ -0,0 +1,27 @@
+<!--
+
+    Copyright © 2016 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-grid grid-configuration="vm.tenantGridConfig">
+	<details-buttons tb-help="'tenants'" help-container-id="help-container">
+		<div id="help-container"></div>
+	</details-buttons>
+	<tb-tenant tenant="vm.grid.operatingItem()"
+			   is-edit="vm.grid.detailsConfig.isDetailsEditMode"
+			   the-form="vm.grid.detailsForm"
+			   on-manage-users="vm.openTenantUsers(event, vm.grid.detailsConfig.currentItem)"
+			   on-delete-tenant="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-tenant>
+</tb-grid>
diff --git a/ui/src/app/user/add-user.tpl.html b/ui/src/app/user/add-user.tpl.html
new file mode 100644
index 0000000..28657da
--- /dev/null
+++ b/ui/src/app/user/add-user.tpl.html
@@ -0,0 +1,45 @@
+<!--
+
+    Copyright © 2016 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="{{ 'user.add' | translate }}" tb-help="'users'" help-container-id="help-container">
+	<form name="theForm" ng-submit="vm.add()">
+	    <md-toolbar>
+	      <div class="md-toolbar-tools">
+	        <h2 translate>user.add</h2>
+	        <span flex></span>
+			<div id="help-container"></div>
+	        <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-show="loading"></md-progress-linear>
+  	    <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
+	    <md-dialog-content>
+	      <div class="md-dialog-content">
+  	        	<tb-user user="vm.item" is-edit="true" the-form="theForm"></tb-user>
+	      </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">
+		  		{{ 'action.add' | 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>
diff --git a/ui/src/app/user/index.js b/ui/src/app/user/index.js
new file mode 100644
index 0000000..68ff865
--- /dev/null
+++ b/ui/src/app/user/index.js
@@ -0,0 +1,34 @@
+/*
+ * Copyright © 2016 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 uiRouter from 'angular-ui-router';
+import thingsboardGrid from '../components/grid.directive';
+import thingsboardApiUser from '../api/user.service';
+import thingsboardToast from '../services/toast';
+
+import UserRoutes from './user.routes';
+import UserController from './user.controller';
+import UserDirective from './user.directive';
+
+export default angular.module('thingsboard.user', [
+    uiRouter,
+    thingsboardGrid,
+    thingsboardApiUser,
+    thingsboardToast
+])
+    .config(UserRoutes)
+    .controller('UserController', UserController)
+    .directive('tbUser', UserDirective)
+    .name;
diff --git a/ui/src/app/user/user.controller.js b/ui/src/app/user/user.controller.js
new file mode 100644
index 0000000..733f16d
--- /dev/null
+++ b/ui/src/app/user/user.controller.js
@@ -0,0 +1,153 @@
+/*
+ * Copyright © 2016 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 addUserTemplate from './add-user.tpl.html';
+import userCard from './user-card.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+
+/*@ngInject*/
+export default function UserController(userService, toast, $scope, $controller, $state, $stateParams, $translate) {
+
+    var tenantId = $stateParams.tenantId;
+    var customerId = $stateParams.customerId;
+    var usersType = $state.$current.data.usersType;
+
+    var userActionsList = [
+        {
+            onAction: function ($event, item) {
+                vm.grid.deleteItem($event, item);
+            },
+            name: function() { return $translate.instant('action.delete') },
+            details: function() { return $translate.instant('user.delete') },
+            icon: "delete"
+        }
+    ];
+
+    var vm = this;
+
+    vm.userGridConfig = {
+        deleteItemTitleFunc: deleteUserTitle,
+        deleteItemContentFunc: deleteUserText,
+        deleteItemsTitleFunc: deleteUsersTitle,
+        deleteItemsActionTitleFunc: deleteUsersActionTitle,
+        deleteItemsContentFunc: deleteUsersText,
+
+        deleteItemFunc: deleteUser,
+
+        getItemTitleFunc: getUserTitle,
+        itemCardTemplateUrl: userCard,
+
+        actionsList: userActionsList,
+
+        onGridInited: gridInited,
+
+        addItemTemplateUrl: addUserTemplate,
+
+        addItemText: function() { return $translate.instant('user.add-user-text') },
+        noItemsText: function() { return $translate.instant('user.no-users-text') },
+        itemDetailsText: function() { return $translate.instant('user.user-details') }
+    };
+
+    if (angular.isDefined($stateParams.items) && $stateParams.items !== null) {
+        vm.userGridConfig.items = $stateParams.items;
+    }
+
+    if (angular.isDefined($stateParams.topIndex) && $stateParams.topIndex > 0) {
+        vm.userGridConfig.topIndex = $stateParams.topIndex;
+    }
+
+    vm.resendActivation = resendActivation;
+
+    initController();
+
+    function initController() {
+        var fetchUsersFunction = null;
+        var saveUserFunction = null;
+        var refreshUsersParamsFunction = null;
+
+        if (usersType === 'tenant') {
+            fetchUsersFunction = function (pageLink) {
+                return userService.getTenantAdmins(tenantId, pageLink);
+            };
+            saveUserFunction = function (user) {
+                user.authority = "TENANT_ADMIN";
+                user.tenantId = {id: tenantId};
+                return userService.saveUser(user);
+            };
+            refreshUsersParamsFunction = function () {
+                return {"tenantId": tenantId, "topIndex": vm.topIndex};
+            };
+
+        } else if (usersType === 'customer') {
+            fetchUsersFunction = function (pageLink) {
+                return userService.getCustomerUsers(customerId, pageLink);
+            };
+            saveUserFunction = function (user) {
+                user.authority = "CUSTOMER_USER";
+                user.customerId = {id: customerId};
+                return userService.saveUser(user);
+            };
+            refreshUsersParamsFunction = function () {
+                return {"customerId": customerId, "topIndex": vm.topIndex};
+            };
+        }
+
+        vm.userGridConfig.refreshParamsFunc = refreshUsersParamsFunction;
+        vm.userGridConfig.fetchItemsFunc = fetchUsersFunction;
+        vm.userGridConfig.saveItemFunc = saveUserFunction;
+    }
+
+    function deleteUserTitle(user) {
+        return $translate.instant('user.delete-user-title', {userEmail: user.email});
+    }
+
+    function deleteUserText() {
+        return $translate.instant('user.delete-user-text');
+    }
+
+    function deleteUsersTitle(selectedCount) {
+        return $translate.instant('user.delete-users-title', {count: selectedCount}, 'messageformat');
+    }
+
+    function deleteUsersActionTitle(selectedCount) {
+        return $translate.instant('user.delete-users-action-title', {count: selectedCount}, 'messageformat');
+    }
+
+    function deleteUsersText() {
+        return $translate.instant('user.delete-users-text');
+    }
+
+    function gridInited(grid) {
+        vm.grid = grid;
+    }
+
+    function getUserTitle(user) {
+        return user ? user.email : '';
+    }
+
+    function deleteUser(userId) {
+        return userService.deleteUser(userId);
+    }
+
+    function resendActivation(user) {
+        userService.sendActivationEmail(user.email).then(function success() {
+            toast.showSuccess($translate.instant('user.activation-email-sent-message'));
+        });
+    }
+}
diff --git a/ui/src/app/user/user.directive.js b/ui/src/app/user/user.directive.js
new file mode 100644
index 0000000..d9d38ab
--- /dev/null
+++ b/ui/src/app/user/user.directive.js
@@ -0,0 +1,40 @@
+/*
+ * Copyright © 2016 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 userFieldsetTemplate from './user-fieldset.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function UserDirective($compile, $templateCache) {
+    var linker = function (scope, element) {
+        var template = $templateCache.get(userFieldsetTemplate);
+        element.html(template);
+        $compile(element.contents())(scope);
+    }
+    return {
+        restrict: "E",
+        link: linker,
+        scope: {
+            user: '=',
+            isEdit: '=',
+            theForm: '=',
+            onResendActivation: '&',
+            onDeleteUser: '&'
+        }
+    };
+}
diff --git a/ui/src/app/user/user.routes.js b/ui/src/app/user/user.routes.js
new file mode 100644
index 0000000..75688ea
--- /dev/null
+++ b/ui/src/app/user/user.routes.js
@@ -0,0 +1,69 @@
+/*
+ * Copyright © 2016 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 usersTemplate from '../user/users.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function UserRoutes($stateProvider) {
+
+    $stateProvider
+        .state('home.tenants.users', {
+            url: '/:tenantId/users',
+            params: {'topIndex': 0},
+            module: 'private',
+            auth: ['SYS_ADMIN'],
+            views: {
+                "content@home": {
+                    templateUrl: usersTemplate,
+                    controllerAs: 'vm',
+                    controller: 'UserController'
+                }
+            },
+            data: {
+                usersType: 'tenant',
+                searchEnabled: true,
+                pageTitle: 'user.tenant-admins'
+            },
+            ncyBreadcrumb: {
+                label: '{"icon": "account_circle", "label": "user.tenant-admins"}'
+            }
+        })
+        .state('home.customers.users', {
+            url: '/:customerId/users',
+            params: {'topIndex': 0},
+            module: 'private',
+            auth: ['TENANT_ADMIN'],
+            views: {
+                "content@home": {
+                    templateUrl: usersTemplate,
+                    controllerAs: 'vm',
+                    controller: 'UserController'
+                }
+            },
+            data: {
+                usersType: 'customer',
+                searchEnabled: true,
+                pageTitle: 'user.customer-users'
+            },
+            ncyBreadcrumb: {
+                label: '{"icon": "account_circle", "label": "user.customer-users"}'
+            }
+        });
+
+}
diff --git a/ui/src/app/user/user-card.tpl.html b/ui/src/app/user/user-card.tpl.html
new file mode 100644
index 0000000..ee0aa33
--- /dev/null
+++ b/ui/src/app/user/user-card.tpl.html
@@ -0,0 +1,18 @@
+<!--
+
+    Copyright © 2016 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<div class="tb-uppercase">{{item ? ( ( item.firstName ? item.firstName : '' ) + ' ' + ( item.lastName ? item.lastName : '') ) : ''}}</div>
diff --git a/ui/src/app/user/user-fieldset.tpl.html b/ui/src/app/user/user-fieldset.tpl.html
new file mode 100644
index 0000000..e80d628
--- /dev/null
+++ b/ui/src/app/user/user-fieldset.tpl.html
@@ -0,0 +1,47 @@
+<!--
+
+    Copyright © 2016 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-button ng-click="onResendActivation({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{
+    'user.resend-activation' | translate }}
+</md-button>
+<md-button ng-click="onDeleteUser({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{ 'user.delete' |
+    translate }}
+</md-button>
+
+<md-content class="md-padding" layout="column">
+    <fieldset ng-disabled="loading || !isEdit">
+        <md-input-container class="md-block">
+            <label translate>user.email</label>
+            <input required name="email" type="email" ng-model="user.email">
+            <div ng-messages="theForm.email.$error">
+                <div translate ng-message="required">user.email-required</div>
+            </div>
+        </md-input-container>
+        <md-input-container class="md-block">
+            <label translate>user.first-name</label>
+            <input name="firstname" ng-model="user.firstName">
+        </md-input-container>
+        <md-input-container class="md-block">
+            <label translate>user.last-name</label>
+            <input name="lastname" ng-model="user.lastName">
+        </md-input-container>
+        <md-input-container class="md-block">
+            <label translate>user.description</label>
+            <textarea ng-model="user.additionalInfo.description" rows="2"></textarea>
+        </md-input-container>
+    </fieldset>
+</md-content>
diff --git a/ui/src/app/user/users.tpl.html b/ui/src/app/user/users.tpl.html
new file mode 100644
index 0000000..24d7982
--- /dev/null
+++ b/ui/src/app/user/users.tpl.html
@@ -0,0 +1,27 @@
+<!--
+
+    Copyright © 2016 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-grid grid-configuration="vm.userGridConfig">
+	<details-buttons tb-help="'users'" help-container-id="help-container">
+		<div id="help-container"></div>
+	</details-buttons>
+	<tb-user user="vm.grid.operatingItem()"
+			 is-edit="vm.grid.detailsConfig.isDetailsEditMode"
+			 the-form="vm.grid.detailsForm"
+			 on-resend-activation="vm.resendActivation(vm.grid.detailsConfig.currentItem)"
+			 on-delete-user="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-user>
+</tb-grid>
diff --git a/ui/src/app/widget/add-widgets-bundle.tpl.html b/ui/src/app/widget/add-widgets-bundle.tpl.html
new file mode 100644
index 0000000..15e080f
--- /dev/null
+++ b/ui/src/app/widget/add-widgets-bundle.tpl.html
@@ -0,0 +1,48 @@
+<!--
+
+    Copyright © 2016 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="{{ 'widgets-bundle.add' | translate }}" tb-help="'widgetsBundles'" help-container-id="help-container">
+    <form name="theForm" ng-submit="vm.add()">
+        <md-toolbar>
+            <div class="md-toolbar-tools">
+                <h2 translate>widgets-bundle.add</h2>
+                <span flex></span>
+                <div id="help-container"></div>
+                <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-show="loading"></md-progress-linear>
+        <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
+        <md-dialog-content>
+            <div class="md-dialog-content">
+                <tb-widgets-bundle widgets-bundle="vm.item" is-edit="true" the-form="theForm"></tb-widgets-bundle>
+            </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">
+                {{ 'action.add' | 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>
diff --git a/ui/src/app/widget/index.js b/ui/src/app/widget/index.js
new file mode 100644
index 0000000..92c5426
--- /dev/null
+++ b/ui/src/app/widget/index.js
@@ -0,0 +1,59 @@
+/*
+ * Copyright © 2016 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 './widget-editor.scss';
+
+import 'angular-hotkeys';
+import 'angular-ui-ace';
+
+import uiRouter from 'angular-ui-router';
+import thingsboardApiUser from '../api/user.service';
+import thingsboardApiWidget from '../api/widget.service';
+import thingsboardTypes from '../common/types.constant';
+import thingsboardToast from '../services/toast';
+import thingsboardConfirmOnExit from '../components/confirm-on-exit.directive';
+import thingsboardDashboard from '../components/dashboard.directive';
+import thingsboardExpandFullscreen from '../components/expand-fullscreen.directive';
+import thingsboardCircularProgress from '../components/circular-progress.directive';
+
+import WidgetLibraryRoutes from './widget-library.routes';
+import WidgetLibraryController from './widget-library.controller';
+import SelectWidgetTypeController from './select-widget-type.controller';
+import WidgetEditorController from './widget-editor.controller';
+import WidgetsBundleController from './widgets-bundle.controller';
+import WidgetsBundleDirective from './widgets-bundle.directive';
+import SaveWidgetTypeAsController from './save-widget-type-as.controller';
+
+export default angular.module('thingsboard.widget-library', [
+    uiRouter,
+    thingsboardApiWidget,
+    thingsboardApiUser,
+    thingsboardTypes,
+    thingsboardToast,
+    thingsboardConfirmOnExit,
+    thingsboardDashboard,
+    thingsboardExpandFullscreen,
+    thingsboardCircularProgress,
+    'cfp.hotkeys',
+    'ui.ace'
+])
+    .config(WidgetLibraryRoutes)
+    .controller('WidgetLibraryController', WidgetLibraryController)
+    .controller('SelectWidgetTypeController', SelectWidgetTypeController)
+    .controller('WidgetEditorController', WidgetEditorController)
+    .controller('WidgetsBundleController', WidgetsBundleController)
+    .controller('SaveWidgetTypeAsController', SaveWidgetTypeAsController)
+    .directive('tbWidgetsBundle', WidgetsBundleDirective)
+    .name;
diff --git a/ui/src/app/widget/lib/analogue-linear-gauge.js b/ui/src/app/widget/lib/analogue-linear-gauge.js
new file mode 100644
index 0000000..72b5d6e
--- /dev/null
+++ b/ui/src/app/widget/lib/analogue-linear-gauge.js
@@ -0,0 +1,184 @@
+/*
+ * Copyright © 2016 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 $ from 'jquery';
+import canvasGauges from 'canvas-gauges';
+import tinycolor from 'tinycolor2';
+
+/* eslint-disable angular/angularelement */
+export default class TbAnalogueLinearGauge {
+    constructor(containerElement, settings, data, canvasId) {
+
+        canvasGauges.performance = window.performance; // eslint-disable-line no-undef, angular/window-service
+
+        var gaugeElement = $('#'+canvasId, containerElement);
+
+        var minValue = settings.minValue || 0;
+        var maxValue = settings.maxValue || 100;
+
+        var dataKey = data[0].dataKey;
+        var keyColor = settings.defaultColor || dataKey.color;
+
+        var majorTicksCount = settings.majorTicksCount || 10;
+        var total = maxValue-minValue;
+        var step = (total/majorTicksCount);
+        step = parseFloat(parseFloat(step).toPrecision(12));
+
+        var majorTicks = [];
+        var highlights = [];
+        var tick = 0;
+
+        while(tick<=total) {
+            var majorTick = tick + minValue;
+            majorTicks.push(majorTick);
+            var nextTick = tick+step;
+            nextTick = parseFloat(parseFloat(nextTick).toPrecision(12));
+            if (tick<total) {
+                var highlightColor = tinycolor(keyColor);
+                var percent = tick/total;
+                highlightColor.setAlpha(percent);
+                var highlight = {
+                    from: tick,
+                    to: nextTick,
+                    color: highlightColor.toRgbString()
+                }
+                highlights.push(highlight);
+            }
+            tick = nextTick;
+        }
+
+        var colorNumbers = tinycolor(keyColor).darken(20).toRgbString();
+        var barStrokeColor = tinycolor(keyColor).darken().setAlpha(0.6).toRgbString();
+
+        var progressColorStart = tinycolor(keyColor).setAlpha(0.05).toRgbString();
+        var progressColorEnd = tinycolor(keyColor).darken().toRgbString();
+
+        var gaugeData = {
+
+            renderTo: gaugeElement[0],
+
+            /* Generic options */
+
+            minValue: minValue,
+            maxValue: maxValue,
+            majorTicks: majorTicks,
+            minorTicks: settings.minorTicks || 2,
+            units: settings.units,
+            title: ((settings.showUnitTitle !== false) ?
+                (settings.unitTitle && settings.unitTitle.length > 0 ?
+                    settings.unitTitle : dataKey.label) : ''),
+
+            borders: settings.showBorder === true,
+            borderShadowWidth: (settings.showBorder === true) ? 3 : 0,
+            borderOuterWidth: (settings.showBorder === true) ? 3 : 0,
+            borderMiddleWidth: (settings.showBorder === true) ? 3 : 0,
+            borderInnerWidth: (settings.showBorder === true) ? 3 : 0,
+
+            // borders
+
+            // number formats
+            valueInt: settings.valueInt || 3,
+            valueDec: (angular.isDefined(settings.valueDec) && settings.valueDec !== null)
+                ? settings.valueDec : 2,
+            majorTicksInt: 1,
+            majorTicksDec: 0,
+
+            valueBox: settings.valueBox !== false,
+            valueBoxStroke: 5,
+            valueBoxWidth: 0,
+            valueText: '',
+            valueTextShadow: true,
+            valueBoxBorderRadius: 2.5,
+
+            //highlights
+            highlights: (settings.highlights && settings.highlights.length > 0) ? settings.highlights : highlights,
+            highlightsWidth: (angular.isDefined(settings.highlightsWidth) && settings.highlightsWidth !== null) ? settings.highlightsWidth : 10,
+
+            //fonts
+            fontNumbers: settings.numbersFont && settings.numbersFont.family ? settings.numbersFont.family : 'RobotoDraft',
+            fontTitle: settings.titleFont && settings.titleFont.family ? settings.titleFont.family : 'RobotoDraft',
+            fontUnits: settings.unitsFont && settings.unitsFont.family ? settings.unitsFont.family : 'RobotoDraft',
+            fontValue: settings.valueFont && settings.valueFont.family ? settings.valueFont.family : 'RobotoDraft',
+
+            fontNumbersSize: settings.numbersFont && settings.numbersFont.size ? settings.numbersFont.size : 18,
+            fontTitleSize: settings.titleFont && settings.titleFont.size ? settings.titleFont.size : 24,
+            fontUnitsSize: settings.unitsFont && settings.unitsFont.size ? settings.unitsFont.size : 22,
+            fontValueSize: settings.valueFont && settings.valueFont.size ? settings.valueFont.size : 40,
+
+            fontNumbersStyle: settings.numbersFont && settings.numbersFont.style ? settings.numbersFont.style : 'normal',
+            fontTitleStyle: settings.titleFont && settings.titleFont.style ? settings.titleFont.style : 'normal',
+            fontUnitsStyle: settings.unitsFont && settings.unitsFont.style ? settings.unitsFont.style : 'normal',
+            fontValueStyle: settings.valueFont && settings.valueFont.style ? settings.valueFont.style : 'normal',
+
+            fontNumbersWeight: settings.numbersFont && settings.numbersFont.weight ? settings.numbersFont.weight : '500',
+            fontTitleWeight: settings.titleFont && settings.titleFont.weight ? settings.titleFont.weight : '500',
+            fontUnitsWeight: settings.unitsFont && settings.unitsFont.weight ? settings.unitsFont.weight : '500',
+            fontValueWeight: settings.valueFont && settings.valueFont.weight ? settings.valueFont.weight : '500',
+
+            colorNumbers: settings.numbersFont && settings.numbersFont.color ? settings.numbersFont.color : colorNumbers,
+            colorTitle: settings.titleFont && settings.titleFont.color ? settings.titleFont.color : '#888',
+            colorUnits: settings.unitsFont && settings.unitsFont.color ? settings.unitsFont.color : '#888',
+            colorValueText: settings.valueFont && settings.valueFont.color ? settings.valueFont.color : '#444',
+            colorValueTextShadow: settings.valueFont && settings.valueFont.shadowColor ? settings.valueFont.shadowColor : 'rgba(0,0,0,0.3)',
+
+            //colors
+            colorPlate: settings.colorPlate || '#fff',
+            colorMajorTicks: settings.colorMajorTicks || '#444',
+            colorMinorTicks: settings.colorMinorTicks || '#666',
+            colorNeedle: settings.colorNeedle || keyColor,
+            colorNeedleEnd: settings.colorNeedleEnd || keyColor,
+
+            colorValueBoxRect: settings.colorValueBoxRect || '#888',
+            colorValueBoxRectEnd: settings.colorValueBoxRectEnd || '#666',
+            colorValueBoxBackground: settings.colorValueBoxBackground || '#babab2',
+            colorValueBoxShadow: settings.colorValueBoxShadow || 'rgba(0,0,0,1)',
+            colorNeedleShadowUp: settings.colorNeedleShadowUp || 'rgba(2,255,255,0.2)',
+            colorNeedleShadowDown: settings.colorNeedleShadowDown || 'rgba(188,143,143,0.45)',
+
+            // animations
+            animation: settings.animation !== false,
+            animationDuration: (angular.isDefined(settings.animationDuration) && settings.animationDuration !== null) ? settings.animationDuration : 500,
+            animationRule: settings.animationRule || 'cycle',
+
+            /* Linear gauge specific */
+
+            barStrokeWidth: (angular.isDefined(settings.barStrokeWidth) && settings.barStrokeWidth !== null) ? settings.barStrokeWidth : 2.5,
+            colorBarStroke: settings.colorBarStroke || barStrokeColor,
+            colorBar: settings.colorBar || "#fff",
+            colorBarEnd: settings.colorBarEnd || "#ddd",
+            colorBarProgress: settings.colorBarProgress || progressColorStart,
+            colorBarProgressEnd: settings.colorBarProgressEnd || progressColorEnd
+        };
+        this.gauge = new canvasGauges.LinearGauge(gaugeData).draw();
+    }
+
+    redraw(width, height, data, sizeChanged) {
+        if (sizeChanged) {
+            this.gauge.update({width: width, height: height});
+        }
+
+        if (data.length > 0) {
+            var cellData = data[0];
+            if (cellData.data.length > 0) {
+                var tvPair = cellData.data[cellData.data.length -
+                1];
+                var value = tvPair[1];
+                this.gauge.value = value;
+            }
+        }
+    }
+}
+
+/* eslint-enable angular/angularelement */
diff --git a/ui/src/app/widget/lib/analogue-radial-gauge.js b/ui/src/app/widget/lib/analogue-radial-gauge.js
new file mode 100644
index 0000000..c970b9d
--- /dev/null
+++ b/ui/src/app/widget/lib/analogue-radial-gauge.js
@@ -0,0 +1,193 @@
+/*
+ * Copyright © 2016 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 $ from 'jquery';
+import canvasGauges from 'canvas-gauges';
+import tinycolor from 'tinycolor2';
+
+/* eslint-disable angular/angularelement */
+
+export default class TbAnalogueRadialGauge {
+    constructor(containerElement, settings, data, canvasId) {
+
+        canvasGauges.performance = window.performance; // eslint-disable-line no-undef, angular/window-service
+
+        var gaugeElement = $('#'+canvasId, containerElement);
+
+        var minValue = settings.minValue || 0;
+        var maxValue = settings.maxValue || 100;
+
+        var dataKey = data[0].dataKey;
+        var keyColor = settings.defaultColor || dataKey.color;
+
+        var majorTicksCount = settings.majorTicksCount || 10;
+        var total = maxValue-minValue;
+        var step = (total/majorTicksCount);
+        step = parseFloat(parseFloat(step).toPrecision(12));
+
+        var majorTicks = [];
+        var highlights = [];
+        var tick = minValue;
+
+        while(tick<=maxValue) {
+            majorTicks.push(tick);
+            var nextTick = tick+step;
+            nextTick = parseFloat(parseFloat(nextTick).toPrecision(12));
+            if (tick<maxValue) {
+                var highlightColor = tinycolor(keyColor);
+                var percent = (tick-minValue)/total;
+                highlightColor.setAlpha(percent);
+                var highlight = {
+                    from: tick,
+                    to: nextTick,
+                    color: highlightColor.toRgbString()
+                }
+                highlights.push(highlight);
+            }
+            tick = nextTick;
+        }
+
+        var colorNumbers = tinycolor(keyColor).darken(20).toRgbString();
+
+        var gaugeData = {
+
+            renderTo: gaugeElement[0],
+
+            /* Generic options */
+
+            minValue: minValue,
+            maxValue: maxValue,
+            majorTicks: majorTicks,
+            minorTicks: settings.minorTicks || 2,
+            units: settings.units,
+            title: ((settings.showUnitTitle !== false) ?
+                (settings.unitTitle && settings.unitTitle.length > 0 ?
+                    settings.unitTitle : dataKey.label) : ''),
+
+            borders: settings.showBorder !== false,
+            borderShadowWidth: (settings.showBorder !== false) ? 3 : 0,
+
+            // borders
+            //borderOuterWidth: (settings.showBorder !== false) ? 3 : 0,
+            //borderMiddleWidth: (settings.showBorder !== false) ? 3 : 0,
+            //borderInnerWidth: (settings.showBorder !== false) ? 3 : 0,
+            //borderShadowWidth: (settings.showBorder !== false) ? 3 : 0,
+
+            // number formats
+            valueInt: settings.valueInt || 3,
+            valueDec: (angular.isDefined(settings.valueDec) && settings.valueDec !== null)
+                ? settings.valueDec : 2,
+            majorTicksInt: 1,
+            majorTicksDec: 0,
+
+            valueBox: settings.valueBox !== false,
+            valueBoxStroke: 5,
+            valueBoxWidth: 0,
+            valueText: '',
+            valueTextShadow: true,
+            valueBoxBorderRadius: 2.5,
+
+            //highlights
+            highlights: (settings.highlights && settings.highlights.length > 0) ? settings.highlights : highlights,
+            highlightsWidth: (angular.isDefined(settings.highlightsWidth) && settings.highlightsWidth !== null) ? settings.highlightsWidth : 15,
+
+            //fonts
+            fontNumbers: settings.numbersFont && settings.numbersFont.family ? settings.numbersFont.family : 'RobotoDraft',
+            fontTitle: settings.titleFont && settings.titleFont.family ? settings.titleFont.family : 'RobotoDraft',
+            fontUnits: settings.unitsFont && settings.unitsFont.family ? settings.unitsFont.family : 'RobotoDraft',
+            fontValue: settings.valueFont && settings.valueFont.family ? settings.valueFont.family : 'RobotoDraft',
+
+            fontNumbersSize: settings.numbersFont && settings.numbersFont.size ? settings.numbersFont.size : 18,
+            fontTitleSize: settings.titleFont && settings.titleFont.size ? settings.titleFont.size : 24,
+            fontUnitsSize: settings.unitsFont && settings.unitsFont.size ? settings.unitsFont.size : 22,
+            fontValueSize: settings.valueFont && settings.valueFont.size ? settings.valueFont.size : 40,
+
+            fontNumbersStyle: settings.numbersFont && settings.numbersFont.style ? settings.numbersFont.style : 'normal',
+            fontTitleStyle: settings.titleFont && settings.titleFont.style ? settings.titleFont.style : 'normal',
+            fontUnitsStyle: settings.unitsFont && settings.unitsFont.style ? settings.unitsFont.style : 'normal',
+            fontValueStyle: settings.valueFont && settings.valueFont.style ? settings.valueFont.style : 'normal',
+
+            fontNumbersWeight: settings.numbersFont && settings.numbersFont.weight ? settings.numbersFont.weight : '500',
+            fontTitleWeight: settings.titleFont && settings.titleFont.weight ? settings.titleFont.weight : '500',
+            fontUnitsWeight: settings.unitsFont && settings.unitsFont.weight ? settings.unitsFont.weight : '500',
+            fontValueWeight: settings.valueFont && settings.valueFont.weight ? settings.valueFont.weight : '500',
+
+            colorNumbers: settings.numbersFont && settings.numbersFont.color ? settings.numbersFont.color : colorNumbers,
+            colorTitle: settings.titleFont && settings.titleFont.color ? settings.titleFont.color : '#888',
+            colorUnits: settings.unitsFont && settings.unitsFont.color ? settings.unitsFont.color : '#888',
+            colorValueText: settings.valueFont && settings.valueFont.color ? settings.valueFont.color : '#444',
+            colorValueTextShadow: settings.valueFont && settings.valueFont.shadowColor ? settings.valueFont.shadowColor : 'rgba(0,0,0,0.3)',
+
+            //colors
+            colorPlate: settings.colorPlate || '#fff',
+            colorMajorTicks: settings.colorMajorTicks || '#444',
+            colorMinorTicks: settings.colorMinorTicks || '#666',
+            colorNeedle: settings.colorNeedle || keyColor,
+            colorNeedleEnd: settings.colorNeedleEnd || keyColor,
+
+            colorValueBoxRect: settings.colorValueBoxRect || '#888',
+            colorValueBoxRectEnd: settings.colorValueBoxRectEnd || '#666',
+            colorValueBoxBackground: settings.colorValueBoxBackground || '#babab2',
+            colorValueBoxShadow: settings.colorValueBoxShadow || 'rgba(0,0,0,1)',
+            colorNeedleShadowUp: settings.colorNeedleShadowUp || 'rgba(2,255,255,0.2)',
+            colorNeedleShadowDown: settings.colorNeedleShadowDown || 'rgba(188,143,143,0.45)',
+
+            // animations
+            animation: settings.animation !== false,
+            animationDuration: (angular.isDefined(settings.animationDuration) && settings.animationDuration !== null) ? settings.animationDuration : 500,
+            animationRule: settings.animationRule || 'cycle',
+
+            /* Radial gauge specific */
+
+            ticksAngle: settings.ticksAngle || 270,
+            startAngle: settings.startAngle || 45,
+
+            // colors
+
+            colorNeedleCircleOuter: '#f0f0f0',
+            colorNeedleCircleOuterEnd: '#ccc',
+            colorNeedleCircleInner: '#e8e8e8', //tinycolor(keyColor).lighten(30).toRgbString(),//'#e8e8e8',
+            colorNeedleCircleInnerEnd: '#f5f5f5',
+
+            // needle
+            needleCircleSize: settings.needleCircleSize || 10,
+            needleCircleInner: true,
+            needleCircleOuter: true,
+
+            // custom animations
+            animationTarget: 'needle' // 'needle' or 'plate'
+
+        };
+        this.gauge = new canvasGauges.RadialGauge(gaugeData).draw();
+    }
+
+    redraw(width, height, data, sizeChanged) {
+        if (sizeChanged) {
+            this.gauge.update({width: width, height: height});
+        }
+
+        if (data.length > 0) {
+            var cellData = data[0];
+            if (cellData.data.length > 0) {
+                var tvPair = cellData.data[cellData.data.length -
+                1];
+                var value = tvPair[1];
+                this.gauge.value = value;
+            }
+        }
+    }
+}
+
+/* eslint-enable angular/angularelement */
diff --git a/ui/src/app/widget/lib/digital-gauge.js b/ui/src/app/widget/lib/digital-gauge.js
new file mode 100644
index 0000000..4e1418d
--- /dev/null
+++ b/ui/src/app/widget/lib/digital-gauge.js
@@ -0,0 +1,586 @@
+/*
+ * Copyright © 2016 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 $ from 'jquery';
+import tinycolor from 'tinycolor2';
+import 'justgage';
+import Raphael from 'raphael';
+
+/* eslint-disable angular/angularelement */
+
+export default class TbDigitalGauge {
+    constructor(containerElement, settings, data) {
+
+        var tbGauge = this;
+
+        window.Raphael = Raphael; // eslint-disable-line no-undef, angular/window-service
+
+        var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; // eslint-disable-line no-undef
+
+        var gaugeElement = $(containerElement);
+
+        this.localSettings = {};
+
+        this.localSettings.minValue = settings.minValue || 0;
+        this.localSettings.maxValue = settings.maxValue || 100;
+        this.localSettings.gaugeType = settings.gaugeType || 'arc';
+        this.localSettings.donutStartAngle = (angular.isDefined(settings.donutStartAngle) && settings.donutStartAngle !== null)
+            ? settings.donutStartAngle : 90;
+        this.localSettings.neonGlowBrightness = settings.neonGlowBrightness || 0;
+        this.localSettings.dashThickness = settings.dashThickness || 0;
+        this.localSettings.roundedLineCap = settings.roundedLineCap === true;
+
+        var dataKey = data[0].dataKey;
+        var keyColor = settings.defaultColor || dataKey.color;
+
+        this.localSettings.title = ((settings.showTitle === true) ?
+            (settings.title && settings.title.length > 0 ?
+                settings.title : dataKey.label) : '');
+
+        this.localSettings.unitTitle = ((settings.showUnitTitle === true) ?
+            (settings.unitTitle && settings.unitTitle.length > 0 ?
+                settings.unitTitle : dataKey.label) : '');
+
+        this.localSettings.gaugeWidthScale = settings.gaugeWidthScale || 0.75;
+        this.localSettings.gaugeColor = settings.gaugeColor || tinycolor(keyColor).setAlpha(0.2).toRgbString();
+
+        if (!settings.levelColors || settings.levelColors.length <= 0) {
+            this.localSettings.levelColors = [keyColor, keyColor];
+        } else {
+            this.localSettings.levelColors = settings.levelColors.slice();
+        }
+        if (this.localSettings.neonGlowBrightness) {
+            this.localSettings.origLevelColors = [];
+            for (var i = 0; i < this.localSettings.levelColors.length; i++) {
+                this.localSettings.origLevelColors.push(this.localSettings.levelColors[i]);
+                this.localSettings.levelColors[i] = tinycolor(this.localSettings.levelColors[i]).brighten(this.localSettings.neonGlowBrightness).toHexString();
+            }
+            var colorsCount = this.localSettings.origLevelColors.length;
+            var inc = colorsCount > 1 ? (1 / (colorsCount - 1)) : 1;
+            this.localSettings.colorsRange = [];
+            for (i = 0; i < this.localSettings.origLevelColors.length; i++) {
+                var percentage = inc * i;
+                var tColor = tinycolor(this.localSettings.origLevelColors[i]);
+                this.localSettings.colorsRange[i] = {
+                    pct: percentage,
+                    color: tColor.toRgb(),
+                    rgbString: tColor.toRgbString
+                };
+            }
+        }
+
+
+        this.localSettings.refreshAnimationType = settings.refreshAnimationType || '>';
+        this.localSettings.refreshAnimationTime = settings.refreshAnimationTime || 700;
+        this.localSettings.startAnimationType = settings.startAnimationType || '>';
+        this.localSettings.startAnimationTime = settings.startAnimationTime || 700;
+        this.localSettings.decimals = (angular.isDefined(settings.decimals) && settings.decimals !== null)
+            ? settings.decimals : 0;
+        this.localSettings.units = settings.units || '';
+        this.localSettings.hideValue = settings.showValue !== true;
+        this.localSettings.hideMinMax = settings.showMinMax !== true;
+
+        this.localSettings.titleFont = {};
+        var settingsTitleFont = settings.titleFont;
+        if (!settingsTitleFont) {
+            settingsTitleFont = {};
+        }
+
+        this.localSettings.titleFont.family = settingsTitleFont.family || 'RobotoDraft';
+        this.localSettings.titleFont.size = settingsTitleFont.size ? settingsTitleFont.size : 12;
+        this.localSettings.titleFont.style = settingsTitleFont.style ? settingsTitleFont.style : 'normal';
+        this.localSettings.titleFont.weight = settingsTitleFont.weight ? settingsTitleFont.weight : '500';
+        this.localSettings.titleFont.color = settingsTitleFont.color ? settingsTitleFont.color : keyColor;
+
+        this.localSettings.labelFont = {};
+        var settingsLabelFont = settings.labelFont;
+        if (!settingsLabelFont) {
+            settingsLabelFont = {};
+        }
+
+        this.localSettings.labelFont.family = settingsLabelFont.family || 'RobotoDraft';
+        this.localSettings.labelFont.size = settingsLabelFont.size ? settingsLabelFont.size : 8;
+        this.localSettings.labelFont.style = settingsLabelFont.style ? settingsLabelFont.style : 'normal';
+        this.localSettings.labelFont.weight = settingsLabelFont.weight ? settingsLabelFont.weight : '500';
+        this.localSettings.labelFont.color = settingsLabelFont.color ? settingsLabelFont.color : keyColor;
+
+        this.localSettings.valueFont = {};
+        var settingsValueFont = settings.valueFont;
+        if (!settingsValueFont) {
+            settingsValueFont = {};
+        }
+
+        this.localSettings.valueFont.family = settingsValueFont.family || 'RobotoDraft';
+        this.localSettings.valueFont.size = settingsValueFont.size ? settingsValueFont.size : 18;
+        this.localSettings.valueFont.style = settingsValueFont.style ? settingsValueFont.style : 'normal';
+        this.localSettings.valueFont.weight = settingsValueFont.weight ? settingsValueFont.weight : '500';
+        this.localSettings.valueFont.color = settingsValueFont.color ? settingsValueFont.color : keyColor;
+
+        this.localSettings.minMaxFont = {};
+        var settingsMinMaxFont = settings.minMaxFont;
+        if (!settingsMinMaxFont) {
+            settingsMinMaxFont = {};
+        }
+
+        this.localSettings.minMaxFont.family = settingsMinMaxFont.family || 'RobotoDraft';
+        this.localSettings.minMaxFont.size = settingsMinMaxFont.size ? settingsMinMaxFont.size : 10;
+        this.localSettings.minMaxFont.style = settingsMinMaxFont.style ? settingsMinMaxFont.style : 'normal';
+        this.localSettings.minMaxFont.weight = settingsMinMaxFont.weight ? settingsMinMaxFont.weight : '500';
+        this.localSettings.minMaxFont.color = settingsMinMaxFont.color ? settingsMinMaxFont.color : keyColor;
+
+        if (this.localSettings.neonGlowBrightness) {
+            this.localSettings.titleFont.origColor = this.localSettings.titleFont.color;
+            this.localSettings.titleFont.color = tinycolor(this.localSettings.titleFont.color).brighten(this.localSettings.neonGlowBrightness).toHexString();
+            this.localSettings.labelFont.origColor = this.localSettings.labelFont.color;
+            this.localSettings.labelFont.color = tinycolor(this.localSettings.labelFont.color).brighten(this.localSettings.neonGlowBrightness).toHexString();
+            this.localSettings.valueFont.origColor = this.localSettings.valueFont.color;
+            this.localSettings.valueFont.color = tinycolor(this.localSettings.valueFont.color).brighten(this.localSettings.neonGlowBrightness).toHexString();
+            this.localSettings.minMaxFont.origColor = this.localSettings.minMaxFont.color;
+            this.localSettings.minMaxFont.color = tinycolor(this.localSettings.minMaxFont.color).brighten(this.localSettings.neonGlowBrightness).toHexString();
+        }
+
+        var gaugeOptions = {
+            parentNode: gaugeElement[0],
+            value: 0,
+            min: this.localSettings.minValue,
+            max: this.localSettings.maxValue,
+            title: this.localSettings.title,
+            label: this.localSettings.unitTitle,
+            humanFriendlyDecimal: 0,
+            gaugeWidthScale: this.localSettings.gaugeWidthScale,
+            relativeGaugeSize: true,
+            gaugeColor: this.localSettings.gaugeColor,
+            levelColors: this.localSettings.levelColors,
+            refreshAnimationType: this.localSettings.refreshAnimationType,
+            refreshAnimationTime: this.localSettings.refreshAnimationTime,
+            startAnimationType: this.localSettings.startAnimationType,
+            startAnimationTime: this.localSettings.startAnimationTime,
+            humanFriendly: false,
+            donut: this.localSettings.gaugeType === 'donut',
+            donutStartAngle: this.localSettings.donutStartAngle,
+            decimals: this.localSettings.decimals,
+            pointer: false,
+            symbol: this.localSettings.units,
+            hideValue: this.localSettings.hideValue,
+            hideMinMax: this.localSettings.hideMinMax,
+            titleFontColor: this.localSettings.titleFont.color,
+            labelFontColor: this.localSettings.labelFont.color,
+            valueFontColor: this.localSettings.valueFont.color,
+            valueFontFamily: this.localSettings.valueFont.family
+        };
+
+        this.gauge = new JustGage(gaugeOptions); // eslint-disable-line no-undef
+
+        var gParams = this.gauge.params;
+
+        var titleTextElement = $(this.gauge.txtTitle.node);
+        titleTextElement.css('fontFamily', this.localSettings.titleFont.family);
+        titleTextElement.css('fontSize', this.localSettings.titleFont.size + 'px');
+        titleTextElement.css('fontStyle', this.localSettings.titleFont.style);
+        titleTextElement.css('fontWeight', this.localSettings.titleFont.weight);
+        titleTextElement.css('textTransform', 'uppercase');
+
+        var labelTextElement = $(this.gauge.txtLabel.node);
+        labelTextElement.css('fontFamily', this.localSettings.labelFont.family);
+        labelTextElement.css('fontSize', this.localSettings.labelFont.size + 'px');
+        labelTextElement.css('fontStyle', this.localSettings.labelFont.style);
+        labelTextElement.css('fontWeight', this.localSettings.labelFont.weight);
+        labelTextElement.css('textTransform', 'uppercase');
+
+        var valueTextElement = $(this.gauge.txtValue.node);
+        valueTextElement.css('fontSize', this.localSettings.valueFont.size + 'px');
+        valueTextElement.css('fontStyle', this.localSettings.valueFont.style);
+        valueTextElement.css('fontWeight', this.localSettings.valueFont.weight);
+
+        var minValTextElement = $(this.gauge.txtMin.node);
+        var maxValTextElement = $(this.gauge.txtMax.node);
+        minValTextElement.css('fontFamily', this.localSettings.minMaxFont.family);
+        maxValTextElement.css('fontFamily', this.localSettings.minMaxFont.family);
+        minValTextElement.css('fontSize', this.localSettings.minMaxFont.size+'px');
+        maxValTextElement.css('fontSize', this.localSettings.minMaxFont.size+'px');
+        minValTextElement.css('fontStyle', this.localSettings.minMaxFont.style);
+        maxValTextElement.css('fontStyle', this.localSettings.minMaxFont.style);
+        minValTextElement.css('fontWeight', this.localSettings.minMaxFont.weight);
+        maxValTextElement.css('fontWeight', this.localSettings.minMaxFont.weight);
+        minValTextElement.css('fill', this.localSettings.minMaxFont.color);
+        maxValTextElement.css('fill', this.localSettings.minMaxFont.color);
+
+        var gaugeLevelElement = $(this.gauge.level.node);
+        var gaugeBackElement = $(this.gauge.gauge.node);
+
+        var w = gParams.widgetW;
+        var gws = this.localSettings.gaugeWidthScale;
+        var Ro, Ri;
+        if (this.localSettings.gaugeType === 'donut') {
+            Ro = w / 2 - w / 7;
+        } else {
+            Ro = w / 2 - w / 10;
+        }
+        Ri = Ro - w / 6.666666666666667 * gws;
+        gParams.strokeWidth = Ro - Ri;
+
+        gParams.viewport = {
+            x: 0,
+            y: 0,
+            width: gParams.canvasW,
+            height: gParams.canvasH
+        }
+        var maxW;
+        if (this.localSettings.gaugeType === 'donut') {
+            if (gaugeOptions.title && gaugeOptions.title.length > 0) {
+                gParams.viewport.height = 140;
+            } else {
+                gParams.viewport.y = 17;
+                gParams.viewport.height = 120;
+            }
+            gParams.viewport.x = 40;
+            gParams.viewport.width = 120;
+            $('tspan', labelTextElement).attr('dy', '6');
+            if (!this.localSettings.unitTitle || this.localSettings.unitTitle.length === 0) {
+                var Cy = gParams.widgetH / 1.95 + gParams.dy;
+                gParams.valueY = Cy + (this.localSettings.valueFont.size-4)/2;
+                this.gauge.txtValue.attr({"y": gParams.valueY });
+            }
+        } else if (this.localSettings.gaugeType === 'arc') {
+            if (gaugeOptions.title && gaugeOptions.title.length > 0) {
+                gParams.viewport.y = 5;
+                gParams.viewport.height = 140;
+            } else {
+                gParams.viewport.y = 40;
+                gParams.viewport.height = 100;
+            }
+            if (this.localSettings.roundedLineCap) {
+                $('tspan', minValTextElement).attr('dy', ''+(gParams.strokeWidth/2));
+                $('tspan', maxValTextElement).attr('dy', ''+(gParams.strokeWidth/2));
+            }
+        } else if (this.localSettings.gaugeType === 'horizontalBar') {
+            gParams.titleY = gParams.dy + gParams.widgetH / 3.5 + (this.localSettings.title === '' ? 0 : this.localSettings.titleFont.size);
+            this.gauge.txtTitle.attr({"y": gParams.titleY });
+            gParams.titleBottom = gParams.titleY + (this.localSettings.title === '' ? 0 : 8);
+
+            gParams.valueY = gParams.titleBottom + (this.localSettings.hideValue ? 0 : this.localSettings.valueFont.size);
+            gParams.barTop = gParams.valueY + 8;
+            gParams.barBottom = gParams.barTop + gParams.strokeWidth;
+
+            this.gauge.txtValue.attr({"y": gParams.valueY });
+
+            if (this.localSettings.hideMinMax && this.localSettings.unitTitle === '') {
+                gParams.labelY = gParams.barBottom;
+                gParams.barLeft = this.localSettings.minMaxFont.size/3;
+                gParams.barRight = gParams.viewport.width - this.localSettings.minMaxFont.size/3;
+            } else {
+                maxW = Math.max(this.gauge.txtMin.node.getComputedTextLength(), this.gauge.txtMax.node.getComputedTextLength());
+                gParams.minX = maxW/2 + this.localSettings.minMaxFont.size/3;
+                gParams.maxX = gParams.viewport.width - maxW/2 - this.localSettings.minMaxFont.size/3;
+                gParams.barLeft = gParams.minX;
+                gParams.barRight = gParams.maxX;
+                gParams.labelY = gParams.barBottom + 4 + this.localSettings.labelFont.size;
+                this.gauge.txtLabel.attr({"y": gParams.labelY });
+                this.gauge.txtMin.attr({"x": gParams.minX, "y": gParams.labelY });
+                this.gauge.txtMax.attr({"x": gParams.maxX, "y": gParams.labelY });
+            }
+            gParams.viewport.y = 40;
+            gParams.viewport.height = gParams.labelY-25;
+        } else if (this.localSettings.gaugeType === 'verticalBar') {
+            gParams.titleY = (this.localSettings.title === '' ? 0 : this.localSettings.titleFont.size) + 8;
+            this.gauge.txtTitle.attr({"y": gParams.titleY });
+            gParams.titleBottom = gParams.titleY + (this.localSettings.title === '' ? 0 : 8);
+
+            gParams.valueY = gParams.titleBottom + (this.localSettings.hideValue ? 0 : this.localSettings.valueFont.size);
+            gParams.barTop = gParams.valueY + 8;
+            this.gauge.txtValue.attr({"y": gParams.valueY });
+
+            gParams.labelY = gParams.widgetH - 16;
+            if (this.localSettings.unitTitle === '') {
+                gParams.barBottom = gParams.labelY;
+            } else {
+                gParams.barBottom = gParams.labelY - 4 - this.localSettings.labelFont.size;
+                this.gauge.txtLabel.attr({"y": gParams.labelY });
+            }
+            gParams.minX = gParams.maxX = (gParams.widgetW/2 + gParams.dx) + gParams.strokeWidth/2 + this.localSettings.minMaxFont.size/3;
+            gParams.minY = gParams.barBottom;
+            gParams.maxY = gParams.barTop;
+            this.gauge.txtMin.attr({"text-anchor": "start", "x": gParams.minX, "y": gParams.minY });
+            this.gauge.txtMax.attr({"text-anchor": "start", "x": gParams.maxX, "y": gParams.maxY });
+            maxW = Math.max(this.gauge.txtMin.node.getComputedTextLength(), this.gauge.txtMax.node.getComputedTextLength());
+            gParams.prefWidth = gParams.strokeWidth + (maxW + this.localSettings.minMaxFont.size ) * 2;
+            gParams.viewport.x = (gParams.canvasW - gParams.prefWidth)/2;
+            gParams.viewport.width = gParams.prefWidth;
+        }
+        this.gauge.canvas.setViewBox(gParams.viewport.x, gParams.viewport.y, gParams.viewport.width, gParams.viewport.height, true);
+
+        if (this.localSettings.dashThickness) {
+            var Rm = Ri + gParams.strokeWidth * 0.5;
+            var circumference = Math.PI * Rm;
+            if (this.localSettings.gaugeType === 'donut') {
+                circumference *=2;
+            }
+            var dashCount = Math.floor(circumference / (this.localSettings.dashThickness));
+            if (this.localSettings.gaugeType === 'donut') {
+                dashCount = (dashCount | 1) - 1;
+            } else {
+                dashCount = (dashCount - 1) | 1;
+            }
+            var dashLength = circumference/dashCount;
+            gaugeLevelElement.attr('stroke-dasharray', '' + dashLength + 'px');
+            gaugeBackElement.attr('stroke-dasharray', '' + dashLength + 'px');
+        }
+
+        function getColor(val, pct) {
+
+            var lower, upper, range, rangePct, pctLower, pctUpper, color;
+
+            if (tbGauge.localSettings.colorsRange.length === 1) {
+                return tbGauge.localSettings.colorsRange[0].rgbString;
+            }
+            if (pct === 0) {
+                return tbGauge.localSettings.colorsRange[0].rgbString;
+            }
+
+            for (var j = 0; j < tbGauge.localSettings.colorsRange.length; j++) {
+                if (pct <= tbGauge.localSettings.colorsRange[j].pct) {
+                    lower = tbGauge.localSettings.colorsRange[j - 1];
+                    upper = tbGauge.localSettings.colorsRange[j];
+                    range = upper.pct - lower.pct;
+                    rangePct = (pct - lower.pct) / range;
+                    pctLower = 1 - rangePct;
+                    pctUpper = rangePct;
+                    color = tinycolor({
+                        r: Math.floor(lower.color.r * pctLower + upper.color.r * pctUpper),
+                        g: Math.floor(lower.color.g * pctLower + upper.color.g * pctUpper),
+                        b: Math.floor(lower.color.b * pctLower + upper.color.b * pctUpper)
+                    });
+                    return color.toRgbString();
+                }
+            }
+
+        }
+
+        this.gauge.canvas.customAttributes.pki = function(value, min, max, w, h, dx, dy, gws, donut, reverse) { // eslint-disable-line no-unused-vars
+            var alpha, Rm, Ro, Ri, Cx, Cy, Xm, Ym, Xo, Yo, path;
+
+            if (tbGauge.localSettings.neonGlowBrightness && !isFirefox
+                && tbGauge.floodColorElement1 && tbGauge.floodColorElement2) {
+                var progress = (value - min) / (max - min);
+                var resultColor = getColor(value, progress);
+                var brightenColor1 = tinycolor(resultColor).brighten(tbGauge.localSettings.neonGlowBrightness).toRgbString();
+                var brightenColor2 = resultColor;
+                tbGauge.floodColorElement1.setAttribute('flood-color', brightenColor1);
+                tbGauge.floodColorElement2.setAttribute('flood-color', brightenColor2);
+            }
+
+            var gaugeType = tbGauge.localSettings.gaugeType;
+
+            if (gaugeType === 'donut') {
+                alpha = (1 - 2 * (value - min) / (max - min)) * Math.PI;
+                Ro = w / 2 - w / 7;
+                Ri = Ro - w / 6.666666666666667 * gws;
+                Rm = Ri + (Ro - Ri)/2;
+
+                Cx = w / 2 + dx;
+                Cy = h / 1.95 + dy;
+
+                Xm = w / 2 + dx + Rm * Math.cos(alpha);
+                Ym = h - (h - Cy) - Rm * Math.sin(alpha);
+
+                path = "M" + (Cx - Rm) + "," + Cy + " ";
+                if ((value - min) > ((max - min) / 2)) {
+                    path += "A" + Rm + "," + Rm + " 0 0 1 " + (Cx + Rm) + "," + Cy + " ";
+                    path += "A" + Rm + "," + Rm + " 0 0 1 " + Xm + "," + Ym + " ";
+                } else {
+                    path += "A" + Rm + "," + Rm + " 0 0 1 " + Xm + "," + Ym + " ";
+                }
+                return {
+                    path: path
+                };
+
+            } else if (gaugeType === 'arc') {
+                alpha = (1 - (value - min) / (max - min)) * Math.PI;
+                Ro = w / 2 - w / 10;
+                Ri = Ro - w / 6.666666666666667 * gws;
+                Rm = Ri + (Ro - Ri)/2;
+
+                Cx = w / 2 + dx;
+                Cy = h / 1.25 + dy;
+
+                Xm = w / 2 + dx + Rm * Math.cos(alpha);
+                Ym = h - (h - Cy) - Rm * Math.sin(alpha);
+
+                path = "M" + (Cx - Rm) + "," + Cy + " ";
+                path += "A" + Rm + "," + Rm + " 0 0 1 " + Xm + "," + Ym + " ";
+
+                return {
+                    path: path
+                };
+            } else if (gaugeType === 'horizontalBar') {
+                Cx = tbGauge.gauge.params.barLeft;
+                Cy = tbGauge.gauge.params.barTop + tbGauge.gauge.params.strokeWidth/2;
+                Ro = (tbGauge.gauge.params.barRight - tbGauge.gauge.params.barLeft)/2;
+                alpha = (value - min) / (max - min);
+                Xo = Cx + 2 * Ro * alpha;
+                path = "M" + Cx + "," + Cy + " ";
+                path += "H" + " " + Xo;
+                return {
+                    path: path
+                };
+            } else if (gaugeType === 'verticalBar') {
+                Cx = w / 2 + dx;
+                Cy = tbGauge.gauge.params.barBottom;
+                Ro = (tbGauge.gauge.params.barBottom - tbGauge.gauge.params.barTop)/2;
+                alpha = (value - min) / (max - min);
+                Yo = Cy - 2 * Ro * alpha;
+                path = "M" + Cx + "," + Cy + " ";
+                path += "V" + " " + Yo;
+                return {
+                    path: path
+                };
+            }
+        };
+
+        var gaugeAttrs = {
+            "stroke":  this.gauge.gauge.attrs.fill,
+            "fill": 'rgba(0,0,0,0)',
+            pki: [  this.gauge.config.max,
+                this.gauge.config.min,
+                this.gauge.config.max,
+                gParams.widgetW,
+                gParams.widgetH,
+                gParams.dx,
+                gParams.dy,
+                this.gauge.config.gaugeWidthScale,
+                this.gauge.config.donut,
+                this.gauge.config.reverse
+            ]
+        };
+        gaugeAttrs['stroke-width'] = gParams.strokeWidth;
+
+
+        var gaugeLevelAttrs = {
+            "stroke":  this.gauge.level.attrs.fill,
+            "fill": 'rgba(0,0,0,0)'
+        };
+        gaugeLevelAttrs['stroke-width'] = gParams.strokeWidth;
+        if (this.localSettings.roundedLineCap) {
+            gaugeAttrs['stroke-linecap'] = 'round';
+            gaugeLevelAttrs['stroke-linecap'] = 'round';
+        }
+
+        this.gauge.gauge.attr(gaugeAttrs);
+        this.gauge.level.attr(gaugeLevelAttrs);
+
+        this.gauge.level.animate = function(attrs, refreshAnimationTime, refreshAnimationType) {
+            if (attrs.fill) {
+                attrs.stroke = attrs.fill;
+                attrs.fill = 'rgba(0,0,0,0)';
+            }
+            return Raphael.el.animate.call(tbGauge.gauge.level, attrs, refreshAnimationTime, refreshAnimationType);
+        }
+
+        function neonShadow(color) {
+            var brightenColor = tinycolor(color).brighten(tbGauge.localSettings.neonGlowBrightness);
+            return     '0 0 10px '+brightenColor+','+
+                '0 0 20px  '+brightenColor+','+
+                '0 0 30px  '+brightenColor+','+
+                '0 0 40px  '+ color +','+
+                '0 0 70px  '+ color +','+
+                '0 0 80px  '+ color +','+
+                '0 0 100px  '+ color +','+
+                '0 0 150px  '+ color;
+        }
+
+        if (this.localSettings.neonGlowBrightness) {
+            titleTextElement.css('textShadow', neonShadow(this.localSettings.titleFont.origColor));
+            valueTextElement.css('textShadow', neonShadow(this.localSettings.valueFont.origColor));
+            labelTextElement.css('textShadow', neonShadow(this.localSettings.labelFont.origColor));
+            minValTextElement.css('textShadow', neonShadow(this.localSettings.minMaxFont.origColor));
+            maxValTextElement.css('textShadow', neonShadow(this.localSettings.minMaxFont.origColor));
+        }
+
+        if (this.localSettings.neonGlowBrightness && !isFirefox) {
+            var filterX = (gParams.viewport.x / gParams.viewport.width)*100 + '%';
+            var filterY = (gParams.viewport.y / gParams.viewport.height)*100 + '%';
+            var svgBackFilterId = 'backBlurFilter' + Math.random();
+            var svgBackFilter = document.createElementNS("http://www.w3.org/2000/svg", "filter"); // eslint-disable-line no-undef, angular/document-service
+            svgBackFilter.setAttribute('id', svgBackFilterId);
+            svgBackFilter.setAttribute('filterUnits', 'userSpaceOnUse');
+            svgBackFilter.setAttribute('x', filterX);
+            svgBackFilter.setAttribute('y', filterY);
+            svgBackFilter.setAttribute('width', '100%');
+            svgBackFilter.setAttribute('height', '100%');
+            svgBackFilter.innerHTML =
+                '<feComponentTransfer>'+
+                '<feFuncR type="linear" slope="1.5"/>'+
+                '<feFuncG type="linear" slope="1.5"/>'+
+                '<feFuncB type="linear" slope="1.5"/>'+
+                '</feComponentTransfer>'+
+                '<feGaussianBlur stdDeviation="3" result="coloredBlur"></feGaussianBlur>'+
+                '<feMerge>'+
+                '<feMergeNode in="coloredBlur"/>'+
+                '<feMergeNode in="SourceGraphic"/>'+
+                '</feMerge>';
+            gaugeBackElement.attr('filter', 'url(#'+svgBackFilterId+')');
+
+            var svgFillFilterId = 'fillBlurFilter' + Math.random();
+            var svgFillFilter = document.createElementNS("http://www.w3.org/2000/svg", "filter"); // eslint-disable-line no-undef, angular/document-service
+            svgFillFilter.setAttribute('id', svgFillFilterId);
+            svgFillFilter.setAttribute('filterUnits', 'userSpaceOnUse');
+            svgFillFilter.setAttribute('x', filterX);
+            svgFillFilter.setAttribute('y', filterY);
+            svgFillFilter.setAttribute('width', '100%');
+            svgFillFilter.setAttribute('height', '100%');
+
+            var brightenColor1 = tinycolor(this.localSettings.origLevelColors[0]).brighten(this.localSettings.neonGlowBrightness).toRgbString();
+            var brightenColor2 = tinycolor(this.localSettings.origLevelColors[0]).toRgbString();
+            svgFillFilter.innerHTML =
+                '<feFlood flood-color="'+brightenColor1+'" result="flood1" />'+
+                '<feComposite in="flood1" in2="SourceGraphic" operator="in" result="floodShape" />'+
+                '<feGaussianBlur in="floodShape" stdDeviation="3" result="blur" />'+
+                '<feFlood flood-color="'+brightenColor2+'" result="flood2" />'+
+                '<feComposite in="flood2" in2="SourceGraphic" operator="in" result="floodShape2" />'+
+                '<feGaussianBlur in="floodShape2" stdDeviation="12" result="blur2" />'+
+                '<feMerge result="blurs">'+
+                '  <feMergeNode in="blur2"/>'+
+                '  <feMergeNode in="blur2"/>'+
+                '  <feMergeNode in="blur"/>'+
+                '  <feMergeNode in="blur"/>'+
+                '  <feMergeNode in="SourceGraphic"/>'+
+                '</feMerge>';
+            this.floodColorElement1 = $('feFlood:nth-of-type(1)', svgFillFilter)[0];
+            this.floodColorElement2 = $('feFlood:nth-of-type(2)', svgFillFilter)[0];
+            gaugeLevelElement.attr('filter', 'url(#'+svgFillFilterId+')');
+
+            var svgDefsElement = $('svg > defs', containerElement);
+            svgDefsElement[0].appendChild(svgBackFilter);
+            svgDefsElement[0].appendChild(svgFillFilter);
+        } else {
+            gaugeBackElement.attr('filter', '');
+            gaugeLevelElement.attr('filter', '');
+        }
+    }
+
+    redraw(data) {
+        if (data.length > 0) {
+            var cellData = data[0];
+            if (cellData.data.length > 0) {
+                var tvPair = cellData.data[cellData.data.length -
+                1];
+                var value = tvPair[1];
+                this.gauge.refresh(value);
+            }
+        }
+    }
+}
+
+/* eslint-enable angular/angularelement */
diff --git a/ui/src/app/widget/save-widget-type-as.controller.js b/ui/src/app/widget/save-widget-type-as.controller.js
new file mode 100644
index 0000000..a514741
--- /dev/null
+++ b/ui/src/app/widget/save-widget-type-as.controller.js
@@ -0,0 +1,41 @@
+/*
+ * Copyright © 2016 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 SaveWidgetTypeAsController($mdDialog, $q, userService) {
+
+    var vm = this;
+
+    vm.saveAs = saveAs;
+    vm.cancel = cancel;
+
+    vm.widgetName;
+    vm.widgetsBundle;
+
+    vm.bundlesScope = 'system';
+
+    if (userService.getAuthority() === 'TENANT_ADMIN') {
+        vm.bundlesScope = 'tenant';
+    }
+
+    function cancel () {
+        $mdDialog.cancel();
+    }
+
+    function saveAs () {
+        $mdDialog.hide({widgetName: vm.widgetName, bundleId: vm.widgetsBundle.id.id, bundleAlias: vm.widgetsBundle.alias});
+    }
+
+}
diff --git a/ui/src/app/widget/save-widget-type-as.tpl.html b/ui/src/app/widget/save-widget-type-as.tpl.html
new file mode 100644
index 0000000..ec84ef3
--- /dev/null
+++ b/ui/src/app/widget/save-widget-type-as.tpl.html
@@ -0,0 +1,60 @@
+<!--
+
+    Copyright © 2016 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="{{ 'widget.save-widget-type-as' | translate }}">
+    <form name="theForm" ng-submit="vm.saveAs()">
+        <md-toolbar>
+            <div class="md-toolbar-tools">
+                <h2 translate>widget.save-widget-type-as</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-show="loading"></md-progress-linear>
+        <span style="min-height: 5px;" flex="" ng-show="!loading"></span>
+        <md-dialog-content>
+            <div class="md-dialog-content">
+                <fieldset>
+                    <span translate>widget.save-widget-type-as-text</span>
+                    <md-input-container class="md-block">
+                        <label translate>widget.title</label>
+                        <input required name="title" ng-model="vm.widgetName">
+                        <div ng-messages="theForm.title.$error">
+                            <div translate ng-message="required">widget.title-required</div>
+                        </div>
+                    </md-input-container>
+                    <tb-widgets-bundle-select flex
+                                              ng-model="vm.widgetsBundle"
+                                              tb-required="true"
+                                              bundles-scope="{{vm.bundlesScope}}">
+                    </tb-widgets-bundle-select>
+                </fieldset>
+            </div>
+        </md-dialog-content>
+        <md-dialog-actions layout="row">
+            <span flex></span>
+            <md-button ng-disabled="loading || theForm.$invalid" type="submit" class="md-raised md-primary">
+                {{ 'action.saveAs' | 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>
diff --git a/ui/src/app/widget/select-widget-type.controller.js b/ui/src/app/widget/select-widget-type.controller.js
new file mode 100644
index 0000000..cb21e96
--- /dev/null
+++ b/ui/src/app/widget/select-widget-type.controller.js
@@ -0,0 +1,34 @@
+/*
+ * Copyright © 2016 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 SelectWidgetTypeController($mdDialog, types) {
+
+    var vm = this;
+
+    vm.types = types;
+
+    vm.cancel = cancel;
+    vm.typeSelected = typeSelected;
+
+    function cancel() {
+        $mdDialog.cancel();
+    }
+
+    function typeSelected(widgetType) {
+        $mdDialog.hide(widgetType);
+    }
+
+}
diff --git a/ui/src/app/widget/select-widget-type.tpl.html b/ui/src/app/widget/select-widget-type.tpl.html
new file mode 100644
index 0000000..a805401
--- /dev/null
+++ b/ui/src/app/widget/select-widget-type.tpl.html
@@ -0,0 +1,67 @@
+<!--
+
+    Copyright © 2016 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 style="width: 800px;" aria-label="{{ 'widget.select-widget-type' | translate }}">
+    <form name="theForm">
+        <md-toolbar>
+            <div class="md-toolbar-tools">
+                <h2 translate>widget.select-widget-type</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-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="column" layout-gt-sm="row" layout-align="center center">
+                        <md-button class="tb-card-button md-raised md-primary" layout="column"
+                                   ng-click="vm.typeSelected(vm.types.widgetType.timeseries.value)">
+                            <md-icon class="material-icons tb-md-96" aria-label="{{ vm.types.widgetType.timeseries.name | translate }}">
+                                timeline
+                            </md-icon>
+                            <span translate>{{vm.types.widgetType.timeseries.name}}</span>
+                        </md-button>
+                        <md-button class="tb-card-button md-raised md-primary" layout="column"
+                                   ng-click="vm.typeSelected(vm.types.widgetType.latest.value)">
+                            <md-icon class="material-icons tb-md-96"
+                                     aria-label="{{ vm.types.widgetType.latest.name | translate }}">track_changes
+                            </md-icon>
+                            <span translate>{{vm.types.widgetType.latest.name}}</span>
+                        </md-button>
+                        <md-button class="tb-card-button md-raised md-primary" layout="column"
+                                   ng-click="vm.typeSelected(vm.types.widgetType.rpc.value)">
+                            <md-icon class="material-icons tb-md-96"
+                                     aria-label="{{ vm.types.widgetType.rpc.name | translate }}" md-svg-icon="mdi:developer-board">
+                            </md-icon>
+                            <span translate>{{vm.types.widgetType.rpc.name}}</span>
+                        </md-button>
+                    </div>
+                </fieldset>
+            </div>
+        </md-dialog-content>
+        <md-dialog-actions layout="row">
+            <span flex></span>
+            <md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' |
+                translate }}
+            </md-button>
+        </md-dialog-actions>
+    </form>
+</md-dialog>
diff --git a/ui/src/app/widget/widget-editor.controller.js b/ui/src/app/widget/widget-editor.controller.js
new file mode 100644
index 0000000..ca9008a
--- /dev/null
+++ b/ui/src/app/widget/widget-editor.controller.js
@@ -0,0 +1,645 @@
+/*
+ * Copyright © 2016 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 $ from 'jquery';
+import ace from 'brace';
+import 'brace/ext/language_tools';
+import 'brace/mode/javascript';
+import 'brace/mode/html';
+import 'brace/mode/css';
+import 'brace/mode/json';
+import 'ace-builds/src-min-noconflict/snippets/javascript';
+import 'ace-builds/src-min-noconflict/snippets/text';
+import 'ace-builds/src-min-noconflict/snippets/html';
+import 'ace-builds/src-min-noconflict/snippets/css';
+import 'ace-builds/src-min-noconflict/snippets/json';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import saveWidgetTypeAsTemplate from './save-widget-type-as.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+import Split from 'split.js';
+import beautify from 'js-beautify';
+
+const js_beautify = beautify.js;
+const html_beautify = beautify.html;
+const css_beautify = beautify.css;
+
+/* eslint-disable angular/angularelement */
+
+/*@ngInject*/
+export default function WidgetEditorController(widgetService, userService, types, toast, hotkeys,
+                                               $element, $rootScope, $scope, $state, $stateParams, $timeout,
+                                               $window, $document, $translate, $mdDialog) {
+
+    var Range = ace.acequire("ace/range").Range;
+    var ace_editors = [];
+    var js_editor;
+    var iframe = $('iframe', $element);
+    var gotError = false;
+    var errorMarkers = [];
+    var errorAnnotationId = -1;
+    var elem = $($element);
+
+    var widgetsBundleId = $stateParams.widgetsBundleId;
+
+    var vm = this;
+
+    vm.widgetsBundle;
+    vm.isDirty = false;
+    vm.fullscreen = false;
+    vm.widgetType = null;
+    vm.widget = null;
+    vm.origWidget = null;
+    vm.widgetTypes = types.widgetType;
+    vm.iframeWidgetEditModeInited = false;
+    vm.layoutInited = false;
+    vm.htmlEditorOptions = {
+        useWrapMode: true,
+        mode: 'html',
+        advanced: {
+            enableSnippets: true,
+            enableBasicAutocompletion: true,
+            enableLiveAutocompletion: true
+        },
+        onLoad: function (_ace) {
+            ace_editors.push(_ace);
+        }
+    };
+    vm.cssEditorOptions = {
+        useWrapMode: true,
+        mode: 'css',
+        advanced: {
+            enableSnippets: true,
+            enableBasicAutocompletion: true,
+            enableLiveAutocompletion: true
+        },
+        onLoad: function (_ace) {
+            ace_editors.push(_ace);
+        }
+    };
+    vm.jsonSettingsEditorOptions = {
+        useWrapMode: true,
+        mode: 'json',
+        advanced: {
+            enableSnippets: true,
+            enableBasicAutocompletion: true,
+            enableLiveAutocompletion: true
+        },
+        onLoad: function (_ace) {
+            ace_editors.push(_ace);
+        }
+    };
+    vm.dataKeyJsonSettingsEditorOptions = {
+        useWrapMode: true,
+        mode: 'json',
+        advanced: {
+            enableSnippets: true,
+            enableBasicAutocompletion: true,
+            enableLiveAutocompletion: true
+        },
+        onLoad: function (_ace) {
+            ace_editors.push(_ace);
+        }
+    };
+    vm.jsEditorOptions = {
+        useWrapMode: true,
+        mode: 'javascript',
+        advanced: {
+            enableSnippets: true,
+            enableBasicAutocompletion: true,
+            enableLiveAutocompletion: true
+        },
+        onLoad: function (_ace) {
+            ace_editors.push(_ace);
+            js_editor = _ace;
+            js_editor.session.on("change", function () {
+                cleanupJsErrors();
+            });
+        }
+    };
+
+    vm.addResource = addResource;
+    vm.applyWidgetScript = applyWidgetScript;
+    vm.beautifyCss = beautifyCss;
+    vm.beautifyDataKeyJson = beautifyDataKeyJson;
+    vm.beautifyHtml = beautifyHtml;
+    vm.beautifyJs = beautifyJs;
+    vm.beautifyJson = beautifyJson;
+    vm.removeResource = removeResource;
+    vm.undoDisabled = undoDisabled;
+    vm.undoWidget = undoWidget;
+    vm.saveDisabled = saveDisabled;
+    vm.saveWidget = saveWidget;
+    vm.saveAsDisabled = saveAsDisabled;
+    vm.saveWidgetAs = saveWidgetAs;
+    vm.toggleFullscreen = toggleFullscreen;
+    vm.isReadOnly = isReadOnly;
+
+    initWidgetEditor();
+
+    function initWidgetEditor() {
+
+        $rootScope.loading = true;
+
+        widgetService.getWidgetsBundle(widgetsBundleId).then(
+            function success(widgetsBundle) {
+                vm.widgetsBundle = widgetsBundle;
+                if ($stateParams.widgetTypeId) {
+                    widgetService.getWidgetTypeById($stateParams.widgetTypeId).then(
+                        function success(widgetType) {
+                            setWidgetType(widgetType)
+                            widgetTypeLoaded();
+                        },
+                        function fail() {
+                            toast.showError($translate.instant('widget.widget-type-load-failed-error'));
+                            widgetTypeLoaded();
+                        }
+                    );
+                } else {
+                    var type = $stateParams.widgetType;
+                    if (!type) {
+                        type = types.widgetType.timeseries.value;
+                    }
+                    widgetService.getWidgetTemplate(type).then(
+                        function success(widgetTemplate) {
+                            vm.widget = angular.copy(widgetTemplate);
+                            vm.widget.widgetName = null;
+                            vm.origWidget = angular.copy(vm.widget);
+                            vm.isDirty = true;
+                            widgetTypeLoaded();
+                        },
+                        function fail() {
+                            toast.showError($translate.instant('widget.widget-template-load-failed-error'));
+                            widgetTypeLoaded();
+                        }
+                    );
+                }
+            },
+            function fail() {
+                toast.showError($translate.instant('widget.widget-type-load-failed-error'));
+                widgetTypeLoaded();
+            }
+        );
+
+    }
+
+    function setWidgetType(widgetType) {
+        vm.widgetType = widgetType;
+        vm.widget = widgetService.toWidgetInfo(vm.widgetType);
+        var config = angular.fromJson(vm.widget.defaultConfig);
+        vm.widget.defaultConfig = angular.toJson(config)
+        vm.origWidget = angular.copy(vm.widget);
+        vm.isDirty = false;
+    }
+
+    function widgetTypeLoaded() {
+        initHotKeys();
+
+        initWatchers();
+
+        angular.element($document[0]).ready(function () {
+            var w = elem.width();
+            if (w > 0) {
+                initSplitLayout();
+            } else {
+                $scope.$watch(
+                    function () {
+                        return elem[0].offsetWidth || parseInt(elem.css('width'), 10);
+                    },
+                    function (newSize) {
+                        if (newSize > 0) {
+                            initSplitLayout();
+                        }
+                    }
+                );
+            }
+        });
+
+        iframe.attr('data-widget', angular.toJson(vm.widget));
+        iframe.attr('src', '/widget-editor');
+    }
+
+    function undoDisabled() {
+        return $scope.loading
+                || !vm.isDirty
+                || !vm.iframeWidgetEditModeInited
+                || vm.saveWidgetPending
+                || vm.saveWidgetAsPending;
+    }
+
+    function saveDisabled() {
+        return vm.isReadOnly()
+                || $scope.loading
+                || !vm.isDirty
+                || !vm.iframeWidgetEditModeInited
+                || vm.saveWidgetPending
+                || vm.saveWidgetAsPending;
+    }
+
+    function saveAsDisabled() {
+        return $scope.loading
+            || !vm.iframeWidgetEditModeInited
+            || vm.saveWidgetPending
+            || vm.saveWidgetAsPending;
+    }
+
+    function initHotKeys() {
+        $translate(['widget.undo', 'widget.save', 'widget.saveAs', 'widget.toggle-fullscreen', 'widget.run']).then(function (translations) {
+            hotkeys.bindTo($scope)
+                .add({
+                    combo: 'ctrl+q',
+                    description: translations['widget.undo'],
+                    allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
+                    callback: function (event) {
+                        if (!undoDisabled()) {
+                            event.preventDefault();
+                            undoWidget();
+                        }
+                    }
+                })
+                .add({
+                    combo: 'ctrl+s',
+                    description: translations['widget.save'],
+                    allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
+                    callback: function (event) {
+                        if (!saveDisabled()) {
+                            event.preventDefault();
+                            saveWidget();
+                        }
+                    }
+                })
+                .add({
+                    combo: 'shift+ctrl+s',
+                    description: translations['widget.saveAs'],
+                    allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
+                    callback: function (event) {
+                        if (!saveAsDisabled()) {
+                            event.preventDefault();
+                            saveWidgetAs();
+                        }
+                    }
+                })
+                .add({
+                    combo: 'shift+ctrl+f',
+                    description: translations['widget.toggle-fullscreen'],
+                    allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
+                    callback: function (event) {
+                        event.preventDefault();
+                        toggleFullscreen();
+                    }
+                })
+                .add({
+                    combo: 'ctrl+enter',
+                    description: translations['widget.run'],
+                    allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
+                    callback: function (event) {
+                        event.preventDefault();
+                        applyWidgetScript();
+                    }
+            });
+        });
+    }
+
+    function initWatchWidget() {
+        $scope.widgetWatcher = $scope.$watch('vm.widget', function (newVal, oldVal) {
+            if (!angular.equals(newVal, oldVal)) {
+                vm.isDirty = true;
+            }
+        }, true);
+    }
+
+    function initWatchers() {
+        initWatchWidget();
+
+        $scope.$watch('vm.widget.type', function (newVal, oldVal) {
+            if (!angular.equals(newVal, oldVal)) {
+                var config = angular.fromJson(vm.widget.defaultConfig);
+                if (vm.widget.type !== types.widgetType.rpc.value) {
+                    if (config.targetDeviceAliases) {
+                        delete config.targetDeviceAliases;
+                    }
+                    if (!config.datasources) {
+                        config.datasources = [];
+                    }
+                    if (!config.timewindow) {
+                        config.timewindow = {
+                            realtime: {
+                                timewindowMs: 60000
+                            }
+                        };
+                    }
+                    for (var i in config.datasources) {
+                        var datasource = config.datasources[i];
+                        datasource.type = vm.widget.type;
+                        if (vm.widget.type !== types.widgetType.timeseries.value && datasource.intervalSec) {
+                            delete datasource.intervalSec;
+                        } else if (vm.widget.type === types.widgetType.timeseries.value && !datasource.intervalSec) {
+                            datasource.intervalSec = 60;
+                        }
+                    }
+                } else {
+                    if (config.datasources) {
+                        delete config.datasources;
+                    }
+                    if (config.timewindow) {
+                        delete config.timewindow;
+                    }
+                    if (!config.targetDeviceAliases) {
+                        config.targetDeviceAliases = [];
+                    }
+                }
+                vm.widget.defaultConfig = angular.toJson(config);
+            }
+        });
+
+        $scope.$on('widgetEditModeInited', function () {
+            vm.iframeWidgetEditModeInited = true;
+            if (vm.saveWidgetPending || vm.saveWidgetAsPending) {
+                if (!vm.saveWidgetTimeout) {
+                    vm.saveWidgetTimeout = $timeout(function () {
+                        if (!gotError) {
+                            if (vm.saveWidgetPending) {
+                                commitSaveWidget();
+                            } else if (vm.saveWidgetAsPending) {
+                                commitSaveWidgetAs();
+                            }
+                        } else {
+                            toast.showError($translate.instant('widget.unable-to-save-widget-error'));
+                            vm.saveWidgetPending = false;
+                            vm.saveWidgetAsPending = false;
+                            initWatchWidget();
+                        }
+                        vm.saveWidgetTimeout = undefined;
+                    }, 1500);
+                }
+            }
+        });
+
+        $scope.$on('widgetEditUpdated', function (event, widget) {
+            vm.widget.sizeX = widget.sizeX / 2;
+            vm.widget.sizeY = widget.sizeY / 2;
+            vm.widget.defaultConfig = angular.toJson(widget.config);
+            iframe.attr('data-widget', angular.toJson(vm.widget));
+        });
+
+        $scope.$on('widgetException', function (event, details) {
+            if (!gotError) {
+                gotError = true;
+                var errorInfo = 'Error:';
+                if (details.name) {
+                    errorInfo += ' ' + details.name + ':';
+                }
+                if (details.message) {
+                    errorInfo += ' ' + details.message;
+                }
+                if (details.lineNumber) {
+                    errorInfo += '<br>Line ' + details.lineNumber;
+                    if (details.columnNumber) {
+                        errorInfo += ' column ' + details.columnNumber;
+                    }
+                    errorInfo += ' of script.';
+                }
+                if (!vm.saveWidgetPending && !vm.saveWidgetAsPending) {
+                    toast.showError(errorInfo, $('#javascript_panel', $element)[0]);
+                }
+                if (js_editor && details.lineNumber) {
+                    var line = details.lineNumber - 1;
+                    var column = 0;
+                    if (details.columnNumber) {
+                        column = details.columnNumber;
+                    }
+
+                    var errorMarkerId = js_editor.session.addMarker(new Range(line, 0, line, Infinity), "ace_active-line", "screenLine");
+                    errorMarkers.push(errorMarkerId);
+                    var annotations = js_editor.session.getAnnotations();
+                    var errorAnnotation = {
+                        row: line,
+                        column: column,
+                        text: details.message,
+                        type: "error"
+                    };
+                    errorAnnotationId = annotations.push(errorAnnotation) - 1;
+                    js_editor.session.setAnnotations(annotations);
+                }
+            }
+        });
+    }
+
+    function cleanupJsErrors() {
+        toast.hide();
+        for (var i = 0; i < errorMarkers.length; i++) {
+            js_editor.session.removeMarker(errorMarkers[i]);
+        }
+        errorMarkers = [];
+        if (errorAnnotationId && errorAnnotationId > -1) {
+            var annotations = js_editor.session.getAnnotations();
+            annotations.splice(errorAnnotationId, 1);
+            js_editor.session.setAnnotations(annotations);
+            errorAnnotationId = -1;
+        }
+    }
+
+    function onDividerDrag() {
+        for (var i in ace_editors) {
+            var ace = ace_editors[i];
+            ace.resize();
+            ace.renderer.updateFull();
+        }
+    }
+
+    function initSplitLayout() {
+        if (!vm.layoutInited) {
+            Split([$('#top_panel', $element)[0], $('#bottom_panel', $element)[0]], {
+                sizes: [35, 65],
+                gutterSize: 8,
+                cursor: 'row-resize',
+                direction: 'vertical',
+                onDrag: function () {
+                    onDividerDrag()
+                }
+            });
+
+            Split([$('#top_left_panel', $element)[0], $('#top_right_panel', $element)[0]], {
+                sizes: [50, 50],
+                gutterSize: 8,
+                cursor: 'col-resize',
+                onDrag: function () {
+                    onDividerDrag()
+                }
+            });
+
+            Split([$('#javascript_panel', $element)[0], $('#frame_panel', $element)[0]], {
+                sizes: [50, 50],
+                gutterSize: 8,
+                cursor: 'col-resize',
+                onDrag: function () {
+                    onDividerDrag()
+                }
+            });
+
+            onDividerDrag();
+
+            $scope.$applyAsync(function () {
+                vm.layoutInited = true;
+                $rootScope.loading = false;
+                var w = angular.element($window);
+                $timeout(function () {
+                    w.triggerHandler('resize')
+                });
+            });
+
+        }
+    }
+
+    function removeResource(index) {
+        if (index > -1) {
+            vm.widget.resources.splice(index, 1);
+        }
+    }
+
+    function addResource() {
+        vm.widget.resources.push({url: ''});
+    }
+
+    function applyWidgetScript() {
+        cleanupJsErrors();
+        gotError = false;
+        vm.iframeWidgetEditModeInited = false;
+        var config = angular.fromJson(vm.widget.defaultConfig);
+        config.title = vm.widget.widgetName;
+        vm.widget.defaultConfig = angular.toJson(config);
+        iframe.attr('data-widget', angular.toJson(vm.widget));
+        iframe[0].contentWindow.location.reload(true);
+    }
+
+    function toggleFullscreen() {
+        vm.fullscreen = !vm.fullscreen;
+    }
+
+    function isReadOnly() {
+        if (userService.getAuthority() === 'TENANT_ADMIN') {
+            return !vm.widgetsBundle || vm.widgetsBundle.tenantId.id === types.id.nullUid;
+        } else {
+            return userService.getAuthority() != 'SYS_ADMIN';
+        }
+    }
+
+    function undoWidget() {
+        if ($scope.widgetWatcher) {
+            $scope.widgetWatcher();
+        }
+        vm.widget = angular.copy(vm.origWidget);
+        vm.isDirty = false;
+        initWatchWidget();
+        applyWidgetScript();
+    }
+
+    function saveWidget() {
+        if (!vm.widget.widgetName) {
+            toast.showError($translate.instant('widget.missing-widget-title-error'));
+        } else {
+            $scope.widgetWatcher();
+            vm.saveWidgetPending = true;
+            applyWidgetScript();
+        }
+    }
+
+    function saveWidgetAs($event) {
+        $scope.widgetWatcher();
+        vm.saveWidgetAsPending = true;
+        vm.saveWidgetAsEvent = $event;
+        applyWidgetScript();
+    }
+
+    function commitSaveWidget() {
+        var id = (vm.widgetType && vm.widgetType.id) ? vm.widgetType.id : undefined;
+        widgetService.saveWidgetType(vm.widget, id, vm.widgetsBundle.alias).then(
+            function success(widgetType) {
+                setWidgetType(widgetType)
+                vm.saveWidgetPending = false;
+                initWatchWidget();
+                toast.showSuccess($translate.instant('widget.widget-saved'), 500);
+            }, function fail() {
+                vm.saveWidgetPending = false;
+                initWatchWidget();
+            }
+        );
+    }
+
+    function commitSaveWidgetAs() {
+        $mdDialog.show({
+            controller: 'SaveWidgetTypeAsController',
+            controllerAs: 'vm',
+            templateUrl: saveWidgetTypeAsTemplate,
+            parent: angular.element($document[0].body),
+            fullscreen: true,
+            targetEvent: vm.saveWidgetAsEvent
+        }).then(function (saveWidgetAsData) {
+            vm.widget.widgetName = saveWidgetAsData.widgetName;
+            vm.widget.alias = undefined;
+            var config = angular.fromJson(vm.widget.defaultConfig);
+            config.title = vm.widget.widgetName;
+            vm.widget.defaultConfig = angular.toJson(config);
+
+            vm.saveWidgetAsPending = false;
+            vm.isDirty = false;
+            initWatchWidget();
+            widgetService.saveWidgetType(vm.widget, undefined, saveWidgetAsData.bundleAlias).then(
+                function success(widgetType) {
+                    $state.go('home.widgets-bundles.widget-types.widget-type',
+                        {widgetsBundleId: saveWidgetAsData.bundleId, widgetTypeId: widgetType.id.id});
+                },
+                function fail() {
+                    vm.saveWidgetAsPending = false;
+                    initWatchWidget();
+                }
+            );
+        }, function () {
+            vm.saveWidgetAsPending = false;
+            initWatchWidget();
+        });
+    }
+
+    function beautifyJs() {
+        var res = js_beautify(vm.widget.controllerScript, {indent_size: 4, wrap_line_length: 60});
+        vm.widget.controllerScript = res;
+    }
+
+    function beautifyHtml() {
+        var res = html_beautify(vm.widget.templateHtml, {indent_size: 4, wrap_line_length: 60});
+        vm.widget.templateHtml = res;
+    }
+
+    function beautifyCss() {
+        var res = css_beautify(vm.widget.templateCss, {indent_size: 4});
+        vm.widget.templateCss = res;
+    }
+
+    function beautifyJson() {
+        var res = js_beautify(vm.widget.settingsSchema, {indent_size: 4});
+        vm.widget.settingsSchema = res;
+    }
+
+    function beautifyDataKeyJson() {
+        var res = js_beautify(vm.widget.dataKeySettingsSchema, {indent_size: 4});
+        vm.widget.dataKeySettingsSchema = res;
+    }
+
+}
+
+/* eslint-enable angular/angularelement */
\ No newline at end of file
diff --git a/ui/src/app/widget/widget-editor.scss b/ui/src/app/widget/widget-editor.scss
new file mode 100644
index 0000000..ef79477
--- /dev/null
+++ b/ui/src/app/widget/widget-editor.scss
@@ -0,0 +1,151 @@
+/**
+ * Copyright © 2016 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 "~compass-sass-mixins/lib/compass";
+
+$edit-toolbar-height: 40px;
+
+.tb-editor {
+  .tb-split {
+    @include box-sizing(border-box);
+    overflow-y: auto;
+    overflow-x: hidden;
+  }
+
+  .ace_editor {
+    font-size: 14px !important;
+  }
+
+  .tb-content {
+    border: 1px solid #C0C0C0;
+  }
+
+  .gutter {
+    background-color: transparent;
+
+    background-repeat: no-repeat;
+    background-position: 50%;
+  }
+
+  .gutter.gutter-horizontal {
+    cursor: col-resize;
+    background-image: url('../../../node_modules/split.js/grips/vertical.png');
+  }
+
+  .gutter.gutter-vertical {
+    cursor: row-resize;
+    background-image: url('../../../node_modules/split.js/grips/horizontal.png');
+  }
+
+  .tb-split.tb-split-horizontal, .gutter.gutter-horizontal {
+    height: 100%;
+    float: left;
+  }
+
+  .tb-split.tb-split-vertical {
+    display: flex;
+    .tb-split.tb-content {
+      height: 100%;
+    }
+  }
+}
+
+.tb-split-vertical {
+  md-tabs {
+
+    md-tabs-content-wrapper {
+      height: calc(100% - 49px);
+
+      md-tab-content {
+        height: 100%;
+
+        & > div {
+          height: 100%;
+        }
+
+      }
+
+    }
+
+  }
+}
+
+div.tb-editor-area-title-panel {
+  position: absolute;
+  font-size: 0.800rem;
+  font-weight: 500;
+  top: 5px;
+  right: 20px;
+  z-index: 5;
+  label {
+    color: #00acc1;
+    background: rgba(220, 220, 220, 0.35);
+    border-radius: 5px;
+    padding: 4px;
+    text-transform: uppercase;
+  }
+  .md-button {
+    color: #7B7B7B;
+    min-width: 32px;
+    min-height: 15px;
+    line-height: 15px;
+    font-size: 0.800rem;
+    margin: 0;
+    padding: 4px;
+    background: rgba(220, 220, 220, 0.35);
+  }
+}
+
+.tb-resize-container {
+  overflow-y: auto;
+  height: 100%;
+  width: 100%;
+  position: relative;
+
+  .ace_editor {
+    height: 100%;
+  }
+}
+
+md-toolbar.tb-edit-toolbar {
+
+  min-height: $edit-toolbar-height !important;
+  max-height: $edit-toolbar-height !important;
+
+  .md-toolbar-tools {
+    min-height: $edit-toolbar-height !important;
+    max-height: $edit-toolbar-height !important;
+    .md-button {
+      min-width: 65px;
+      min-height: 30px;
+      line-height: 30px;
+      font-size: 12px;
+      md-icon {
+        font-size: 20px;
+      }
+      span {
+        padding-right: 6px;
+      }
+    }
+    md-input-container {
+      input {
+        font-size: 1.200rem;
+        font-weight: 400;
+        letter-spacing: 0.005em;
+        height: 28px;
+      }
+    }
+  }
+}
diff --git a/ui/src/app/widget/widget-editor.tpl.html b/ui/src/app/widget/widget-editor.tpl.html
new file mode 100644
index 0000000..a5e661d
--- /dev/null
+++ b/ui/src/app/widget/widget-editor.tpl.html
@@ -0,0 +1,258 @@
+<!--
+
+    Copyright © 2016 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 flex layout="column" tb-confirm-on-exit is-dirty="vm.isDirty">
+    <div flex layout="column" tb-expand-fullscreen="vm.fullscreen" hide-expand-button="true">
+        <md-toolbar class="md-whiteframe-z1 tb-edit-toolbar md-hue-3">
+            <div flex class="md-toolbar-tools">
+                <md-input-container>
+                    <label>&nbsp;</label>
+                    <input ng-disabled="vm.isReadOnly()" ng-model="vm.widget.widgetName" placeholder="{{ 'widget.title' | translate }}">
+                </md-input-container>
+                <md-input-container>
+                    <md-select ng-disabled="vm.isReadOnly()" placeholder="{{ 'widget.type' | translate }}" required id="widgetType"
+                               ng-model="vm.widget.type">
+                        <md-option ng-repeat="type in vm.widgetTypes" value="{{type.value}}">
+                            {{ type.name | translate }}
+                        </md-option>
+                    </md-select>
+                </md-input-container>
+                <span flex=""></span>
+                <md-button hide-xs hide-sm aria-label="{{ 'widget.run' | translate }}" ng-disabled="!vm.iframeWidgetEditModeInited"
+                           ng-click="vm.applyWidgetScript()">
+                    <md-tooltip md-direction="bottom">
+                        {{ 'widget.run' | translate }} (CTRL + Return)
+                    </md-tooltip>
+                    <md-icon aria-label="{{ 'action.run' | translate }}">play_arrow</md-icon>
+                    <span translate>action.run</span>
+                </md-button>
+                <md-button hide-xs hide-sm ng-disabled="vm.undoDisabled()" class="md-raised"
+                           aria-label="{{ 'widget.undo' | translate }}" ng-click="vm.undoWidget()">
+                    <md-tooltip md-direction="bottom">
+                        {{ 'widget.undo' | translate }} (CTRL + Q)
+                    </md-tooltip>
+                    <md-icon aria-label="{{ 'action.undo' | translate }}">undo</md-icon>
+                    <span translate>action.undo</span>
+                </md-button>
+                <md-button ng-if="!vm.isReadOnly()" hide-xs hide-sm ng-disabled="vm.saveDisabled()" class="md-raised"
+                           aria-label="{{ 'widget.save' | translate }}" ng-click="vm.saveWidget()" tb-circular-progress="vm.saveWidgetPending">
+                    <md-tooltip md-direction="bottom">
+                        {{ 'widget.save' | translate }} (CTRL + S)
+                    </md-tooltip>
+                    <md-icon aria-label="{{ 'action.save' | translate }}">save</md-icon>
+                    <span translate>action.save</span>
+                </md-button>
+                <md-button hide-xs hide-sm ng-disabled="vm.saveAsDisabled()" class="md-raised"
+                           aria-label="{{ 'widget.saveAs' | translate }}" ng-click="vm.saveWidgetAs($event)" tb-circular-progress="vm.saveWidgetAsPending">
+                    <md-tooltip md-direction="bottom">
+                        {{ 'widget.saveAs' | translate }} (Shift + CTRL + S)
+                    </md-tooltip>
+                    <md-icon aria-label="{{ 'action.saveAs' | translate }}">save</md-icon>
+                    <span translate>action.saveAs</span>
+                </md-button>
+                <md-button hide-xs hide-sm aria-label="{{ 'widget.toggle-fullscreen' | translate }}" ng-click="vm.toggleFullscreen()">
+                    <md-tooltip md-direction="bottom">
+                        {{ 'widget.toggle-fullscreen' | translate }} (Shift + CTRL + F)
+                    </md-tooltip>
+                    <md-icon ng-show="!vm.fullscreen" aria-label="{{ 'widget.toggle-fullscreen' | translate }}">
+                        fullscreen
+                    </md-icon>
+                    <md-icon ng-show="vm.fullscreen" aria-label="{{ 'widget.toggle-fullscreen' | translate }}">
+                        fullscreen_exit
+                    </md-icon>
+                    <span translate hide-xs hide-sm>widget.toggle-fullscreen</span>
+                </md-button>
+                <md-menu hide-gt-sm md-position-mode="target-right target">
+                    <md-button class="md-icon-button"  aria-label="{{ 'home.open-user-menu' | translate }}" ng-click="$mdOpenMenu($event)">
+                        <md-icon md-menu-origin aria-label="{{ 'home.open-user-menu' | translate }}" class="material-icons">more_vert</md-icon>
+                    </md-button>
+                    <md-menu-content width="4">
+                        <md-menu-item>
+                            <md-button ng-disabled="!vm.iframeWidgetEditModeInited"
+                                    ng-click="vm.applyWidgetScript()">
+                                <md-icon md-menu-align-target aria-label="{{ 'action.run' | translate }}" class="material-icons">play_arrow</md-icon>
+                                <span translate>action.run</span>
+                            </md-button>
+                        </md-menu-item>
+                        <md-menu-item>
+                            <md-button ng-disabled="vm.undoDisabled()"
+                                       ng-click="vm.undoWidget()">
+                                <md-icon md-menu-align-target aria-label="{{ 'action.undo' | translate }}" class="material-icons">undo</md-icon>
+                                <span translate>action.undo</span>
+                            </md-button>
+                        </md-menu-item>
+                        <md-menu-item ng-if="!vm.isReadOnly()">
+                            <md-button ng-disabled="vm.saveDisabled()"
+                                    ng-click="vm.saveWidget()">
+                                <md-icon md-menu-align-target aria-label="{{ 'action.save' | translate }}" class="material-icons">save</md-icon>
+                                <span translate>action.save</span>
+                            </md-button>
+                        </md-menu-item>
+                        <md-menu-item>
+                            <md-button ng-disabled="vm.saveAsDisabled()"
+                                       ng-click="vm.saveWidgetAs($event)">
+                                <md-icon md-menu-align-target aria-label="{{ 'action.saveAs' | translate }}" class="material-icons">save</md-icon>
+                                <span translate>action.saveAs</span>
+                            </md-button>
+                        </md-menu-item>
+                    </md-menu-content>
+                </md-menu>
+            </div>
+        </md-toolbar>
+        <div flex style="position: relative;">
+            <div class="tb-editor tb-absolute-fill" style="">
+                <div id="top_panel" class="tb-split tb-split-vertical">
+                    <div id="top_left_panel" class="tb-split tb-content">
+                        <md-tabs md-selected="1" md-dynamic-height md-border-bottom style="width: 100%; height: 100%;">
+                            <md-tab label="{{ 'widget.resources' | translate }}" style="width: 100%; height: 100%;">
+                                <div class="tb-resize-container" style="background-color: #fff;">
+                                    <div class="md-padding">
+                                        <div flex layout="row"
+                                             ng-repeat="resource in vm.widget.resources track by $index"
+                                             style="max-height: 40px;" layout-align="start center">
+                                            <md-input-container flex md-no-float class="md-block"
+                                                                style="margin: 10px 0px 0px 0px; max-height: 40px;">
+                                                <input placeholder="{{ 'widget.resource-url' | translate }}"
+                                                       ng-required="true" name="resource" ng-model="resource.url">
+                                            </md-input-container>
+                                            <md-button ng-disabled="loading" class="md-icon-button md-primary"
+                                                       ng-click="vm.removeResource($index)"
+                                                       aria-label="{{ 'action.remove' | translate }}">
+                                                <md-tooltip md-direction="top">
+                                                    {{ 'widget.remove-resource' | translate }}
+                                                </md-tooltip>
+                                                <md-icon aria-label="{{ 'action.delete' | translate }}"
+                                                         class="material-icons">
+                                                    close
+                                                </md-icon>
+                                            </md-button>
+                                        </div>
+                                        <div>
+                                            <md-button ng-disabled="loading" class="md-primary md-raised"
+                                                       ng-click="vm.addResource()"
+                                                       aria-label="{{ 'action.add' | translate }}">
+                                                <md-tooltip md-direction="top">
+                                                    {{ 'widget.add-resource' | translate }}
+                                                </md-tooltip>
+                                                <span translate>action.add</span>
+                                            </md-button>
+                                        </div>
+                                    </div>
+                                </div>
+                            </md-tab>
+                            <md-tab label="{{ 'widget.html' | translate }}" style="width: 100%; height: 100%;">
+                                <div class="tb-resize-container" tb-expand-fullscreen expand-button-id="expand-button">
+                                    <div class="tb-editor-area-title-panel">
+                                        <md-button aria-label="{{ 'widget.tidy' | translate }}"
+                                                   ng-click="vm.beautifyHtml()">{{ 'widget.tidy' | translate }}
+                                        </md-button>
+                                        <md-button id="expand-button"
+                                                   aria-label="{{ 'fullscreen.fullscreen' | translate }}"
+                                                   class="md-icon-button tb-md-32"></md-button>
+                                    </div>
+                                    <div id="html_input" ui-ace="vm.htmlEditorOptions"
+                                         ng-model="vm.widget.templateHtml"></div>
+                                </div>
+                            </md-tab>
+                            <md-tab label="{{ 'widget.css' | translate }}" style="width: 100%; height: 100%;">
+                                <div class="tb-resize-container" tb-expand-fullscreen expand-button-id="expand-button">
+                                    <div class="tb-editor-area-title-panel">
+                                        <md-button aria-label="{{ 'widget.tidy' | translate }}"
+                                                   ng-click="vm.beautifyCss()">{{ 'widget.tidy' | translate }}
+                                        </md-button>
+                                        <md-button id="expand-button"
+                                                   aria-label="{{ 'fullscreen.fullscreen' | translate }}"
+                                                   class="md-icon-button tb-md-32"></md-button>
+                                    </div>
+                                    <div id="css_input" ui-ace="vm.cssEditorOptions"
+                                         ng-model="vm.widget.templateCss"></div>
+                                </div>
+                            </md-tab>
+                        </md-tabs>
+                    </div>
+                    <div id="top_right_panel" class="tb-split tb-content">
+                        <md-tabs md-dynamic-height md-border-bottom style="width: 100%; height: 100%;">
+                            <md-tab label="{{ 'widget.settings-schema' | translate }}"
+                                    style="width: 100%; height: 100%;">
+                                <div class="tb-resize-container" tb-expand-fullscreen expand-button-id="expand-button">
+                                    <div class="tb-editor-area-title-panel">
+                                        <md-button aria-label="{{ 'widget.tidy' | translate }}"
+                                                   ng-click="vm.beautifyJson()">{{ 'widget.tidy' | translate }}
+                                        </md-button>
+                                        <md-button id="expand-button"
+                                                   aria-label="{{ 'fullscreen.fullscreen' | translate }}"
+                                                   class="md-icon-button tb-md-32"></md-button>
+                                    </div>
+                                    <div id="settings_json_input" ui-ace="vm.jsonSettingsEditorOptions"
+                                         ng-model="vm.widget.settingsSchema"></div>
+                                </div>
+                            </md-tab>
+                            <md-tab label="{{ 'widget.datakey-settings-schema' | translate }}"
+                                    style="width: 100%; height: 100%;">
+                                <div class="tb-resize-container" tb-expand-fullscreen expand-button-id="expand-button">
+                                    <div class="tb-editor-area-title-panel">
+                                        <md-button aria-label="{{ 'widget.tidy' | translate }}"
+                                                   ng-click="vm.beautifyDataKeyJson()">{{ 'widget.tidy' | translate }}
+                                        </md-button>
+                                        <md-button id="expand-button"
+                                                   aria-label="{{ 'fullscreen.fullscreen' | translate }}"
+                                                   class="md-icon-button tb-md-32"></md-button>
+                                    </div>
+                                    <div id="settings_json_input" ui-ace="vm.dataKeyJsonSettingsEditorOptions"
+                                         ng-model="vm.widget.dataKeySettingsSchema"></div>
+                                </div>
+                            </md-tab>
+                        </md-tabs>
+                    </div>
+                </div>
+                <div id="bottom_panel" class="tb-split tb-split-vertical">
+                    <div id="javascript_panel" class="tb-split tb-content">
+                        <div class="tb-resize-container" tb-expand-fullscreen expand-button-id="expand-button">
+                            <div class="tb-editor-area-title-panel">
+                                <label translate for="javascript_input">widget.javascript</label>
+                                <md-button aria-label="{{ 'widget.tidy' | translate }}" ng-click="vm.beautifyJs()">{{
+                                    'widget.tidy' | translate }}
+                                </md-button>
+                                <md-button id="expand-button" aria-label="{{ 'fullscreen.fullscreen' | translate }}"
+                                           class="md-icon-button tb-md-32"></md-button>
+                            </div>
+                            <div id="javascript_input" ui-ace="vm.jsEditorOptions"
+                                 ng-model="vm.widget.controllerScript"></div>
+                        </div>
+                    </div>
+                    <div id="frame_panel" class="tb-split tb-content" style="overflow-y: hidden; position: relative;">
+                        <md-content flex layout="column" class="tb-progress-cover" layout-align="center center"
+                                    ng-show="!vm.iframeWidgetEditModeInited">
+                            <md-progress-circular md-mode="indeterminate" class="md-warn"
+                                                  md-diameter="100"></md-progress-circular>
+                        </md-content>
+                        <div tb-expand-fullscreen expand-button-id="expand-button" style="width: 100%; height: 100%;">
+                            <iframe frameborder="0" height="100%" width="100%"></iframe>
+                            <md-button id="expand-button" aria-label="Fullscreen"
+                                       class="md-icon-button tb-fullscreen-button-style"
+                                       style="position: absolute; top: 10px; left: 10px; bottom: initial;"
+                            ></md-button>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <md-content flex layout="column" class="tb-progress-cover" layout-align="center center" ng-show="!vm.layoutInited">
+        <md-progress-circular md-mode="indeterminate" class="md-warn" md-diameter="100"></md-progress-circular>
+    </md-content>
+</div>
diff --git a/ui/src/app/widget/widget-library.controller.js b/ui/src/app/widget/widget-library.controller.js
new file mode 100644
index 0000000..3beb0ae
--- /dev/null
+++ b/ui/src/app/widget/widget-library.controller.js
@@ -0,0 +1,176 @@
+/*
+ * Copyright © 2016 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 selectWidgetTypeTemplate from './select-widget-type.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function WidgetLibraryController($scope, $rootScope, widgetService, userService,
+                                                $state, $stateParams, $document, $mdDialog, $translate, $filter, types) {
+
+    var vm = this;
+
+    var widgetsBundleId = $stateParams.widgetsBundleId;
+
+    vm.widgetsBundle;
+    vm.widgetTypes = [];
+
+    vm.noData = noData;
+    vm.addWidgetType = addWidgetType;
+    vm.openWidgetType = openWidgetType;
+    vm.removeWidgetType = removeWidgetType;
+    vm.loadWidgetLibrary = loadWidgetLibrary;
+    vm.addWidgetType = addWidgetType;
+    vm.isReadOnly = isReadOnly;
+
+    function loadWidgetLibrary() {
+        $rootScope.loading = true;
+        widgetService.getWidgetsBundle(widgetsBundleId).then(
+            function success(widgetsBundle) {
+                vm.widgetsBundle = widgetsBundle;
+                if (vm.widgetsBundle) {
+                    var bundleAlias = vm.widgetsBundle.alias;
+                    var isSystem = vm.widgetsBundle.tenantId.id === types.id.nullUid;
+
+                    widgetService.getBundleWidgetTypes(bundleAlias, isSystem).then(
+                        function (widgetTypes) {
+
+                            widgetTypes = $filter('orderBy')(widgetTypes, ['-descriptor.type','name']);
+
+                            var top = 0;
+                            var lastTop = [0, 0, 0];
+                            var col = 0;
+                            var column = 0;
+
+                            if (widgetTypes.length > 0) {
+                                loadNext(0);
+                            } else {
+                                $rootScope.loading = false;
+                            }
+
+                            function loadNextOrComplete(i) {
+                                i++;
+                                if (i < widgetTypes.length) {
+                                    loadNext(i);
+                                } else {
+                                    $rootScope.loading = false;
+                                }
+                            }
+
+                            function loadNext(i) {
+                                var widgetType = widgetTypes[i];
+                                $scope.$applyAsync(function() {
+                                    var widgetTypeInfo = widgetService.toWidgetInfo(widgetType);
+                                    var sizeX = 8;
+                                    var sizeY = Math.floor(widgetTypeInfo.sizeY);
+                                    var widget = {
+                                        id: widgetType.id,
+                                        isSystemType: isSystem,
+                                        bundleAlias: bundleAlias,
+                                        typeAlias: widgetTypeInfo.alias,
+                                        type: widgetTypeInfo.type,
+                                        title: widgetTypeInfo.widgetName,
+                                        sizeX: sizeX,
+                                        sizeY: sizeY,
+                                        row: top,
+                                        col: col,
+                                        config: angular.fromJson(widgetTypeInfo.defaultConfig)
+                                    };
+                                    widget.config.title = widgetTypeInfo.widgetName;
+                                    vm.widgetTypes.push(widget);
+                                    top+=sizeY;
+                                    if (top > lastTop[column] + 10) {
+                                        lastTop[column] = top;
+                                        column++;
+                                        if (column > 2) {
+                                            column = 0;
+                                        }
+                                        top = lastTop[column];
+                                        col = column * 8;
+                                    }
+                                    loadNextOrComplete(i);
+                                });
+                           }
+                        }
+                    );
+                } else {
+                    $rootScope.loading = false;
+                }
+            }, function fail() {
+                $rootScope.loading = false;
+            }
+        );
+    }
+
+    function noData() {
+        return vm.widgetTypes.length == 0;
+    }
+
+    function addWidgetType($event) {
+        vm.openWidgetType($event);
+    }
+
+    function isReadOnly() {
+        if (userService.getAuthority() === 'TENANT_ADMIN') {
+            return !vm.widgetsBundle || vm.widgetsBundle.tenantId.id === types.id.nullUid;
+        } else {
+            return userService.getAuthority() != 'SYS_ADMIN';
+        }
+    }
+
+    function openWidgetType(event, widget) {
+        if (event) {
+            event.stopPropagation();
+        }
+        if (widget) {
+            $state.go('home.widgets-bundles.widget-types.widget-type',
+                {widgetTypeId: widget.id.id});
+        } else {
+            $mdDialog.show({
+                controller: 'SelectWidgetTypeController',
+                controllerAs: 'vm',
+                templateUrl: selectWidgetTypeTemplate,
+                parent: angular.element($document[0].body),
+                fullscreen: true,
+                targetEvent: event
+            }).then(function (widgetType) {
+                $state.go('home.widgets-bundles.widget-types.widget-type',
+                    {widgetType: widgetType});
+            }, function () {
+            });
+        }
+    }
+
+    function removeWidgetType(event, widget) {
+        var confirm = $mdDialog.confirm()
+            .targetEvent(event)
+            .title($translate.instant('widget.remove-widget-type-title', {widgetName: widget.config.title}))
+            .htmlContent($translate.instant('widget.remove-widget-type-text'))
+            .ariaLabel($translate.instant('widget.remove-widget-type'))
+            .cancel($translate.instant('action.no'))
+            .ok($translate.instant('action.yes'));
+        $mdDialog.show(confirm).then(function () {
+            widgetService.deleteWidgetType(widget.bundleAlias, widget.typeAlias, widget.isSystemType).then(
+                function success() {
+                    vm.widgetTypes.splice(vm.widgetTypes.indexOf(widget), 1);
+                },
+                function fail() {}
+            );
+        });
+    }
+}
diff --git a/ui/src/app/widget/widget-library.routes.js b/ui/src/app/widget/widget-library.routes.js
new file mode 100644
index 0000000..0141f84
--- /dev/null
+++ b/ui/src/app/widget/widget-library.routes.js
@@ -0,0 +1,107 @@
+/*
+ * Copyright © 2016 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 widgetLibraryTemplate from './widget-library.tpl.html';
+import widgetEditorTemplate from './widget-editor.tpl.html';
+import dashboardTemplate from '../dashboard/dashboard.tpl.html';
+import widgetsBundlesTemplate from './widgets-bundles.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function WidgetLibraryRoutes($stateProvider) {
+    $stateProvider
+        .state('home.widgets-bundles', {
+            url: '/widgets-bundles',
+            params: {'topIndex': 0},
+            module: 'private',
+            auth: ['SYS_ADMIN', 'TENANT_ADMIN'],
+            views: {
+                "content@home": {
+                    templateUrl: widgetsBundlesTemplate,
+                    controller: 'WidgetsBundleController',
+                    controllerAs: 'vm'
+                }
+            },
+            data: {
+                searchEnabled: true,
+                pageTitle: 'widgets-bundle.widgets-bundles'
+            },
+            ncyBreadcrumb: {
+                label: '{"icon": "now_widgets", "label": "widgets-bundle.widgets-bundles"}'
+            }
+        })
+        .state('home.widgets-bundles.widget-types', {
+            url: '/:widgetsBundleId/widgetTypes',
+            params: {'topIndex': 0},
+            module: 'private',
+            auth: ['SYS_ADMIN', 'TENANT_ADMIN'],
+            views: {
+                "content@home": {
+                    templateUrl: widgetLibraryTemplate,
+                    controller: 'WidgetLibraryController',
+                    controllerAs: 'vm'
+                }
+            },
+            data: {
+                searchEnabled: false,
+                pageTitle: 'widget.widget-library'
+            },
+            ncyBreadcrumb: {
+                label: '{"icon": "now_widgets", "label": "{{ vm.widgetsBundle.title }}", "translate": "false"}'
+            }
+        })
+        .state('home.widgets-bundles.widget-types.widget-type', {
+            url: '/:widgetTypeId',
+            module: 'private',
+            auth: ['SYS_ADMIN', 'TENANT_ADMIN'],
+            views: {
+                "content@home": {
+                    templateUrl: widgetEditorTemplate,
+                    controller: 'WidgetEditorController',
+                    controllerAs: 'vm'
+                }
+            },
+            params: {
+                widgetType: null
+            },
+            data: {
+                searchEnabled: false,
+                pageTitle: 'widget.editor'
+            },
+            ncyBreadcrumb: {
+                label: '{"icon": "insert_chart", "label": "{{ vm.widget.widgetName }}", "translate": "false"}'
+            }
+        })
+        .state('widgetEditor', {
+            url: '/widget-editor',
+            module: 'private',
+            auth: ['SYS_ADMIN', 'TENANT_ADMIN'],
+            views: {
+                "@": {
+                    templateUrl: dashboardTemplate,
+                    controller: 'DashboardController',
+                    controllerAs: 'vm'
+                }
+            },
+            data: {
+                widgetEditMode: true,
+                searchEnabled: false,
+                pageTitle: 'widget.editor'
+            }
+        })
+}
diff --git a/ui/src/app/widget/widget-library.tpl.html b/ui/src/app/widget/widget-library.tpl.html
new file mode 100644
index 0000000..5be27b7
--- /dev/null
+++ b/ui/src/app/widget/widget-library.tpl.html
@@ -0,0 +1,48 @@
+<!--
+
+    Copyright © 2016 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<section ng-show="!loading && vm.noData()" layout-align="center center"
+		 style="text-transform: uppercase; display: flex; z-index: 1;"
+		 class="md-headline tb-absolute-fill">
+	 <md-button ng-if="!vm.isReadOnly()" class="tb-add-new-widget" ng-click="vm.addWidgetType($event)">
+	 	 <md-icon aria-label="{{ 'action.add' | translate }}" class="material-icons tb-md-96">add</md-icon>
+	 	 {{ 'widget.add-widget-type' | translate }}
+	 </md-button>
+	 <span translate ng-if="vm.isReadOnly()"
+		  layout-align="center center"
+		  style="text-transform: uppercase; display: flex;"
+		  class="md-headline tb-absolute-fill">widgets-bundle.empty</span>
+</section>
+<tb-dashboard
+	widgets="vm.widgetTypes"
+	is-edit="false"
+	is-edit-action-enabled="true"
+	is-remove-action-enabled="!vm.isReadOnly()"
+	on-edit-widget="vm.openWidgetType(event, widget)"
+	on-remove-widget="vm.removeWidgetType(event, widget)"
+	load-widgets="vm.loadWidgetLibrary()">
+</tb-dashboard>
+<section layout="row" layout-wrap class="tb-footer-buttons md-fab ">
+	<md-button ng-if="!vm.isReadOnly()" ng-disabled="loading" class="tb-btn-footer md-accent md-hue-2 md-fab" ng-click="vm.addWidgetType($event)" aria-label="{{ 'widget.add-widget-type' | translate }}" >
+        <md-tooltip md-direction="top">
+			{{ 'widget.add-widget-type' | translate }}
+        </md-tooltip>	
+        <md-icon aria-label="{{ 'action.add' | translate }}" class="material-icons">
+          add
+        </md-icon>		          
+	</md-button>
+</section>
diff --git a/ui/src/app/widget/widgets-bundle.controller.js b/ui/src/app/widget/widgets-bundle.controller.js
new file mode 100644
index 0000000..e6a2fe5
--- /dev/null
+++ b/ui/src/app/widget/widgets-bundle.controller.js
@@ -0,0 +1,148 @@
+/*
+ * Copyright © 2016 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 addWidgetsBundleTemplate from './add-widgets-bundle.tpl.html';
+import widgetsBundleCard from './widgets-bundle-card.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function WidgetsBundleController(widgetService, userService, $state, $stateParams, $filter, $translate, types) {
+
+    var widgetsBundleActionsList = [
+        {
+            onAction: function ($event, item) {
+                vm.grid.openItem($event, item);
+            },
+            name: function() { return $translate.instant('widgets-bundle.details') },
+            details: function() { return $translate.instant('widgets-bundle.widgets-bundle-details') },
+            icon: "edit"
+        },
+        {
+            onAction: function ($event, item) {
+                vm.grid.deleteItem($event, item);
+            },
+            name: function() { return $translate.instant('action.delete') },
+            details: function() { return $translate.instant('widgets-bundle.delete') },
+            icon: "delete",
+            isEnabled: isWidgetsBundleEditable
+        }
+   ];
+
+    var vm = this;
+
+    vm.types = types;
+
+    vm.widgetsBundleGridConfig = {
+
+        refreshParamsFunc: null,
+
+        deleteItemTitleFunc: deleteWidgetsBundleTitle,
+        deleteItemContentFunc: deleteWidgetsBundleText,
+        deleteItemsTitleFunc: deleteWidgetsBundlesTitle,
+        deleteItemsActionTitleFunc: deleteWidgetsBundlesActionTitle,
+        deleteItemsContentFunc: deleteWidgetsBundlesText,
+
+        fetchItemsFunc: fetchWidgetsBundles,
+        saveItemFunc: saveWidgetsBundle,
+        clickItemFunc: openWidgetsBundle,
+        deleteItemFunc: deleteWidgetsBundle,
+
+        getItemTitleFunc: getWidgetsBundleTitle,
+        itemCardTemplateUrl: widgetsBundleCard,
+        parentCtl: vm,
+
+        actionsList: widgetsBundleActionsList,
+
+        onGridInited: gridInited,
+
+        addItemTemplateUrl: addWidgetsBundleTemplate,
+
+        addItemText: function() { return $translate.instant('widgets-bundle.add-widgets-bundle-text') },
+        noItemsText: function() { return $translate.instant('widgets-bundle.no-widgets-bundles-text') },
+        itemDetailsText: function() { return $translate.instant('widgets-bundle.widgets-bundle-details') },
+        isSelectionEnabled: isWidgetsBundleEditable,
+        isDetailsReadOnly: function(widgetsBundle) {
+             return !isWidgetsBundleEditable(widgetsBundle);
+        }
+
+    };
+
+    if (angular.isDefined($stateParams.items) && $stateParams.items !== null) {
+        vm.widgetsBundleGridConfig.items = $stateParams.items;
+    }
+
+    if (angular.isDefined($stateParams.topIndex) && $stateParams.topIndex > 0) {
+        vm.widgetsBundleGridConfig.topIndex = $stateParams.topIndex;
+    }
+
+    function deleteWidgetsBundleTitle(widgetsBundle) {
+        return $translate.instant('widgets-bundle.delete-widgets-bundle-title', {widgetsBundleTitle: widgetsBundle.title});
+    }
+
+    function deleteWidgetsBundleText() {
+        return $translate.instant('widgets-bundle.delete-widgets-bundle-text');
+    }
+
+    function deleteWidgetsBundlesTitle(selectedCount) {
+        return $translate.instant('widgets-bundle.delete-widgets-bundles-title', {count: selectedCount}, 'messageformat');
+    }
+
+    function deleteWidgetsBundlesActionTitle(selectedCount) {
+        return $translate.instant('widgets-bundle.delete-widgets-bundles-action-title', {count: selectedCount}, 'messageformat');
+    }
+
+    function deleteWidgetsBundlesText() {
+        return $translate.instant('widgets-bundle.delete-widgets-bundles-text');
+    }
+
+    function gridInited(grid) {
+        vm.grid = grid;
+    }
+
+    function fetchWidgetsBundles(pageLink) {
+        return widgetService.getAllWidgetsBundlesByPageLink(pageLink);
+    }
+
+    function saveWidgetsBundle(widgetsBundle) {
+        return widgetService.saveWidgetsBundle(widgetsBundle);
+    }
+
+    function deleteWidgetsBundle(widgetsBundleId) {
+        return widgetService.deleteWidgetsBundle(widgetsBundleId);
+    }
+
+    function getWidgetsBundleTitle(widgetsBundle) {
+        return widgetsBundle ? widgetsBundle.title : '';
+    }
+
+    function isWidgetsBundleEditable(widgetsBundle) {
+        if (userService.getAuthority() === 'TENANT_ADMIN') {
+            return widgetsBundle && widgetsBundle.tenantId.id != types.id.nullUid;
+        } else {
+            return userService.getAuthority() === 'SYS_ADMIN';
+        }
+    }
+
+    function openWidgetsBundle($event, widgetsBundle) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        $state.go('home.widgets-bundles.widget-types', {widgetsBundleId: widgetsBundle.id.id});
+    }
+
+}
diff --git a/ui/src/app/widget/widgets-bundle.directive.js b/ui/src/app/widget/widgets-bundle.directive.js
new file mode 100644
index 0000000..744e2ec
--- /dev/null
+++ b/ui/src/app/widget/widgets-bundle.directive.js
@@ -0,0 +1,40 @@
+/*
+ * Copyright © 2016 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 widgetsBundleFieldsetTemplate from './widgets-bundle-fieldset.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function WidgetsBundleDirective($compile, $templateCache) {
+    var linker = function (scope, element) {
+        var template = $templateCache.get(widgetsBundleFieldsetTemplate);
+        element.html(template);
+        $compile(element.contents())(scope);
+    }
+    return {
+        restrict: "E",
+        link: linker,
+        scope: {
+            widgetsBundle: '=',
+            isEdit: '=',
+            isReadOnly: '=',
+            theForm: '=',
+            onDeleteWidgetsBundle: '&'
+        }
+    };
+}
diff --git a/ui/src/app/widget/widgets-bundle-card.tpl.html b/ui/src/app/widget/widgets-bundle-card.tpl.html
new file mode 100644
index 0000000..1493bc3
--- /dev/null
+++ b/ui/src/app/widget/widgets-bundle-card.tpl.html
@@ -0,0 +1,18 @@
+<!--
+
+    Copyright © 2016 The Thingsboard Authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<div class="tb-uppercase" ng-if="item && parentCtl.types.id.nullUid === item.tenantId.id" translate>widgets-bundle.system</div>
diff --git a/ui/src/app/widget/widgets-bundle-fieldset.tpl.html b/ui/src/app/widget/widgets-bundle-fieldset.tpl.html
new file mode 100644
index 0000000..b72f3ea
--- /dev/null
+++ b/ui/src/app/widget/widgets-bundle-fieldset.tpl.html
@@ -0,0 +1,30 @@
+<!--
+
+    Copyright © 2016 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-button ng-click="onDeleteWidgetsBundle({event: $event})" ng-show="!isEdit && !isReadOnly" class="md-raised md-primary">{{ 'widgets-bundle.delete' | translate }}</md-button>
+
+<md-content class="md-padding" layout="column">
+    <fieldset ng-disabled="loading || !isEdit">
+        <md-input-container class="md-block">
+            <label translate>widgets-bundle.title</label>
+            <input required name="title" ng-model="widgetsBundle.title">
+            <div ng-messages="theForm.title.$error">
+                <div translate ng-message="required">widgets-bundle.title-required</div>
+            </div>
+        </md-input-container>
+    </fieldset>
+</md-content>
diff --git a/ui/src/app/widget/widgets-bundles.tpl.html b/ui/src/app/widget/widgets-bundles.tpl.html
new file mode 100644
index 0000000..0fdf96e
--- /dev/null
+++ b/ui/src/app/widget/widgets-bundles.tpl.html
@@ -0,0 +1,24 @@
+<!--
+
+    Copyright © 2016 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-grid grid-configuration="vm.widgetsBundleGridConfig">
+    <tb-widgets-bundle widgets-bundle="vm.grid.operatingItem()"
+               is-edit="vm.grid.detailsConfig.isDetailsEditMode"
+               is-read-only="vm.grid.isDetailsReadOnly(vm.grid.operatingItem())"
+               the-form="vm.grid.detailsForm"
+               on-delete-widgets-bundle="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-widgets-bundle>
+</tb-grid>
diff --git a/ui/src/font/Segment7Standard.otf b/ui/src/font/Segment7Standard.otf
new file mode 100644
index 0000000..7429b0d
Binary files /dev/null and b/ui/src/font/Segment7Standard.otf differ

ui/src/index.html 37(+37 -0)

diff --git a/ui/src/index.html b/ui/src/index.html
new file mode 100644
index 0000000..ee625ac
--- /dev/null
+++ b/ui/src/index.html
@@ -0,0 +1,37 @@
+<!--
+
+    Copyright © 2016 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.
+
+-->
+<!DOCTYPE html>
+<html ng-app="thingsboard" ng-strict-di>
+  <head>
+    <title ng-bind="pageTitle"></title>
+    <base href="/" />
+    <meta charset="utf-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <meta name="description" content="" />
+    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no" />
+    <link rel="icon"
+          type="image/x-icon"
+          href="static/thingsboard.ico" />
+    <link rel="stylesheet" href="//fonts.googleapis.com/css?family=RobotoDraft:100,100italic,300,300italic,400,500,700,900,400italic" />
+    <link rel="stylesheet" href="//fonts.googleapis.com/icon?family=Material+Icons" />
+  </head>
+  <body>
+    <ui-view layout="row" layout-fill>
+    </ui-view>
+  </body>
+</html>
diff --git a/ui/src/locale/en_US.json b/ui/src/locale/en_US.json
new file mode 100644
index 0000000..4a5e48d
--- /dev/null
+++ b/ui/src/locale/en_US.json
@@ -0,0 +1,631 @@
+{
+  "access": {
+    "unauthorized": "Unauthorized",
+    "unauthorized-access": "Unauthorized Access",
+    "unauthorized-access-text": "You should sign in to have access to this resource!",
+    "access-forbidden": "Access Forbidden",
+    "access-forbidden-text": "You haven't access rights to this location!<br/>Try to sign in with different user if you still wish to gain access to this location.",
+    "refresh-token-expired": "Session has expired",
+    "refresh-token-failed": "Unable to refresh session"
+  },
+  "action": {
+    "activate": "Activate",
+    "suspend": "Suspend",
+    "save": "Save",
+    "saveAs": "Save as",
+    "cancel": "Cancel",
+    "ok": "OK",
+    "delete": "Delete",
+    "add": "Add",
+    "yes": "Yes",
+    "no": "No",
+    "update": "Update",
+    "remove": "Remove",
+    "search": "Search",
+    "assign": "Assign",
+    "unassign": "Unassign",
+    "apply": "Apply",
+    "apply-changes": "Apply changes",
+    "edit-mode": "Edit mode",
+    "enter-edit-mode": "Enter edit mode",
+    "decline-changes": "Decline changes",
+    "close": "Close",
+    "back": "Back",
+    "run": "Run",
+    "sign-in": "Sign in!",
+    "edit": "Edit",
+    "view": "View",
+    "create": "Create",
+    "drag": "Drag",
+    "refresh": "Refresh",
+    "undo": "Undo"
+  },
+  "admin": {
+    "general": "General",
+    "general-settings": "General Settings",
+    "outgoing-mail": "Outgoing Mail",
+    "outgoing-mail-settings": "Outgoing Mail Settings",
+    "system-settings": "System Settings",
+    "test-mail-sent": "Test mail was successfully sent!",
+    "base-url": "Base URL",
+    "base-url-required": "Base URL is required.",
+    "mail-from": "Mail From",
+    "mail-from-required": "Mail From is required.",
+    "smtp-protocol": "SMTP protocol",
+    "smtp-host": "SMTP host",
+    "smtp-host-required": "SMTP host is required.",
+    "smtp-port": "SMTP port",
+    "smtp-port-required": "You must supply a smtp port.",
+    "smtp-port-invalid": "That doesn't look like a valid smtp port.",
+    "timeout-msec": "Timeout (msec)",
+    "timeout-required": "Timeout is required.",
+    "timeout-invalid": "That doesn't look like a valid timeout.",
+    "enable-tls": "Enable TLS",
+    "send-test-mail": "Send test mail"
+  },
+  "attribute": {
+    "attributes": "Attributes",
+    "latest-telemetry": "Latest telemetry",
+    "attributes-scope": "Device attributes scope",
+    "scope-latest-telemetry": "Latest telemetry",
+    "scope-client": "Client attributes",
+    "scope-server": "Server attributes",
+    "scope-shared": "Shared attributes",
+    "add": "Add attribute",
+    "key": "Key",
+    "key-required": "Attribute key is required.",
+    "value": "Value",
+    "value-required": "Attribute value is required.",
+    "delete-attributes-title": "Are you sure you want to delete { count, select, 1 {1 attribute} other {# attributes} }?",
+    "delete-attributes-text": "Be careful, after the confirmation all selected attributes will be removed.",
+    "delete-attributes": "Delete attributes",
+    "enter-attribute-value": "Enter attribute value",
+    "show-on-widget": "Show on widget",
+    "widget-mode": "Widget mode",
+    "next-widget": "Next widget",
+    "prev-widget": "Previous widget",
+    "add-to-dashboard": "Add to dashboard",
+    "add-widget-to-dashboard": "Add widget to dashboard",
+    "selected-attributes": "{ count, select, 1 {1 attribute} other {# attributes} } selected",
+    "selected-telemetry": "{ count, select, 1 {1 telemetry unit} other {# telemetry units} } selected"
+  },
+  "confirm-on-exit": {
+    "message": "You have unsaved changes. Are you sure you want to leave this page?",
+    "html-message": "You have unsaved changes.<br/>Are you sure you want to leave this page?",
+    "title": "Unsaved changes"
+  },
+  "contact": {
+    "country": "Country",
+    "city": "City",
+    "state": "State",
+    "postal-code": "Postal code",
+    "postal-code-invalid": "Only digits are allowed.",
+    "address": "Address",
+    "address2": "Address 2",
+    "phone": "Phone",
+    "email": "Email",
+    "no-address": "No address"
+  },
+  "common": {
+    "username": "Username",
+    "password": "Password",
+    "enter-username": "Enter username",
+    "enter-password": "Enter password",
+    "enter-search": "Enter search"
+  },
+  "customer": {
+    "customers": "Customers",
+    "management": "Customer management",
+    "dashboard": "Customer Dashboard",
+    "dashboards": "Customer Dashboards",
+    "devices": "Customer Devices",
+    "add": "Add Customer",
+    "delete": "Delete customer",
+    "manage-customer-users": "Manage customer users",
+    "manage-customer-devices": "Manage customer devices",
+    "manage-customer-dashboards": "Manage customer dashboards",
+    "add-customer-text": "Add new customer",
+    "no-customers-text": "No customers found",
+    "customer-details": "Customer details",
+    "delete-customer-title": "Are you sure you want to delete the customer '{{customerTitle}}'?",
+    "delete-customer-text": "Be careful, after the confirmation the customer and all related data will become unrecoverable.",
+    "delete-customers-title": "Are you sure you want to delete { count, select, 1 {1 customer} other {# customers} }?",
+    "delete-customers-action-title": "Delete { count, select, 1 {1 customer} other {# customers} }",
+    "delete-customers-text": "Be careful, after the confirmation all selected customers will be removed and all related data will become unrecoverable.",
+    "manage-users": "Manage users",
+    "manage-devices": "Manage devices",
+    "manage-dashboards": "Manage dashboards",
+    "title": "Title",
+    "title-required": "Title is required.",
+    "description": "Description"
+  },
+  "datetime": {
+    "date-from": "Date from",
+    "time-from": "Time from",
+    "date-to": "Date to",
+    "time-to": "Time to"
+  },
+  "dashboard": {
+    "dashboard": "Dashboard",
+    "dashboards": "Dashboards",
+    "management": "Dashboard management",
+    "view-dashboards": "View Dashboards",
+    "add": "Add Dashboard",
+    "assign-dashboard-to-customer": "Assign Dashboard(s) To Customer",
+    "assign-dashboard-to-customer-text": "Please select the dashboards to assign to the customer",
+    "assign-to-customer-text": "Please select the customer to assign the dashboard(s)",
+    "assign-to-customer": "Assign to customer",
+    "unassign-from-customer": "Unassign from customer",
+    "no-dashboards-text": "No dashboards found",
+    "no-widgets": "No widgets configured",
+    "add-widget": "Add new widget",
+    "title": "Title",
+    "select-widget-title": "Select widget",
+    "select-widget-subtitle": "List of available widget types",
+    "delete": "Delete dashboard",
+    "title": "Title",
+    "title-required": "Title is required.",
+    "description": "Description",
+    "details": "Details",
+    "dashboard-details": "Dashboard details",
+    "add-dashboard-text": "Add new dashboard",
+    "no-dashboards-text": "No dashboards found",
+    "assign-dashboards": "Assign dashboards",
+    "assign-new-dashboard": "Assign new dashboard",
+    "assign-dashboards-text": "Assign { count, select, 1 {1 dashboard} other {# dashboards} } to customer",
+    "delete-dashboards": "Delete dashboards",
+    "unassign-dashboards": "Unassign dashboards",
+    "unassign-dashboards-action-title": "Unassign { count, select, 1 {1 dashboard} other {# dashboards} } from customer",
+    "delete-dashboard-title": "Are you sure you want to delete the dashboard '{{dashboardTitle}}'?",
+    "delete-dashboard-text": "Be careful, after the confirmation the dashboard and all related data will become unrecoverable.",
+    "delete-dashboards-title": "Are you sure you want to delete { count, select, 1 {1 dashboard} other {# dashboards} }?",
+    "delete-dashboards-action-title": "Delete { count, select, 1 {1 dashboard} other {# dashboards} }",
+    "delete-dashboards-text": "Be careful, after the confirmation all selected dashboards will be removed and all related data will become unrecoverable.",
+    "unassign-dashboard-title": "Are you sure you want to unassign the dashboard '{{dashboardTitle}}'?",
+    "unassign-dashboard-text": "After the confirmation the dashboard will be unassigned and won't be accessible by the customer.",
+    "unassign-dashboard": "Unassign dashboard",
+    "unassign-dashboards-title": "Are you sure you want to unassign { count, select, 1 {1 dashboard} other {# dashboards} }?",
+    "unassign-dashboards-text": "After the confirmation all selected dashboards will be unassigned and won't be accessible by the customer.",
+    "select-dashboard": "Select dashboard",
+    "no-dashboards-matching": "No dashboards matching '{{dashboard}}' were found.",
+    "dashboard-required": "Dashboard is required.",
+    "select-existing": "Select existing dashboard",
+    "create-new": "Create new dashboard",
+    "new-dashboard-title": "New dashboard title",
+    "open-dashboard": "Open dashboard"
+  },
+  "datakey": {
+    "settings": "Settings",
+    "advanced": "Advanced",
+    "label": "Label",
+    "color": "Color",
+    "data-generation-func": "Data generation function",
+    "use-data-post-processing-func": "Use data post-processing function",
+    "configuration": "Data key configuration",
+    "timeseries": "Timeseries",
+    "attributes": "Attributes",
+    "timeseries-required": "Device timeseries is required.",
+    "timeseries-or-attributes-required": "Device timeseries/attributes is required.",
+    "function-types": "Function types",
+    "function-types-required": "Function types is required."
+  },
+  "datasource": {
+    "type": "Datasource type",
+    "add-datasource-prompt": "Please add datasource"
+  },
+  "details": {
+    "edit-mode": "Edit mode",
+    "toggle-edit-mode": "Toggle edit mode"
+  },
+  "device": {
+    "device": "Device",
+    "device-required": "Device is required.",
+    "devices": "Devices",
+    "management": "Device management",
+    "view-devices": "View Devices",
+    "device-alias": "Device alias",
+    "aliases": "Device aliases",
+    "no-alias-matching": "'{{alias}}' not found.",
+    "no-aliases-found": "No aliases found.",
+    "no-key-matching": "'{{key}}' not found.",
+    "no-keys-found": "No keys found.",
+    "create-new-alias": "Create a new one!",
+    "create-new-key": "Create a new one!",
+    "duplicate-alias-error": "Duplicate alias found '{{alias}}'.<br>Device aliases must be unique whithin the dashboard.",
+    "select-device-for-alias": "Select device for '{{alias}}' alias",
+    "no-devices-matching": "No devices matching '{{device}}' were found.",
+    "alias": "Alias",
+    "alias-required": "Device alias is required.",
+    "remove-alias": "Remove device alias",
+    "add-alias": "Add device alias",
+    "add": "Add Device",
+    "assign-to-customer": "Assign to customer",
+    "assign-device-to-customer": "Assign Device(s) To Customer",
+    "assign-device-to-customer-text": "Please select the devices to assign to the customer",
+    "no-devices-text": "No devices found",
+    "assign-to-customer-text": "Please select the customer to assign the device(s)",
+    "device-details": "Device details",
+    "add-device-text": "Add new device",
+    "credentials": "Credentials",
+    "manage-credentials": "Manage credentials",
+    "delete": "Delete device",
+    "assign-devices": "Assign devices",
+    "assign-devices-text": "Assign { count, select, 1 {1 device} other {# devices} } to customer",
+    "delete-devices": "Delete devices",
+    "unassign-from-customer": "Unassign from customer",
+    "unassign-devices": "Unassign devices",
+    "unassign-devices-action-title": "Unassign { count, select, 1 {1 device} other {# devices} } from customer",
+    "assign-new-device": "Assign new device",
+    "view-credentials": "View credentials",
+    "delete-device-title": "Are you sure you want to delete the device '{{deviceName}}'?",
+    "delete-device-text": "Be careful, after the confirmation the device and all related data will become unrecoverable.",
+    "delete-devices-title": "Are you sure you want to delete { count, select, 1 {1 device} other {# devices} }?",
+    "delete-devices-action-title": "Delete { count, select, 1 {1 device} other {# devices} }",
+    "delete-devices-text": "Be careful, after the confirmation all selected devices will be removed and all related data will become unrecoverable.",
+    "unassign-device-title": "Are you sure you want to unassign the device '{{deviceName}}'?",
+    "unassign-device-text": "After the confirmation the device will be unassigned and won't be accessible by the customer.",
+    "unassign-device": "Unassign device",
+    "unassign-devices-title": "Are you sure you want to unassign { count, select, 1 {1 device} other {# devices} }?",
+    "unassign-devices-text": "After the confirmation all selected devices will be unassigned and won't be accessible by the customer.",
+    "device-credentials": "Device Credentials",
+    "credentials-type": "Credentials type",
+    "access-token": "Access token",
+    "access-token-required": "Access token is required.",
+    "access-token-invalid": "Access token length must be from 1 to 20 characters.",
+    "secret": "Secret",
+    "secret-required": "Secret is required.",
+    "name": "Name",
+    "name-required": "Name is required.",
+    "description": "Description",
+    "events": "Events",
+    "details": "Details",
+    "copyId": "Copy device Id",
+    "idCopiedMessage": "Device Id has been copied to clipboard",
+    "assignedToCustomer": "Assigned to customer",
+    "unable-delete-device-alias-title": "Unable to delete device alias",
+    "unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}"
+  },
+  "dialog": {
+    "close": "Close dialog"
+  },
+  "error": {
+    "unable-to-connect": "Unable to connect to the server! Please check your internet connection.",
+    "unhandled-error-code": "Unhandled error code: {{errorCode}}",
+    "unknown-error": "Unknown error"
+  },
+  "event": {
+    "event-type": "Event type",
+    "type-alarm": "Alarm",
+    "type-error": "Error",
+    "type-lc-event": "Lifecycle event",
+    "type-stats": "Statistics",
+    "no-events-prompt": "No events found",
+    "error": "Error",
+    "alarm": "Alarm",
+    "event-time": "Event time",
+    "server": "Server",
+    "body": "Body",
+    "method": "Method",
+    "event": "Event",
+    "status": "Status",
+    "success": "Success",
+    "failed": "Failed",
+    "messages-processed": "Messages processed",
+    "errors-occurred": "Errors occurred"
+  },
+  "fullscreen": {
+    "expand": "Expand to fullscreen",
+    "exit": "Exit fullscreen",
+    "toggle": "Toggle fullscreen mode",
+    "fullscreen": "Fullscreen"
+  },
+  "function": {
+    "function": "Function"
+  },
+  "grid": {
+    "delete-item-title": "Are you sure you want to delete this item?",
+    "delete-item-text": "Be careful, after the confirmation this item and all related data will become unrecoverable.",
+    "delete-items-title": "Are you sure you want to delete { count, select, 1 {1 item} other {# items} }?",
+    "delete-items-action-title": "Delete { count, select, 1 {1 item} other {# items} }",
+    "delete-items-text": "Be careful, after the confirmation all selected items will be removed and all related data will become unrecoverable.",
+    "add-item-text": "Add new item",
+    "no-items-text": "No items found",
+    "item-details": "Item details",
+    "delete-item": "Delete Item",
+    "delete-items": "Delete Items",
+    "scroll-to-top": "Scroll to top"
+  },
+  "help": {
+    "goto-help-page": "Go to help page"
+  },
+  "home": {
+    "home": "Home",
+    "profile": "Profile",
+    "logout": "Logout",
+    "menu": "Menu",
+    "avatar": "Avatar",
+    "open-user-menu": "Open user menu"
+  },
+  "item": {
+    "selected": "Selected"
+  },
+  "js-func": {
+    "no-return-error": "Function must return value!",
+    "return-type-mismatch": "Function must return value of '{{type}}' type!"
+  },
+  "login": {
+    "login": "Login",
+    "request-password-reset": "Request Password Reset",
+    "reset-password": "Reset Password",
+    "create-password": "Create Password",
+    "passwords-mismatch-error": "Entered passwords must be same!",
+    "password-again": "Password again",
+    "sign-in": "Please sign in",
+    "username": "Username (email)",
+    "remember-me": "Remember me",
+    "forgot-password": "Forgot Password?",
+    "login": "Login",
+    "password-reset": "Password reset",
+    "new-password": "New password",
+    "new-password-again": "New password again",
+    "password-link-sent-message": "Password reset link was successfully sent!",
+    "request-password-reset": "Request password reset",
+    "email": "Email"
+  },
+  "plugin": {
+    "plugins": "Plugins",
+    "delete": "Delete plugin",
+    "activate": "Activate plugin",
+    "suspend": "Suspend plugin",
+    "active": "Active",
+    "suspended": "Suspended",
+    "name": "Name",
+    "name-required": "Name is required.",
+    "description": "Description",
+    "add": "Add Plugin",
+    "delete-plugin-title": "Are you sure you want to delete the plugin '{{pluginName}}'?",
+    "delete-plugin-text": "Be careful, after the confirmation the plugin and all related data will become unrecoverable.",
+    "delete-plugins-title": "Are you sure you want to delete { count, select, 1 {1 plugin} other {# plugins} }?",
+    "delete-plugins-action-title": "Delete { count, select, 1 {1 plugin} other {# plugins} }",
+    "delete-plugins-text": "Be careful, after the confirmation all selected plugins will be removed and all related data will become unrecoverable.",
+    "add-plugin-text": "Add new plugin",
+    "no-plugins-text": "No plugins found",
+    "plugin-details": "Plugin details",
+    "api-token": "API token",
+    "api-token-required": "API token is required.",
+    "type": "Plugin type",
+    "type-required": "Plugin type is required.",
+    "configuration": "Plugin configuration",
+    "system": "System",
+    "select-plugin": "Select plugin",
+    "plugin": "Plugin",
+    "no-plugins-matching": "No plugins matching '{{plugin}}' were found.",
+    "plugin-required": "Plugin is required.",
+    "plugin-require-match": "Please select an existing plugin.",
+    "events": "Events",
+    "details": "Details"
+  },
+  "profile": {
+    "profile": "Profile",
+    "change-password": "Change Password",
+    "current-password": "Current password"
+  },
+  "rule": {
+    "rules": "Rules",
+    "delete": "Delete rule",
+    "activate": "Activate rule",
+    "suspend": "Suspend rule",
+    "active": "Active",
+    "suspended": "Suspended",
+    "name": "Name",
+    "name-required": "Name is required.",
+    "description": "Description",
+    "add": "Add Rule",
+    "delete-rule-title": "Are you sure you want to delete the rule '{{ruleName}}'?",
+    "delete-rule-text": "Be careful, after the confirmation the rule and all related data will become unrecoverable.",
+    "delete-rules-title": "Are you sure you want to delete { count, select, 1 {1 rule} other {# rules} }?",
+    "delete-rules-action-title": "Delete { count, select, 1 {1 rule} other {# rules} }",
+    "delete-rules-text": "Be careful, after the confirmation all selected rules will be removed and all related data will become unrecoverable.",
+    "add-rule-text": "Add new rule",
+    "no-rules-text": "No rules found",
+    "rule-details": "Rule details",
+    "filters": "Filters",
+    "filter": "Filter",
+    "add-filter-prompt": "Please add filter",
+    "remove-filter": "Remove filter",
+    "add-filter": "Add filter",
+    "filter-name": "Filter name",
+    "filter-type": "Filter type",
+    "edit-filter": "Edit filter",
+    "view-filter": "View filter",
+    "component-name": "Name",
+    "component-name-required": "Name is required.",
+    "component-type": "Type",
+    "component-type-required": "Type is required.",
+    "processor": "Processor",
+    "no-processor-configured": "No processor configured",
+    "create-processor": "Create processor",
+    "processor": "Processor",
+    "processor-name": "Processor name",
+    "processor-type": "Processor type",
+    "plugin-action": "Plugin action",
+    "action-name": "Action name",
+    "action-type": "Action type",
+    "create-action-prompt": "Please create action",
+    "create-action": "Create action",
+    "details": "Details",
+    "events": "Events",
+    "system": "System"
+  },
+  "rule-plugin": {
+    "management": "Rules and plugins management"
+  },
+  "tenant": {
+    "tenants": "Tenants",
+    "management": "Tenant management",
+    "add": "Add Tenant",
+    "admins": "Admins",
+    "manage-tenant-admins": "Manage tenant admins",
+    "delete": "Delete tenant",
+    "add-tenant-text": "Add new tenant",
+    "no-tenants-text": "No tenants found",
+    "tenant-details": "Tenant details",
+    "delete-tenant-title": "Are you sure you want to delete the tenant '{{tenantTitle}}'?",
+    "delete-tenant-text": "Be careful, after the confirmation the tenant and all related data will become unrecoverable.",
+    "delete-tenants-title": "Are you sure you want to delete { count, select, 1 {1 tenant} other {# tenants} }?",
+    "delete-tenants-action-title": "Delete { count, select, 1 {1 tenant} other {# tenants} }",
+    "delete-tenants-text": "Be careful, after the confirmation all selected tenants will be removed and all related data will become unrecoverable.",
+    "title": "Title",
+    "title-required": "Title is required.",
+    "description": "Description"
+  },
+  "timeinterval": {
+    "seconds-interval": "{ seconds, select, 1 {1 second} other {# seconds} }",
+    "minutes-interval": "{ minutes, select, 1 {1 minute} other {# minutes} }",
+    "hours-interval": "{ hours, select, 1 {1 hour} other {# hours} }",
+    "days-interval": "{ days, select, 1 {1 day} other {# days} }",
+    "days": "Days",
+    "hours": "Hours",
+    "minutes": "Minutes",
+    "seconds": "Seconds"
+  },
+  "timewindow": {
+    "days": "{ days, select, 1 { day } other {# days } }",
+    "hours": "{ hours, select, 0 { hour } 1 {1 hour } other {# hours } }",
+    "minutes": "{ minutes, select, 0 { minute } 1 {1 minute } other {# minutes } }",
+    "seconds": "{ seconds, select, 0 { second } 1 {1 second } other {# seconds } }",
+    "realtime": "Realtime",
+    "history": "History",
+    "last-prefix": "last",
+    "period": "from {{ startTime }} to {{ endTime }}",
+    "edit": "Edit timewindow",
+    "date-range": "Date range",
+    "last": "Last",
+    "time-period": "Time period"
+  },
+  "user": {
+    "users": "Users",
+    "customer-users": "Customer Users",
+    "tenant-admins": "Tenant Admins",
+    "sys-admin": "System administrator",
+    "tenant-admin": "Tenant administrator",
+    "customer": "Customer",
+    "anonymous": "Anonymous",
+    "add": "Add User",
+    "delete": "Delete user",
+    "add-user-text": "Add new user",
+    "no-users-text": "No users found",
+    "user-details": "User details",
+    "delete-user-title": "Are you sure you want to delete the user '{{userEmail}}'?",
+    "delete-user-text": "Be careful, after the confirmation the user and all related data will become unrecoverable.",
+    "delete-users-title": "Are you sure you want to delete { count, select, 1 {1 user} other {# users} }?",
+    "delete-users-action-title": "Delete { count, select, 1 {1 user} other {# users} }",
+    "delete-users-text": "Be careful, after the confirmation all selected users will be removed and all related data will become unrecoverable.",
+    "activation-email-sent-message": "Activation email was successfully sent!",
+    "resend-activation": "Resend activation",
+    "email": "Email",
+    "email-required": "Email is required.",
+    "first-name": "First Name",
+    "last-name": "Last Name",
+    "description": "Description"
+  },
+  "value": {
+    "type": "Value type",
+    "string": "String",
+    "string-value": "String value",
+    "integer": "Integer",
+    "integer-value": "Integer value",
+    "invalid-integer-value": "Invalid integer value",
+    "double": "Double",
+    "double-value": "Double value",
+    "boolean": "Boolean",
+    "boolean-value": "Boolean value",
+    "false": "False",
+    "true": "True"
+  },
+  "widget": {
+    "widget-library": "Widgets Library",
+    "widget-bundle": "Widgets Bundle",
+    "select-widgets-bundle": "Select widgets bundle",
+    "management": "Widget management",
+    "editor": "Widget Editor",
+    "widget-type-not-found": "Problem loading widget configuration.<br>Probably associated\n    widget type was removed.",
+    "widget-type-load-error": "Widget wasn't loaded due to the following errors:",
+    "remove": "Remove widget",
+    "edit": "Edit widget",
+    "remove-widget-title": "Are you sure you want to remove the widget '{{widgetTitle}}'?",
+    "remove-widget-text": "After the confirmation the widget and all related data will become unrecoverable.",
+    "timeseries": "Time series",
+    "latest-values": "Latest values",
+    "rpc": "Control widget",
+    "select-widget-type": "Select widget type",
+    "missing-widget-title-error": "Widget title must be specified!",
+    "widget-saved": "Widget saved",
+    "unable-to-save-widget-error": "Unable to save widget! Widget has errors!",
+    "save": "Save widget",
+    "saveAs": "Save widget as",
+    "save-widget-type-as": "Save widget type as",
+    "save-widget-type-as-text": "Please enter new widget title and/or select target widgets bundle",
+    "toggle-fullscreen": "Toggle fullscreen",
+    "run": "Run widget",
+    "title": "Widget title",
+    "title-required": "Widget title is required.",
+    "type": "Widget type",
+    "resources": "Resources",
+    "resource-url": "JavaScript/CSS URI",
+    "remove-resource": "Remove resource",
+    "add-resource": "Add resource",
+    "html": "HTML",
+    "tidy": "Tidy",
+    "css": "CSS",
+    "settings-schema": "Settings schema",
+    "datakey-settings-schema": "Data key settings schema",
+    "javascript": "Javascript",
+    "remove-widget-type-title": "Are you sure you want to remove the widget type '{{widgetName}}'?",
+    "remove-widget-type-text": "After the confirmation the widget type and all related data will become unrecoverable.",
+    "remove-widget-type": "Remove widget type",
+    "add-widget-type": "Add new widget type",
+    "widget-type-load-failed-error": "Failed to load widget type!",
+    "widget-template-load-failed-error": "Failed to load widget template!",
+    "add": "Add Widget",
+    "undo": "Undo widget changes"
+  },
+  "widgets-bundle": {
+    "current": "Current bundle",
+    "widgets-bundles": "Widgets Bundles",
+    "add": "Add Widgets Bundle",
+    "delete": "Delete widgets bundle",
+    "title": "Title",
+    "title-required": "Title is required.",
+    "add-widgets-bundle-text": "Add new widgets bundle",
+    "no-widgets-bundles-text": "No widgets bundles found",
+    "empty": "Widgets bundle is empty",
+    "details": "Details",
+    "widgets-bundle-details": "Widgets bundle details",
+    "delete-widgets-bundle-title": "Are you sure you want to delete the widgets bundle '{{widgetsBundleTitle}}'?",
+    "delete-widgets-bundle-text": "Be careful, after the confirmation the widgets bundle and all related data will become unrecoverable.",
+    "delete-widgets-bundles-title": "Are you sure you want to delete { count, select, 1 {1 widgets bundle} other {# widgets bundles} }?",
+    "delete-widgets-bundles-action-title": "Delete { count, select, 1 {1 widgets bundle} other {# widgets bundles} }",
+    "delete-widgets-bundles-text": "Be careful, after the confirmation all selected widgets bundles will be removed and all related data will become unrecoverable.",
+    "no-widgets-bundles-matching": "No widgets bundles matching '{{widgetsBundle}}' were found.",
+    "widgets-bundle-required": "Widgets bundle is required.",
+    "system": "System"
+  },
+  "widget-config": {
+    "settings": "Settings",
+    "advanced": "Advanced",
+    "title": "Title",
+    "general-settings": "General settings",
+    "display-title": "Display title",
+    "background-color": "Background color",
+    "text-color": "Text color",
+    "padding": "Padding",
+    "timewindow": "Timewindow",
+    "datasources": "Datasources",
+    "datasource-type": "Type",
+    "datasource-parameters": "Parameters",
+    "remove-datasource": "Remove datasource",
+    "add-datasource": "Add datasource",
+    "target-device": "Target device"
+  }
+}
diff --git a/ui/src/scss/animations.scss b/ui/src/scss/animations.scss
new file mode 100644
index 0000000..58f1f5e
--- /dev/null
+++ b/ui/src/scss/animations.scss
@@ -0,0 +1,44 @@
+/**
+ * Copyright © 2016 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 "~compass-sass-mixins/lib/animate";
+
+@include keyframes(tbMoveFromTopFade) {
+  from {
+    opacity: 0;
+    @include transform(translate(0, -100%));
+  }
+}
+
+@include keyframes(tbMoveToTopFade) {
+  to {
+    opacity: 0;
+    @include transform(translate(0, -100%));
+  }
+}
+
+@include keyframes(tbMoveFromBottomFade) {
+  from {
+    opacity: 0;
+    @include transform(translate(0, 100%));
+  }
+}
+
+@include keyframes(tbMoveToBottomFade) {
+  to {
+    opacity: 0;
+    @include transform(translate(0, 100%));
+  }
+}
\ No newline at end of file
diff --git a/ui/src/scss/constants.scss b/ui/src/scss/constants.scss
new file mode 100644
index 0000000..74f06e6
--- /dev/null
+++ b/ui/src/scss/constants.scss
@@ -0,0 +1,43 @@
+/**
+ * Copyright © 2016 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 "~sass-material-colors/sass/sass-material-colors";
+
+// Colors
+
+$gray: #eee;
+
+$primary-palette-color: 'indigo';
+$hue-1: '300';
+$hue-2: '800';
+$hue-3: 'a100';
+
+$primary-hue-1: material-color($primary-palette-color, $hue-1);
+$primary-hue-2: material-color($primary-palette-color, $hue-2);
+$primary-hue-3: rgb(207, 216, 220);
+
+// Layout
+
+$layout-breakpoint-xs: 600px !default;
+$layout-breakpoint-sm: 960px !default;
+$layout-breakpoint-md: 1280px !default;
+$layout-breakpoint-xmd: 1600px !default;
+$layout-breakpoint-lg: 1920px !default;
+
+$layout-breakpoint-gt-xs: 601px !default;
+$layout-breakpoint-gt-sm: 961px !default;
+$layout-breakpoint-gt-md: 1281px !default;
+$layout-breakpoint-gt-xmd: 1601px !default;
+$layout-breakpoint-gt-lg: 1921px !default;
\ No newline at end of file

ui/src/scss/main.scss 402(+402 -0)

diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss
new file mode 100644
index 0000000..e8f8283
--- /dev/null
+++ b/ui/src/scss/main.scss
@@ -0,0 +1,402 @@
+/**
+ * Copyright © 2016 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 "~compass-sass-mixins/lib/compass";
+@import "constants";
+@import "animations";
+
+@font-face {
+  font-family: 'Segment7Standard';
+  src: url('../font/Segment7Standard.otf') format('opentype');
+  font-weight: normal;
+  font-style: italic;
+}
+
+/***************
+ * TYPE DEFAULTS
+ ***************/
+
+button, html, input, select, textarea {
+  font-family: RobotoDraft, Roboto, 'Helvetica Neue', sans-serif;
+}
+
+.mdi-set {
+  line-height: 1;
+  letter-spacing: normal;
+  text-transform: none;
+  white-space: nowrap;
+  word-wrap: normal;
+  direction: ltr;
+  -webkit-font-feature-settings: 'liga';
+}
+
+a {
+  color: #106CC8;
+  text-decoration: none;
+  font-weight: 400;
+  border-bottom: 1px solid rgba(64, 84, 178, 0.25);
+  @include transition(border-bottom 0.35s);
+}
+
+a:hover, a:focus {
+  border-bottom: 1px solid #4054B2;
+}
+
+h1, h2, h3, h4, h5, h6 {
+  margin-bottom: 1rem;
+  margin-top: 1rem;
+}
+
+h1 {
+  font-size: 3.400rem;
+  font-weight: 400;
+  line-height: 4rem;
+}
+
+h2 {
+  font-size: 2.400rem;
+  font-weight: 400;
+  line-height: 3.2rem;
+}
+
+h3 {
+  font-size: 2.000rem;
+  font-weight: 500;
+  letter-spacing: 0.005em;
+}
+
+h4 {
+  font-size: 1.600rem;
+  font-weight: 400;
+  letter-spacing: 0.010em;
+  line-height: 2.4rem;
+}
+
+p {
+  font-size: 1.6rem;
+  font-weight: 400;
+  letter-spacing: 0.010em;
+  line-height: 1.6em;
+  margin: 0.8em 0 1.6em;
+}
+
+strong {
+  font-weight: 500;
+}
+
+blockquote {
+  border-left: 3px solid rgba(0, 0, 0, 0.12);
+  font-style: italic;
+  margin-left: 0;
+  padding-left: 16px;
+}
+
+fieldset {
+  border: none;
+  padding: 0;
+  margin: 0;
+}
+
+/*********************************
+ * MATERIAL DESIGN CUSTOMIZATIONS
+ ********************************/
+
+a.md-button {
+  border-bottom: none;
+}
+
+form {
+  md-content {
+    background-color: #fff;
+  }
+}
+
+md-bottom-sheet .md-subheader {
+  font-family: RobotoDraft, Roboto, 'Helvetica Neue', sans-serif;
+}
+
+.md-chips {
+  font-family: RobotoDraft, Roboto, 'Helvetica Neue', sans-serif;
+}
+
+md-content.md-default-theme, md-content {
+  background-color: $gray;
+}
+
+md-card {
+  background-color: #fff;
+  h2:first-of-type {
+    margin-top: 0;
+  }
+}
+
+.md-button:not([disabled]).md-icon-button:hover {
+  background-color: rgba(158, 158, 158, 0.2);
+}
+
+md-toolbar:not(.md-hue-1),
+.md-fab {
+  fill: #fff;
+}
+
+md-toolbar md-input-container .md-errors-spacer {
+  min-height: 0px;
+}
+
+md-toolbar {
+  md-select.md-default-theme:not([disabled]):focus .md-select-value, md-select:not([disabled]):focus .md-select-value {
+    color: #fff;
+  }
+}
+
+md-menu-item {
+  overflow: hidden;
+  fill: #737373;
+}
+
+md-menu-item {
+  .md-button {
+    display: block;
+  }
+}
+
+div.md-toast-text {
+  width: 100%;
+  max-width: 500px;
+  max-height: inherit;
+  word-wrap: break-word;
+}
+
+md-toast .md-button {
+  min-width: 88px;
+}
+
+md-sidenav {
+  overflow: hidden;
+  fill: #737373;
+}
+
+.md-panel-outer-wrapper {
+  overflow-y: auto;
+}
+
+.md-radio-interactive input, button {
+  pointer-events: all;
+}
+
+/***********************
+ * THINGSBOARD SPECIFIC
+ ***********************/
+
+.tb-readonly-label {
+  color: rgba(0,0,0,0.54);
+}
+
+/***********************
+ * Prompt
+ ***********************/
+
+.tb-prompt {
+  color: rgba(0,0,0,0.38);
+  text-transform: uppercase;
+  display: flex;
+  font-size: 18px;
+  font-weight: 400;
+  line-height: 18px;
+}
+
+/***********************
+ * Errors
+ ***********************/
+
+.tb-error-messages {
+  height: 24px; //30px
+}
+
+.tb-error-message {
+  font-size: 12px;
+  line-height: 14px;
+  overflow: hidden;
+  padding: 10px 0px 0px 10px;
+  color: rgb(221,44,0);
+  margin-top: -6px;
+}
+
+.tb-error-message.ng-animate {
+  @include transition(all .3s cubic-bezier(.55,0,.55,.2));
+}
+
+.tb-error-message.ng-enter-prepare, .tb-error-message.ng-enter {
+  opacity:0;
+  margin-top: -24px;
+}
+
+.tb-error-message.ng-enter.ng-enter-active {
+  opacity:1;
+  margin-top: -6px;
+}
+
+.tb-error-message.ng-leave {
+  opacity:1;
+  margin-top: -6px;
+}
+.tb-error-message.ng-leave.ng-leave-active {
+  opacity:0;
+  margin-top: -24px;
+}
+
+/***********************
+ * Tabs
+ ***********************/
+
+md-tabs.tb-headless {
+  margin-top: -50px;
+}
+
+/***********************
+ * Buttons
+ ***********************/
+
+.md-button.tb-card-button {
+  width: 100%;
+  height: 100%;
+  max-width: 240px;
+  span {
+    padding: 10px 10px 20px 10px;
+    font-size: 18px;
+    font-weight: 400;
+    white-space: normal;
+    line-height: 18px;
+  }
+}
+
+.md-button.tb-add-new-widget {
+  border-style: dashed;
+  border-width: 2px;
+  font-size: 24px;
+  padding-right: 12px;
+}
+
+/***********************
+ * Header buttons
+ ***********************/
+
+section.tb-header-buttons {
+  position: absolute;
+  right: 0px;
+  top: 86px;
+  z-index: 3;
+  @media (min-width: $layout-breakpoint-sm) {
+    top: 86px;
+  }
+}
+
+section.tb-top-header-buttons {
+  top: 23px;
+}
+
+.tb-header-buttons .tb-btn-header {
+  @include animation(tbMoveFromTopFade .3s ease both);
+  position: relative !important;
+  display: inline-block !important;
+}
+
+.tb-header-buttons .tb-btn-header.ng-hide {
+  @include animation(tbMoveToTopFade .3s ease both);
+}
+
+/***********************
+ * Footer buttons
+ ***********************/
+
+section.tb-footer-buttons {
+  position: fixed;
+  right: 20px;
+  bottom: 20px;
+  z-index: 2;
+}
+
+.tb-footer-buttons .tb-btn-footer {
+  @include animation(tbMoveFromBottomFade .3s ease both);
+  position: relative !important;
+  display: inline-block !important;
+}
+
+.tb-footer-buttons .tb-btn-footer.ng-hide {
+  @include animation(tbMoveToBottomFade .3s ease both);
+}
+
+._md-toast-open-bottom .tb-footer-buttons {
+  @include transition(all .4s cubic-bezier(.25, .8, .25, 1));
+  @include transform(translate3d(0, -42px, 0));
+}
+
+/***********************
+ * Icons
+ ***********************/
+
+.md-icon-button.tb-md-32 {
+  vertical-align: middle;
+  width: 32px;
+  height: 32px;
+  min-width: 32px;
+  min-height: 32px;
+  margin: 0px !important;
+  padding: 0px !important;
+}
+
+.material-icons.tb-md-20 {
+  font-size: 20px;
+  width: 20px;
+  height: 20px;
+  min-width: 20px;
+  min-height: 20px;
+}
+
+.material-icons.tb-md-96 {
+  font-size: 96px;
+  width: 96px;
+  height: 96px;
+}
+
+/***********************
+ * Layout
+ ***********************/
+
+.tb-absolute-fill {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+}
+
+.tb-progress-cover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 6;
+  opacity: 1;
+}
+
+/***********************
+ * ACE
+ ***********************/
+
+.ace_editor {
+  font-size: 16px !important;
+}
diff --git a/ui/src/scss/mixins.scss b/ui/src/scss/mixins.scss
new file mode 100644
index 0000000..593cf24
--- /dev/null
+++ b/ui/src/scss/mixins.scss
@@ -0,0 +1,34 @@
+/**
+ * Copyright © 2016 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 "~compass-sass-mixins/lib/compass";
+
+@mixin input-placeholder {
+  // replaces compass/css/user-interface/input-placeholder()
+  &::-webkit-input-placeholder  {
+    @content;
+  }
+  &:-moz-placeholder {
+    @content;
+    opacity: 1;
+  }
+  &::-moz-placeholder {
+    @content;
+    opacity: 1;
+  }
+  &:-ms-input-placeholder {
+    @content;
+  }
+}
\ No newline at end of file
diff --git a/ui/src/svg/logo_title_white.svg b/ui/src/svg/logo_title_white.svg
new file mode 100644
index 0000000..fff3681
--- /dev/null
+++ b/ui/src/svg/logo_title_white.svg
@@ -0,0 +1,42 @@
+<svg id="svg2" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="320" width="1740" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox="0 0 1740 320.00002">
+ <g id="layer1" transform="translate(0 -732.36)">
+  <g id="g4652" stroke-width="28">
+   <g stroke-width="28">
+    <g id="g5204-0" transform="matrix(-.66287 .69913 -.66371 -.70001 863.46 1410.6)">
+     <g id="g5175-6" transform="translate(2.5254 3.0305)"></g>
+     <g id="g5175-9-8" transform="translate(41.25 -30.543)" stroke-width="28"></g>
+    </g>
+    <g id="g5175-6-35" transform="matrix(.57657 .52719 -.57729 .52786 584.63 346.6)"></g>
+    <g id="g5204-0-5-0" transform="matrix(.66287 -.69913 .66371 .70001 -543.46 380.47)">
+     <g id="g5175-6-35-9" transform="translate(2.5254 3.0305)"></g>
+     <g id="g5175-9-8-2-6" transform="translate(41.25 -30.543)" stroke-width="28"></g>
+    </g>
+   </g>
+   <g id="text4931">
+    <g id="g4983" fill="#fff">
+     <g id="g5263">
+      <path id="path5235" d="m413.4 820.82v27.185h20.959v14.838h-20.959v69.623q0 6.7444 2.8015 10.168 2.8015 3.3203 9.5459 3.3203 3.3203 0 9.1309-1.2451v15.564q-7.5745 2.0752-14.734 2.0752-12.866 0-19.403-7.782-6.5369-7.782-6.5369-22.101v-69.623h-20.441v-14.838h20.441v-27.185h19.196z"/>
+      <path id="path5237" d="m475.86 861.6q12.762-15.668 33.203-15.668 35.59 0 35.901 40.155v74.188h-19.196v-74.292q-0.10376-12.14-5.603-17.95-5.3955-5.8106-16.913-5.8106-9.3384 0-16.394 4.9805-7.0557 4.9805-10.999 13.074v79.999h-19.196v-159.38h19.196v60.699z"/>
+      <path id="path5239" d="m594.56 960.27h-19.196v-112.27h19.196v112.27zm-20.75-142.05q0-4.6692 2.8015-7.8857 2.9053-3.2166 8.5083-3.2166t8.5083 3.2166 2.9053 7.8857q0 4.6692-2.9053 7.782t-8.5083 3.1128-8.5083-3.1128q-2.8015-3.1128-2.8015-7.782z"/>
+      <path id="path5241" d="m643.33 848 0.62256 14.111q12.866-16.187 33.618-16.187 35.59 0 35.901 40.155v74.188h-19.196v-74.292q-0.10376-12.14-5.603-17.95-5.3955-5.8106-16.913-5.8106-9.3384 0-16.394 4.9805-7.0557 4.9805-10.999 13.074v79.999h-19.196v-112.27h18.158z"/>
+      <path id="path5243" d="m738.06 903.2q0-26.251 12.14-41.711 12.14-15.564 32.166-15.564 20.544 0 32.062 14.526l0.93384-12.451h17.535v109.57q0 21.79-12.97 34.344-12.866 12.555-34.656 12.555-12.14 0-23.761-5.1879-11.621-5.188-17.743-14.215l9.9609-11.517q12.347 15.253 30.194 15.253 14.008 0 21.79-7.8858 7.8857-7.8857 7.8857-22.205v-9.6497q-11.517 13.281-31.439 13.281-19.714 0-31.958-15.875-12.14-15.875-12.14-43.268zm19.299 2.179q0 18.988 7.782 29.883 7.782 10.791 21.79 10.791 18.158 0 26.666-16.498v-51.257q-8.8196-16.083-26.459-16.083-14.008 0-21.893 10.895-7.8857 10.895-7.8857 32.269z"/>
+      <path id="path5245" d="m927.11 930.49q0-7.782-5.9143-12.036-5.8106-4.3579-20.441-7.4707-14.526-3.1128-23.138-7.4707-8.5083-4.3579-12.659-10.376-4.0466-6.0181-4.0466-14.319 0-13.8 11.621-23.346 11.725-9.5459 29.883-9.5459 19.092 0 30.92 9.8572 11.932 9.8572 11.932 25.214h-19.299q0-7.8857-6.7444-13.593-6.6406-5.7068-16.809-5.7068-10.48 0-16.394 4.5654-5.9143 4.5654-5.9143 11.932 0 6.9519 5.4993 10.48 5.4993 3.5278 19.818 6.7444 14.423 3.2166 23.346 7.6782 8.9233 4.4617 13.177 10.791 4.3579 6.2256 4.3579 15.253 0 15.045-12.036 24.176-12.036 9.0271-31.232 9.0271-13.489 0-23.865-4.7729-10.376-4.773-16.29-13.281-5.8105-8.6121-5.8105-18.573h19.196q0.5188 9.6497 7.6782 15.356 7.2632 5.603 19.092 5.603 10.895 0 17.432-4.3579 6.6406-4.4617 6.6406-11.829z"/>
+      <path id="path5247" d="m1066.4 905.38q0 25.732-11.829 41.4-11.829 15.564-31.75 15.564-21.271 0-32.892-15.045l-0.93384 12.97h-17.639v-159.38h19.196v59.454q11.621-14.423 32.062-14.423t32.062 15.46q11.725 15.46 11.725 42.334v1.6602zm-19.196-2.179q0-19.611-7.5745-30.298-7.5744-10.687-21.79-10.687-18.988 0-27.289 17.639v48.56q8.8196 17.639 27.496 17.639 13.8 0 21.478-10.687 7.6782-10.687 7.6782-32.166z"/>
+      <path id="path5249" d="m1085.3 903.1q0-16.498 6.4331-29.675 6.5369-13.177 18.054-20.337 11.621-7.1594 26.459-7.1594 22.931 0 37.042 15.875 14.215 15.875 14.215 42.23v1.3489q0 16.394-6.3293 29.468-6.2256 12.97-17.95 20.233-11.621 7.2632-26.77 7.2632-22.827 0-37.042-15.875-14.111-15.875-14.111-42.023v-1.3489zm19.299 2.2827q0 18.677 8.6121 29.987 8.7158 11.31 23.242 11.31 14.63 0 23.242-11.414 8.6121-11.517 8.6121-32.166 0-18.469-8.8196-29.883-8.7158-11.517-23.242-11.517-14.215 0-22.931 11.31-8.7158 11.31-8.7158 32.373z"/>
+      <path id="path5251" d="m1280.9 960.27q-1.6601-3.3203-2.6977-11.829-13.385 13.904-31.958 13.904-16.602 0-27.289-9.3384-10.584-9.4421-10.584-23.865 0-17.535 13.281-27.185 13.385-9.7534 37.561-9.7534h18.677v-8.8196q0-10.065-6.0181-15.979-6.018-6.0181-17.743-6.0181-10.272 0-17.224 5.188-6.9519 5.188-6.9519 12.555h-19.299q0-8.4045 5.9143-16.187 6.0181-7.8857 16.186-12.451 10.272-4.5654 22.516-4.5654 19.403 0 30.402 9.7534 10.998 9.6496 11.414 26.666v51.672q0 15.46 3.9428 24.591v1.6602h-20.129zm-31.854-14.63q9.0271 0 17.12-4.6692 8.0932-4.6692 11.725-12.14v-23.035h-15.045q-35.278 0-35.278 20.648 0 9.0271 6.0181 14.111 6.0181 5.0842 15.46 5.0842z"/>
+      <path id="path5253" d="m1381.7 865.23q-4.3579-0.72631-9.4422-0.72631-18.884 0-25.629 16.083v79.688h-19.196v-112.27h18.677l0.3113 12.97q9.4421-15.045 26.77-15.045 5.603 0 8.5083 1.4526v17.847z"/>
+      <path id="path5255" d="m1394.5 903.2q0-25.836 12.244-41.504 12.244-15.771 32.062-15.771 19.714 0 31.232 13.489v-58.521h19.196v159.38h-17.639l-0.9338-12.036q-11.517 14.111-32.062 14.111-19.507 0-31.854-15.979-12.244-15.979-12.244-41.711v-1.4526zm19.196 2.179q0 19.092 7.8858 29.883 7.8857 10.791 21.79 10.791 18.262 0 26.666-16.394v-51.569q-8.6121-15.875-26.459-15.875-14.111 0-21.997 10.895-7.8858 10.895-7.8858 32.269z"/>
+      <path id="path5257" d="m1519.6 950.21q0-4.9805 2.9053-8.3008 3.009-3.3203 8.9233-3.3203t8.9234 3.3203q3.1127 3.3203 3.1127 8.3008 0 4.773-3.1127 7.9895-3.0091 3.2166-8.9234 3.2166t-8.9233-3.2166q-2.9053-3.2166-2.9053-7.9895z"/>
+      <path id="path5259" d="m1596 960.27h-19.196v-112.27h19.196v112.27zm-20.752-142.05q0-4.6692 2.8015-7.8857 2.9053-3.2166 8.5083-3.2166t8.5083 3.2166 2.9053 7.8857q0 4.6692-2.9053 7.782t-8.5083 3.1128-8.5083-3.1128q-2.8015-3.1128-2.8015-7.782z"/>
+      <path id="path5261" d="m1621.6 903.1q0-16.498 6.4332-29.675 6.5368-13.177 18.054-20.337 11.621-7.1594 26.459-7.1594 22.931 0 37.042 15.875 14.215 15.875 14.215 42.23v1.3489q0 16.394-6.3293 29.468-6.2256 12.97-17.95 20.233-11.621 7.2632-26.77 7.2632-22.827 0-37.042-15.875-14.111-15.875-14.111-42.023v-1.3489zm19.299 2.2827q0 18.677 8.612 29.987 8.7158 11.31 23.242 11.31 14.63 0 23.242-11.414 8.6121-11.517 8.6121-32.166 0-18.469-8.8196-29.883-8.7158-11.517-23.242-11.517-14.215 0-22.931 11.31-8.7158 11.31-8.7158 32.373z"/>
+     </g>
+     <g id="g4241" transform="translate(-.000061238 .000078147)" stroke-width="28">
+      <path id="path4278" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m151.13 732.36c-28.363 0-54.915 7.9153-77.613 21.537-6.4723-5.2623-14.605-8.1915-23.067-8.1937a8.7661 8.7661 0 0 0 -0.0035 0c-20.154 0.00073-36.679 16.528-36.678 36.682a8.7661 8.7661 0 0 0 0 0.0106c0.0099 8.4147 2.9267 16.483 8.1033 22.927-13.83 22.83-21.87 49.58-21.87 78.17a8.7661 8.7661 0 1 0 17.531 0c0-24.702 6.7193-47.748 18.378-67.574 4.5663 1.9846 9.4727 3.1496 14.519 3.1573a8.7661 8.7661 0 0 0 0.01241 0c20.155 0.00062 36.683-16.527 36.682-36.682a8.7661 8.7661 0 0 0 0 -0.004c-0.0016-4.9994-1.1387-9.8628-3.0828-14.397 19.717-11.484 42.585-18.095 67.085-18.095a8.7661 8.7661 0 1 0 0 -17.531zm-100.69 30.88c5.9129 0.002 11.191 2.5121 14.836 7.0769a8.7661 8.7661 0 0 0 0.1826 0.21451c2.6715 3.3798 4.1326 7.5531 4.1341 11.863-0.0015 10.677-8.468 19.144-19.144 19.148-4.37-0.008-8.6011-1.5088-12-4.2546a8.7661 8.7661 0 0 0 -0.01241 -0.009c-4.514-3.6331-7.1358-9.0979-7.1442-14.893 0.0025-10.677 8.4701-19.144 19.148-19.146z"/>
+      <path id="path4486-0-9-6-9-7-6-5" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m66.992 835.19c-1.492 1.5583-2.3663 3.7103-2.2576 6.0713 0.0962 2.0447 0.91322 4.0204 2.3372 5.5177 6.8051 6.8559 20.223 20.223 20.223 20.223l11.844-11.83s-12.973-12.961-20.176-20.171c-1.604-1.6325-3.7498-2.3141-6.012-2.3243-2.3583-0.0112-4.4673 0.95389-5.9592 2.5122zm32.147 19.983-36.639 36.639c-3.9751 3.976-3.9751 10.421 0 14.397l18.156 18.156 31.753 31.753 30.478 30.478c3.9758 3.9759 10.422 3.9763 14.398 0l24.791-24.791 37.914-37.914 36.639-36.639c3.9754-3.9764 3.975-10.422-0.00067-14.398l-18.63-18.63-31.75-31.76-30.01-30c-3.976-3.9751-10.421-3.9751-14.397 0l-24.79 24.79-37.91 37.91zm37.911-37.91s-12.973-12.961-20.176-20.171c-1.604-1.6325-3.7498-2.3141-6.012-2.3243-4.7166-0.0226-8.4341 3.8616-8.2169 8.5834 0.0962 2.0447 0.91322 4.0204 2.3372 5.5177 6.8051 6.8559 20.223 20.223 20.223 20.223l11.844-11.83zm69.193 5.2132s12.961-12.973 20.171-20.176c1.6325-1.604 2.3141-3.7498 2.3243-6.012 0.0225-4.7166-3.8616-8.4341-8.5834-8.2169-2.0447 0.0962-4.0204 0.91322-5.5177 2.3372-6.8559 6.8051-20.223 20.223-20.223 20.223l11.83 11.844zm31.753 31.753s12.961-12.973 20.171-20.176c1.6325-1.604 2.314-3.7498 2.3243-6.012 0.0225-4.7166-3.8616-8.4341-8.5834-8.2169-2.0447 0.0962-4.0204 0.91323-5.5177 2.3372-6.8559 6.8051-20.223 20.223-20.223 20.223l11.83 11.844zm-18.009 69.667s12.973 12.961 20.178 20.169c1.604 1.6324 3.7498 2.314 6.012 2.3243 4.7166 0.0225 8.4342-3.8615 8.2169-8.5834l-0.002 0.002c-0.0962-2.0447-0.91415-4.0214-2.3382-5.5186-6.8051-6.8559-20.222-20.222-20.222-20.222l-11.844 11.83zm-37.914 37.914s12.973 12.961 20.178 20.169c1.604 1.6324 3.7498 2.314 6.012 2.3242 4.7166 0.023 8.4342-3.8615 8.2169-8.5834h-0.002c-0.0962-2.0447-0.91323-4.0205-2.3372-5.5177-6.8051-6.8559-20.223-20.223-20.223-20.223l-11.844 11.83zm-69.667-5.6871s-12.961 12.973-20.169 20.178c-1.6324 1.604-2.314 3.7498-2.3243 6.012-0.02251 4.7166 3.8615 8.4342 8.5834 8.2169h-0.0019c2.0447-0.096 4.0204-0.9132 5.5177-2.3373 6.8561-6.8048 20.223-20.223 20.223-20.223l-11.82-11.84zm-31.743-31.74s-12.961 12.973-20.169 20.178c-1.6324 1.604-2.314 3.7498-2.3243 6.012-0.02251 4.7167 3.8615 8.4342 8.5834 8.2169h-0.0019c2.0447-0.0962 4.0204-0.91322 5.5177-2.3372 6.8561-6.8047 20.223-20.223 20.223-20.223l-11.829-11.85zm87.237-90.554c1.6794-1.7064 3.9669-2.6599 6.2971-2.626 1.6628 0.0237 3.253 0.55012 4.5625 1.5097l16.499 12.1c3.2009 2.297 4.1445 6.6589 2.2308 10.312-1.9137 3.6528-6.1234 5.5243-9.9506 4.4227l6.124 23.948c1.1128 4.3517-1.564 8.9677-5.9833 10.317l-44.642 13.631 8.2456 31.883c1.1728 4.3696-1.5017 9.0445-5.9546 10.407s-8.9756-1.1106-10.068-5.5047l-10.282-39.769c-1.1265-4.3556 1.5493-8.9847 5.9759-10.338l44.661-13.637-4.1219-16.118c-2.7634 3.0643-7.2335 3.8084-10.586 1.7615-3.3531-2.0469-4.6144-6.2896-2.9861-10.047l8.1169-19.454c0.43322-1.0376 1.0673-1.9888 1.8624-2.7973z" fill-rule="evenodd"/>
+      <path id="path4278-5" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m168.87 1052.4c28.363 0 54.915-7.9155 77.614-21.538 6.4724 5.2624 14.605 8.1917 23.067 8.1939a8.7662 8.7662 0 0 0 0.004 0c20.155-0.0007 36.68-16.528 36.679-36.682a8.7662 8.7662 0 0 0 0 -0.011c-0.01-8.4149-2.9267-16.484-8.1034-22.927 13.825-22.82 21.866-49.572 21.866-78.162a8.7662 8.7662 0 1 0 -17.531 0c0 24.703-6.7194 47.749-18.378 67.575-4.5664-1.9846-9.4728-3.1496-14.519-3.1573a8.7662 8.7662 0 0 0 -0.0124 0c-20.155-0.00062-36.683 16.527-36.682 36.682 0.002 4.9994 1.1387 9.8628 3.0829 14.397-19.717 11.484-42.586 18.095-67.086 18.095a8.7662 8.7662 0 1 0 0 17.531zm100.69-30.875c-5.913 0-11.191-2.5122-14.836-7.0769a8.7662 8.7662 0 0 0 -0.18261 -0.2146c-2.6715-3.3799-4.1327-7.5531-4.1341-11.863 0.002-10.677 8.4681-19.144 19.144-19.148 4.37 0.008 8.6012 1.5088 12 4.2547a8.7662 8.7662 0 0 0 0.0124 0.009c4.5141 3.6332 7.1359 9.098 7.1443 14.893-0.003 10.677-8.4702 19.145-19.148 19.146z"/>
+     </g>
+    </g>
+   </g>
+  </g>
+ </g>
+</svg>
diff --git a/ui/src/svg/logo_white.svg b/ui/src/svg/logo_white.svg
new file mode 100644
index 0000000..52a38c9
--- /dev/null
+++ b/ui/src/svg/logo_white.svg
@@ -0,0 +1,10 @@
+<svg id="svg2" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="320" width="320" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox="0 0 320 320.00001">
+ <g id="layer1" transform="translate(0 -732.36)" stroke-width="28">
+  <g id="g5175-6-32" transform="matrix(1.0471 1.0606 -1.0484 1.0619 931.69 -208.55)"></g>
+  <g id="g4241" fill="#fff">
+   <path id="path4278" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m151.13 732.36c-28.363 0-54.915 7.9153-77.613 21.537-6.4723-5.2623-14.605-8.1915-23.067-8.1937a8.7661 8.7661 0 0 0 -0.0035 0c-20.154 0.00073-36.679 16.528-36.678 36.682a8.7661 8.7661 0 0 0 0 0.0106c0.0099 8.4147 2.9267 16.483 8.1033 22.927-13.83 22.83-21.87 49.58-21.87 78.17a8.7661 8.7661 0 1 0 17.531 0c0-24.702 6.7193-47.748 18.378-67.574 4.5663 1.9846 9.4727 3.1496 14.519 3.1573a8.7661 8.7661 0 0 0 0.01241 0c20.155 0.00062 36.683-16.527 36.682-36.682a8.7661 8.7661 0 0 0 0 -0.004c-0.0016-4.9994-1.1387-9.8628-3.0828-14.397 19.717-11.484 42.585-18.095 67.085-18.095a8.7661 8.7661 0 1 0 0 -17.531zm-100.69 30.88c5.9129 0.002 11.191 2.5121 14.836 7.0769a8.7661 8.7661 0 0 0 0.1826 0.21451c2.6715 3.3798 4.1326 7.5531 4.1341 11.863-0.0015 10.677-8.468 19.144-19.144 19.148-4.37-0.008-8.6011-1.5088-12-4.2546a8.7661 8.7661 0 0 0 -0.01241 -0.009c-4.514-3.6331-7.1358-9.0979-7.1442-14.893 0.0025-10.677 8.4701-19.144 19.148-19.146z"/>
+   <path id="path4486-0-9-6-9-7-6-5" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m66.992 835.19c-1.492 1.5583-2.3663 3.7103-2.2576 6.0713 0.0962 2.0447 0.91322 4.0204 2.3372 5.5177 6.8051 6.8559 20.223 20.223 20.223 20.223l11.844-11.83s-12.973-12.961-20.176-20.171c-1.604-1.6325-3.7498-2.3141-6.012-2.3243-2.3583-0.0112-4.4673 0.95389-5.9592 2.5122zm32.147 19.983-36.639 36.639c-3.9751 3.976-3.9751 10.421 0 14.397l18.156 18.156 31.753 31.753 30.478 30.478c3.9758 3.9759 10.422 3.9763 14.398 0l24.791-24.791 37.914-37.914 36.639-36.639c3.9754-3.9764 3.975-10.422-0.00067-14.398l-18.63-18.63-31.75-31.76-30.01-30c-3.976-3.9751-10.421-3.9751-14.397 0l-24.79 24.79-37.91 37.91zm37.911-37.91s-12.973-12.961-20.176-20.171c-1.604-1.6325-3.7498-2.3141-6.012-2.3243-4.7166-0.0226-8.4341 3.8616-8.2169 8.5834 0.0962 2.0447 0.91322 4.0204 2.3372 5.5177 6.8051 6.8559 20.223 20.223 20.223 20.223l11.844-11.83zm69.193 5.2132s12.961-12.973 20.171-20.176c1.6325-1.604 2.3141-3.7498 2.3243-6.012 0.0225-4.7166-3.8616-8.4341-8.5834-8.2169-2.0447 0.0962-4.0204 0.91322-5.5177 2.3372-6.8559 6.8051-20.223 20.223-20.223 20.223l11.83 11.844zm31.753 31.753s12.961-12.973 20.171-20.176c1.6325-1.604 2.314-3.7498 2.3243-6.012 0.0225-4.7166-3.8616-8.4341-8.5834-8.2169-2.0447 0.0962-4.0204 0.91323-5.5177 2.3372-6.8559 6.8051-20.223 20.223-20.223 20.223l11.83 11.844zm-18.009 69.667s12.973 12.961 20.178 20.169c1.604 1.6324 3.7498 2.314 6.012 2.3243 4.7166 0.0225 8.4342-3.8615 8.2169-8.5834l-0.002 0.002c-0.0962-2.0447-0.91415-4.0214-2.3382-5.5186-6.8051-6.8559-20.222-20.222-20.222-20.222l-11.844 11.83zm-37.914 37.914s12.973 12.961 20.178 20.169c1.604 1.6324 3.7498 2.314 6.012 2.3242 4.7166 0.023 8.4342-3.8615 8.2169-8.5834h-0.002c-0.0962-2.0447-0.91323-4.0205-2.3372-5.5177-6.8051-6.8559-20.223-20.223-20.223-20.223l-11.844 11.83zm-69.667-5.6871s-12.961 12.973-20.169 20.178c-1.6324 1.604-2.314 3.7498-2.3243 6.012-0.02251 4.7166 3.8615 8.4342 8.5834 8.2169h-0.0019c2.0447-0.096 4.0204-0.9132 5.5177-2.3373 6.8561-6.8048 20.223-20.223 20.223-20.223l-11.82-11.84zm-31.743-31.74s-12.961 12.973-20.169 20.178c-1.6324 1.604-2.314 3.7498-2.3243 6.012-0.02251 4.7167 3.8615 8.4342 8.5834 8.2169h-0.0019c2.0447-0.0962 4.0204-0.91322 5.5177-2.3372 6.8561-6.8047 20.223-20.223 20.223-20.223l-11.829-11.85zm87.237-90.554c1.6794-1.7064 3.9669-2.6599 6.2971-2.626 1.6628 0.0237 3.253 0.55012 4.5625 1.5097l16.499 12.1c3.2009 2.297 4.1445 6.6589 2.2308 10.312-1.9137 3.6528-6.1234 5.5243-9.9506 4.4227l6.124 23.948c1.1128 4.3517-1.564 8.9677-5.9833 10.317l-44.642 13.631 8.2456 31.883c1.1728 4.3696-1.5017 9.0445-5.9546 10.407s-8.9756-1.1106-10.068-5.5047l-10.282-39.769c-1.1265-4.3556 1.5493-8.9847 5.9759-10.338l44.661-13.637-4.1219-16.118c-2.7634 3.0643-7.2335 3.8084-10.586 1.7615-3.3531-2.0469-4.6144-6.2896-2.9861-10.047l8.1169-19.454c0.43322-1.0376 1.0673-1.9888 1.8624-2.7973z" fill-rule="evenodd"/>
+   <path id="path4278-5" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m168.87 1052.4c28.363 0 54.915-7.9155 77.614-21.538 6.4724 5.2624 14.605 8.1917 23.067 8.1939a8.7662 8.7662 0 0 0 0.004 0c20.155-0.0007 36.68-16.528 36.679-36.682a8.7662 8.7662 0 0 0 0 -0.011c-0.01-8.4149-2.9267-16.484-8.1034-22.927 13.825-22.82 21.866-49.572 21.866-78.162a8.7662 8.7662 0 1 0 -17.531 0c0 24.703-6.7194 47.749-18.378 67.575-4.5664-1.9846-9.4728-3.1496-14.519-3.1573a8.7662 8.7662 0 0 0 -0.0124 0c-20.155-0.00062-36.683 16.527-36.682 36.682 0.002 4.9994 1.1387 9.8628 3.0829 14.397-19.717 11.484-42.586 18.095-67.086 18.095a8.7662 8.7662 0 1 0 0 17.531zm100.69-30.875c-5.913 0-11.191-2.5122-14.836-7.0769a8.7662 8.7662 0 0 0 -0.18261 -0.2146c-2.6715-3.3799-4.1327-7.5531-4.1341-11.863 0.002-10.677 8.4681-19.144 19.144-19.148 4.37 0.008 8.6012 1.5088 12 4.2547a8.7662 8.7662 0 0 0 0.0124 0.009c4.5141 3.6332 7.1359 9.098 7.1443 14.893-0.003 10.677-8.4702 19.145-19.148 19.146z"/>
+  </g>
+ </g>
+</svg>
diff --git a/ui/src/svg/mdi.svg b/ui/src/svg/mdi.svg
new file mode 100644
index 0000000..bb8fa1c
--- /dev/null
+++ b/ui/src/svg/mdi.svg
@@ -0,0 +1 @@
+<svg><defs><g id="access-point"><path d="M4.93,4.93C3.12,6.74 2,9.24 2,12C2,14.76 3.12,17.26 4.93,19.07L6.34,17.66C4.89,16.22 4,14.22 4,12C4,9.79 4.89,7.78 6.34,6.34L4.93,4.93M19.07,4.93L17.66,6.34C19.11,7.78 20,9.79 20,12C20,14.22 19.11,16.22 17.66,17.66L19.07,19.07C20.88,17.26 22,14.76 22,12C22,9.24 20.88,6.74 19.07,4.93M7.76,7.76C6.67,8.85 6,10.35 6,12C6,13.65 6.67,15.15 7.76,16.24L9.17,14.83C8.45,14.11 8,13.11 8,12C8,10.89 8.45,9.89 9.17,9.17L7.76,7.76M16.24,7.76L14.83,9.17C15.55,9.89 16,10.89 16,12C16,13.11 15.55,14.11 14.83,14.83L16.24,16.24C17.33,15.15 18,13.65 18,12C18,10.35 17.33,8.85 16.24,7.76M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10Z" /></g><g id="access-point-network"><path d="M4.93,2.93C3.12,4.74 2,7.24 2,10C2,12.76 3.12,15.26 4.93,17.07L6.34,15.66C4.89,14.22 4,12.22 4,10C4,7.79 4.89,5.78 6.34,4.34L4.93,2.93M19.07,2.93L17.66,4.34C19.11,5.78 20,7.79 20,10C20,12.22 19.11,14.22 17.66,15.66L19.07,17.07C20.88,15.26 22,12.76 22,10C22,7.24 20.88,4.74 19.07,2.93M7.76,5.76C6.67,6.85 6,8.35 6,10C6,11.65 6.67,13.15 7.76,14.24L9.17,12.83C8.45,12.11 8,11.11 8,10C8,8.89 8.45,7.89 9.17,7.17L7.76,5.76M16.24,5.76L14.83,7.17C15.55,7.89 16,8.89 16,10C16,11.11 15.55,12.11 14.83,12.83L16.24,14.24C17.33,13.15 18,11.65 18,10C18,8.35 17.33,6.85 16.24,5.76M12,8A2,2 0 0,0 10,10A2,2 0 0,0 12,12A2,2 0 0,0 14,10A2,2 0 0,0 12,8M11,14V18H10A1,1 0 0,0 9,19H2V21H9A1,1 0 0,0 10,22H14A1,1 0 0,0 15,21H22V19H15A1,1 0 0,0 14,18H13V14H11Z" /></g><g id="account"><path d="M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z" /></g><g id="account-alert"><path d="M10,4A4,4 0 0,1 14,8A4,4 0 0,1 10,12A4,4 0 0,1 6,8A4,4 0 0,1 10,4M10,14C14.42,14 18,15.79 18,18V20H2V18C2,15.79 5.58,14 10,14M20,12V7H22V12H20M20,16V14H22V16H20Z" /></g><g id="account-box"><path d="M6,17C6,15 10,13.9 12,13.9C14,13.9 18,15 18,17V18H6M15,9A3,3 0 0,1 12,12A3,3 0 0,1 9,9A3,3 0 0,1 12,6A3,3 0 0,1 15,9M3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3H5C3.89,3 3,3.9 3,5Z" /></g><g id="account-box-outline"><path d="M19,19H5V5H19M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3M16.5,16.25C16.5,14.75 13.5,14 12,14C10.5,14 7.5,14.75 7.5,16.25V17H16.5M12,12.25A2.25,2.25 0 0,0 14.25,10A2.25,2.25 0 0,0 12,7.75A2.25,2.25 0 0,0 9.75,10A2.25,2.25 0 0,0 12,12.25Z" /></g><g id="account-card-details"><path d="M2,3H22C23.05,3 24,3.95 24,5V19C24,20.05 23.05,21 22,21H2C0.95,21 0,20.05 0,19V5C0,3.95 0.95,3 2,3M14,6V7H22V6H14M14,8V9H21.5L22,9V8H14M14,10V11H21V10H14M8,13.91C6,13.91 2,15 2,17V18H14V17C14,15 10,13.91 8,13.91M8,6A3,3 0 0,0 5,9A3,3 0 0,0 8,12A3,3 0 0,0 11,9A3,3 0 0,0 8,6Z" /></g><g id="account-check"><path d="M9,5A3.5,3.5 0 0,1 12.5,8.5A3.5,3.5 0 0,1 9,12A3.5,3.5 0 0,1 5.5,8.5A3.5,3.5 0 0,1 9,5M9,13.75C12.87,13.75 16,15.32 16,17.25V19H2V17.25C2,15.32 5.13,13.75 9,13.75M17,12.66L14.25,9.66L15.41,8.5L17,10.09L20.59,6.5L21.75,7.91L17,12.66Z" /></g><g id="account-circle"><path d="M12,19.2C9.5,19.2 7.29,17.92 6,16C6.03,14 10,12.9 12,12.9C14,12.9 17.97,14 18,16C16.71,17.92 14.5,19.2 12,19.2M12,5A3,3 0 0,1 15,8A3,3 0 0,1 12,11A3,3 0 0,1 9,8A3,3 0 0,1 12,5M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z" /></g><g id="account-convert"><path d="M7.5,21.5L8.85,20.16L12.66,23.97L12,24C5.71,24 0.56,19.16 0.05,13H1.55C1.91,16.76 4.25,19.94 7.5,21.5M16.5,2.5L15.15,3.84L11.34,0.03L12,0C18.29,0 23.44,4.84 23.95,11H22.45C22.09,7.24 19.75,4.07 16.5,2.5M6,17C6,15 10,13.9 12,13.9C14,13.9 18,15 18,17V18H6V17M15,9A3,3 0 0,1 12,12A3,3 0 0,1 9,9A3,3 0 0,1 12,6A3,3 0 0,1 15,9Z" /></g><g id="account-key"><path d="M11,10V12H10V14H8V12H5.83C5.42,13.17 4.31,14 3,14A3,3 0 0,1 0,11A3,3 0 0,1 3,8C4.31,8 5.42,8.83 5.83,10H11M3,10A1,1 0 0,0 2,11A1,1 0 0,0 3,12A1,1 0 0,0 4,11A1,1 0 0,0 3,10M16,14C18.67,14 24,15.34 24,18V20H8V18C8,15.34 13.33,14 16,14M16,12A4,4 0 0,1 12,8A4,4 0 0,1 16,4A4,4 0 0,1 20,8A4,4 0 0,1 16,12Z" /></g><g id="account-location"><path d="M18,16H6V15.1C6,13.1 10,12 12,12C14,12 18,13.1 18,15.1M12,5.3C13.5,5.3 14.7,6.5 14.7,8C14.7,9.5 13.5,10.7 12,10.7C10.5,10.7 9.3,9.5 9.3,8C9.3,6.5 10.5,5.3 12,5.3M19,2H5C3.89,2 3,2.89 3,4V18A2,2 0 0,0 5,20H9L12,23L15,20H19A2,2 0 0,0 21,18V4C21,2.89 20.1,2 19,2Z" /></g><g id="account-minus"><path d="M15,14C12.33,14 7,15.33 7,18V20H23V18C23,15.33 17.67,14 15,14M1,10V12H9V10M15,12A4,4 0 0,0 19,8A4,4 0 0,0 15,4A4,4 0 0,0 11,8A4,4 0 0,0 15,12Z" /></g><g id="account-multiple"><path d="M16,13C15.71,13 15.38,13 15.03,13.05C16.19,13.89 17,15 17,16.5V19H23V16.5C23,14.17 18.33,13 16,13M8,13C5.67,13 1,14.17 1,16.5V19H15V16.5C15,14.17 10.33,13 8,13M8,11A3,3 0 0,0 11,8A3,3 0 0,0 8,5A3,3 0 0,0 5,8A3,3 0 0,0 8,11M16,11A3,3 0 0,0 19,8A3,3 0 0,0 16,5A3,3 0 0,0 13,8A3,3 0 0,0 16,11Z" /></g><g id="account-multiple-minus"><path d="M13,13C11,13 7,14 7,16V18H19V16C19,14 15,13 13,13M19.62,13.16C20.45,13.88 21,14.82 21,16V18H24V16C24,14.46 21.63,13.5 19.62,13.16M13,11A3,3 0 0,0 16,8A3,3 0 0,0 13,5A3,3 0 0,0 10,8A3,3 0 0,0 13,11M18,11A3,3 0 0,0 21,8A3,3 0 0,0 18,5C17.68,5 17.37,5.05 17.08,5.14C17.65,5.95 18,6.94 18,8C18,9.06 17.65,10.04 17.08,10.85C17.37,10.95 17.68,11 18,11M8,10H0V12H8V10Z" /></g><g id="account-multiple-outline"><path d="M16.5,6.5A2,2 0 0,1 18.5,8.5A2,2 0 0,1 16.5,10.5A2,2 0 0,1 14.5,8.5A2,2 0 0,1 16.5,6.5M16.5,12A3.5,3.5 0 0,0 20,8.5A3.5,3.5 0 0,0 16.5,5A3.5,3.5 0 0,0 13,8.5A3.5,3.5 0 0,0 16.5,12M7.5,6.5A2,2 0 0,1 9.5,8.5A2,2 0 0,1 7.5,10.5A2,2 0 0,1 5.5,8.5A2,2 0 0,1 7.5,6.5M7.5,12A3.5,3.5 0 0,0 11,8.5A3.5,3.5 0 0,0 7.5,5A3.5,3.5 0 0,0 4,8.5A3.5,3.5 0 0,0 7.5,12M21.5,17.5H14V16.25C14,15.79 13.8,15.39 13.5,15.03C14.36,14.73 15.44,14.5 16.5,14.5C18.94,14.5 21.5,15.71 21.5,16.25M12.5,17.5H2.5V16.25C2.5,15.71 5.06,14.5 7.5,14.5C9.94,14.5 12.5,15.71 12.5,16.25M16.5,13C15.3,13 13.43,13.34 12,14C10.57,13.33 8.7,13 7.5,13C5.33,13 1,14.08 1,16.25V19H23V16.25C23,14.08 18.67,13 16.5,13Z" /></g><g id="account-multiple-plus"><path d="M13,13C11,13 7,14 7,16V18H19V16C19,14 15,13 13,13M19.62,13.16C20.45,13.88 21,14.82 21,16V18H24V16C24,14.46 21.63,13.5 19.62,13.16M13,11A3,3 0 0,0 16,8A3,3 0 0,0 13,5A3,3 0 0,0 10,8A3,3 0 0,0 13,11M18,11A3,3 0 0,0 21,8A3,3 0 0,0 18,5C17.68,5 17.37,5.05 17.08,5.14C17.65,5.95 18,6.94 18,8C18,9.06 17.65,10.04 17.08,10.85C17.37,10.95 17.68,11 18,11M8,10H5V7H3V10H0V12H3V15H5V12H8V10Z" /></g><g id="account-network"><path d="M13,16V18H14A1,1 0 0,1 15,19H22V21H15A1,1 0 0,1 14,22H10A1,1 0 0,1 9,21H2V19H9A1,1 0 0,1 10,18H11V16H5V14.5C5,12.57 8.13,11 12,11C15.87,11 19,12.57 19,14.5V16H13M12,2A3.5,3.5 0 0,1 15.5,5.5A3.5,3.5 0 0,1 12,9A3.5,3.5 0 0,1 8.5,5.5A3.5,3.5 0 0,1 12,2Z" /></g><g id="account-off"><path d="M12,4A4,4 0 0,1 16,8C16,9.95 14.6,11.58 12.75,11.93L8.07,7.25C8.42,5.4 10.05,4 12,4M12.28,14L18.28,20L20,21.72L18.73,23L15.73,20H4V18C4,16.16 6.5,14.61 9.87,14.14L2.78,7.05L4.05,5.78L12.28,14M20,18V19.18L15.14,14.32C18,14.93 20,16.35 20,18Z" /></g><g id="account-outline"><path d="M12,13C9.33,13 4,14.33 4,17V20H20V17C20,14.33 14.67,13 12,13M12,4A4,4 0 0,0 8,8A4,4 0 0,0 12,12A4,4 0 0,0 16,8A4,4 0 0,0 12,4M12,14.9C14.97,14.9 18.1,16.36 18.1,17V18.1H5.9V17C5.9,16.36 9,14.9 12,14.9M12,5.9A2.1,2.1 0 0,1 14.1,8A2.1,2.1 0 0,1 12,10.1A2.1,2.1 0 0,1 9.9,8A2.1,2.1 0 0,1 12,5.9Z" /></g><g id="account-plus"><path d="M15,14C12.33,14 7,15.33 7,18V20H23V18C23,15.33 17.67,14 15,14M6,10V7H4V10H1V12H4V15H6V12H9V10M15,12A4,4 0 0,0 19,8A4,4 0 0,0 15,4A4,4 0 0,0 11,8A4,4 0 0,0 15,12Z" /></g><g id="account-remove"><path d="M15,14C17.67,14 23,15.33 23,18V20H7V18C7,15.33 12.33,14 15,14M15,12A4,4 0 0,1 11,8A4,4 0 0,1 15,4A4,4 0 0,1 19,8A4,4 0 0,1 15,12M5,9.59L7.12,7.46L8.54,8.88L6.41,11L8.54,13.12L7.12,14.54L5,12.41L2.88,14.54L1.46,13.12L3.59,11L1.46,8.88L2.88,7.46L5,9.59Z" /></g><g id="account-search"><path d="M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.43,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.43C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,14C11.11,14 12.5,13.15 13.32,11.88C12.5,10.75 11.11,10 9.5,10C7.89,10 6.5,10.75 5.68,11.88C6.5,13.15 7.89,14 9.5,14M9.5,5A1.75,1.75 0 0,0 7.75,6.75A1.75,1.75 0 0,0 9.5,8.5A1.75,1.75 0 0,0 11.25,6.75A1.75,1.75 0 0,0 9.5,5Z" /></g><g id="account-settings"><path d="M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14M7,22H9V24H7V22M11,22H13V24H11V22M15,22H17V24H15V22Z" /></g><g id="account-settings-variant"><path d="M9,4A4,4 0 0,0 5,8A4,4 0 0,0 9,12A4,4 0 0,0 13,8A4,4 0 0,0 9,4M9,14C6.33,14 1,15.33 1,18V20H12.08C12.03,19.67 12,19.34 12,19C12,17.5 12.5,16 13.41,14.8C11.88,14.28 10.18,14 9,14M18,14C17.87,14 17.76,14.09 17.74,14.21L17.55,15.53C17.25,15.66 16.96,15.82 16.7,16L15.46,15.5C15.35,15.5 15.22,15.5 15.15,15.63L14.15,17.36C14.09,17.47 14.11,17.6 14.21,17.68L15.27,18.5C15.25,18.67 15.24,18.83 15.24,19C15.24,19.17 15.25,19.33 15.27,19.5L14.21,20.32C14.12,20.4 14.09,20.53 14.15,20.64L15.15,22.37C15.21,22.5 15.34,22.5 15.46,22.5L16.7,22C16.96,22.18 17.24,22.35 17.55,22.47L17.74,23.79C17.76,23.91 17.86,24 18,24H20C20.11,24 20.22,23.91 20.24,23.79L20.43,22.47C20.73,22.34 21,22.18 21.27,22L22.5,22.5C22.63,22.5 22.76,22.5 22.83,22.37L23.83,20.64C23.89,20.53 23.86,20.4 23.77,20.32L22.7,19.5C22.72,19.33 22.74,19.17 22.74,19C22.74,18.83 22.73,18.67 22.7,18.5L23.76,17.68C23.85,17.6 23.88,17.47 23.82,17.36L22.82,15.63C22.76,15.5 22.63,15.5 22.5,15.5L21.27,16C21,15.82 20.73,15.65 20.42,15.53L20.23,14.21C20.22,14.09 20.11,14 20,14M19,17.5A1.5,1.5 0 0,1 20.5,19A1.5,1.5 0 0,1 19,20.5C18.16,20.5 17.5,19.83 17.5,19A1.5,1.5 0 0,1 19,17.5Z" /></g><g id="account-star"><path d="M15,14C12.33,14 7,15.33 7,18V20H23V18C23,15.33 17.67,14 15,14M15,12A4,4 0 0,0 19,8A4,4 0 0,0 15,4A4,4 0 0,0 11,8A4,4 0 0,0 15,12M5,13.28L7.45,14.77L6.8,11.96L9,10.08L6.11,9.83L5,7.19L3.87,9.83L1,10.08L3.18,11.96L2.5,14.77L5,13.28Z" /></g><g id="account-star-variant"><path d="M9,14C11.67,14 17,15.33 17,18V20H1V18C1,15.33 6.33,14 9,14M9,12A4,4 0 0,1 5,8A4,4 0 0,1 9,4A4,4 0 0,1 13,8A4,4 0 0,1 9,12M19,13.28L16.54,14.77L17.2,11.96L15,10.08L17.89,9.83L19,7.19L20.13,9.83L23,10.08L20.82,11.96L21.5,14.77L19,13.28Z" /></g><g id="account-switch"><path d="M16,9C18.33,9 23,10.17 23,12.5V15H17V12.5C17,11 16.19,9.89 15.04,9.05L16,9M8,9C10.33,9 15,10.17 15,12.5V15H1V12.5C1,10.17 5.67,9 8,9M8,7A3,3 0 0,1 5,4A3,3 0 0,1 8,1A3,3 0 0,1 11,4A3,3 0 0,1 8,7M16,7A3,3 0 0,1 13,4A3,3 0 0,1 16,1A3,3 0 0,1 19,4A3,3 0 0,1 16,7M9,16.75V19H15V16.75L18.25,20L15,23.25V21H9V23.25L5.75,20L9,16.75Z" /></g><g id="adjust"><path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M15,12A3,3 0 0,1 12,15A3,3 0 0,1 9,12A3,3 0 0,1 12,9A3,3 0 0,1 15,12Z" /></g><g id="air-conditioner"><path d="M6.59,0.66C8.93,-1.15 11.47,1.06 12.04,4.5C12.47,4.5 12.89,4.62 13.27,4.84C13.79,4.24 14.25,3.42 14.07,2.5C13.65,0.35 16.06,-1.39 18.35,1.58C20.16,3.92 17.95,6.46 14.5,7.03C14.5,7.46 14.39,7.89 14.16,8.27C14.76,8.78 15.58,9.24 16.5,9.06C18.63,8.64 20.38,11.04 17.41,13.34C15.07,15.15 12.53,12.94 11.96,9.5C11.53,9.5 11.11,9.37 10.74,9.15C10.22,9.75 9.75,10.58 9.93,11.5C10.35,13.64 7.94,15.39 5.65,12.42C3.83,10.07 6.05,7.53 9.5,6.97C9.5,6.54 9.63,6.12 9.85,5.74C9.25,5.23 8.43,4.76 7.5,4.94C5.37,5.36 3.62,2.96 6.59,0.66M5,16H7A2,2 0 0,1 9,18V24H7V22H5V24H3V18A2,2 0 0,1 5,16M5,18V20H7V18H5M12.93,16H15L12.07,24H10L12.93,16M18,16H21V18H18V22H21V24H18A2,2 0 0,1 16,22V18A2,2 0 0,1 18,16Z" /></g><g id="airballoon"><path d="M11,23A2,2 0 0,1 9,21V19H15V21A2,2 0 0,1 13,23H11M12,1C12.71,1 13.39,1.09 14.05,1.26C15.22,2.83 16,5.71 16,9C16,11.28 15.62,13.37 15,16A2,2 0 0,1 13,18H11A2,2 0 0,1 9,16C8.38,13.37 8,11.28 8,9C8,5.71 8.78,2.83 9.95,1.26C10.61,1.09 11.29,1 12,1M20,8C20,11.18 18.15,15.92 15.46,17.21C16.41,15.39 17,11.83 17,9C17,6.17 16.41,3.61 15.46,1.79C18.15,3.08 20,4.82 20,8M4,8C4,4.82 5.85,3.08 8.54,1.79C7.59,3.61 7,6.17 7,9C7,11.83 7.59,15.39 8.54,17.21C5.85,15.92 4,11.18 4,8Z" /></g><g id="airplane"><path d="M21,16V14L13,9V3.5A1.5,1.5 0 0,0 11.5,2A1.5,1.5 0 0,0 10,3.5V9L2,14V16L10,13.5V19L8,20.5V22L11.5,21L15,22V20.5L13,19V13.5L21,16Z" /></g><g id="airplane-landing"><path d="M2.5,19H21.5V21H2.5V19M9.68,13.27L14.03,14.43L19.34,15.85C20.14,16.06 20.96,15.59 21.18,14.79C21.39,14 20.92,13.17 20.12,12.95L14.81,11.53L12.05,2.5L10.12,2V10.28L5.15,8.95L4.22,6.63L2.77,6.24V11.41L4.37,11.84L9.68,13.27Z" /></g><g id="airplane-off"><path d="M3.15,5.27L8.13,10.26L2.15,14V16L10.15,13.5V19L8.15,20.5V22L11.65,21L15.15,22V20.5L13.15,19V15.27L18.87,21L20.15,19.73L4.42,4M13.15,9V3.5A1.5,1.5 0 0,0 11.65,2A1.5,1.5 0 0,0 10.15,3.5V7.18L17.97,15L21.15,16V14L13.15,9Z" /></g><g id="airplane-takeoff"><path d="M2.5,19H21.5V21H2.5V19M22.07,9.64C21.86,8.84 21.03,8.36 20.23,8.58L14.92,10L8,3.57L6.09,4.08L10.23,11.25L5.26,12.58L3.29,11.04L1.84,11.43L3.66,14.59L4.43,15.92L6.03,15.5L11.34,14.07L15.69,12.91L21,11.5C21.81,11.26 22.28,10.44 22.07,9.64Z" /></g><g id="airplay"><path d="M6,22H18L12,16M21,3H3A2,2 0 0,0 1,5V17A2,2 0 0,0 3,19H7V17H3V5H21V17H17V19H21A2,2 0 0,0 23,17V5A2,2 0 0,0 21,3Z" /></g><g id="alarm"><path d="M12,20A7,7 0 0,1 5,13A7,7 0 0,1 12,6A7,7 0 0,1 19,13A7,7 0 0,1 12,20M12,4A9,9 0 0,0 3,13A9,9 0 0,0 12,22A9,9 0 0,0 21,13A9,9 0 0,0 12,4M12.5,8H11V14L15.75,16.85L16.5,15.62L12.5,13.25V8M7.88,3.39L6.6,1.86L2,5.71L3.29,7.24L7.88,3.39M22,5.72L17.4,1.86L16.11,3.39L20.71,7.25L22,5.72Z" /></g><g id="alarm-check"><path d="M10.54,14.53L8.41,12.4L7.35,13.46L10.53,16.64L16.53,10.64L15.47,9.58L10.54,14.53M12,20A7,7 0 0,1 5,13A7,7 0 0,1 12,6A7,7 0 0,1 19,13A7,7 0 0,1 12,20M12,4A9,9 0 0,0 3,13A9,9 0 0,0 12,22A9,9 0 0,0 21,13A9,9 0 0,0 12,4M7.88,3.39L6.6,1.86L2,5.71L3.29,7.24L7.88,3.39M22,5.72L17.4,1.86L16.11,3.39L20.71,7.25L22,5.72Z" /></g><g id="alarm-multiple"><path d="M9.29,3.25L5.16,6.72L4,5.34L8.14,1.87L9.29,3.25M22,5.35L20.84,6.73L16.7,3.25L17.86,1.87L22,5.35M13,4A8,8 0 0,1 21,12A8,8 0 0,1 13,20A8,8 0 0,1 5,12A8,8 0 0,1 13,4M13,6A6,6 0 0,0 7,12A6,6 0 0,0 13,18A6,6 0 0,0 19,12A6,6 0 0,0 13,6M12,7.5H13.5V12.03L16.72,13.5L16.1,14.86L12,13V7.5M1,14C1,11.5 2.13,9.3 3.91,7.83C3.33,9.1 3,10.5 3,12L3.06,13.13L3,14C3,16.28 4.27,18.26 6.14,19.28C7.44,20.5 9.07,21.39 10.89,21.78C10.28,21.92 9.65,22 9,22A8,8 0 0,1 1,14Z" /></g><g id="alarm-off"><path d="M8,3.28L6.6,1.86L5.74,2.57L7.16,4M16.47,18.39C15.26,19.39 13.7,20 12,20A7,7 0 0,1 5,13C5,11.3 5.61,9.74 6.61,8.53M2.92,2.29L1.65,3.57L3,4.9L1.87,5.83L3.29,7.25L4.4,6.31L5.2,7.11C3.83,8.69 3,10.75 3,13A9,9 0 0,0 12,22C14.25,22 16.31,21.17 17.89,19.8L20.09,22L21.36,20.73L3.89,3.27L2.92,2.29M22,5.72L17.4,1.86L16.11,3.39L20.71,7.25L22,5.72M12,6A7,7 0 0,1 19,13C19,13.84 18.84,14.65 18.57,15.4L20.09,16.92C20.67,15.73 21,14.41 21,13A9,9 0 0,0 12,4C10.59,4 9.27,4.33 8.08,4.91L9.6,6.43C10.35,6.16 11.16,6 12,6Z" /></g><g id="alarm-plus"><path d="M13,9H11V12H8V14H11V17H13V14H16V12H13M12,20A7,7 0 0,1 5,13A7,7 0 0,1 12,6A7,7 0 0,1 19,13A7,7 0 0,1 12,20M12,4A9,9 0 0,0 3,13A9,9 0 0,0 12,22A9,9 0 0,0 21,13A9,9 0 0,0 12,4M22,5.72L17.4,1.86L16.11,3.39L20.71,7.25M7.88,3.39L6.6,1.86L2,5.71L3.29,7.24L7.88,3.39Z" /></g><g id="alarm-snooze"><path d="M7.88,3.39L6.6,1.86L2,5.71L3.29,7.24L7.88,3.39M22,5.72L17.4,1.86L16.11,3.39L20.71,7.25L22,5.72M12,4A9,9 0 0,0 3,13A9,9 0 0,0 12,22A9,9 0 0,0 21,13A9,9 0 0,0 12,4M12,20A7,7 0 0,1 5,13A7,7 0 0,1 12,6A7,7 0 0,1 19,13A7,7 0 0,1 12,20M9,11H12.63L9,15.2V17H15V15H11.37L15,10.8V9H9V11Z" /></g><g id="album"><path d="M12,11A1,1 0 0,0 11,12A1,1 0 0,0 12,13A1,1 0 0,0 13,12A1,1 0 0,0 12,11M12,16.5C9.5,16.5 7.5,14.5 7.5,12C7.5,9.5 9.5,7.5 12,7.5C14.5,7.5 16.5,9.5 16.5,12C16.5,14.5 14.5,16.5 12,16.5M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" /></g><g id="alert"><path d="M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z" /></g><g id="alert-box"><path d="M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M13,13V7H11V13H13M13,17V15H11V17H13Z" /></g><g id="alert-circle"><path d="M13,13H11V7H13M13,17H11V15H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" /></g><g id="alert-circle-outline"><path d="M11,15H13V17H11V15M11,7H13V13H11V7M12,2C6.47,2 2,6.5 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20Z" /></g><g id="alert-octagon"><path d="M13,13H11V7H13M12,17.3A1.3,1.3 0 0,1 10.7,16A1.3,1.3 0 0,1 12,14.7A1.3,1.3 0 0,1 13.3,16A1.3,1.3 0 0,1 12,17.3M15.73,3H8.27L3,8.27V15.73L8.27,21H15.73L21,15.73V8.27L15.73,3Z" /></g><g id="alert-outline"><path d="M12,2L1,21H23M12,6L19.53,19H4.47M11,10V14H13V10M11,16V18H13V16" /></g><g id="all-inclusive"><path d="M18.6,6.62C17.16,6.62 15.8,7.18 14.83,8.15L7.8,14.39C7.16,15.03 6.31,15.38 5.4,15.38C3.53,15.38 2,13.87 2,12C2,10.13 3.53,8.62 5.4,8.62C6.31,8.62 7.16,8.97 7.84,9.65L8.97,10.65L10.5,9.31L9.22,8.2C8.2,7.18 6.84,6.62 5.4,6.62C2.42,6.62 0,9.04 0,12C0,14.96 2.42,17.38 5.4,17.38C6.84,17.38 8.2,16.82 9.17,15.85L16.2,9.61C16.84,8.97 17.69,8.62 18.6,8.62C20.47,8.62 22,10.13 22,12C22,13.87 20.47,15.38 18.6,15.38C17.7,15.38 16.84,15.03 16.16,14.35L15,13.34L13.5,14.68L14.78,15.8C15.8,16.81 17.15,17.37 18.6,17.37C21.58,17.37 24,14.96 24,12C24,9 21.58,6.62 18.6,6.62Z" /></g><g id="alpha"><path d="M18.08,17.8C17.62,17.93 17.21,18 16.85,18C15.65,18 14.84,17.12 14.43,15.35H14.38C13.39,17.26 12,18.21 10.25,18.21C8.94,18.21 7.89,17.72 7.1,16.73C6.31,15.74 5.92,14.5 5.92,13C5.92,11.25 6.37,9.85 7.26,8.76C8.15,7.67 9.36,7.12 10.89,7.12C11.71,7.12 12.45,7.35 13.09,7.8C13.73,8.26 14.22,8.9 14.56,9.73H14.6L15.31,7.33H17.87L15.73,12.65C15.97,13.89 16.22,14.74 16.5,15.19C16.74,15.64 17.08,15.87 17.5,15.87C17.74,15.87 17.93,15.83 18.1,15.76L18.08,17.8M13.82,12.56C13.61,11.43 13.27,10.55 12.81,9.95C12.36,9.34 11.81,9.04 11.18,9.04C10.36,9.04 9.7,9.41 9.21,10.14C8.72,10.88 8.5,11.79 8.5,12.86C8.5,13.84 8.69,14.65 9.12,15.31C9.54,15.97 10.11,16.29 10.82,16.29C11.42,16.29 11.97,16 12.46,15.45C12.96,14.88 13.37,14.05 13.7,12.96L13.82,12.56Z" /></g><g id="alphabetical"><path d="M6,11A2,2 0 0,1 8,13V17H4A2,2 0 0,1 2,15V13A2,2 0 0,1 4,11H6M4,13V15H6V13H4M20,13V15H22V17H20A2,2 0 0,1 18,15V13A2,2 0 0,1 20,11H22V13H20M12,7V11H14A2,2 0 0,1 16,13V15A2,2 0 0,1 14,17H12A2,2 0 0,1 10,15V7H12M12,15H14V13H12V15Z" /></g><g id="altimeter"><path d="M7,3V5H17V3H7M9,7V9H15V7H9M2,7.96V16.04L6.03,12L2,7.96M22.03,7.96L18,12L22.03,16.04V7.96M7,11V13H17V11H7M9,15V17H15V15H9M7,19V21H17V19H7Z" /></g><g id="amazon"><path d="M15.93,17.09C15.75,17.25 15.5,17.26 15.3,17.15C14.41,16.41 14.25,16.07 13.76,15.36C12.29,16.86 11.25,17.31 9.34,17.31C7.09,17.31 5.33,15.92 5.33,13.14C5.33,10.96 6.5,9.5 8.19,8.76C9.65,8.12 11.68,8 13.23,7.83V7.5C13.23,6.84 13.28,6.09 12.9,5.54C12.58,5.05 11.95,4.84 11.4,4.84C10.38,4.84 9.47,5.37 9.25,6.45C9.2,6.69 9,6.93 8.78,6.94L6.18,6.66C5.96,6.61 5.72,6.44 5.78,6.1C6.38,2.95 9.23,2 11.78,2C13.08,2 14.78,2.35 15.81,3.33C17.11,4.55 17,6.18 17,7.95V12.12C17,13.37 17.5,13.93 18,14.6C18.17,14.85 18.21,15.14 18,15.31L15.94,17.09H15.93M13.23,10.56V10C11.29,10 9.24,10.39 9.24,12.67C9.24,13.83 9.85,14.62 10.87,14.62C11.63,14.62 12.3,14.15 12.73,13.4C13.25,12.47 13.23,11.6 13.23,10.56M20.16,19.54C18,21.14 14.82,22 12.1,22C8.29,22 4.85,20.59 2.25,18.24C2.05,18.06 2.23,17.81 2.5,17.95C5.28,19.58 8.75,20.56 12.33,20.56C14.74,20.56 17.4,20.06 19.84,19.03C20.21,18.87 20.5,19.27 20.16,19.54M21.07,18.5C20.79,18.14 19.22,18.33 18.5,18.42C18.31,18.44 18.28,18.26 18.47,18.12C19.71,17.24 21.76,17.5 22,17.79C22.24,18.09 21.93,20.14 20.76,21.11C20.58,21.27 20.41,21.18 20.5,21C20.76,20.33 21.35,18.86 21.07,18.5Z" /></g><g id="amazon-clouddrive"><path d="M4.94,11.12C5.23,11.12 5.5,11.16 5.76,11.23C5.77,9.09 7.5,7.35 9.65,7.35C11.27,7.35 12.67,8.35 13.24,9.77C13.83,9 14.74,8.53 15.76,8.53C17.5,8.53 18.94,9.95 18.94,11.71C18.94,11.95 18.91,12.2 18.86,12.43C19.1,12.34 19.37,12.29 19.65,12.29C20.95,12.29 22,13.35 22,14.65C22,15.95 20.95,17 19.65,17C18.35,17 6.36,17 4.94,17C3.32,17 2,15.68 2,14.06C2,12.43 3.32,11.12 4.94,11.12Z" /></g><g id="ambulance"><path d="M18,18.5A1.5,1.5 0 0,0 19.5,17A1.5,1.5 0 0,0 18,15.5A1.5,1.5 0 0,0 16.5,17A1.5,1.5 0 0,0 18,18.5M19.5,9.5H17V12H21.46L19.5,9.5M6,18.5A1.5,1.5 0 0,0 7.5,17A1.5,1.5 0 0,0 6,15.5A1.5,1.5 0 0,0 4.5,17A1.5,1.5 0 0,0 6,18.5M20,8L23,12V17H21A3,3 0 0,1 18,20A3,3 0 0,1 15,17H9A3,3 0 0,1 6,20A3,3 0 0,1 3,17H1V6C1,4.89 1.89,4 3,4H17V8H20M8,6V9H5V11H8V14H10V11H13V9H10V6H8Z" /></g><g id="amplifier"><path d="M10,2H14A1,1 0 0,1 15,3H21V21H19A1,1 0 0,1 18,22A1,1 0 0,1 17,21H7A1,1 0 0,1 6,22A1,1 0 0,1 5,21H3V3H9A1,1 0 0,1 10,2M5,5V9H19V5H5M7,6A1,1 0 0,1 8,7A1,1 0 0,1 7,8A1,1 0 0,1 6,7A1,1 0 0,1 7,6M12,6H14V7H12V6M15,6H16V8H15V6M17,6H18V8H17V6M12,11A4,4 0 0,0 8,15A4,4 0 0,0 12,19A4,4 0 0,0 16,15A4,4 0 0,0 12,11M10,6A1,1 0 0,1 11,7A1,1 0 0,1 10,8A1,1 0 0,1 9,7A1,1 0 0,1 10,6Z" /></g><g id="anchor"><path d="M12,2A3,3 0 0,0 9,5C9,6.27 9.8,7.4 11,7.83V10H8V12H11V18.92C9.16,18.63 7.53,17.57 6.53,16H8V14H3V19H5V17.3C6.58,19.61 9.2,21 12,21C14.8,21 17.42,19.61 19,17.31V19H21V14H16V16H17.46C16.46,17.56 14.83,18.63 13,18.92V12H16V10H13V7.82C14.2,7.4 15,6.27 15,5A3,3 0 0,0 12,2M12,4A1,1 0 0,1 13,5A1,1 0 0,1 12,6A1,1 0 0,1 11,5A1,1 0 0,1 12,4Z" /></g><g id="android"><path d="M15,5H14V4H15M10,5H9V4H10M15.53,2.16L16.84,0.85C17.03,0.66 17.03,0.34 16.84,0.14C16.64,-0.05 16.32,-0.05 16.13,0.14L14.65,1.62C13.85,1.23 12.95,1 12,1C11.04,1 10.14,1.23 9.34,1.63L7.85,0.14C7.66,-0.05 7.34,-0.05 7.15,0.14C6.95,0.34 6.95,0.66 7.15,0.85L8.46,2.16C6.97,3.26 6,5 6,7H18C18,5 17,3.25 15.53,2.16M20.5,8A1.5,1.5 0 0,0 19,9.5V16.5A1.5,1.5 0 0,0 20.5,18A1.5,1.5 0 0,0 22,16.5V9.5A1.5,1.5 0 0,0 20.5,8M3.5,8A1.5,1.5 0 0,0 2,9.5V16.5A1.5,1.5 0 0,0 3.5,18A1.5,1.5 0 0,0 5,16.5V9.5A1.5,1.5 0 0,0 3.5,8M6,18A1,1 0 0,0 7,19H8V22.5A1.5,1.5 0 0,0 9.5,24A1.5,1.5 0 0,0 11,22.5V19H13V22.5A1.5,1.5 0 0,0 14.5,24A1.5,1.5 0 0,0 16,22.5V19H17A1,1 0 0,0 18,18V8H6V18Z" /></g><g id="android-debug-bridge"><path d="M15,9A1,1 0 0,1 14,8A1,1 0 0,1 15,7A1,1 0 0,1 16,8A1,1 0 0,1 15,9M9,9A1,1 0 0,1 8,8A1,1 0 0,1 9,7A1,1 0 0,1 10,8A1,1 0 0,1 9,9M16.12,4.37L18.22,2.27L17.4,1.44L15.09,3.75C14.16,3.28 13.11,3 12,3C10.88,3 9.84,3.28 8.91,3.75L6.6,1.44L5.78,2.27L7.88,4.37C6.14,5.64 5,7.68 5,10V11H19V10C19,7.68 17.86,5.64 16.12,4.37M5,16C5,19.86 8.13,23 12,23A7,7 0 0,0 19,16V12H5V16Z" /></g><g id="android-studio"><path d="M11,2H13V4H13.5A1.5,1.5 0 0,1 15,5.5V9L14.56,9.44L16.2,12.28C17.31,11.19 18,9.68 18,8H20C20,10.42 18.93,12.59 17.23,14.06L20.37,19.5L20.5,21.72L18.63,20.5L15.56,15.17C14.5,15.7 13.28,16 12,16C10.72,16 9.5,15.7 8.44,15.17L5.37,20.5L3.5,21.72L3.63,19.5L9.44,9.44L9,9V5.5A1.5,1.5 0 0,1 10.5,4H11V2M9.44,13.43C10.22,13.8 11.09,14 12,14C12.91,14 13.78,13.8 14.56,13.43L13.1,10.9H13.09C12.47,11.5 11.53,11.5 10.91,10.9H10.9L9.44,13.43M12,6A1,1 0 0,0 11,7A1,1 0 0,0 12,8A1,1 0 0,0 13,7A1,1 0 0,0 12,6Z" /></g><g id="angular"><path d="M12,2.5L20.84,5.65L19.5,17.35L12,21.5L4.5,17.35L3.16,5.65L12,2.5M12,4.6L6.47,17H8.53L9.64,14.22H14.34L15.45,17H17.5L12,4.6M13.62,12.5H10.39L12,8.63L13.62,12.5Z" /></g><g id="animation"><path d="M4,2C2.89,2 2,2.89 2,4V14H4V4H14V2H4M8,6C6.89,6 6,6.89 6,8V18H8V8H18V6H8M12,10C10.89,10 10,10.89 10,12V20C10,21.11 10.89,22 12,22H20C21.11,22 22,21.11 22,20V12C22,10.89 21.11,10 20,10H12Z" /></g><g id="apple"><path d="M18.71,19.5C17.88,20.74 17,21.95 15.66,21.97C14.32,22 13.89,21.18 12.37,21.18C10.84,21.18 10.37,21.95 9.1,22C7.79,22.05 6.8,20.68 5.96,19.47C4.25,17 2.94,12.45 4.7,9.39C5.57,7.87 7.13,6.91 8.82,6.88C10.1,6.86 11.32,7.75 12.11,7.75C12.89,7.75 14.37,6.68 15.92,6.84C16.57,6.87 18.39,7.1 19.56,8.82C19.47,8.88 17.39,10.1 17.41,12.63C17.44,15.65 20.06,16.66 20.09,16.67C20.06,16.74 19.67,18.11 18.71,19.5M13,3.5C13.73,2.67 14.94,2.04 15.94,2C16.07,3.17 15.6,4.35 14.9,5.19C14.21,6.04 13.07,6.7 11.95,6.61C11.8,5.46 12.36,4.26 13,3.5Z" /></g><g id="apple-finder"><path d="M4,4H11.89C12.46,2.91 13.13,1.88 13.93,1L15.04,2.11C14.61,2.7 14.23,3.34 13.89,4H20A2,2 0 0,1 22,6V19A2,2 0 0,1 20,21H14.93L15.26,22.23L13.43,22.95L12.93,21H4A2,2 0 0,1 2,19V6A2,2 0 0,1 4,4M4,6V19H12.54C12.5,18.67 12.44,18.34 12.4,18C12.27,18 12.13,18 12,18C9.25,18 6.78,17.5 5.13,16.76L6.04,15.12C7,15.64 9.17,16 12,16C12.08,16 12.16,16 12.24,16C12.21,15.33 12.22,14.66 12.27,14H9C9,14 9.4,9.97 11,6H4M20,19V6H13C12.1,8.22 11.58,10.46 11.3,12H14.17C14,13.28 13.97,14.62 14.06,15.93C15.87,15.8 17.25,15.5 17.96,15.12L18.87,16.76C17.69,17.3 16.1,17.7 14.29,17.89C14.35,18.27 14.41,18.64 14.5,19H20M6,8H8V11H6V8M16,8H18V11H16V8Z" /></g><g id="apple-ios"><path d="M20,9V7H16A2,2 0 0,0 14,9V11A2,2 0 0,0 16,13H18V15H14V17H18A2,2 0 0,0 20,15V13A2,2 0 0,0 18,11H16V9M11,15H9V9H11M11,7H9A2,2 0 0,0 7,9V15A2,2 0 0,0 9,17H11A2,2 0 0,0 13,15V9A2,2 0 0,0 11,7M4,17H6V11H4M4,9H6V7H4V9Z" /></g><g id="apple-keyboard-caps"><path d="M15,14V8H17.17L12,2.83L6.83,8H9V14H15M12,0L22,10H17V16H7V10H2L12,0M7,18H17V24H7V18M15,20H9V22H15V20Z" /></g><g id="apple-keyboard-command"><path d="M6,2A4,4 0 0,1 10,6V8H14V6A4,4 0 0,1 18,2A4,4 0 0,1 22,6A4,4 0 0,1 18,10H16V14H18A4,4 0 0,1 22,18A4,4 0 0,1 18,22A4,4 0 0,1 14,18V16H10V18A4,4 0 0,1 6,22A4,4 0 0,1 2,18A4,4 0 0,1 6,14H8V10H6A4,4 0 0,1 2,6A4,4 0 0,1 6,2M16,18A2,2 0 0,0 18,20A2,2 0 0,0 20,18A2,2 0 0,0 18,16H16V18M14,10H10V14H14V10M6,16A2,2 0 0,0 4,18A2,2 0 0,0 6,20A2,2 0 0,0 8,18V16H6M8,6A2,2 0 0,0 6,4A2,2 0 0,0 4,6A2,2 0 0,0 6,8H8V6M18,8A2,2 0 0,0 20,6A2,2 0 0,0 18,4A2,2 0 0,0 16,6V8H18Z" /></g><g id="apple-keyboard-control"><path d="M19.78,11.78L18.36,13.19L12,6.83L5.64,13.19L4.22,11.78L12,4L19.78,11.78Z" /></g><g id="apple-keyboard-option"><path d="M3,4H9.11L16.15,18H21V20H14.88L7.84,6H3V4M14,4H21V6H14V4Z" /></g><g id="apple-keyboard-shift"><path d="M15,18V12H17.17L12,6.83L6.83,12H9V18H15M12,4L22,14H17V20H7V14H2L12,4Z" /></g><g id="apple-mobileme"><path d="M22,15.04C22,17.23 20.24,19 18.07,19H5.93C3.76,19 2,17.23 2,15.04C2,13.07 3.43,11.44 5.31,11.14C5.28,11 5.27,10.86 5.27,10.71C5.27,9.33 6.38,8.2 7.76,8.2C8.37,8.2 8.94,8.43 9.37,8.8C10.14,7.05 11.13,5.44 13.91,5.44C17.28,5.44 18.87,8.06 18.87,10.83C18.87,10.94 18.87,11.06 18.86,11.17C20.65,11.54 22,13.13 22,15.04Z" /></g><g id="apple-safari"><path d="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12C4,14.09 4.8,16 6.11,17.41L9.88,9.88L17.41,6.11C16,4.8 14.09,4 12,4M12,20A8,8 0 0,0 20,12C20,9.91 19.2,8 17.89,6.59L14.12,14.12L6.59,17.89C8,19.2 9.91,20 12,20M12,12L11.23,11.23L9.7,14.3L12.77,12.77L12,12M12,17.5H13V19H12V17.5M15.88,15.89L16.59,15.18L17.65,16.24L16.94,16.95L15.88,15.89M17.5,12V11H19V12H17.5M12,6.5H11V5H12V6.5M8.12,8.11L7.41,8.82L6.35,7.76L7.06,7.05L8.12,8.11M6.5,12V13H5V12H6.5Z" /></g><g id="application"><path d="M19,4C20.11,4 21,4.9 21,6V18A2,2 0 0,1 19,20H5C3.89,20 3,19.1 3,18V6A2,2 0 0,1 5,4H19M19,18V8H5V18H19Z" /></g><g id="appnet"><path d="M14.47,9.14C15.07,7.69 16.18,4.28 16.35,3.68C16.5,3.09 16.95,3 17.2,3H19.25C19.59,3 19.78,3.26 19.7,3.68C17.55,11.27 16.09,13.5 16.09,14C16.09,15.28 17.46,17.67 18.74,17.67C19.5,17.67 19.34,16.56 20.19,16.56H21.81C22.07,16.56 22.32,16.82 22.32,17.25C22.32,17.67 21.85,21 18.61,21C15.36,21 14.15,17.08 14.15,17.08C13.73,17.93 11.23,21 8.16,21C2.7,21 1.68,15.2 1.68,11.79C1.68,8.37 3.3,3 7.91,3C12.5,3 14.47,9.14 14.47,9.14M4.5,11.53C4.5,13.5 4.41,17.59 8,17.67C10.04,17.76 11.91,15.2 12.81,13.15C11.57,8.89 10.72,6.33 8,6.33C4.32,6.41 4.5,11.53 4.5,11.53Z" /></g><g id="apps"><path d="M16,20H20V16H16M16,14H20V10H16M10,8H14V4H10M16,8H20V4H16M10,14H14V10H10M4,14H8V10H4M4,20H8V16H4M10,20H14V16H10M4,8H8V4H4V8Z" /></g><g id="archive"><path d="M3,3H21V7H3V3M4,8H20V21H4V8M9.5,11A0.5,0.5 0 0,0 9,11.5V13H15V11.5A0.5,0.5 0 0,0 14.5,11H9.5Z" /></g><g id="arrange-bring-forward"><path d="M2,2H16V16H2V2M22,8V22H8V18H10V20H20V10H18V8H22Z" /></g><g id="arrange-bring-to-front"><path d="M2,2H11V6H9V4H4V9H6V11H2V2M22,13V22H13V18H15V20H20V15H18V13H22M8,8H16V16H8V8Z" /></g><g id="arrange-send-backward"><path d="M2,2H16V16H2V2M22,8V22H8V18H18V8H22M4,4V14H14V4H4Z" /></g><g id="arrange-send-to-back"><path d="M2,2H11V11H2V2M9,4H4V9H9V4M22,13V22H13V13H22M15,20H20V15H15V20M16,8V11H13V8H16M11,16H8V13H11V16Z" /></g><g id="arrow-all"><path d="M13,11H18L16.5,9.5L17.92,8.08L21.84,12L17.92,15.92L16.5,14.5L18,13H13V18L14.5,16.5L15.92,17.92L12,21.84L8.08,17.92L9.5,16.5L11,18V13H6L7.5,14.5L6.08,15.92L2.16,12L6.08,8.08L7.5,9.5L6,11H11V6L9.5,7.5L8.08,6.08L12,2.16L15.92,6.08L14.5,7.5L13,6V11Z" /></g><g id="arrow-bottom-left"><path d="M19,6.41L17.59,5L7,15.59V9H5V19H15V17H8.41L19,6.41Z" /></g><g id="arrow-bottom-right"><path d="M5,6.41L6.41,5L17,15.59V9H19V19H9V17H15.59L5,6.41Z" /></g><g id="arrow-compress"><path d="M19.5,3.09L15,7.59V4H13V11H20V9H16.41L20.91,4.5L19.5,3.09M4,13V15H7.59L3.09,19.5L4.5,20.91L9,16.41V20H11V13H4Z" /></g><g id="arrow-compress-all"><path d="M19.5,3.09L20.91,4.5L16.41,9H20V11H13V4H15V7.59L19.5,3.09M20.91,19.5L19.5,20.91L15,16.41V20H13V13H20V15H16.41L20.91,19.5M4.5,3.09L9,7.59V4H11V11H4V9H7.59L3.09,4.5L4.5,3.09M3.09,19.5L7.59,15H4V13H11V20H9V16.41L4.5,20.91L3.09,19.5Z" /></g><g id="arrow-down"><path d="M11,4H13V16L18.5,10.5L19.92,11.92L12,19.84L4.08,11.92L5.5,10.5L11,16V4Z" /></g><g id="arrow-down-bold"><path d="M10,4H14V13L17.5,9.5L19.92,11.92L12,19.84L4.08,11.92L6.5,9.5L10,13V4Z" /></g><g id="arrow-down-bold-circle"><path d="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,17L17,12H14V8H10V12H7L12,17Z" /></g><g id="arrow-down-bold-circle-outline"><path d="M12,17L7,12H10V8H14V12H17L12,17M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z" /></g><g id="arrow-down-bold-hexagon-outline"><path d="M12,17L7,12H10V8H14V12H17L12,17M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L5,8.09V15.91L12,19.85L19,15.91V8.09L12,4.15Z" /></g><g id="arrow-down-drop-circle"><path d="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M7,10L12,15L17,10H7Z" /></g><g id="arrow-down-drop-circle-outline"><path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4M7,10L12,15L17,10H7Z" /></g><g id="arrow-expand"><path d="M10,21V19H6.41L10.91,14.5L9.5,13.09L5,17.59V14H3V21H10M14.5,10.91L19,6.41V10H21V3H14V5H17.59L13.09,9.5L14.5,10.91Z" /></g><g id="arrow-expand-all"><path d="M9.5,13.09L10.91,14.5L6.41,19H10V21H3V14H5V17.59L9.5,13.09M10.91,9.5L9.5,10.91L5,6.41V10H3V3H10V5H6.41L10.91,9.5M14.5,13.09L19,17.59V14H21V21H14V19H17.59L13.09,14.5L14.5,13.09M13.09,9.5L17.59,5H14V3H21V10H19V6.41L14.5,10.91L13.09,9.5Z" /></g><g id="arrow-left"><path d="M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z" /></g><g id="arrow-left-bold"><path d="M20,10V14H11L14.5,17.5L12.08,19.92L4.16,12L12.08,4.08L14.5,6.5L11,10H20Z" /></g><g id="arrow-left-bold-circle"><path d="M22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12M7,12L12,17V14H16V10H12V7L7,12Z" /></g><g id="arrow-left-bold-circle-outline"><path d="M7,12L12,7V10H16V14H12V17L7,12M22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12M20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12Z" /></g><g id="arrow-left-bold-hexagon-outline"><path d="M7,12L12,7V10H16V14H12V17L7,12M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L5,8.09V15.91L12,19.85L19,15.91V8.09L12,4.15Z" /></g><g id="arrow-left-drop-circle"><path d="M22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12M14,7L9,12L14,17V7Z" /></g><g id="arrow-left-drop-circle-outline"><path d="M22,12A10,10 0 0,0 12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12M20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12M14,7L9,12L14,17V7Z" /></g><g id="arrow-right"><path d="M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z" /></g><g id="arrow-right-bold"><path d="M4,10V14H13L9.5,17.5L11.92,19.92L19.84,12L11.92,4.08L9.5,6.5L13,10H4Z" /></g><g id="arrow-right-bold-circle"><path d="M2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12M17,12L12,7V10H8V14H12V17L17,12Z" /></g><g id="arrow-right-bold-circle-outline"><path d="M17,12L12,17V14H8V10H12V7L17,12M2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12M4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12Z" /></g><g id="arrow-right-bold-hexagon-outline"><path d="M17,12L12,17V14H8V10H12V7L17,12M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L5,8.09V15.91L12,19.85L19,15.91V8.09L12,4.15Z" /></g><g id="arrow-right-drop-circle"><path d="M2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12M10,17L15,12L10,7V17Z" /></g><g id="arrow-right-drop-circle-outline"><path d="M2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2A10,10 0 0,0 2,12M4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12M10,17L15,12L10,7V17Z" /></g><g id="arrow-top-left"><path d="M19,17.59L17.59,19L7,8.41V15H5V5H15V7H8.41L19,17.59Z" /></g><g id="arrow-top-right"><path d="M5,17.59L15.59,7H9V5H19V15H17V8.41L6.41,19L5,17.59Z" /></g><g id="arrow-up"><path d="M13,20H11V8L5.5,13.5L4.08,12.08L12,4.16L19.92,12.08L18.5,13.5L13,8V20Z" /></g><g id="arrow-up-bold"><path d="M14,20H10V11L6.5,14.5L4.08,12.08L12,4.16L19.92,12.08L17.5,14.5L14,11V20Z" /></g><g id="arrow-up-bold-circle"><path d="M12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22M12,7L7,12H10V16H14V12H17L12,7Z" /></g><g id="arrow-up-bold-circle-outline"><path d="M12,7L17,12H14V16H10V12H7L12,7M12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22M12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20Z" /></g><g id="arrow-up-bold-hexagon-outline"><path d="M12,7L17,12H14V16H10V12H7L12,7M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L5,8.09V15.91L12,19.85L19,15.91V8.09L12,4.15Z" /></g><g id="arrow-up-drop-circle"><path d="M12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22M17,14L12,9L7,14H17Z" /></g><g id="arrow-up-drop-circle-outline"><path d="M12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20M17,14L12,9L7,14H17Z" /></g><g id="assistant"><path d="M19,2H5A2,2 0 0,0 3,4V18A2,2 0 0,0 5,20H9L12,23L15,20H19A2,2 0 0,0 21,18V4A2,2 0 0,0 19,2M13.88,12.88L12,17L10.12,12.88L6,11L10.12,9.12L12,5L13.88,9.12L18,11" /></g><g id="at"><path d="M17.42,15C17.79,14.09 18,13.07 18,12C18,8.13 15.31,5 12,5C8.69,5 6,8.13 6,12C6,15.87 8.69,19 12,19C13.54,19 15,19 16,18.22V20.55C15,21 13.46,21 12,21C7.58,21 4,16.97 4,12C4,7.03 7.58,3 12,3C16.42,3 20,7.03 20,12C20,13.85 19.5,15.57 18.65,17H14V15.5C13.36,16.43 12.5,17 11.5,17C9.57,17 8,14.76 8,12C8,9.24 9.57,7 11.5,7C12.5,7 13.36,7.57 14,8.5V8H16V15H17.42M12,9C10.9,9 10,10.34 10,12C10,13.66 10.9,15 12,15C13.1,15 14,13.66 14,12C14,10.34 13.1,9 12,9Z" /></g><g id="attachment"><path d="M7.5,18A5.5,5.5 0 0,1 2,12.5A5.5,5.5 0 0,1 7.5,7H18A4,4 0 0,1 22,11A4,4 0 0,1 18,15H9.5A2.5,2.5 0 0,1 7,12.5A2.5,2.5 0 0,1 9.5,10H17V11.5H9.5A1,1 0 0,0 8.5,12.5A1,1 0 0,0 9.5,13.5H18A2.5,2.5 0 0,0 20.5,11A2.5,2.5 0 0,0 18,8.5H7.5A4,4 0 0,0 3.5,12.5A4,4 0 0,0 7.5,16.5H17V18H7.5Z" /></g><g id="audiobook"><path d="M18,22H6A2,2 0 0,1 4,20V4C4,2.89 4.9,2 6,2H7V9L9.5,7.5L12,9V2H18A2,2 0 0,1 20,4V20A2,2 0 0,1 18,22M13,15A2,2 0 0,0 11,17A2,2 0 0,0 13,19A2,2 0 0,0 15,17V12H18V10H14V15.27C13.71,15.1 13.36,15 13,15Z" /></g><g id="auto-fix"><path d="M7.5,5.6L5,7L6.4,4.5L5,2L7.5,3.4L10,2L8.6,4.5L10,7L7.5,5.6M19.5,15.4L22,14L20.6,16.5L22,19L19.5,17.6L17,19L18.4,16.5L17,14L19.5,15.4M22,2L20.6,4.5L22,7L19.5,5.6L17,7L18.4,4.5L17,2L19.5,3.4L22,2M13.34,12.78L15.78,10.34L13.66,8.22L11.22,10.66L13.34,12.78M14.37,7.29L16.71,9.63C17.1,10 17.1,10.65 16.71,11.04L5.04,22.71C4.65,23.1 4,23.1 3.63,22.71L1.29,20.37C0.9,20 0.9,19.35 1.29,18.96L12.96,7.29C13.35,6.9 14,6.9 14.37,7.29Z" /></g><g id="auto-upload"><path d="M5.35,12.65L6.5,9L7.65,12.65M5.5,7L2.3,16H4.2L4.9,14H8.1L8.8,16H10.7L7.5,7M11,20H22V18H11M14,16H19V11H22L16.5,5.5L11,11H14V16Z" /></g><g id="autorenew"><path d="M12,6V9L16,5L12,1V4A8,8 0 0,0 4,12C4,13.57 4.46,15.03 5.24,16.26L6.7,14.8C6.25,13.97 6,13 6,12A6,6 0 0,1 12,6M18.76,7.74L17.3,9.2C17.74,10.04 18,11 18,12A6,6 0 0,1 12,18V15L8,19L12,23V20A8,8 0 0,0 20,12C20,10.43 19.54,8.97 18.76,7.74Z" /></g><g id="av-timer"><path d="M11,17A1,1 0 0,0 12,18A1,1 0 0,0 13,17A1,1 0 0,0 12,16A1,1 0 0,0 11,17M11,3V7H13V5.08C16.39,5.57 19,8.47 19,12A7,7 0 0,1 12,19A7,7 0 0,1 5,12C5,10.32 5.59,8.78 6.58,7.58L12,13L13.41,11.59L6.61,4.79V4.81C4.42,6.45 3,9.05 3,12A9,9 0 0,0 12,21A9,9 0 0,0 21,12A9,9 0 0,0 12,3M18,12A1,1 0 0,0 17,11A1,1 0 0,0 16,12A1,1 0 0,0 17,13A1,1 0 0,0 18,12M6,12A1,1 0 0,0 7,13A1,1 0 0,0 8,12A1,1 0 0,0 7,11A1,1 0 0,0 6,12Z" /></g><g id="baby"><path d="M18.5,4A2.5,2.5 0 0,1 21,6.5A2.5,2.5 0 0,1 18.5,9A2.5,2.5 0 0,1 16,6.5A2.5,2.5 0 0,1 18.5,4M4.5,20A1.5,1.5 0 0,1 3,18.5A1.5,1.5 0 0,1 4.5,17H11.5A1.5,1.5 0 0,1 13,18.5A1.5,1.5 0 0,1 11.5,20H4.5M16.09,19L14.69,15H11L6.75,10.75C6.75,10.75 9,8.25 12.5,8.25C15.5,8.25 15.85,9.25 16.06,9.87L18.92,18C19.2,18.78 18.78,19.64 18,19.92C17.22,20.19 16.36,19.78 16.09,19Z" /></g><g id="baby-buggy"><path d="M13,2V10H21A8,8 0 0,0 13,2M19.32,15.89C20.37,14.54 21,12.84 21,11H6.44L5.5,9H2V11H4.22C4.22,11 6.11,15.07 6.34,15.42C5.24,16 4.5,17.17 4.5,18.5A3.5,3.5 0 0,0 8,22C9.76,22 11.22,20.7 11.46,19H13.54C13.78,20.7 15.24,22 17,22A3.5,3.5 0 0,0 20.5,18.5C20.5,17.46 20.04,16.53 19.32,15.89M8,20A1.5,1.5 0 0,1 6.5,18.5A1.5,1.5 0 0,1 8,17A1.5,1.5 0 0,1 9.5,18.5A1.5,1.5 0 0,1 8,20M17,20A1.5,1.5 0 0,1 15.5,18.5A1.5,1.5 0 0,1 17,17A1.5,1.5 0 0,1 18.5,18.5A1.5,1.5 0 0,1 17,20Z" /></g><g id="backburger"><path d="M5,13L9,17L7.6,18.42L1.18,12L7.6,5.58L9,7L5,11H21V13H5M21,6V8H11V6H21M21,16V18H11V16H21Z" /></g><g id="backspace"><path d="M22,3H7C6.31,3 5.77,3.35 5.41,3.88L0,12L5.41,20.11C5.77,20.64 6.31,21 7,21H22A2,2 0 0,0 24,19V5A2,2 0 0,0 22,3M19,15.59L17.59,17L14,13.41L10.41,17L9,15.59L12.59,12L9,8.41L10.41,7L14,10.59L17.59,7L19,8.41L15.41,12" /></g><g id="backup-restore"><path d="M12,3A9,9 0 0,0 3,12H0L4,16L8,12H5A7,7 0 0,1 12,5A7,7 0 0,1 19,12A7,7 0 0,1 12,19C10.5,19 9.09,18.5 7.94,17.7L6.5,19.14C8.04,20.3 9.94,21 12,21A9,9 0 0,0 21,12A9,9 0 0,0 12,3M14,12A2,2 0 0,0 12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12Z" /></g><g id="bandcamp"><path d="M22,6L15.5,18H2L8.5,6H22Z" /></g><g id="bank"><path d="M11.5,1L2,6V8H21V6M16,10V17H19V10M2,22H21V19H2M10,10V17H13V10M4,10V17H7V10H4Z" /></g><g id="barcode"><path d="M2,6H4V18H2V6M5,6H6V18H5V6M7,6H10V18H7V6M11,6H12V18H11V6M14,6H16V18H14V6M17,6H20V18H17V6M21,6H22V18H21V6Z" /></g><g id="barcode-scan"><path d="M4,6H6V18H4V6M7,6H8V18H7V6M9,6H12V18H9V6M13,6H14V18H13V6M16,6H18V18H16V6M19,6H20V18H19V6M2,4V8H0V4A2,2 0 0,1 2,2H6V4H2M22,2A2,2 0 0,1 24,4V8H22V4H18V2H22M2,16V20H6V22H2A2,2 0 0,1 0,20V16H2M22,20V16H24V20A2,2 0 0,1 22,22H18V20H22Z" /></g><g id="barley"><path d="M7.33,18.33C6.5,17.17 6.5,15.83 6.5,14.5C8.17,15.5 9.83,16.5 10.67,17.67L11,18.23V15.95C9.5,15.05 8.08,14.13 7.33,13.08C6.5,11.92 6.5,10.58 6.5,9.25C8.17,10.25 9.83,11.25 10.67,12.42L11,13V10.7C9.5,9.8 8.08,8.88 7.33,7.83C6.5,6.67 6.5,5.33 6.5,4C8.17,5 9.83,6 10.67,7.17C10.77,7.31 10.86,7.46 10.94,7.62C10.77,7 10.66,6.42 10.65,5.82C10.64,4.31 11.3,2.76 11.96,1.21C12.65,2.69 13.34,4.18 13.35,5.69C13.36,6.32 13.25,6.96 13.07,7.59C13.15,7.45 13.23,7.31 13.33,7.17C14.17,6 15.83,5 17.5,4C17.5,5.33 17.5,6.67 16.67,7.83C15.92,8.88 14.5,9.8 13,10.7V13L13.33,12.42C14.17,11.25 15.83,10.25 17.5,9.25C17.5,10.58 17.5,11.92 16.67,13.08C15.92,14.13 14.5,15.05 13,15.95V18.23L13.33,17.67C14.17,16.5 15.83,15.5 17.5,14.5C17.5,15.83 17.5,17.17 16.67,18.33C15.92,19.38 14.5,20.3 13,21.2V23H11V21.2C9.5,20.3 8.08,19.38 7.33,18.33Z" /></g><g id="barrel"><path d="M18,19H19V21H5V19H6V13H5V11H6V5H5V3H19V5H18V11H19V13H18V19M9,13A3,3 0 0,0 12,16A3,3 0 0,0 15,13C15,11 12,7.63 12,7.63C12,7.63 9,11 9,13Z" /></g><g id="basecamp"><path d="M3.39,15.64C3.4,15.55 3.42,15.45 3.45,15.36C3.5,15.18 3.54,15 3.6,14.84C3.82,14.19 4.16,13.58 4.5,13C4.7,12.7 4.89,12.41 5.07,12.12C5.26,11.83 5.45,11.54 5.67,11.26C6,10.81 6.45,10.33 7,10.15C7.79,9.9 8.37,10.71 8.82,11.22C9.08,11.5 9.36,11.8 9.71,11.97C9.88,12.04 10.06,12.08 10.24,12.07C10.5,12.05 10.73,11.87 10.93,11.71C11.46,11.27 11.9,10.7 12.31,10.15C12.77,9.55 13.21,8.93 13.73,8.38C13.95,8.15 14.18,7.85 14.5,7.75C14.62,7.71 14.77,7.72 14.91,7.78C15,7.82 15.05,7.87 15.1,7.92C15.17,8 15.25,8.04 15.32,8.09C15.88,8.5 16.4,9 16.89,9.5C17.31,9.93 17.72,10.39 18.1,10.86C18.5,11.32 18.84,11.79 19.15,12.3C19.53,12.93 19.85,13.58 20.21,14.21C20.53,14.79 20.86,15.46 20.53,16.12C20.5,16.15 20.5,16.19 20.5,16.22C19.91,17.19 18.88,17.79 17.86,18.18C16.63,18.65 15.32,18.88 14,18.97C12.66,19.07 11.3,19.06 9.95,18.94C8.73,18.82 7.5,18.6 6.36,18.16C5.4,17.79 4.5,17.25 3.84,16.43C3.69,16.23 3.56,16.03 3.45,15.81C3.43,15.79 3.42,15.76 3.41,15.74C3.39,15.7 3.38,15.68 3.39,15.64M2.08,16.5C2.22,16.73 2.38,16.93 2.54,17.12C2.86,17.5 3.23,17.85 3.62,18.16C4.46,18.81 5.43,19.28 6.44,19.61C7.6,20 8.82,20.19 10.04,20.29C11.45,20.41 12.89,20.41 14.3,20.26C15.6,20.12 16.91,19.85 18.13,19.37C19.21,18.94 20.21,18.32 21.08,17.54C21.31,17.34 21.53,17.13 21.7,16.88C21.86,16.67 22,16.44 22,16.18C22,15.88 22,15.57 21.92,15.27C21.85,14.94 21.76,14.62 21.68,14.3C21.65,14.18 21.62,14.06 21.59,13.94C21.27,12.53 20.78,11.16 20.12,9.87C19.56,8.79 18.87,7.76 18.06,6.84C17.31,6 16.43,5.22 15.43,4.68C14.9,4.38 14.33,4.15 13.75,4C13.44,3.88 13.12,3.81 12.8,3.74C12.71,3.73 12.63,3.71 12.55,3.71C12.44,3.71 12.33,3.71 12.23,3.71C12,3.71 11.82,3.71 11.61,3.71C11.5,3.71 11.43,3.7 11.33,3.71C11.25,3.72 11.16,3.74 11.08,3.75C10.91,3.78 10.75,3.81 10.59,3.85C10.27,3.92 9.96,4 9.65,4.14C9.04,4.38 8.47,4.7 7.93,5.08C6.87,5.8 5.95,6.73 5.18,7.75C4.37,8.83 3.71,10.04 3.21,11.3C2.67,12.64 2.3,14.04 2.07,15.47C2.04,15.65 2,15.84 2,16C2,16.12 2,16.22 2,16.32C2,16.37 2,16.4 2.03,16.44C2.04,16.46 2.06,16.5 2.08,16.5Z" /></g><g id="basket"><path d="M5.5,21C4.72,21 4.04,20.55 3.71,19.9V19.9L1.1,10.44L1,10A1,1 0 0,1 2,9H6.58L11.18,2.43C11.36,2.17 11.66,2 12,2C12.34,2 12.65,2.17 12.83,2.44L17.42,9H22A1,1 0 0,1 23,10L22.96,10.29L20.29,19.9C19.96,20.55 19.28,21 18.5,21H5.5M12,4.74L9,9H15L12,4.74M12,13A2,2 0 0,0 10,15A2,2 0 0,0 12,17A2,2 0 0,0 14,15A2,2 0 0,0 12,13Z" /></g><g id="basket-fill"><path d="M3,2H6V5H3V2M6,7H9V10H6V7M8,2H11V5H8V2M17,11L12,6H15V2H19V6H22L17,11M7.5,22C6.72,22 6.04,21.55 5.71,20.9V20.9L3.1,13.44L3,13A1,1 0 0,1 4,12H20A1,1 0 0,1 21,13L20.96,13.29L18.29,20.9C17.96,21.55 17.28,22 16.5,22H7.5M7.61,20H16.39L18.57,14H5.42L7.61,20Z" /></g><g id="basket-unfill"><path d="M3,10H6V7H3V10M5,5H8V2H5V5M8,10H11V7H8V10M17,1L12,6H15V10H19V6H22L17,1M7.5,22C6.72,22 6.04,21.55 5.71,20.9V20.9L3.1,13.44L3,13A1,1 0 0,1 4,12H20A1,1 0 0,1 21,13L20.96,13.29L18.29,20.9C17.96,21.55 17.28,22 16.5,22H7.5M7.61,20H16.39L18.57,14H5.42L7.61,20Z" /></g><g id="battery"><path d="M16.67,4H15V2H9V4H7.33A1.33,1.33 0 0,0 6,5.33V20.67C6,21.4 6.6,22 7.33,22H16.67A1.33,1.33 0 0,0 18,20.67V5.33C18,4.6 17.4,4 16.67,4Z" /></g><g id="battery-10"><path d="M16,18H8V6H16M16.67,4H15V2H9V4H7.33A1.33,1.33 0 0,0 6,5.33V20.67C6,21.4 6.6,22 7.33,22H16.67A1.33,1.33 0 0,0 18,20.67V5.33C18,4.6 17.4,4 16.67,4Z" /></g><g id="battery-20"><path d="M16,17H8V6H16M16.67,4H15V2H9V4H7.33A1.33,1.33 0 0,0 6,5.33V20.67C6,21.4 6.6,22 7.33,22H16.67A1.33,1.33 0 0,0 18,20.67V5.33C18,4.6 17.4,4 16.67,4Z" /></g><g id="battery-30"><path d="M16,15H8V6H16M16.67,4H15V2H9V4H7.33A1.33,1.33 0 0,0 6,5.33V20.67C6,21.4 6.6,22 7.33,22H16.67A1.33,1.33 0 0,0 18,20.67V5.33C18,4.6 17.4,4 16.67,4Z" /></g><g id="battery-40"><path d="M16,14H8V6H16M16.67,4H15V2H9V4H7.33A1.33,1.33 0 0,0 6,5.33V20.67C6,21.4 6.6,22 7.33,22H16.67A1.33,1.33 0 0,0 18,20.67V5.33C18,4.6 17.4,4 16.67,4Z" /></g><g id="battery-50"><path d="M16,13H8V6H16M16.67,4H15V2H9V4H7.33A1.33,1.33 0 0,0 6,5.33V20.67C6,21.4 6.6,22 7.33,22H16.67A1.33,1.33 0 0,0 18,20.67V5.33C18,4.6 17.4,4 16.67,4Z" /></g><g id="battery-60"><path d="M16,12H8V6H16M16.67,4H15V2H9V4H7.33A1.33,1.33 0 0,0 6,5.33V20.67C6,21.4 6.6,22 7.33,22H16.67A1.33,1.33 0 0,0 18,20.67V5.33C18,4.6 17.4,4 16.67,4Z" /></g><g id="battery-70"><path d="M16,10H8V6H16M16.67,4H15V2H9V4H7.33A1.33,1.33 0 0,0 6,5.33V20.67C6,21.4 6.6,22 7.33,22H16.67A1.33,1.33 0 0,0 18,20.67V5.33C18,4.6 17.4,4 16.67,4Z" /></g><g id="battery-80"><path d="M16,9H8V6H16M16.67,4H15V2H9V4H7.33A1.33,1.33 0 0,0 6,5.33V20.67C6,21.4 6.6,22 7.33,22H16.67A1.33,1.33 0 0,0 18,20.67V5.33C18,4.6 17.4,4 16.67,4Z" /></g><g id="battery-90"><path d="M16,8H8V6H16M16.67,4H15V2H9V4H7.33A1.33,1.33 0 0,0 6,5.33V20.67C6,21.4 6.6,22 7.33,22H16.67A1.33,1.33 0 0,0 18,20.67V5.33C18,4.6 17.4,4 16.67,4Z" /></g><g id="battery-alert"><path d="M13,14H11V9H13M13,18H11V16H13M16.67,4H15V2H9V4H7.33A1.33,1.33 0 0,0 6,5.33V20.67C6,21.4 6.6,22 7.33,22H16.67A1.33,1.33 0 0,0 18,20.67V5.33C18,4.6 17.4,4 16.67,4Z" /></g><g id="battery-charging"><path d="M16.67,4H15V2H9V4H7.33A1.33,1.33 0 0,0 6,5.33V20.66C6,21.4 6.6,22 7.33,22H16.66C17.4,22 18,21.4 18,20.67V5.33C18,4.6 17.4,4 16.67,4M11,20V14.5H9L13,7V12.5H15" /></g><g id="battery-charging-100"><path d="M23,11H20V4L15,14H18V22M12.67,4H11V2H5V4H3.33A1.33,1.33 0 0,0 2,5.33V20.67C2,21.4 2.6,22 3.33,22H12.67C13.4,22 14,21.4 14,20.67V5.33A1.33,1.33 0 0,0 12.67,4Z" /></g><g id="battery-charging-20"><path d="M23.05,11H20.05V4L15.05,14H18.05V22M12.05,17H4.05V6H12.05M12.72,4H11.05V2H5.05V4H3.38A1.33,1.33 0 0,0 2.05,5.33V20.67C2.05,21.4 2.65,22 3.38,22H12.72C13.45,22 14.05,21.4 14.05,20.67V5.33A1.33,1.33 0 0,0 12.72,4Z" /></g><g id="battery-charging-30"><path d="M12,15H4V6H12M12.67,4H11V2H5V4H3.33A1.33,1.33 0 0,0 2,5.33V20.67C2,21.4 2.6,22 3.33,22H12.67C13.4,22 14,21.4 14,20.67V5.33A1.33,1.33 0 0,0 12.67,4M23,11H20V4L15,14H18V22L23,11Z" /></g><g id="battery-charging-40"><path d="M23,11H20V4L15,14H18V22M12,13H4V6H12M12.67,4H11V2H5V4H3.33A1.33,1.33 0 0,0 2,5.33V20.67C2,21.4 2.6,22 3.33,22H12.67C13.4,22 14,21.4 14,20.67V5.33A1.33,1.33 0 0,0 12.67,4Z" /></g><g id="battery-charging-60"><path d="M12,11H4V6H12M12.67,4H11V2H5V4H3.33A1.33,1.33 0 0,0 2,5.33V20.67C2,21.4 2.6,22 3.33,22H12.67C13.4,22 14,21.4 14,20.67V5.33A1.33,1.33 0 0,0 12.67,4M23,11H20V4L15,14H18V22L23,11Z" /></g><g id="battery-charging-80"><path d="M23,11H20V4L15,14H18V22M12,9H4V6H12M12.67,4H11V2H5V4H3.33A1.33,1.33 0 0,0 2,5.33V20.67C2,21.4 2.6,22 3.33,22H12.67C13.4,22 14,21.4 14,20.67V5.33A1.33,1.33 0 0,0 12.67,4Z" /></g><g id="battery-charging-90"><path d="M23,11H20V4L15,14H18V22M12,8H4V6H12M12.67,4H11V2H5V4H3.33A1.33,1.33 0 0,0 2,5.33V20.67C2,21.4 2.6,22 3.33,22H12.67C13.4,22 14,21.4 14,20.67V5.33A1.33,1.33 0 0,0 12.67,4Z" /></g><g id="battery-minus"><path d="M16.67,4C17.4,4 18,4.6 18,5.33V20.67A1.33,1.33 0 0,1 16.67,22H7.33C6.6,22 6,21.4 6,20.67V5.33A1.33,1.33 0 0,1 7.33,4H9V2H15V4H16.67M8,12V14H16V12" /></g><g id="battery-negative"><path d="M11.67,4A1.33,1.33 0 0,1 13,5.33V20.67C13,21.4 12.4,22 11.67,22H2.33C1.6,22 1,21.4 1,20.67V5.33A1.33,1.33 0 0,1 2.33,4H4V2H10V4H11.67M15,12H23V14H15V12M3,13H11V6H3V13Z" /></g><g id="battery-outline"><path d="M16,20H8V6H16M16.67,4H15V2H9V4H7.33A1.33,1.33 0 0,0 6,5.33V20.67C6,21.4 6.6,22 7.33,22H16.67A1.33,1.33 0 0,0 18,20.67V5.33C18,4.6 17.4,4 16.67,4Z" /></g><g id="battery-plus"><path d="M16.67,4C17.4,4 18,4.6 18,5.33V20.67A1.33,1.33 0 0,1 16.67,22H7.33C6.6,22 6,21.4 6,20.67V5.33A1.33,1.33 0 0,1 7.33,4H9V2H15V4H16.67M16,14V12H13V9H11V12H8V14H11V17H13V14H16Z" /></g><g id="battery-positive"><path d="M11.67,4A1.33,1.33 0 0,1 13,5.33V20.67C13,21.4 12.4,22 11.67,22H2.33C1.6,22 1,21.4 1,20.67V5.33A1.33,1.33 0 0,1 2.33,4H4V2H10V4H11.67M23,14H20V17H18V14H15V12H18V9H20V12H23V14M3,13H11V6H3V13Z" /></g><g id="battery-unknown"><path d="M15.07,12.25L14.17,13.17C13.63,13.71 13.25,14.18 13.09,15H11.05C11.16,14.1 11.56,13.28 12.17,12.67L13.41,11.41C13.78,11.05 14,10.55 14,10C14,8.89 13.1,8 12,8A2,2 0 0,0 10,10H8A4,4 0 0,1 12,6A4,4 0 0,1 16,10C16,10.88 15.64,11.68 15.07,12.25M13,19H11V17H13M16.67,4H15V2H9V4H7.33A1.33,1.33 0 0,0 6,5.33V20.66C6,21.4 6.6,22 7.33,22H16.67C17.4,22 18,21.4 18,20.66V5.33C18,4.59 17.4,4 16.67,4Z" /></g><g id="beach"><path d="M15,18.54C17.13,18.21 19.5,18 22,18V22H5C5,21.35 8.2,19.86 13,18.9V12.4C12.16,12.65 11.45,13.21 11,13.95C10.39,12.93 9.27,12.25 8,12.25C6.73,12.25 5.61,12.93 5,13.95C5.03,10.37 8.5,7.43 13,7.04V7A1,1 0 0,1 14,6A1,1 0 0,1 15,7V7.04C19.5,7.43 22.96,10.37 23,13.95C22.39,12.93 21.27,12.25 20,12.25C18.73,12.25 17.61,12.93 17,13.95C16.55,13.21 15.84,12.65 15,12.39V18.54M7,2A5,5 0 0,1 2,7V2H7Z" /></g><g id="beaker"><path d="M3,3H21V5A2,2 0 0,0 19,7V19A2,2 0 0,1 17,21H7A2,2 0 0,1 5,19V7A2,2 0 0,0 3,5V3M7,5V7H12V8H7V9H10V10H7V11H10V12H7V13H12V14H7V15H10V16H7V19H17V5H7Z" /></g><g id="beats"><path d="M7,12A5,5 0 0,0 12,17A5,5 0 0,0 17,12A5,5 0 0,0 12,7C10.87,7 9.84,7.37 9,8V2.46C9.95,2.16 10.95,2 12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12C2,8.3 4,5.07 7,3.34V12M14.5,12C14.5,12.37 14.3,12.68 14,12.86L12.11,14.29C11.94,14.42 11.73,14.5 11.5,14.5A1,1 0 0,1 10.5,13.5V10.5A1,1 0 0,1 11.5,9.5C11.73,9.5 11.94,9.58 12.11,9.71L14,11.14C14.3,11.32 14.5,11.63 14.5,12Z" /></g><g id="beer"><path d="M4,2H19L17,22H6L4,2M6.2,4L7.8,20H8.8L7.43,6.34C8.5,6 9.89,5.89 11,7C12.56,8.56 15.33,7.69 16.5,7.23L16.8,4H6.2Z" /></g><g id="behance"><path d="M19.58,12.27C19.54,11.65 19.33,11.18 18.96,10.86C18.59,10.54 18.13,10.38 17.58,10.38C17,10.38 16.5,10.55 16.19,10.89C15.86,11.23 15.65,11.69 15.57,12.27M21.92,12.04C22,12.45 22,13.04 22,13.81H15.5C15.55,14.71 15.85,15.33 16.44,15.69C16.79,15.92 17.22,16.03 17.73,16.03C18.26,16.03 18.69,15.89 19,15.62C19.2,15.47 19.36,15.27 19.5,15H21.88C21.82,15.54 21.53,16.07 21,16.62C20.22,17.5 19.1,17.92 17.66,17.92C16.47,17.92 15.43,17.55 14.5,16.82C13.62,16.09 13.16,14.9 13.16,13.25C13.16,11.7 13.57,10.5 14.39,9.7C15.21,8.87 16.27,8.46 17.58,8.46C18.35,8.46 19.05,8.6 19.67,8.88C20.29,9.16 20.81,9.59 21.21,10.2C21.58,10.73 21.81,11.34 21.92,12.04M9.58,14.07C9.58,13.42 9.31,12.97 8.79,12.73C8.5,12.6 8.08,12.53 7.54,12.5H4.87V15.84H7.5C8.04,15.84 8.46,15.77 8.76,15.62C9.31,15.35 9.58,14.83 9.58,14.07M4.87,10.46H7.5C8.04,10.46 8.5,10.36 8.82,10.15C9.16,9.95 9.32,9.58 9.32,9.06C9.32,8.5 9.1,8.1 8.66,7.91C8.27,7.78 7.78,7.72 7.19,7.72H4.87M11.72,12.42C12.04,12.92 12.2,13.53 12.2,14.24C12.2,15 12,15.64 11.65,16.23C11.41,16.62 11.12,16.94 10.77,17.21C10.37,17.5 9.9,17.72 9.36,17.83C8.82,17.94 8.24,18 7.61,18H2V5.55H8C9.53,5.58 10.6,6 11.23,6.88C11.61,7.41 11.8,8.04 11.8,8.78C11.8,9.54 11.61,10.15 11.23,10.61C11,10.87 10.7,11.11 10.28,11.32C10.91,11.55 11.39,11.92 11.72,12.42M20.06,7.32H15.05V6.07H20.06V7.32Z" /></g><g id="bell"><path d="M14,20A2,2 0 0,1 12,22A2,2 0 0,1 10,20H14M12,2A1,1 0 0,1 13,3V4.08C15.84,4.56 18,7.03 18,10V16L21,19H3L6,16V10C6,7.03 8.16,4.56 11,4.08V3A1,1 0 0,1 12,2Z" /></g><g id="bell-off"><path d="M14,20A2,2 0 0,1 12,22A2,2 0 0,1 10,20H14M19.74,21.57L17.17,19H3L6,16V10C6,9.35 6.1,8.72 6.3,8.13L3.47,5.3L4.89,3.89L7.29,6.29L21.15,20.15L19.74,21.57M11,4.08V3A1,1 0 0,1 12,2A1,1 0 0,1 13,3V4.08C15.84,4.56 18,7.03 18,10V14.17L8.77,4.94C9.44,4.5 10.19,4.22 11,4.08Z" /></g><g id="bell-outline"><path d="M16,17H7V10.5C7,8 9,6 11.5,6C14,6 16,8 16,10.5M18,16V10.5C18,7.43 15.86,4.86 13,4.18V3.5A1.5,1.5 0 0,0 11.5,2A1.5,1.5 0 0,0 10,3.5V4.18C7.13,4.86 5,7.43 5,10.5V16L3,18V19H20V18M11.5,22A2,2 0 0,0 13.5,20H9.5A2,2 0 0,0 11.5,22Z" /></g><g id="bell-plus"><path d="M10,21C10,22.11 10.9,23 12,23A2,2 0 0,0 14,21M18.88,16.82V11C18.88,7.75 16.63,5.03 13.59,4.31V3.59A1.59,1.59 0 0,0 12,2A1.59,1.59 0 0,0 10.41,3.59V4.31C7.37,5.03 5.12,7.75 5.12,11V16.82L3,18.94V20H21V18.94M16,13H13V16H11V13H8V11H11V8H13V11H16" /></g><g id="bell-ring"><path d="M11.5,22C11.64,22 11.77,22 11.9,21.96C12.55,21.82 13.09,21.38 13.34,20.78C13.44,20.54 13.5,20.27 13.5,20H9.5A2,2 0 0,0 11.5,22M18,10.5C18,7.43 15.86,4.86 13,4.18V3.5A1.5,1.5 0 0,0 11.5,2A1.5,1.5 0 0,0 10,3.5V4.18C7.13,4.86 5,7.43 5,10.5V16L3,18V19H20V18L18,16M19.97,10H21.97C21.82,6.79 20.24,3.97 17.85,2.15L16.42,3.58C18.46,5 19.82,7.35 19.97,10M6.58,3.58L5.15,2.15C2.76,3.97 1.18,6.79 1,10H3C3.18,7.35 4.54,5 6.58,3.58Z" /></g><g id="bell-ring-outline"><path d="M16,17V10.5C16,8 14,6 11.5,6C9,6 7,8 7,10.5V17H16M18,16L20,18V19H3V18L5,16V10.5C5,7.43 7.13,4.86 10,4.18V3.5A1.5,1.5 0 0,1 11.5,2A1.5,1.5 0 0,1 13,3.5V4.18C15.86,4.86 18,7.43 18,10.5V16M11.5,22A2,2 0 0,1 9.5,20H13.5A2,2 0 0,1 11.5,22M19.97,10C19.82,7.35 18.46,5 16.42,3.58L17.85,2.15C20.24,3.97 21.82,6.79 21.97,10H19.97M6.58,3.58C4.54,5 3.18,7.35 3,10H1C1.18,6.79 2.76,3.97 5.15,2.15L6.58,3.58Z" /></g><g id="bell-sleep"><path d="M14,9.8L11.2,13.2H14V15H9V13.2L11.8,9.8H9V8H14M18,16V10.5C18,7.43 15.86,4.86 13,4.18V3.5A1.5,1.5 0 0,0 11.5,2A1.5,1.5 0 0,0 10,3.5V4.18C7.13,4.86 5,7.43 5,10.5V16L3,18V19H20V18M11.5,22A2,2 0 0,0 13.5,20H9.5A2,2 0 0,0 11.5,22Z" /></g><g id="beta"><path d="M9.23,17.59V23.12H6.88V6.72C6.88,5.27 7.31,4.13 8.16,3.28C9,2.43 10.17,2 11.61,2C13,2 14.07,2.34 14.87,3C15.66,3.68 16.05,4.62 16.05,5.81C16.05,6.63 15.79,7.4 15.27,8.11C14.75,8.82 14.08,9.31 13.25,9.58V9.62C14.5,9.82 15.47,10.27 16.13,11C16.79,11.71 17.12,12.62 17.12,13.74C17.12,15.06 16.66,16.14 15.75,16.97C14.83,17.8 13.63,18.21 12.13,18.21C11.07,18.21 10.1,18 9.23,17.59M10.72,10.75V8.83C11.59,8.72 12.3,8.4 12.87,7.86C13.43,7.31 13.71,6.7 13.71,6C13.71,4.62 13,3.92 11.6,3.92C10.84,3.92 10.25,4.16 9.84,4.65C9.43,5.14 9.23,5.82 9.23,6.71V15.5C10.14,16.03 11.03,16.29 11.89,16.29C12.73,16.29 13.39,16.07 13.86,15.64C14.33,15.2 14.56,14.58 14.56,13.79C14.56,12 13.28,11 10.72,10.75Z" /></g><g id="bible"><path d="M5.81,2H7V9L9.5,7.5L12,9V2H18A2,2 0 0,1 20,4V20C20,21.05 19.05,22 18,22H6C4.95,22 4,21.05 4,20V4C4,3 4.83,2.09 5.81,2M13,10V13H10V15H13V20H15V15H18V13H15V10H13Z" /></g><g id="bike"><path d="M5,20.5A3.5,3.5 0 0,1 1.5,17A3.5,3.5 0 0,1 5,13.5A3.5,3.5 0 0,1 8.5,17A3.5,3.5 0 0,1 5,20.5M5,12A5,5 0 0,0 0,17A5,5 0 0,0 5,22A5,5 0 0,0 10,17A5,5 0 0,0 5,12M14.8,10H19V8.2H15.8L13.86,4.93C13.57,4.43 13,4.1 12.4,4.1C11.93,4.1 11.5,4.29 11.2,4.6L7.5,8.29C7.19,8.6 7,9 7,9.5C7,10.13 7.33,10.66 7.85,10.97L11.2,13V18H13V11.5L10.75,9.85L13.07,7.5M19,20.5A3.5,3.5 0 0,1 15.5,17A3.5,3.5 0 0,1 19,13.5A3.5,3.5 0 0,1 22.5,17A3.5,3.5 0 0,1 19,20.5M19,12A5,5 0 0,0 14,17A5,5 0 0,0 19,22A5,5 0 0,0 24,17A5,5 0 0,0 19,12M16,4.8C17,4.8 17.8,4 17.8,3C17.8,2 17,1.2 16,1.2C15,1.2 14.2,2 14.2,3C14.2,4 15,4.8 16,4.8Z" /></g><g id="bing"><path d="M5,3V19L8.72,21L18,15.82V11.73H18L9.77,8.95L11.38,12.84L13.94,14L8.7,16.92V4.27L5,3" /></g><g id="binoculars"><path d="M11,6H13V13H11V6M9,20A1,1 0 0,1 8,21H5A1,1 0 0,1 4,20V15L6,6H10V13A1,1 0 0,1 9,14V20M10,5H7V3H10V5M15,20V14A1,1 0 0,1 14,13V6H18L20,15V20A1,1 0 0,1 19,21H16A1,1 0 0,1 15,20M14,5V3H17V5H14Z" /></g><g id="bio"><path d="M17,12H20A2,2 0 0,1 22,14V17A2,2 0 0,1 20,19H17A2,2 0 0,1 15,17V14A2,2 0 0,1 17,12M17,14V17H20V14H17M2,7H7A2,2 0 0,1 9,9V11A2,2 0 0,1 7,13A2,2 0 0,1 9,15V17A2,2 0 0,1 7,19H2V13L2,7M4,9V12H7V9H4M4,17H7V14H4V17M11,13H13V19H11V13M11,9H13V11H11V9Z" /></g><g id="biohazard"><path d="M23,16.06C23,16.29 23,16.5 22.96,16.7C22.78,14.14 20.64,12.11 18,12.11C17.63,12.11 17.27,12.16 16.92,12.23C16.96,12.5 17,12.73 17,13C17,15.35 15.31,17.32 13.07,17.81C13.42,20.05 15.31,21.79 17.65,21.96C17.43,22 17.22,22 17,22C14.92,22 13.07,20.94 12,19.34C10.93,20.94 9.09,22 7,22C6.78,22 6.57,22 6.35,21.96C8.69,21.79 10.57,20.06 10.93,17.81C8.68,17.32 7,15.35 7,13C7,12.73 7.04,12.5 7.07,12.23C6.73,12.16 6.37,12.11 6,12.11C3.36,12.11 1.22,14.14 1.03,16.7C1,16.5 1,16.29 1,16.06C1,12.85 3.59,10.24 6.81,10.14C6.3,9.27 6,8.25 6,7.17C6,4.94 7.23,3 9.06,2C7.81,2.9 7,4.34 7,6C7,7.35 7.56,8.59 8.47,9.5C9.38,8.59 10.62,8.04 12,8.04C13.37,8.04 14.62,8.59 15.5,9.5C16.43,8.59 17,7.35 17,6C17,4.34 16.18,2.9 14.94,2C16.77,3 18,4.94 18,7.17C18,8.25 17.7,9.27 17.19,10.14C20.42,10.24 23,12.85 23,16.06M9.27,10.11C10.05,10.62 11,10.92 12,10.92C13,10.92 13.95,10.62 14.73,10.11C14,9.45 13.06,9.03 12,9.03C10.94,9.03 10,9.45 9.27,10.11M12,14.47C12.82,14.47 13.5,13.8 13.5,13A1.5,1.5 0 0,0 12,11.5A1.5,1.5 0 0,0 10.5,13C10.5,13.8 11.17,14.47 12,14.47M10.97,16.79C10.87,14.9 9.71,13.29 8.05,12.55C8.03,12.7 8,12.84 8,13C8,14.82 9.27,16.34 10.97,16.79M15.96,12.55C14.29,13.29 13.12,14.9 13,16.79C14.73,16.34 16,14.82 16,13C16,12.84 15.97,12.7 15.96,12.55Z" /></g><g id="bitbucket"><path d="M12,5.76C15.06,5.77 17.55,5.24 17.55,4.59C17.55,3.94 15.07,3.41 12,3.4C8.94,3.4 6.45,3.92 6.45,4.57C6.45,5.23 8.93,5.76 12,5.76M12,14.4C13.5,14.4 14.75,13.16 14.75,11.64A2.75,2.75 0 0,0 12,8.89C10.5,8.89 9.25,10.12 9.25,11.64C9.25,13.16 10.5,14.4 12,14.4M12,2C16.77,2 20.66,3.28 20.66,4.87C20.66,5.29 19.62,11.31 19.21,13.69C19.03,14.76 16.26,16.33 12,16.33V16.31L12,16.33C7.74,16.33 4.97,14.76 4.79,13.69C4.38,11.31 3.34,5.29 3.34,4.87C3.34,3.28 7.23,2 12,2M18.23,16.08C18.38,16.08 18.53,16.19 18.53,16.42V16.5C18.19,18.26 17.95,19.5 17.91,19.71C17.62,21 15.07,22 12,22V22C8.93,22 6.38,21 6.09,19.71C6.05,19.5 5.81,18.26 5.47,16.5V16.42C5.47,16.19 5.62,16.08 5.77,16.08C5.91,16.08 6,16.17 6,16.17C6,16.17 8.14,17.86 12,17.86C15.86,17.86 18,16.17 18,16.17C18,16.17 18.09,16.08 18.23,16.08M13.38,11.64C13.38,12.4 12.76,13 12,13C11.24,13 10.62,12.4 10.62,11.64A1.38,1.38 0 0,1 12,10.26A1.38,1.38 0 0,1 13.38,11.64Z" /></g><g id="black-mesa"><path d="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12C4,14.39 5.05,16.53 6.71,18H9V12H17L19.15,15.59C19.69,14.5 20,13.29 20,12A8,8 0 0,0 12,4Z" /></g><g id="blackberry"><path d="M5.45,10.28C6.4,10.28 7.5,11.05 7.5,12C7.5,12.95 6.4,13.72 5.45,13.72H2L2.69,10.28H5.45M6.14,4.76C7.09,4.76 8.21,5.53 8.21,6.5C8.21,7.43 7.09,8.21 6.14,8.21H2.69L3.38,4.76H6.14M13.03,4.76C14,4.76 15.1,5.53 15.1,6.5C15.1,7.43 14,8.21 13.03,8.21H9.41L10.1,4.76H13.03M12.34,10.28C13.3,10.28 14.41,11.05 14.41,12C14.41,12.95 13.3,13.72 12.34,13.72H8.72L9.41,10.28H12.34M10.97,15.79C11.92,15.79 13.03,16.57 13.03,17.5C13.03,18.47 11.92,19.24 10.97,19.24H7.5L8.21,15.79H10.97M18.55,13.72C19.5,13.72 20.62,14.5 20.62,15.45C20.62,16.4 19.5,17.17 18.55,17.17H15.1L15.79,13.72H18.55M19.93,8.21C20.88,8.21 22,9 22,9.93C22,10.88 20.88,11.66 19.93,11.66H16.5L17.17,8.21H19.93Z" /></g><g id="blender"><path d="M8,3C8,3.34 8.17,3.69 8.5,3.88L12,6H2.5A1.5,1.5 0 0,0 1,7.5A1.5,1.5 0 0,0 2.5,9H8.41L2,13C1.16,13.5 1,14.22 1,15C1,16 1.77,17 3,17C3.69,17 4.39,16.5 5,16L7,14.38C7.2,18.62 10.71,22 15,22A8,8 0 0,0 23,14C23,11.08 21.43,8.5 19.09,7.13C19.06,7.11 19.03,7.08 19,7.06C19,7.06 18.92,7 18.86,6.97C15.76,4.88 13.03,3.72 9.55,2.13C9.34,2.04 9.16,2 9,2C8.4,2 8,2.46 8,3M15,9A5,5 0 0,1 20,14A5,5 0 0,1 15,19A5,5 0 0,1 10,14A5,5 0 0,1 15,9M15,10.5A3.5,3.5 0 0,0 11.5,14A3.5,3.5 0 0,0 15,17.5A3.5,3.5 0 0,0 18.5,14A3.5,3.5 0 0,0 15,10.5Z" /></g><g id="blinds"><path d="M3,2H21A1,1 0 0,1 22,3V5A1,1 0 0,1 21,6H20V13A1,1 0 0,1 19,14H13V16.17C14.17,16.58 15,17.69 15,19A3,3 0 0,1 12,22A3,3 0 0,1 9,19C9,17.69 9.83,16.58 11,16.17V14H5A1,1 0 0,1 4,13V6H3A1,1 0 0,1 2,5V3A1,1 0 0,1 3,2M12,18A1,1 0 0,0 11,19A1,1 0 0,0 12,20A1,1 0 0,0 13,19A1,1 0 0,0 12,18Z" /></g><g id="block-helper"><path d="M12,0A12,12 0 0,1 24,12A12,12 0 0,1 12,24A12,12 0 0,1 0,12A12,12 0 0,1 12,0M12,2A10,10 0 0,0 2,12C2,14.4 2.85,16.6 4.26,18.33L18.33,4.26C16.6,2.85 14.4,2 12,2M12,22A10,10 0 0,0 22,12C22,9.6 21.15,7.4 19.74,5.67L5.67,19.74C7.4,21.15 9.6,22 12,22Z" /></g><g id="blogger"><path d="M14,13H9.95A1,1 0 0,0 8.95,14A1,1 0 0,0 9.95,15H14A1,1 0 0,0 15,14A1,1 0 0,0 14,13M9.95,10H12.55A1,1 0 0,0 13.55,9A1,1 0 0,0 12.55,8H9.95A1,1 0 0,0 8.95,9A1,1 0 0,0 9.95,10M16,9V10A1,1 0 0,0 17,11A1,1 0 0,1 18,12V15A3,3 0 0,1 15,18H9A3,3 0 0,1 6,15V8A3,3 0 0,1 9,5H13A3,3 0 0,1 16,8M20,2H4C2.89,2 2,2.89 2,4V20A2,2 0 0,0 4,22H20A2,2 0 0,0 22,20V4C22,2.89 21.1,2 20,2Z" /></g><g id="bluetooth"><path d="M14.88,16.29L13,18.17V14.41M13,5.83L14.88,7.71L13,9.58M17.71,7.71L12,2H11V9.58L6.41,5L5,6.41L10.59,12L5,17.58L6.41,19L11,14.41V22H12L17.71,16.29L13.41,12L17.71,7.71Z" /></g><g id="bluetooth-audio"><path d="M12.88,16.29L11,18.17V14.41M11,5.83L12.88,7.71L11,9.58M15.71,7.71L10,2H9V9.58L4.41,5L3,6.41L8.59,12L3,17.58L4.41,19L9,14.41V22H10L15.71,16.29L11.41,12M19.53,6.71L18.26,8C18.89,9.18 19.25,10.55 19.25,12C19.25,13.45 18.89,14.82 18.26,16L19.46,17.22C20.43,15.68 21,13.87 21,11.91C21,10 20.46,8.23 19.53,6.71M14.24,12L16.56,14.33C16.84,13.6 17,12.82 17,12C17,11.18 16.84,10.4 16.57,9.68L14.24,12Z" /></g><g id="bluetooth-connect"><path d="M19,10L17,12L19,14L21,12M14.88,16.29L13,18.17V14.41M13,5.83L14.88,7.71L13,9.58M17.71,7.71L12,2H11V9.58L6.41,5L5,6.41L10.59,12L5,17.58L6.41,19L11,14.41V22H12L17.71,16.29L13.41,12M7,12L5,10L3,12L5,14L7,12Z" /></g><g id="bluetooth-off"><path d="M13,5.83L14.88,7.71L13.28,9.31L14.69,10.72L17.71,7.7L12,2H11V7.03L13,9.03M5.41,4L4,5.41L10.59,12L5,17.59L6.41,19L11,14.41V22H12L16.29,17.71L18.59,20L20,18.59M13,18.17V14.41L14.88,16.29" /></g><g id="bluetooth-settings"><path d="M14.88,14.29L13,16.17V12.41L14.88,14.29M13,3.83L14.88,5.71L13,7.59M17.71,5.71L12,0H11V7.59L6.41,3L5,4.41L10.59,10L5,15.59L6.41,17L11,12.41V20H12L17.71,14.29L13.41,10L17.71,5.71M15,24H17V22H15M7,24H9V22H7M11,24H13V22H11V24Z" /></g><g id="bluetooth-transfer"><path d="M14.71,7.71L10.41,12L14.71,16.29L9,22H8V14.41L3.41,19L2,17.59L7.59,12L2,6.41L3.41,5L8,9.59V2H9L14.71,7.71M10,5.83V9.59L11.88,7.71L10,5.83M11.88,16.29L10,14.41V18.17L11.88,16.29M22,8H20V11H18V8H16L19,4L22,8M22,16L19,20L16,16H18V13H20V16H22Z" /></g><g id="blur"><path d="M14,8.5A1.5,1.5 0 0,0 12.5,10A1.5,1.5 0 0,0 14,11.5A1.5,1.5 0 0,0 15.5,10A1.5,1.5 0 0,0 14,8.5M14,12.5A1.5,1.5 0 0,0 12.5,14A1.5,1.5 0 0,0 14,15.5A1.5,1.5 0 0,0 15.5,14A1.5,1.5 0 0,0 14,12.5M10,17A1,1 0 0,0 9,18A1,1 0 0,0 10,19A1,1 0 0,0 11,18A1,1 0 0,0 10,17M10,8.5A1.5,1.5 0 0,0 8.5,10A1.5,1.5 0 0,0 10,11.5A1.5,1.5 0 0,0 11.5,10A1.5,1.5 0 0,0 10,8.5M14,20.5A0.5,0.5 0 0,0 13.5,21A0.5,0.5 0 0,0 14,21.5A0.5,0.5 0 0,0 14.5,21A0.5,0.5 0 0,0 14,20.5M14,17A1,1 0 0,0 13,18A1,1 0 0,0 14,19A1,1 0 0,0 15,18A1,1 0 0,0 14,17M21,13.5A0.5,0.5 0 0,0 20.5,14A0.5,0.5 0 0,0 21,14.5A0.5,0.5 0 0,0 21.5,14A0.5,0.5 0 0,0 21,13.5M18,5A1,1 0 0,0 17,6A1,1 0 0,0 18,7A1,1 0 0,0 19,6A1,1 0 0,0 18,5M18,9A1,1 0 0,0 17,10A1,1 0 0,0 18,11A1,1 0 0,0 19,10A1,1 0 0,0 18,9M18,17A1,1 0 0,0 17,18A1,1 0 0,0 18,19A1,1 0 0,0 19,18A1,1 0 0,0 18,17M18,13A1,1 0 0,0 17,14A1,1 0 0,0 18,15A1,1 0 0,0 19,14A1,1 0 0,0 18,13M10,12.5A1.5,1.5 0 0,0 8.5,14A1.5,1.5 0 0,0 10,15.5A1.5,1.5 0 0,0 11.5,14A1.5,1.5 0 0,0 10,12.5M10,7A1,1 0 0,0 11,6A1,1 0 0,0 10,5A1,1 0 0,0 9,6A1,1 0 0,0 10,7M10,3.5A0.5,0.5 0 0,0 10.5,3A0.5,0.5 0 0,0 10,2.5A0.5,0.5 0 0,0 9.5,3A0.5,0.5 0 0,0 10,3.5M10,20.5A0.5,0.5 0 0,0 9.5,21A0.5,0.5 0 0,0 10,21.5A0.5,0.5 0 0,0 10.5,21A0.5,0.5 0 0,0 10,20.5M3,13.5A0.5,0.5 0 0,0 2.5,14A0.5,0.5 0 0,0 3,14.5A0.5,0.5 0 0,0 3.5,14A0.5,0.5 0 0,0 3,13.5M14,3.5A0.5,0.5 0 0,0 14.5,3A0.5,0.5 0 0,0 14,2.5A0.5,0.5 0 0,0 13.5,3A0.5,0.5 0 0,0 14,3.5M14,7A1,1 0 0,0 15,6A1,1 0 0,0 14,5A1,1 0 0,0 13,6A1,1 0 0,0 14,7M21,10.5A0.5,0.5 0 0,0 21.5,10A0.5,0.5 0 0,0 21,9.5A0.5,0.5 0 0,0 20.5,10A0.5,0.5 0 0,0 21,10.5M6,5A1,1 0 0,0 5,6A1,1 0 0,0 6,7A1,1 0 0,0 7,6A1,1 0 0,0 6,5M3,9.5A0.5,0.5 0 0,0 2.5,10A0.5,0.5 0 0,0 3,10.5A0.5,0.5 0 0,0 3.5,10A0.5,0.5 0 0,0 3,9.5M6,9A1,1 0 0,0 5,10A1,1 0 0,0 6,11A1,1 0 0,0 7,10A1,1 0 0,0 6,9M6,17A1,1 0 0,0 5,18A1,1 0 0,0 6,19A1,1 0 0,0 7,18A1,1 0 0,0 6,17M6,13A1,1 0 0,0 5,14A1,1 0 0,0 6,15A1,1 0 0,0 7,14A1,1 0 0,0 6,13Z" /></g><g id="blur-linear"><path d="M13,17A1,1 0 0,0 14,16A1,1 0 0,0 13,15A1,1 0 0,0 12,16A1,1 0 0,0 13,17M13,13A1,1 0 0,0 14,12A1,1 0 0,0 13,11A1,1 0 0,0 12,12A1,1 0 0,0 13,13M13,9A1,1 0 0,0 14,8A1,1 0 0,0 13,7A1,1 0 0,0 12,8A1,1 0 0,0 13,9M17,12.5A0.5,0.5 0 0,0 17.5,12A0.5,0.5 0 0,0 17,11.5A0.5,0.5 0 0,0 16.5,12A0.5,0.5 0 0,0 17,12.5M17,8.5A0.5,0.5 0 0,0 17.5,8A0.5,0.5 0 0,0 17,7.5A0.5,0.5 0 0,0 16.5,8A0.5,0.5 0 0,0 17,8.5M3,3V5H21V3M17,16.5A0.5,0.5 0 0,0 17.5,16A0.5,0.5 0 0,0 17,15.5A0.5,0.5 0 0,0 16.5,16A0.5,0.5 0 0,0 17,16.5M9,17A1,1 0 0,0 10,16A1,1 0 0,0 9,15A1,1 0 0,0 8,16A1,1 0 0,0 9,17M5,13.5A1.5,1.5 0 0,0 6.5,12A1.5,1.5 0 0,0 5,10.5A1.5,1.5 0 0,0 3.5,12A1.5,1.5 0 0,0 5,13.5M5,9.5A1.5,1.5 0 0,0 6.5,8A1.5,1.5 0 0,0 5,6.5A1.5,1.5 0 0,0 3.5,8A1.5,1.5 0 0,0 5,9.5M3,21H21V19H3M9,9A1,1 0 0,0 10,8A1,1 0 0,0 9,7A1,1 0 0,0 8,8A1,1 0 0,0 9,9M9,13A1,1 0 0,0 10,12A1,1 0 0,0 9,11A1,1 0 0,0 8,12A1,1 0 0,0 9,13M5,17.5A1.5,1.5 0 0,0 6.5,16A1.5,1.5 0 0,0 5,14.5A1.5,1.5 0 0,0 3.5,16A1.5,1.5 0 0,0 5,17.5Z" /></g><g id="blur-off"><path d="M3,13.5A0.5,0.5 0 0,0 2.5,14A0.5,0.5 0 0,0 3,14.5A0.5,0.5 0 0,0 3.5,14A0.5,0.5 0 0,0 3,13.5M6,17A1,1 0 0,0 5,18A1,1 0 0,0 6,19A1,1 0 0,0 7,18A1,1 0 0,0 6,17M10,20.5A0.5,0.5 0 0,0 9.5,21A0.5,0.5 0 0,0 10,21.5A0.5,0.5 0 0,0 10.5,21A0.5,0.5 0 0,0 10,20.5M3,9.5A0.5,0.5 0 0,0 2.5,10A0.5,0.5 0 0,0 3,10.5A0.5,0.5 0 0,0 3.5,10A0.5,0.5 0 0,0 3,9.5M6,13A1,1 0 0,0 5,14A1,1 0 0,0 6,15A1,1 0 0,0 7,14A1,1 0 0,0 6,13M21,13.5A0.5,0.5 0 0,0 20.5,14A0.5,0.5 0 0,0 21,14.5A0.5,0.5 0 0,0 21.5,14A0.5,0.5 0 0,0 21,13.5M10,17A1,1 0 0,0 9,18A1,1 0 0,0 10,19A1,1 0 0,0 11,18A1,1 0 0,0 10,17M2.5,5.27L6.28,9.05L6,9A1,1 0 0,0 5,10A1,1 0 0,0 6,11A1,1 0 0,0 7,10C7,9.9 6.97,9.81 6.94,9.72L9.75,12.53C9.04,12.64 8.5,13.26 8.5,14A1.5,1.5 0 0,0 10,15.5C10.74,15.5 11.36,14.96 11.47,14.25L14.28,17.06C14.19,17.03 14.1,17 14,17A1,1 0 0,0 13,18A1,1 0 0,0 14,19A1,1 0 0,0 15,18C15,17.9 14.97,17.81 14.94,17.72L18.72,21.5L20,20.23L3.77,4L2.5,5.27M14,20.5A0.5,0.5 0 0,0 13.5,21A0.5,0.5 0 0,0 14,21.5A0.5,0.5 0 0,0 14.5,21A0.5,0.5 0 0,0 14,20.5M18,7A1,1 0 0,0 19,6A1,1 0 0,0 18,5A1,1 0 0,0 17,6A1,1 0 0,0 18,7M18,11A1,1 0 0,0 19,10A1,1 0 0,0 18,9A1,1 0 0,0 17,10A1,1 0 0,0 18,11M18,15A1,1 0 0,0 19,14A1,1 0 0,0 18,13A1,1 0 0,0 17,14A1,1 0 0,0 18,15M10,7A1,1 0 0,0 11,6A1,1 0 0,0 10,5A1,1 0 0,0 9,6A1,1 0 0,0 10,7M21,10.5A0.5,0.5 0 0,0 21.5,10A0.5,0.5 0 0,0 21,9.5A0.5,0.5 0 0,0 20.5,10A0.5,0.5 0 0,0 21,10.5M10,3.5A0.5,0.5 0 0,0 10.5,3A0.5,0.5 0 0,0 10,2.5A0.5,0.5 0 0,0 9.5,3A0.5,0.5 0 0,0 10,3.5M14,3.5A0.5,0.5 0 0,0 14.5,3A0.5,0.5 0 0,0 14,2.5A0.5,0.5 0 0,0 13.5,3A0.5,0.5 0 0,0 14,3.5M13.8,11.5H14A1.5,1.5 0 0,0 15.5,10A1.5,1.5 0 0,0 14,8.5A1.5,1.5 0 0,0 12.5,10V10.2C12.61,10.87 13.13,11.39 13.8,11.5M14,7A1,1 0 0,0 15,6A1,1 0 0,0 14,5A1,1 0 0,0 13,6A1,1 0 0,0 14,7Z" /></g><g id="blur-radial"><path d="M14,13A1,1 0 0,0 13,14A1,1 0 0,0 14,15A1,1 0 0,0 15,14A1,1 0 0,0 14,13M14,16.5A0.5,0.5 0 0,0 13.5,17A0.5,0.5 0 0,0 14,17.5A0.5,0.5 0 0,0 14.5,17A0.5,0.5 0 0,0 14,16.5M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M17,9.5A0.5,0.5 0 0,0 16.5,10A0.5,0.5 0 0,0 17,10.5A0.5,0.5 0 0,0 17.5,10A0.5,0.5 0 0,0 17,9.5M17,13.5A0.5,0.5 0 0,0 16.5,14A0.5,0.5 0 0,0 17,14.5A0.5,0.5 0 0,0 17.5,14A0.5,0.5 0 0,0 17,13.5M14,7.5A0.5,0.5 0 0,0 14.5,7A0.5,0.5 0 0,0 14,6.5A0.5,0.5 0 0,0 13.5,7A0.5,0.5 0 0,0 14,7.5M14,9A1,1 0 0,0 13,10A1,1 0 0,0 14,11A1,1 0 0,0 15,10A1,1 0 0,0 14,9M10,7.5A0.5,0.5 0 0,0 10.5,7A0.5,0.5 0 0,0 10,6.5A0.5,0.5 0 0,0 9.5,7A0.5,0.5 0 0,0 10,7.5M7,13.5A0.5,0.5 0 0,0 6.5,14A0.5,0.5 0 0,0 7,14.5A0.5,0.5 0 0,0 7.5,14A0.5,0.5 0 0,0 7,13.5M10,16.5A0.5,0.5 0 0,0 9.5,17A0.5,0.5 0 0,0 10,17.5A0.5,0.5 0 0,0 10.5,17A0.5,0.5 0 0,0 10,16.5M7,9.5A0.5,0.5 0 0,0 6.5,10A0.5,0.5 0 0,0 7,10.5A0.5,0.5 0 0,0 7.5,10A0.5,0.5 0 0,0 7,9.5M10,13A1,1 0 0,0 9,14A1,1 0 0,0 10,15A1,1 0 0,0 11,14A1,1 0 0,0 10,13M10,9A1,1 0 0,0 9,10A1,1 0 0,0 10,11A1,1 0 0,0 11,10A1,1 0 0,0 10,9Z" /></g><g id="bomb"><path d="M11.25,6A3.25,3.25 0 0,1 14.5,2.75A3.25,3.25 0 0,1 17.75,6C17.75,6.42 18.08,6.75 18.5,6.75C18.92,6.75 19.25,6.42 19.25,6V5.25H20.75V6A2.25,2.25 0 0,1 18.5,8.25A2.25,2.25 0 0,1 16.25,6A1.75,1.75 0 0,0 14.5,4.25A1.75,1.75 0 0,0 12.75,6H14V7.29C16.89,8.15 19,10.83 19,14A7,7 0 0,1 12,21A7,7 0 0,1 5,14C5,10.83 7.11,8.15 10,7.29V6H11.25M22,6H24V7H22V6M19,4V2H20V4H19M20.91,4.38L22.33,2.96L23.04,3.67L21.62,5.09L20.91,4.38Z" /></g><g id="bone"><path d="M8,14A3,3 0 0,1 5,17A3,3 0 0,1 2,14C2,13.23 2.29,12.53 2.76,12C2.29,11.47 2,10.77 2,10A3,3 0 0,1 5,7A3,3 0 0,1 8,10C9.33,10.08 10.67,10.17 12,10.17C13.33,10.17 14.67,10.08 16,10A3,3 0 0,1 19,7A3,3 0 0,1 22,10C22,10.77 21.71,11.47 21.24,12C21.71,12.53 22,13.23 22,14A3,3 0 0,1 19,17A3,3 0 0,1 16,14C14.67,13.92 13.33,13.83 12,13.83C10.67,13.83 9.33,13.92 8,14Z" /></g><g id="book"><path d="M18,22A2,2 0 0,0 20,20V4C20,2.89 19.1,2 18,2H12V9L9.5,7.5L7,9V2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18Z" /></g><g id="book-minus"><path d="M18,22H6A2,2 0 0,1 4,20V4C4,2.89 4.9,2 6,2H7V9L9.5,7.5L12,9V2H18A2,2 0 0,1 20,4V20A2,2 0 0,1 18,22M18,18V16H12V18H18Z" /></g><g id="book-multiple"><path d="M19,18H9A2,2 0 0,1 7,16V4A2,2 0 0,1 9,2H10V7L12,5.5L14,7V2H19A2,2 0 0,1 21,4V16A2,2 0 0,1 19,18M17,20V22H5A2,2 0 0,1 3,20V6H5V20H17Z" /></g><g id="book-multiple-variant"><path d="M19,18H9A2,2 0 0,1 7,16V4A2,2 0 0,1 9,2H19A2,2 0 0,1 21,4V16A2,2 0 0,1 19,18M10,9L12,7.5L14,9V4H10V9M17,20V22H5A2,2 0 0,1 3,20V6H5V20H17Z" /></g><g id="book-open"><path d="M13,12H20V13.5H13M13,9.5H20V11H13M13,14.5H20V16H13M21,4H3A2,2 0 0,0 1,6V19A2,2 0 0,0 3,21H21A2,2 0 0,0 23,19V6A2,2 0 0,0 21,4M21,19H12V6H21" /></g><g id="book-open-page-variant"><path d="M19,2L14,6.5V17.5L19,13V2M6.5,5C4.55,5 2.45,5.4 1,6.5V21.16C1,21.41 1.25,21.66 1.5,21.66C1.6,21.66 1.65,21.59 1.75,21.59C3.1,20.94 5.05,20.5 6.5,20.5C8.45,20.5 10.55,20.9 12,22C13.35,21.15 15.8,20.5 17.5,20.5C19.15,20.5 20.85,20.81 22.25,21.56C22.35,21.61 22.4,21.59 22.5,21.59C22.75,21.59 23,21.34 23,21.09V6.5C22.4,6.05 21.75,5.75 21,5.5V7.5L21,13V19C19.9,18.65 18.7,18.5 17.5,18.5C15.8,18.5 13.35,19.15 12,20V13L12,8.5V6.5C10.55,5.4 8.45,5 6.5,5V5Z" /></g><g id="book-open-variant"><path d="M21,5C19.89,4.65 18.67,4.5 17.5,4.5C15.55,4.5 13.45,4.9 12,6C10.55,4.9 8.45,4.5 6.5,4.5C4.55,4.5 2.45,4.9 1,6V20.65C1,20.9 1.25,21.15 1.5,21.15C1.6,21.15 1.65,21.1 1.75,21.1C3.1,20.45 5.05,20 6.5,20C8.45,20 10.55,20.4 12,21.5C13.35,20.65 15.8,20 17.5,20C19.15,20 20.85,20.3 22.25,21.05C22.35,21.1 22.4,21.1 22.5,21.1C22.75,21.1 23,20.85 23,20.6V6C22.4,5.55 21.75,5.25 21,5M21,18.5C19.9,18.15 18.7,18 17.5,18C15.8,18 13.35,18.65 12,19.5V8C13.35,7.15 15.8,6.5 17.5,6.5C18.7,6.5 19.9,6.65 21,7V18.5Z" /></g><g id="book-plus"><path d="M18,22H6A2,2 0 0,1 4,20V4C4,2.89 4.9,2 6,2H7V9L9.5,7.5L12,9V2H18A2,2 0 0,1 20,4V20A2,2 0 0,1 18,22M14,20H16V18H18V16H16V14H14V16H12V18H14V20Z" /></g><g id="book-variant"><path d="M6,4H11V12L8.5,10.5L6,12M18,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V4A2,2 0 0,0 18,2Z" /></g><g id="bookmark"><path d="M17,3H7A2,2 0 0,0 5,5V21L12,18L19,21V5C19,3.89 18.1,3 17,3Z" /></g><g id="bookmark-check"><path d="M17,3A2,2 0 0,1 19,5V21L12,18L5,21V5C5,3.89 5.9,3 7,3H17M11,14L17.25,7.76L15.84,6.34L11,11.18L8.41,8.59L7,10L11,14Z" /></g><g id="bookmark-music"><path d="M17,3A2,2 0 0,1 19,5V21L12,18L5,21V5C5,3.89 5.9,3 7,3H17M11,11A2,2 0 0,0 9,13A2,2 0 0,0 11,15A2,2 0 0,0 13,13V8H16V6H12V11.27C11.71,11.1 11.36,11 11,11Z" /></g><g id="bookmark-outline"><path d="M17,18L12,15.82L7,18V5H17M17,3H7A2,2 0 0,0 5,5V21L12,18L19,21V5C19,3.89 18.1,3 17,3Z" /></g><g id="bookmark-plus"><path d="M17,3A2,2 0 0,1 19,5V21L12,18L5,21V5C5,3.89 5.9,3 7,3H17M11,7V9H9V11H11V13H13V11H15V9H13V7H11Z" /></g><g id="bookmark-plus-outline"><path d="M17,18V5H7V18L12,15.82L17,18M17,3A2,2 0 0,1 19,5V21L12,18L5,21V5C5,3.89 5.9,3 7,3H17M11,7H13V9H15V11H13V13H11V11H9V9H11V7Z" /></g><g id="bookmark-remove"><path d="M17,3A2,2 0 0,1 19,5V21L12,18L5,21V5C5,3.89 5.9,3 7,3H17M8.17,8.58L10.59,11L8.17,13.41L9.59,14.83L12,12.41L14.41,14.83L15.83,13.41L13.41,11L15.83,8.58L14.41,7.17L12,9.58L9.59,7.17L8.17,8.58Z" /></g><g id="boombox"><path d="M7,5L5,7V8H3A1,1 0 0,0 2,9V17A1,1 0 0,0 3,18H21A1,1 0 0,0 22,17V9A1,1 0 0,0 21,8H19V7L17,5H7M7,7H17V8H7V7M11,9H13A0.5,0.5 0 0,1 13.5,9.5A0.5,0.5 0 0,1 13,10H11A0.5,0.5 0 0,1 10.5,9.5A0.5,0.5 0 0,1 11,9M7.5,10.5A3,3 0 0,1 10.5,13.5A3,3 0 0,1 7.5,16.5A3,3 0 0,1 4.5,13.5A3,3 0 0,1 7.5,10.5M16.5,10.5A3,3 0 0,1 19.5,13.5A3,3 0 0,1 16.5,16.5A3,3 0 0,1 13.5,13.5A3,3 0 0,1 16.5,10.5M7.5,12A1.5,1.5 0 0,0 6,13.5A1.5,1.5 0 0,0 7.5,15A1.5,1.5 0 0,0 9,13.5A1.5,1.5 0 0,0 7.5,12M16.5,12A1.5,1.5 0 0,0 15,13.5A1.5,1.5 0 0,0 16.5,15A1.5,1.5 0 0,0 18,13.5A1.5,1.5 0 0,0 16.5,12Z" /></g><g id="border-all"><path d="M19,11H13V5H19M19,19H13V13H19M11,11H5V5H11M11,19H5V13H11M3,21H21V3H3V21Z" /></g><g id="border-bottom"><path d="M5,15H3V17H5M3,21H21V19H3M5,11H3V13H5M19,9H21V7H19M19,5H21V3H19M5,7H3V9H5M19,17H21V15H19M19,13H21V11H19M17,3H15V5H17M13,3H11V5H13M17,11H15V13H17M13,7H11V9H13M5,3H3V5H5M13,11H11V13H13M9,3H7V5H9M13,15H11V17H13M9,11H7V13H9V11Z" /></g><g id="border-color"><path d="M20.71,4.04C21.1,3.65 21.1,3 20.71,2.63L18.37,0.29C18,-0.1 17.35,-0.1 16.96,0.29L15,2.25L18.75,6M17.75,7L14,3.25L4,13.25V17H7.75L17.75,7Z" /></g><g id="border-horizontal"><path d="M19,21H21V19H19M15,21H17V19H15M11,17H13V15H11M19,9H21V7H19M19,5H21V3H19M3,13H21V11H3M11,21H13V19H11M19,17H21V15H19M13,3H11V5H13M13,7H11V9H13M17,3H15V5H17M9,3H7V5H9M5,3H3V5H5M7,21H9V19H7M3,17H5V15H3M5,7H3V9H5M3,21H5V19H3V21Z" /></g><g id="border-inside"><path d="M19,17H21V15H19M19,21H21V19H19M13,3H11V11H3V13H11V21H13V13H21V11H13M15,21H17V19H15M19,5H21V3H19M19,9H21V7H19M17,3H15V5H17M5,3H3V5H5M9,3H7V5H9M3,17H5V15H3M5,7H3V9H5M7,21H9V19H7M3,21H5V19H3V21Z" /></g><g id="border-left"><path d="M15,5H17V3H15M15,13H17V11H15M19,21H21V19H19M19,13H21V11H19M19,5H21V3H19M19,17H21V15H19M15,21H17V19H15M19,9H21V7H19M3,21H5V3H3M7,13H9V11H7M7,5H9V3H7M7,21H9V19H7M11,13H13V11H11M11,9H13V7H11M11,5H13V3H11M11,17H13V15H11M11,21H13V19H11V21Z" /></g><g id="border-none"><path d="M15,5H17V3H15M15,13H17V11H15M15,21H17V19H15M11,5H13V3H11M19,5H21V3H19M11,9H13V7H11M19,9H21V7H19M19,21H21V19H19M19,13H21V11H19M19,17H21V15H19M11,13H13V11H11M3,5H5V3H3M3,9H5V7H3M3,13H5V11H3M3,17H5V15H3M3,21H5V19H3M11,21H13V19H11M11,17H13V15H11M7,21H9V19H7M7,13H9V11H7M7,5H9V3H7V5Z" /></g><g id="border-outside"><path d="M9,11H7V13H9M13,15H11V17H13M19,19H5V5H19M3,21H21V3H3M17,11H15V13H17M13,11H11V13H13M13,7H11V9H13V7Z" /></g><g id="border-right"><path d="M11,9H13V7H11M11,5H13V3H11M11,13H13V11H11M15,5H17V3H15M15,21H17V19H15M19,21H21V3H19M15,13H17V11H15M11,17H13V15H11M3,9H5V7H3M3,17H5V15H3M3,13H5V11H3M11,21H13V19H11M3,21H5V19H3M7,13H9V11H7M7,5H9V3H7M3,5H5V3H3M7,21H9V19H7V21Z" /></g><g id="border-style"><path d="M15,21H17V19H15M19,21H21V19H19M7,21H9V19H7M11,21H13V19H11M19,17H21V15H19M19,13H21V11H19M3,3V21H5V5H21V3M19,9H21V7H19" /></g><g id="border-top"><path d="M15,13H17V11H15M19,21H21V19H19M11,9H13V7H11M15,21H17V19H15M19,17H21V15H19M3,5H21V3H3M19,13H21V11H19M19,9H21V7H19M11,17H13V15H11M3,9H5V7H3M3,13H5V11H3M3,21H5V19H3M3,17H5V15H3M11,21H13V19H11M11,13H13V11H11M7,13H9V11H7M7,21H9V19H7V21Z" /></g><g id="border-vertical"><path d="M15,13H17V11H15M15,21H17V19H15M15,5H17V3H15M19,9H21V7H19M19,5H21V3H19M19,13H21V11H19M19,21H21V19H19M11,21H13V3H11M19,17H21V15H19M7,5H9V3H7M3,17H5V15H3M3,21H5V19H3M3,13H5V11H3M7,13H9V11H7M7,21H9V19H7M3,5H5V3H3M3,9H5V7H3V9Z" /></g><g id="bow-tie"><path d="M15,14L21,17V7L15,10V14M9,14L3,17V7L9,10V14M10,10H14V14H10V10Z" /></g><g id="bowl"><path d="M22,15A7,7 0 0,1 15,22H9A7,7 0 0,1 2,15V12H15.58L20.3,4.44L22,5.5L17.94,12H22V15Z" /></g><g id="bowling"><path d="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12.5,11A1.5,1.5 0 0,0 11,12.5A1.5,1.5 0 0,0 12.5,14A1.5,1.5 0 0,0 14,12.5A1.5,1.5 0 0,0 12.5,11M12,5A2,2 0 0,0 10,7A2,2 0 0,0 12,9A2,2 0 0,0 14,7A2,2 0 0,0 12,5M5.93,8.5C5.38,9.45 5.71,10.67 6.66,11.22C7.62,11.78 8.84,11.45 9.4,10.5C9.95,9.53 9.62,8.31 8.66,7.76C7.71,7.21 6.5,7.53 5.93,8.5Z" /></g><g id="box"><path d="M15.39,14.04V14.04C15.39,12.62 14.24,11.47 12.82,11.47C11.41,11.47 10.26,12.62 10.26,14.04V14.04C10.26,15.45 11.41,16.6 12.82,16.6C14.24,16.6 15.39,15.45 15.39,14.04M17.1,14.04C17.1,16.4 15.18,18.31 12.82,18.31C11.19,18.31 9.77,17.39 9.05,16.04C8.33,17.39 6.91,18.31 5.28,18.31C2.94,18.31 1.04,16.43 1,14.11V14.11H1V7H1V7C1,6.56 1.39,6.18 1.86,6.18C2.33,6.18 2.7,6.56 2.71,7V7H2.71V10.62C3.43,10.08 4.32,9.76 5.28,9.76C6.91,9.76 8.33,10.68 9.05,12.03C9.77,10.68 11.19,9.76 12.82,9.76C15.18,9.76 17.1,11.68 17.1,14.04V14.04M7.84,14.04V14.04C7.84,12.62 6.69,11.47 5.28,11.47C3.86,11.47 2.71,12.62 2.71,14.04V14.04C2.71,15.45 3.86,16.6 5.28,16.6C6.69,16.6 7.84,15.45 7.84,14.04M22.84,16.96V16.96C22.95,17.12 23,17.3 23,17.47C23,17.73 22.88,18 22.66,18.15C22.5,18.26 22.33,18.32 22.15,18.32C21.9,18.32 21.65,18.21 21.5,18L19.59,15.47L17.7,18V18C17.53,18.21 17.28,18.32 17.03,18.32C16.85,18.32 16.67,18.26 16.5,18.15C16.29,18 16.17,17.72 16.17,17.46C16.17,17.29 16.23,17.11 16.33,16.96V16.96H16.33V16.96L18.5,14.04L16.33,11.11V11.11H16.33V11.11C16.22,10.96 16.17,10.79 16.17,10.61C16.17,10.35 16.29,10.1 16.5,9.93C16.89,9.65 17.41,9.72 17.7,10.09V10.09L19.59,12.61L21.5,10.09C21.76,9.72 22.29,9.65 22.66,9.93C22.89,10.1 23,10.36 23,10.63C23,10.8 22.95,10.97 22.84,11.11V11.11H22.84V11.11L20.66,14.04L22.84,16.96V16.96H22.84Z" /></g><g id="box-cutter"><path d="M7.22,11.91C6.89,12.24 6.71,12.65 6.66,13.08L12.17,15.44L20.66,6.96C21.44,6.17 21.44,4.91 20.66,4.13L19.24,2.71C18.46,1.93 17.2,1.93 16.41,2.71L7.22,11.91M5,16V21.75L10.81,16.53L5.81,14.53L5,16M17.12,4.83C17.5,4.44 18.15,4.44 18.54,4.83C18.93,5.23 18.93,5.86 18.54,6.25C18.15,6.64 17.5,6.64 17.12,6.25C16.73,5.86 16.73,5.23 17.12,4.83Z" /></g><g id="box-shadow"><path d="M3,3H18V18H3V3M19,19H21V21H19V19M19,16H21V18H19V16M19,13H21V15H19V13M19,10H21V12H19V10M19,7H21V9H19V7M16,19H18V21H16V19M13,19H15V21H13V19M10,19H12V21H10V19M7,19H9V21H7V19Z" /></g><g id="bridge"><path d="M7,14V10.91C6.28,10.58 5.61,10.18 5,9.71V14H7M5,18H3V16H1V14H3V7H5V8.43C6.8,10 9.27,11 12,11C14.73,11 17.2,10 19,8.43V7H21V14H23V16H21V18H19V16H5V18M17,10.91V14H19V9.71C18.39,10.18 17.72,10.58 17,10.91M16,14V11.32C15.36,11.55 14.69,11.72 14,11.84V14H16M13,14V11.96L12,12L11,11.96V14H13M10,14V11.84C9.31,11.72 8.64,11.55 8,11.32V14H10Z" /></g><g id="briefcase"><path d="M14,6H10V4H14M20,6H16V4L14,2H10L8,4V6H4C2.89,6 2,6.89 2,8V19A2,2 0 0,0 4,21H20A2,2 0 0,0 22,19V8C22,6.89 21.1,6 20,6Z" /></g><g id="briefcase-check"><path d="M10.5,17.5L7,14L8.41,12.59L10.5,14.67L15.68,9.5L17.09,10.91M10,4H14V6H10M20,6H16V4L14,2H10L8,4V6H4C2.89,6 2,6.89 2,8V19C2,20.11 2.89,21 4,21H20C21.11,21 22,20.11 22,19V8C22,6.89 21.11,6 20,6Z" /></g><g id="briefcase-download"><path d="M12,19L7,14H10V10H14V14H17M10,4H14V6H10M20,6H16V4L14,2H10L8,4V6H4C2.89,6 2,6.89 2,8V19A2,2 0 0,0 4,21H20A2,2 0 0,0 22,19V8C22,6.89 21.1,6 20,6Z" /></g><g id="briefcase-upload"><path d="M20,6A2,2 0 0,1 22,8V19A2,2 0 0,1 20,21H4C2.89,21 2,20.1 2,19V8C2,6.89 2.89,6 4,6H8V4L10,2H14L16,4V6H20M10,4V6H14V4H10M12,9L7,14H10V18H14V14H17L12,9Z" /></g><g id="brightness-1"><path d="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2Z" /></g><g id="brightness-2"><path d="M10,2C8.18,2 6.47,2.5 5,3.35C8,5.08 10,8.3 10,12C10,15.7 8,18.92 5,20.65C6.47,21.5 8.18,22 10,22A10,10 0 0,0 20,12A10,10 0 0,0 10,2Z" /></g><g id="brightness-3"><path d="M9,2C7.95,2 6.95,2.16 6,2.46C10.06,3.73 13,7.5 13,12C13,16.5 10.06,20.27 6,21.54C6.95,21.84 7.95,22 9,22A10,10 0 0,0 19,12A10,10 0 0,0 9,2Z" /></g><g id="brightness-4"><path d="M12,18C11.11,18 10.26,17.8 9.5,17.45C11.56,16.5 13,14.42 13,12C13,9.58 11.56,7.5 9.5,6.55C10.26,6.2 11.11,6 12,6A6,6 0 0,1 18,12A6,6 0 0,1 12,18M20,8.69V4H15.31L12,0.69L8.69,4H4V8.69L0.69,12L4,15.31V20H8.69L12,23.31L15.31,20H20V15.31L23.31,12L20,8.69Z" /></g><g id="brightness-5"><path d="M12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6A6,6 0 0,1 18,12A6,6 0 0,1 12,18M20,15.31L23.31,12L20,8.69V4H15.31L12,0.69L8.69,4H4V8.69L0.69,12L4,15.31V20H8.69L12,23.31L15.31,20H20V15.31Z" /></g><g id="brightness-6"><path d="M12,18V6A6,6 0 0,1 18,12A6,6 0 0,1 12,18M20,15.31L23.31,12L20,8.69V4H15.31L12,0.69L8.69,4H4V8.69L0.69,12L4,15.31V20H8.69L12,23.31L15.31,20H20V15.31Z" /></g><g id="brightness-7"><path d="M12,8A4,4 0 0,0 8,12A4,4 0 0,0 12,16A4,4 0 0,0 16,12A4,4 0 0,0 12,8M12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6A6,6 0 0,1 18,12A6,6 0 0,1 12,18M20,8.69V4H15.31L12,0.69L8.69,4H4V8.69L0.69,12L4,15.31V20H8.69L12,23.31L15.31,20H20V15.31L23.31,12L20,8.69Z" /></g><g id="brightness-auto"><path d="M14.3,16L13.6,14H10.4L9.7,16H7.8L11,7H13L16.2,16H14.3M20,8.69V4H15.31L12,0.69L8.69,4H4V8.69L0.69,12L4,15.31V20H8.69L12,23.31L15.31,20H20V15.31L23.31,12L20,8.69M10.85,12.65H13.15L12,9L10.85,12.65Z" /></g><g id="broom"><path d="M19.36,2.72L20.78,4.14L15.06,9.85C16.13,11.39 16.28,13.24 15.38,14.44L9.06,8.12C10.26,7.22 12.11,7.37 13.65,8.44L19.36,2.72M5.93,17.57C3.92,15.56 2.69,13.16 2.35,10.92L7.23,8.83L14.67,16.27L12.58,21.15C10.34,20.81 7.94,19.58 5.93,17.57Z" /></g><g id="brush"><path d="M20.71,4.63L19.37,3.29C19,2.9 18.35,2.9 17.96,3.29L9,12.25L11.75,15L20.71,6.04C21.1,5.65 21.1,5 20.71,4.63M7,14A3,3 0 0,0 4,17C4,18.31 2.84,19 2,19C2.92,20.22 4.5,21 6,21A4,4 0 0,0 10,17A3,3 0 0,0 7,14Z" /></g><g id="buffer"><path d="M12.6,2.86C15.27,4.1 18,5.39 20.66,6.63C20.81,6.7 21,6.75 21,6.95C21,7.15 20.81,7.19 20.66,7.26C18,8.5 15.3,9.77 12.62,11C12.21,11.21 11.79,11.21 11.38,11C8.69,9.76 6,8.5 3.32,7.25C3.18,7.19 3,7.14 3,6.94C3,6.76 3.18,6.71 3.31,6.65C6,5.39 8.74,4.1 11.44,2.85C11.73,2.72 12.3,2.73 12.6,2.86M12,21.15C11.8,21.15 11.66,21.07 11.38,20.97C8.69,19.73 6,18.47 3.33,17.22C3.19,17.15 3,17.11 3,16.9C3,16.7 3.19,16.66 3.34,16.59C3.78,16.38 4.23,16.17 4.67,15.96C5.12,15.76 5.56,15.76 6,15.97C7.79,16.8 9.57,17.63 11.35,18.46C11.79,18.67 12.23,18.66 12.67,18.46C14.45,17.62 16.23,16.79 18,15.96C18.44,15.76 18.87,15.75 19.29,15.95C19.77,16.16 20.24,16.39 20.71,16.61C20.78,16.64 20.85,16.68 20.91,16.73C21.04,16.83 21.04,17 20.91,17.08C20.83,17.14 20.74,17.19 20.65,17.23C18,18.5 15.33,19.72 12.66,20.95C12.46,21.05 12.19,21.15 12,21.15M12,16.17C11.9,16.17 11.55,16.07 11.36,16C8.68,14.74 6,13.5 3.34,12.24C3.2,12.18 3,12.13 3,11.93C3,11.72 3.2,11.68 3.35,11.61C3.8,11.39 4.25,11.18 4.7,10.97C5.13,10.78 5.56,10.78 6,11C7.78,11.82 9.58,12.66 11.38,13.5C11.79,13.69 12.21,13.69 12.63,13.5C14.43,12.65 16.23,11.81 18.04,10.97C18.45,10.78 18.87,10.78 19.29,10.97C19.76,11.19 20.24,11.41 20.71,11.63C20.77,11.66 20.84,11.69 20.9,11.74C21.04,11.85 21.04,12 20.89,12.12C20.84,12.16 20.77,12.19 20.71,12.22C18,13.5 15.31,14.75 12.61,16C12.42,16.09 12.08,16.17 12,16.17Z" /></g><g id="bug"><path d="M14,12H10V10H14M14,16H10V14H14M20,8H17.19C16.74,7.22 16.12,6.55 15.37,6.04L17,4.41L15.59,3L13.42,5.17C12.96,5.06 12.5,5 12,5C11.5,5 11.04,5.06 10.59,5.17L8.41,3L7,4.41L8.62,6.04C7.88,6.55 7.26,7.22 6.81,8H4V10H6.09C6.04,10.33 6,10.66 6,11V12H4V14H6V15C6,15.34 6.04,15.67 6.09,16H4V18H6.81C7.85,19.79 9.78,21 12,21C14.22,21 16.15,19.79 17.19,18H20V16H17.91C17.96,15.67 18,15.34 18,15V14H20V12H18V11C18,10.66 17.96,10.33 17.91,10H20V8Z" /></g><g id="bulletin-board"><path d="M12.04,2.5L9.53,5H14.53L12.04,2.5M4,7V20H20V7H4M12,0L17,5V5H20A2,2 0 0,1 22,7V20A2,2 0 0,1 20,22H4A2,2 0 0,1 2,20V7A2,2 0 0,1 4,5H7V5L12,0M7,18V14H12V18H7M14,17V10H18V17H14M6,12V9H11V12H6Z" /></g><g id="bullhorn"><path d="M16,12V16A1,1 0 0,1 15,17C14.83,17 14.67,17 14.06,16.5C13.44,16 12.39,15 11.31,14.5C10.31,14.04 9.28,14 8.26,14L9.47,17.32L9.5,17.5A0.5,0.5 0 0,1 9,18H7C6.78,18 6.59,17.86 6.53,17.66L5.19,14H5A1,1 0 0,1 4,13A2,2 0 0,1 2,11A2,2 0 0,1 4,9A1,1 0 0,1 5,8H8C9.11,8 10.22,8 11.31,7.5C12.39,7 13.44,6 14.06,5.5C14.67,5 14.83,5 15,5A1,1 0 0,1 16,6V10A1,1 0 0,1 17,11A1,1 0 0,1 16,12M21,11C21,12.38 20.44,13.63 19.54,14.54L18.12,13.12C18.66,12.58 19,11.83 19,11C19,10.17 18.66,9.42 18.12,8.88L19.54,7.46C20.44,8.37 21,9.62 21,11Z" /></g><g id="bullseye"><path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4M12,6A6,6 0 0,0 6,12A6,6 0 0,0 12,18A6,6 0 0,0 18,12A6,6 0 0,0 12,6M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10Z" /></g><g id="burst-mode"><path d="M1,5H3V19H1V5M5,5H7V19H5V5M22,5H10A1,1 0 0,0 9,6V18A1,1 0 0,0 10,19H22A1,1 0 0,0 23,18V6A1,1 0 0,0 22,5M11,17L13.5,13.85L15.29,16L17.79,12.78L21,17H11Z" /></g><g id="bus"><path d="M18,11H6V6H18M16.5,17A1.5,1.5 0 0,1 15,15.5A1.5,1.5 0 0,1 16.5,14A1.5,1.5 0 0,1 18,15.5A1.5,1.5 0 0,1 16.5,17M7.5,17A1.5,1.5 0 0,1 6,15.5A1.5,1.5 0 0,1 7.5,14A1.5,1.5 0 0,1 9,15.5A1.5,1.5 0 0,1 7.5,17M4,16C4,16.88 4.39,17.67 5,18.22V20A1,1 0 0,0 6,21H7A1,1 0 0,0 8,20V19H16V20A1,1 0 0,0 17,21H18A1,1 0 0,0 19,20V18.22C19.61,17.67 20,16.88 20,16V6C20,2.5 16.42,2 12,2C7.58,2 4,2.5 4,6V16Z" /></g><g id="cached"><path d="M19,8L15,12H18A6,6 0 0,1 12,18C11,18 10.03,17.75 9.2,17.3L7.74,18.76C8.97,19.54 10.43,20 12,20A8,8 0 0,0 20,12H23M6,12A6,6 0 0,1 12,6C13,6 13.97,6.25 14.8,6.7L16.26,5.24C15.03,4.46 13.57,4 12,4A8,8 0 0,0 4,12H1L5,16L9,12" /></g><g id="cake"><path d="M11.5,0.5C12,0.75 13,2.4 13,3.5C13,4.6 12.33,5 11.5,5C10.67,5 10,4.85 10,3.75C10,2.65 11,2 11.5,0.5M18.5,9C21,9 23,11 23,13.5C23,15.06 22.21,16.43 21,17.24V23H12L3,23V17.24C1.79,16.43 1,15.06 1,13.5C1,11 3,9 5.5,9H10V6H13V9H18.5M12,16A2.5,2.5 0 0,0 14.5,13.5H16A2.5,2.5 0 0,0 18.5,16A2.5,2.5 0 0,0 21,13.5A2.5,2.5 0 0,0 18.5,11H5.5A2.5,2.5 0 0,0 3,13.5A2.5,2.5 0 0,0 5.5,16A2.5,2.5 0 0,0 8,13.5H9.5A2.5,2.5 0 0,0 12,16Z" /></g><g id="cake-layered"><path d="M21,21V17C21,15.89 20.1,15 19,15H18V12C18,10.89 17.1,10 16,10H13V8H11V10H8C6.89,10 6,10.89 6,12V15H5C3.89,15 3,15.89 3,17V21H1V23H23V21M12,7A2,2 0 0,0 14,5C14,4.62 13.9,4.27 13.71,3.97L12,1L10.28,3.97C10.1,4.27 10,4.62 10,5A2,2 0 0,0 12,7Z" /></g><g id="cake-variant"><path d="M12,6C13.11,6 14,5.1 14,4C14,3.62 13.9,3.27 13.71,2.97L12,0L10.29,2.97C10.1,3.27 10,3.62 10,4A2,2 0 0,0 12,6M16.6,16L15.53,14.92L14.45,16C13.15,17.29 10.87,17.3 9.56,16L8.5,14.92L7.4,16C6.75,16.64 5.88,17 4.96,17C4.23,17 3.56,16.77 3,16.39V21A1,1 0 0,0 4,22H20A1,1 0 0,0 21,21V16.39C20.44,16.77 19.77,17 19.04,17C18.12,17 17.25,16.64 16.6,16M18,9H13V7H11V9H6A3,3 0 0,0 3,12V13.54C3,14.62 3.88,15.5 4.96,15.5C5.5,15.5 6,15.3 6.34,14.93L8.5,12.8L10.61,14.93C11.35,15.67 12.64,15.67 13.38,14.93L15.5,12.8L17.65,14.93C18,15.3 18.5,15.5 19.03,15.5C20.11,15.5 21,14.62 21,13.54V12A3,3 0 0,0 18,9Z" /></g><g id="calculator"><path d="M7,2H17A2,2 0 0,1 19,4V20A2,2 0 0,1 17,22H7A2,2 0 0,1 5,20V4A2,2 0 0,1 7,2M7,4V8H17V4H7M7,10V12H9V10H7M11,10V12H13V10H11M15,10V12H17V10H15M7,14V16H9V14H7M11,14V16H13V14H11M15,14V16H17V14H15M7,18V20H9V18H7M11,18V20H13V18H11M15,18V20H17V18H15Z" /></g><g id="calendar"><path d="M19,19H5V8H19M16,1V3H8V1H6V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3H18V1M17,12H12V17H17V12Z" /></g><g id="calendar-blank"><path d="M19,19H5V8H19M16,1V3H8V1H6V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3H18V1" /></g><g id="calendar-check"><path d="M19,19H5V8H19M19,3H18V1H16V3H8V1H6V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M16.53,11.06L15.47,10L10.59,14.88L8.47,12.76L7.41,13.82L10.59,17L16.53,11.06Z" /></g><g id="calendar-clock"><path d="M15,13H16.5V15.82L18.94,17.23L18.19,18.53L15,16.69V13M19,8H5V19H9.67C9.24,18.09 9,17.07 9,16A7,7 0 0,1 16,9C17.07,9 18.09,9.24 19,9.67V8M5,21C3.89,21 3,20.1 3,19V5C3,3.89 3.89,3 5,3H6V1H8V3H16V1H18V3H19A2,2 0 0,1 21,5V11.1C22.24,12.36 23,14.09 23,16A7,7 0 0,1 16,23C14.09,23 12.36,22.24 11.1,21H5M16,11.15A4.85,4.85 0 0,0 11.15,16C11.15,18.68 13.32,20.85 16,20.85A4.85,4.85 0 0,0 20.85,16C20.85,13.32 18.68,11.15 16,11.15Z" /></g><g id="calendar-multiple"><path d="M21,17V8H7V17H21M21,3A2,2 0 0,1 23,5V17A2,2 0 0,1 21,19H7C5.89,19 5,18.1 5,17V5A2,2 0 0,1 7,3H8V1H10V3H18V1H20V3H21M3,21H17V23H3C1.89,23 1,22.1 1,21V9H3V21M19,15H15V11H19V15Z" /></g><g id="calendar-multiple-check"><path d="M21,17V8H7V17H21M21,3A2,2 0 0,1 23,5V17A2,2 0 0,1 21,19H7C5.89,19 5,18.1 5,17V5A2,2 0 0,1 7,3H8V1H10V3H18V1H20V3H21M17.53,11.06L13.09,15.5L10.41,12.82L11.47,11.76L13.09,13.38L16.47,10L17.53,11.06M3,21H17V23H3C1.89,23 1,22.1 1,21V9H3V21Z" /></g><g id="calendar-plus"><path d="M19,19V7H5V19H19M16,1H18V3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5C3.89,21 3,20.1 3,19V5C3,3.89 3.89,3 5,3H6V1H8V3H16V1M11,9H13V12H16V14H13V17H11V14H8V12H11V9Z" /></g><g id="calendar-question"><path d="M6,1V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3H18V1H16V3H8V1H6M5,8H19V19H5V8M12.19,9C11.32,9 10.62,9.2 10.08,9.59C9.56,10 9.3,10.57 9.31,11.36L9.32,11.39H11.25C11.26,11.09 11.35,10.86 11.53,10.7C11.71,10.55 11.93,10.47 12.19,10.47C12.5,10.47 12.76,10.57 12.94,10.75C13.12,10.94 13.2,11.2 13.2,11.5C13.2,11.82 13.13,12.09 12.97,12.32C12.83,12.55 12.62,12.75 12.36,12.91C11.85,13.25 11.5,13.55 11.31,13.82C11.11,14.08 11,14.5 11,15H13C13,14.69 13.04,14.44 13.13,14.26C13.22,14.08 13.39,13.9 13.64,13.74C14.09,13.5 14.46,13.21 14.75,12.81C15.04,12.41 15.19,12 15.19,11.5C15.19,10.74 14.92,10.13 14.38,9.68C13.85,9.23 13.12,9 12.19,9M11,16V18H13V16H11Z" /></g><g id="calendar-range"><path d="M9,11H7V13H9V11M13,11H11V13H13V11M17,11H15V13H17V11M19,4H18V2H16V4H8V2H6V4H5C3.89,4 3,4.9 3,6V20A2,2 0 0,0 5,22H19A2,2 0 0,0 21,20V6A2,2 0 0,0 19,4M19,20H5V9H19V20Z" /></g><g id="calendar-remove"><path d="M19,19H5V8H19M19,3H18V1H16V3H8V1H6V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M9.31,17L11.75,14.56L14.19,17L15.25,15.94L12.81,13.5L15.25,11.06L14.19,10L11.75,12.44L9.31,10L8.25,11.06L10.69,13.5L8.25,15.94L9.31,17Z" /></g><g id="calendar-text"><path d="M14,14H7V16H14M19,19H5V8H19M19,3H18V1H16V3H8V1H6V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M17,10H7V12H17V10Z" /></g><g id="calendar-today"><path d="M7,10H12V15H7M19,19H5V8H19M19,3H18V1H16V3H8V1H6V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" /></g><g id="call-made"><path d="M9,5V7H15.59L4,18.59L5.41,20L17,8.41V15H19V5" /></g><g id="call-merge"><path d="M17,20.41L18.41,19L15,15.59L13.59,17M7.5,8H11V13.59L5.59,19L7,20.41L13,14.41V8H16.5L12,3.5" /></g><g id="call-missed"><path d="M19.59,7L12,14.59L6.41,9H11V7H3V15H5V10.41L12,17.41L21,8.41" /></g><g id="call-received"><path d="M20,5.41L18.59,4L7,15.59V9H5V19H15V17H8.41" /></g><g id="call-split"><path d="M14,4L16.29,6.29L13.41,9.17L14.83,10.59L17.71,7.71L20,10V4M10,4H4V10L6.29,7.71L11,12.41V20H13V11.59L7.71,6.29" /></g><g id="camcorder"><path d="M17,10.5V7A1,1 0 0,0 16,6H4A1,1 0 0,0 3,7V17A1,1 0 0,0 4,18H16A1,1 0 0,0 17,17V13.5L21,17.5V6.5L17,10.5Z" /></g><g id="camcorder-box"><path d="M18,16L14,12.8V16H6V8H14V11.2L18,8M20,4H4A2,2 0 0,0 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V6C22,4.89 21.1,4 20,4Z" /></g><g id="camcorder-box-off"><path d="M6,8H6.73L14,15.27V16H6M2.27,1L1,2.27L3,4.28C2.41,4.62 2,5.26 2,6V18A2,2 0 0,0 4,20H18.73L20.73,22L22,20.73M20,4H7.82L11.82,8H14V10.18L14.57,10.75L18,8V14.18L22,18.17C22,18.11 22,18.06 22,18V6A2,2 0 0,0 20,4Z" /></g><g id="camcorder-off"><path d="M3.27,2L2,3.27L4.73,6H4A1,1 0 0,0 3,7V17A1,1 0 0,0 4,18H16C16.2,18 16.39,17.92 16.54,17.82L19.73,21L21,19.73M21,6.5L17,10.5V7A1,1 0 0,0 16,6H9.82L21,17.18V6.5Z" /></g><g id="camera"><path d="M4,4H7L9,2H15L17,4H20A2,2 0 0,1 22,6V18A2,2 0 0,1 20,20H4A2,2 0 0,1 2,18V6A2,2 0 0,1 4,4M12,7A5,5 0 0,0 7,12A5,5 0 0,0 12,17A5,5 0 0,0 17,12A5,5 0 0,0 12,7M12,9A3,3 0 0,1 15,12A3,3 0 0,1 12,15A3,3 0 0,1 9,12A3,3 0 0,1 12,9Z" /></g><g id="camera-burst"><path d="M1,5H3V19H1V5M5,5H7V19H5V5M22,5H10A1,1 0 0,0 9,6V18A1,1 0 0,0 10,19H22A1,1 0 0,0 23,18V6A1,1 0 0,0 22,5M11,17L13.5,13.85L15.29,16L17.79,12.78L21,17H11Z" /></g><g id="camera-enhance"><path d="M9,3L7.17,5H4A2,2 0 0,0 2,7V19A2,2 0 0,0 4,21H20A2,2 0 0,0 22,19V7A2,2 0 0,0 20,5H16.83L15,3M12,18A5,5 0 0,1 7,13A5,5 0 0,1 12,8A5,5 0 0,1 17,13A5,5 0 0,1 12,18M12,17L13.25,14.25L16,13L13.25,11.75L12,9L10.75,11.75L8,13L10.75,14.25" /></g><g id="camera-front"><path d="M7,2H17V12.5C17,10.83 13.67,10 12,10C10.33,10 7,10.83 7,12.5M17,0H7A2,2 0 0,0 5,2V16A2,2 0 0,0 7,18H17A2,2 0 0,0 19,16V2A2,2 0 0,0 17,0M12,8A2,2 0 0,0 14,6A2,2 0 0,0 12,4A2,2 0 0,0 10,6A2,2 0 0,0 12,8M14,20V22H19V20M10,20H5V22H10V24L13,21L10,18V20Z" /></g><g id="camera-front-variant"><path d="M6,0H18A2,2 0 0,1 20,2V22A2,2 0 0,1 18,24H6A2,2 0 0,1 4,22V2A2,2 0 0,1 6,0M12,6A3,3 0 0,1 15,9A3,3 0 0,1 12,12A3,3 0 0,1 9,9A3,3 0 0,1 12,6M11,1V3H13V1H11M6,4V16.5C6,15.12 8.69,14 12,14C15.31,14 18,15.12 18,16.5V4H6M13,18H9V20H13V22L16,19L13,16V18Z" /></g><g id="camera-iris"><path d="M13.73,15L9.83,21.76C10.53,21.91 11.25,22 12,22C14.4,22 16.6,21.15 18.32,19.75L14.66,13.4M2.46,15C3.38,17.92 5.61,20.26 8.45,21.34L12.12,15M8.54,12L4.64,5.25C3,7 2,9.39 2,12C2,12.68 2.07,13.35 2.2,14H9.69M21.8,10H14.31L14.6,10.5L19.36,18.75C21,16.97 22,14.6 22,12C22,11.31 21.93,10.64 21.8,10M21.54,9C20.62,6.07 18.39,3.74 15.55,2.66L11.88,9M9.4,10.5L14.17,2.24C13.47,2.09 12.75,2 12,2C9.6,2 7.4,2.84 5.68,4.25L9.34,10.6L9.4,10.5Z" /></g><g id="camera-off"><path d="M1.2,4.47L2.5,3.2L20,20.72L18.73,22L16.73,20H4A2,2 0 0,1 2,18V6C2,5.78 2.04,5.57 2.1,5.37L1.2,4.47M7,4L9,2H15L17,4H20A2,2 0 0,1 22,6V18C22,18.6 21.74,19.13 21.32,19.5L16.33,14.5C16.76,13.77 17,12.91 17,12A5,5 0 0,0 12,7C11.09,7 10.23,7.24 9.5,7.67L5.82,4H7M7,12A5,5 0 0,0 12,17C12.5,17 13.03,16.92 13.5,16.77L11.72,15C10.29,14.85 9.15,13.71 9,12.28L7.23,10.5C7.08,10.97 7,11.5 7,12M12,9A3,3 0 0,1 15,12C15,12.35 14.94,12.69 14.83,13L11,9.17C11.31,9.06 11.65,9 12,9Z" /></g><g id="camera-party-mode"><path d="M12,17C10.37,17 8.94,16.21 8,15H12A3,3 0 0,0 15,12C15,11.65 14.93,11.31 14.82,11H16.9C16.96,11.32 17,11.66 17,12A5,5 0 0,1 12,17M12,7C13.63,7 15.06,7.79 16,9H12A3,3 0 0,0 9,12C9,12.35 9.07,12.68 9.18,13H7.1C7.03,12.68 7,12.34 7,12A5,5 0 0,1 12,7M20,4H16.83L15,2H9L7.17,4H4A2,2 0 0,0 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V6C22,4.89 21.1,4 20,4Z" /></g><g id="camera-rear"><path d="M12,6C10.89,6 10,5.1 10,4A2,2 0 0,1 12,2C13.09,2 14,2.9 14,4A2,2 0 0,1 12,6M17,0H7A2,2 0 0,0 5,2V16A2,2 0 0,0 7,18H17A2,2 0 0,0 19,16V2A2,2 0 0,0 17,0M14,20V22H19V20M10,20H5V22H10V24L13,21L10,18V20Z" /></g><g id="camera-rear-variant"><path d="M6,0H18A2,2 0 0,1 20,2V22A2,2 0 0,1 18,24H6A2,2 0 0,1 4,22V2A2,2 0 0,1 6,0M12,2A2,2 0 0,0 10,4A2,2 0 0,0 12,6A2,2 0 0,0 14,4A2,2 0 0,0 12,2M13,18H9V20H13V22L16,19L13,16V18Z" /></g><g id="camera-switch"><path d="M15,15.5V13H9V15.5L5.5,12L9,8.5V11H15V8.5L18.5,12M20,4H16.83L15,2H9L7.17,4H4A2,2 0 0,0 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V6C22,4.89 21.1,4 20,4Z" /></g><g id="camera-timer"><path d="M4.94,6.35C4.55,5.96 4.55,5.32 4.94,4.93C5.33,4.54 5.96,4.54 6.35,4.93L13.07,10.31L13.42,10.59C14.2,11.37 14.2,12.64 13.42,13.42C12.64,14.2 11.37,14.2 10.59,13.42L10.31,13.07L4.94,6.35M12,20A8,8 0 0,0 20,12C20,9.79 19.1,7.79 17.66,6.34L19.07,4.93C20.88,6.74 22,9.24 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12H4A8,8 0 0,0 12,20M12,1A2,2 0 0,1 14,3A2,2 0 0,1 12,5A2,2 0 0,1 10,3A2,2 0 0,1 12,1Z" /></g><g id="candle"><path d="M12.5,2C10.84,2 9.5,5.34 9.5,7A3,3 0 0,0 12.5,10A3,3 0 0,0 15.5,7C15.5,5.34 14.16,2 12.5,2M12.5,6.5A1,1 0 0,1 13.5,7.5A1,1 0 0,1 12.5,8.5A1,1 0 0,1 11.5,7.5A1,1 0 0,1 12.5,6.5M10,11A1,1 0 0,0 9,12V20H7A1,1 0 0,1 6,19V18A1,1 0 0,0 5,17A1,1 0 0,0 4,18V19A3,3 0 0,0 7,22H19A1,1 0 0,0 20,21A1,1 0 0,0 19,20H16V12A1,1 0 0,0 15,11H10Z" /></g><g id="candycane"><path d="M10,10A2,2 0 0,1 8,12A2,2 0 0,1 6,10V8C6,7.37 6.1,6.77 6.27,6.2L10,9.93V10M12,2C12.74,2 13.44,2.13 14.09,2.38L11.97,6C11.14,6 10.44,6.5 10.15,7.25L7.24,4.34C8.34,2.92 10.06,2 12,2M17.76,6.31L14,10.07V8C14,7.62 13.9,7.27 13.72,6.97L15.83,3.38C16.74,4.13 17.42,5.15 17.76,6.31M18,13.09L14,17.09V12.9L18,8.9V13.09M18,20A2,2 0 0,1 16,22A2,2 0 0,1 14,20V19.91L18,15.91V20Z" /></g><g id="car"><path d="M5,11L6.5,6.5H17.5L19,11M17.5,16A1.5,1.5 0 0,1 16,14.5A1.5,1.5 0 0,1 17.5,13A1.5,1.5 0 0,1 19,14.5A1.5,1.5 0 0,1 17.5,16M6.5,16A1.5,1.5 0 0,1 5,14.5A1.5,1.5 0 0,1 6.5,13A1.5,1.5 0 0,1 8,14.5A1.5,1.5 0 0,1 6.5,16M18.92,6C18.72,5.42 18.16,5 17.5,5H6.5C5.84,5 5.28,5.42 5.08,6L3,12V20A1,1 0 0,0 4,21H5A1,1 0 0,0 6,20V19H18V20A1,1 0 0,0 19,21H20A1,1 0 0,0 21,20V12L18.92,6Z" /></g><g id="car-battery"><path d="M4,3V6H1V20H23V6H20V3H14V6H10V3H4M3,8H21V18H3V8M15,10V12H13V14H15V16H17V14H19V12H17V10H15M5,12V14H11V12H5Z" /></g><g id="car-connected"><path d="M5,14H19L17.5,9.5H6.5L5,14M17.5,19A1.5,1.5 0 0,0 19,17.5A1.5,1.5 0 0,0 17.5,16A1.5,1.5 0 0,0 16,17.5A1.5,1.5 0 0,0 17.5,19M6.5,19A1.5,1.5 0 0,0 8,17.5A1.5,1.5 0 0,0 6.5,16A1.5,1.5 0 0,0 5,17.5A1.5,1.5 0 0,0 6.5,19M18.92,9L21,15V23A1,1 0 0,1 20,24H19A1,1 0 0,1 18,23V22H6V23A1,1 0 0,1 5,24H4A1,1 0 0,1 3,23V15L5.08,9C5.28,8.42 5.85,8 6.5,8H17.5C18.15,8 18.72,8.42 18.92,9M12,0C14.12,0 16.15,0.86 17.65,2.35L16.23,3.77C15.11,2.65 13.58,2 12,2C10.42,2 8.89,2.65 7.77,3.77L6.36,2.35C7.85,0.86 9.88,0 12,0M12,4C13.06,4 14.07,4.44 14.82,5.18L13.4,6.6C13.03,6.23 12.53,6 12,6C11.5,6 10.97,6.23 10.6,6.6L9.18,5.18C9.93,4.44 10.94,4 12,4Z" /></g><g id="car-wash"><path d="M5,13L6.5,8.5H17.5L19,13M17.5,18A1.5,1.5 0 0,1 16,16.5A1.5,1.5 0 0,1 17.5,15A1.5,1.5 0 0,1 19,16.5A1.5,1.5 0 0,1 17.5,18M6.5,18A1.5,1.5 0 0,1 5,16.5A1.5,1.5 0 0,1 6.5,15A1.5,1.5 0 0,1 8,16.5A1.5,1.5 0 0,1 6.5,18M18.92,8C18.72,7.42 18.16,7 17.5,7H6.5C5.84,7 5.28,7.42 5.08,8L3,14V22A1,1 0 0,0 4,23H5A1,1 0 0,0 6,22V21H18V22A1,1 0 0,0 19,23H20A1,1 0 0,0 21,22V14M7,5A1.5,1.5 0 0,0 8.5,3.5C8.5,2.5 7,0.8 7,0.8C7,0.8 5.5,2.5 5.5,3.5A1.5,1.5 0 0,0 7,5M12,5A1.5,1.5 0 0,0 13.5,3.5C13.5,2.5 12,0.8 12,0.8C12,0.8 10.5,2.5 10.5,3.5A1.5,1.5 0 0,0 12,5M17,5A1.5,1.5 0 0,0 18.5,3.5C18.5,2.5 17,0.8 17,0.8C17,0.8 15.5,2.5 15.5,3.5A1.5,1.5 0 0,0 17,5Z" /></g><g id="cards"><path d="M21.47,4.35L20.13,3.79V12.82L22.56,6.96C22.97,5.94 22.5,4.77 21.47,4.35M1.97,8.05L6.93,20C7.24,20.77 7.97,21.24 8.74,21.26C9,21.26 9.27,21.21 9.53,21.1L16.9,18.05C17.65,17.74 18.11,17 18.13,16.26C18.14,16 18.09,15.71 18,15.45L13,3.5C12.71,2.73 11.97,2.26 11.19,2.25C10.93,2.25 10.67,2.31 10.42,2.4L3.06,5.45C2.04,5.87 1.55,7.04 1.97,8.05M18.12,4.25A2,2 0 0,0 16.12,2.25H14.67L18.12,10.59" /></g><g id="cards-outline"><path d="M11.19,2.25C10.93,2.25 10.67,2.31 10.42,2.4L3.06,5.45C2.04,5.87 1.55,7.04 1.97,8.05L6.93,20C7.24,20.77 7.97,21.23 8.74,21.25C9,21.25 9.27,21.22 9.53,21.1L16.9,18.05C17.65,17.74 18.11,17 18.13,16.25C18.14,16 18.09,15.71 18,15.45L13,3.5C12.71,2.73 11.97,2.26 11.19,2.25M14.67,2.25L18.12,10.6V4.25A2,2 0 0,0 16.12,2.25M20.13,3.79V12.82L22.56,6.96C22.97,5.94 22.5,4.78 21.47,4.36M11.19,4.22L16.17,16.24L8.78,19.3L3.8,7.29" /></g><g id="cards-playing-outline"><path d="M11.19,2.25C11.97,2.26 12.71,2.73 13,3.5L18,15.45C18.09,15.71 18.14,16 18.13,16.25C18.11,17 17.65,17.74 16.9,18.05L9.53,21.1C9.27,21.22 9,21.25 8.74,21.25C7.97,21.23 7.24,20.77 6.93,20L1.97,8.05C1.55,7.04 2.04,5.87 3.06,5.45L10.42,2.4C10.67,2.31 10.93,2.25 11.19,2.25M14.67,2.25H16.12A2,2 0 0,1 18.12,4.25V10.6L14.67,2.25M20.13,3.79L21.47,4.36C22.5,4.78 22.97,5.94 22.56,6.96L20.13,12.82V3.79M11.19,4.22L3.8,7.29L8.77,19.3L16.17,16.24L11.19,4.22M8.65,8.54L11.88,10.95L11.44,14.96L8.21,12.54L8.65,8.54Z" /></g><g id="carrot"><path d="M16,10L15.8,11H13.5A0.5,0.5 0 0,0 13,11.5A0.5,0.5 0 0,0 13.5,12H15.6L14.6,17H12.5A0.5,0.5 0 0,0 12,17.5A0.5,0.5 0 0,0 12.5,18H14.4L14,20A2,2 0 0,1 12,22A2,2 0 0,1 10,20L9,15H10.5A0.5,0.5 0 0,0 11,14.5A0.5,0.5 0 0,0 10.5,14H8.8L8,10C8,8.8 8.93,7.77 10.29,7.29L8.9,5.28C8.59,4.82 8.7,4.2 9.16,3.89C9.61,3.57 10.23,3.69 10.55,4.14L11,4.8V3A1,1 0 0,1 12,2A1,1 0 0,1 13,3V5.28L14.5,3.54C14.83,3.12 15.47,3.07 15.89,3.43C16.31,3.78 16.36,4.41 16,4.84L13.87,7.35C15.14,7.85 16,8.85 16,10Z" /></g><g id="cart"><path d="M17,18C15.89,18 15,18.89 15,20A2,2 0 0,0 17,22A2,2 0 0,0 19,20C19,18.89 18.1,18 17,18M1,2V4H3L6.6,11.59L5.24,14.04C5.09,14.32 5,14.65 5,15A2,2 0 0,0 7,17H19V15H7.42A0.25,0.25 0 0,1 7.17,14.75C7.17,14.7 7.18,14.66 7.2,14.63L8.1,13H15.55C16.3,13 16.96,12.58 17.3,11.97L20.88,5.5C20.95,5.34 21,5.17 21,5A1,1 0 0,0 20,4H5.21L4.27,2M7,18C5.89,18 5,18.89 5,20A2,2 0 0,0 7,22A2,2 0 0,0 9,20C9,18.89 8.1,18 7,18Z" /></g><g id="cart-off"><path d="M22.73,22.73L1.27,1.27L0,2.54L4.39,6.93L6.6,11.59L5.25,14.04C5.09,14.32 5,14.65 5,15A2,2 0 0,0 7,17H14.46L15.84,18.38C15.34,18.74 15,19.33 15,20A2,2 0 0,0 17,22C17.67,22 18.26,21.67 18.62,21.16L21.46,24L22.73,22.73M7.42,15A0.25,0.25 0 0,1 7.17,14.75L7.2,14.63L8.1,13H10.46L12.46,15H7.42M15.55,13C16.3,13 16.96,12.59 17.3,11.97L20.88,5.5C20.96,5.34 21,5.17 21,5A1,1 0 0,0 20,4H6.54L15.55,13M7,18A2,2 0 0,0 5,20A2,2 0 0,0 7,22A2,2 0 0,0 9,20A2,2 0 0,0 7,18Z" /></g><g id="cart-outline"><path d="M17,18A2,2 0 0,1 19,20A2,2 0 0,1 17,22C15.89,22 15,21.1 15,20C15,18.89 15.89,18 17,18M1,2H4.27L5.21,4H20A1,1 0 0,1 21,5C21,5.17 20.95,5.34 20.88,5.5L17.3,11.97C16.96,12.58 16.3,13 15.55,13H8.1L7.2,14.63L7.17,14.75A0.25,0.25 0 0,0 7.42,15H19V17H7C5.89,17 5,16.1 5,15C5,14.65 5.09,14.32 5.24,14.04L6.6,11.59L3,4H1V2M7,18A2,2 0 0,1 9,20A2,2 0 0,1 7,22C5.89,22 5,21.1 5,20C5,18.89 5.89,18 7,18M16,11L18.78,6H6.14L8.5,11H16Z" /></g><g id="cart-plus"><path d="M11,9H13V6H16V4H13V1H11V4H8V6H11M7,18A2,2 0 0,0 5,20A2,2 0 0,0 7,22A2,2 0 0,0 9,20A2,2 0 0,0 7,18M17,18A2,2 0 0,0 15,20A2,2 0 0,0 17,22A2,2 0 0,0 19,20A2,2 0 0,0 17,18M7.17,14.75L7.2,14.63L8.1,13H15.55C16.3,13 16.96,12.59 17.3,11.97L21.16,4.96L19.42,4H19.41L18.31,6L15.55,11H8.53L8.4,10.73L6.16,6L5.21,4L4.27,2H1V4H3L6.6,11.59L5.25,14.04C5.09,14.32 5,14.65 5,15A2,2 0 0,0 7,17H19V15H7.42C7.29,15 7.17,14.89 7.17,14.75Z" /></g><g id="case-sensitive-alt"><path d="M20,14C20,12.5 19.5,12 18,12H16V11C16,10 16,10 14,10V15.4L14,19H16L18,19C19.5,19 20,18.47 20,17V14M12,12C12,10.5 11.47,10 10,10H6C4.5,10 4,10.5 4,12V19H6V16H10V19H12V12M10,7H14V5H10V7M22,9V20C22,21.11 21.11,22 20,22H4A2,2 0 0,1 2,20V9C2,7.89 2.89,7 4,7H8V5L10,3H14L16,5V7H20A2,2 0 0,1 22,9H22M16,17H18V14H16V17M6,12H10V14H6V12Z" /></g><g id="cash"><path d="M3,6H21V18H3V6M12,9A3,3 0 0,1 15,12A3,3 0 0,1 12,15A3,3 0 0,1 9,12A3,3 0 0,1 12,9M7,8A2,2 0 0,1 5,10V14A2,2 0 0,1 7,16H17A2,2 0 0,1 19,14V10A2,2 0 0,1 17,8H7Z" /></g><g id="cash-100"><path d="M2,5H22V20H2V5M20,18V7H4V18H20M17,8A2,2 0 0,0 19,10V15A2,2 0 0,0 17,17H7A2,2 0 0,0 5,15V10A2,2 0 0,0 7,8H17M17,13V12C17,10.9 16.33,10 15.5,10C14.67,10 14,10.9 14,12V13C14,14.1 14.67,15 15.5,15C16.33,15 17,14.1 17,13M15.5,11A0.5,0.5 0 0,1 16,11.5V13.5A0.5,0.5 0 0,1 15.5,14A0.5,0.5 0 0,1 15,13.5V11.5A0.5,0.5 0 0,1 15.5,11M13,13V12C13,10.9 12.33,10 11.5,10C10.67,10 10,10.9 10,12V13C10,14.1 10.67,15 11.5,15C12.33,15 13,14.1 13,13M11.5,11A0.5,0.5 0 0,1 12,11.5V13.5A0.5,0.5 0 0,1 11.5,14A0.5,0.5 0 0,1 11,13.5V11.5A0.5,0.5 0 0,1 11.5,11M8,15H9V10H8L7,10.5V11.5L8,11V15Z" /></g><g id="cash-multiple"><path d="M5,6H23V18H5V6M14,9A3,3 0 0,1 17,12A3,3 0 0,1 14,15A3,3 0 0,1 11,12A3,3 0 0,1 14,9M9,8A2,2 0 0,1 7,10V14A2,2 0 0,1 9,16H19A2,2 0 0,1 21,14V10A2,2 0 0,1 19,8H9M1,10H3V20H19V22H1V10Z" /></g><g id="cash-usd"><path d="M20,18H4V6H20M20,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V6C22,4.89 21.1,4 20,4M11,17H13V16H14A1,1 0 0,0 15,15V12A1,1 0 0,0 14,11H11V10H15V8H13V7H11V8H10A1,1 0 0,0 9,9V12A1,1 0 0,0 10,13H13V14H9V16H11V17Z" /></g><g id="cast"><path d="M1,10V12A9,9 0 0,1 10,21H12C12,14.92 7.07,10 1,10M1,14V16A5,5 0 0,1 6,21H8A7,7 0 0,0 1,14M1,18V21H4A3,3 0 0,0 1,18M21,3H3C1.89,3 1,3.89 1,5V8H3V5H21V19H14V21H21A2,2 0 0,0 23,19V5C23,3.89 22.1,3 21,3Z" /></g><g id="cast-connected"><path d="M21,3H3C1.89,3 1,3.89 1,5V8H3V5H21V19H14V21H21A2,2 0 0,0 23,19V5C23,3.89 22.1,3 21,3M1,10V12A9,9 0 0,1 10,21H12C12,14.92 7.07,10 1,10M19,7H5V8.63C8.96,9.91 12.09,13.04 13.37,17H19M1,14V16A5,5 0 0,1 6,21H8A7,7 0 0,0 1,14M1,18V21H4A3,3 0 0,0 1,18Z" /></g><g id="castle"><path d="M2,13H4V15H6V13H8V15H10V13H12V15H14V10L17,7V1H19L23,3L19,5V7L22,10V22H11V19A2,2 0 0,0 9,17A2,2 0 0,0 7,19V22H2V13M18,10C17.45,10 17,10.54 17,11.2V13H19V11.2C19,10.54 18.55,10 18,10Z" /></g><g id="cat"><path d="M12,8L10.67,8.09C9.81,7.07 7.4,4.5 5,4.5C5,4.5 3.03,7.46 4.96,11.41C4.41,12.24 4.07,12.67 4,13.66L2.07,13.95L2.28,14.93L4.04,14.67L4.18,15.38L2.61,16.32L3.08,17.21L4.53,16.32C5.68,18.76 8.59,20 12,20C15.41,20 18.32,18.76 19.47,16.32L20.92,17.21L21.39,16.32L19.82,15.38L19.96,14.67L21.72,14.93L21.93,13.95L20,13.66C19.93,12.67 19.59,12.24 19.04,11.41C20.97,7.46 19,4.5 19,4.5C16.6,4.5 14.19,7.07 13.33,8.09L12,8M9,11A1,1 0 0,1 10,12A1,1 0 0,1 9,13A1,1 0 0,1 8,12A1,1 0 0,1 9,11M15,11A1,1 0 0,1 16,12A1,1 0 0,1 15,13A1,1 0 0,1 14,12A1,1 0 0,1 15,11M11,14H13L12.3,15.39C12.5,16.03 13.06,16.5 13.75,16.5A1.5,1.5 0 0,0 15.25,15H15.75A2,2 0 0,1 13.75,17C13,17 12.35,16.59 12,16V16H12C11.65,16.59 11,17 10.25,17A2,2 0 0,1 8.25,15H8.75A1.5,1.5 0 0,0 10.25,16.5C10.94,16.5 11.5,16.03 11.7,15.39L11,14Z" /></g><g id="cellphone"><path d="M17,19H7V5H17M17,1H7C5.89,1 5,1.89 5,3V21A2,2 0 0,0 7,23H17A2,2 0 0,0 19,21V3C19,1.89 18.1,1 17,1Z" /></g><g id="cellphone-android"><path d="M17.25,18H6.75V4H17.25M14,21H10V20H14M16,1H8A3,3 0 0,0 5,4V20A3,3 0 0,0 8,23H16A3,3 0 0,0 19,20V4A3,3 0 0,0 16,1Z" /></g><g id="cellphone-basic"><path d="M15,2A1,1 0 0,0 14,3V6H10C8.89,6 8,6.89 8,8V20C8,21.11 8.89,22 10,22H15C16.11,22 17,21.11 17,20V8C17,7.26 16.6,6.62 16,6.28V3A1,1 0 0,0 15,2M10,8H15V13H10V8M10,15H11V16H10V15M12,15H13V16H12V15M14,15H15V16H14V15M10,17H11V18H10V17M12,17H13V18H12V17M14,17H15V18H14V17M10,19H11V20H10V19M12,19H13V20H12V19M14,19H15V20H14V19Z" /></g><g id="cellphone-dock"><path d="M16,15H8V5H16M16,1H8C6.89,1 6,1.89 6,3V17A2,2 0 0,0 8,19H16A2,2 0 0,0 18,17V3C18,1.89 17.1,1 16,1M8,23H16V21H8V23Z" /></g><g id="cellphone-iphone"><path d="M16,18H7V4H16M11.5,22A1.5,1.5 0 0,1 10,20.5A1.5,1.5 0 0,1 11.5,19A1.5,1.5 0 0,1 13,20.5A1.5,1.5 0 0,1 11.5,22M15.5,1H7.5A2.5,2.5 0 0,0 5,3.5V20.5A2.5,2.5 0 0,0 7.5,23H15.5A2.5,2.5 0 0,0 18,20.5V3.5A2.5,2.5 0 0,0 15.5,1Z" /></g><g id="cellphone-link"><path d="M22,17H18V10H22M23,8H17A1,1 0 0,0 16,9V19A1,1 0 0,0 17,20H23A1,1 0 0,0 24,19V9A1,1 0 0,0 23,8M4,6H22V4H4A2,2 0 0,0 2,6V17H0V20H14V17H4V6Z" /></g><g id="cellphone-link-off"><path d="M23,8H17A1,1 0 0,0 16,9V13.18L18,15.18V10H22V17H19.82L22.82,20H23A1,1 0 0,0 24,19V9A1,1 0 0,0 23,8M4,6.27L14.73,17H4V6.27M1.92,1.65L0.65,2.92L2.47,4.74C2.18,5.08 2,5.5 2,6V17H0V20H17.73L20.08,22.35L21.35,21.08L3.89,3.62L1.92,1.65M22,6V4H6.82L8.82,6H22Z" /></g><g id="cellphone-settings"><path d="M16,16H8V4H16M16,0H8A2,2 0 0,0 6,2V18A2,2 0 0,0 8,20H16A2,2 0 0,0 18,18V2A2,2 0 0,0 16,0M15,24H17V22H15M11,24H13V22H11M7,24H9V22H7V24Z" /></g><g id="certificate"><path d="M4,3C2.89,3 2,3.89 2,5V15A2,2 0 0,0 4,17H12V22L15,19L18,22V17H20A2,2 0 0,0 22,15V8L22,6V5A2,2 0 0,0 20,3H16V3H4M12,5L15,7L18,5V8.5L21,10L18,11.5V15L15,13L12,15V11.5L9,10L12,8.5V5M4,5H9V7H4V5M4,9H7V11H4V9M4,13H9V15H4V13Z" /></g><g id="chair-school"><path d="M22,5V7H17L13.53,12H16V14H14.46L18.17,22H15.97L15.04,20H6.38L5.35,22H3.1L7.23,14H7C6.55,14 6.17,13.7 6.04,13.3L2.87,3.84L3.82,3.5C4.34,3.34 4.91,3.63 5.08,4.15L7.72,12H12.1L15.57,7H12V5H22M9.5,14L7.42,18H14.11L12.26,14H9.5Z" /></g><g id="chart-arc"><path d="M16.18,19.6L14.17,16.12C15.15,15.4 15.83,14.28 15.97,13H20C19.83,15.76 18.35,18.16 16.18,19.6M13,7.03V3C17.3,3.26 20.74,6.7 21,11H16.97C16.74,8.91 15.09,7.26 13,7.03M7,12.5C7,13.14 7.13,13.75 7.38,14.3L3.9,16.31C3.32,15.16 3,13.87 3,12.5C3,7.97 6.54,4.27 11,4V8.03C8.75,8.28 7,10.18 7,12.5M11.5,21C8.53,21 5.92,19.5 4.4,17.18L7.88,15.17C8.7,16.28 10,17 11.5,17C12.14,17 12.75,16.87 13.3,16.62L15.31,20.1C14.16,20.68 12.87,21 11.5,21Z" /></g><g id="chart-areaspline"><path d="M17.45,15.18L22,7.31V19L22,21H2V3H4V15.54L9.5,6L16,9.78L20.24,2.45L21.97,3.45L16.74,12.5L10.23,8.75L4.31,19H6.57L10.96,11.44L17.45,15.18Z" /></g><g id="chart-bar"><path d="M22,21H2V3H4V19H6V10H10V19H12V6H16V19H18V14H22V21Z" /></g><g id="chart-bubble"><path d="M7.2,11.2C8.97,11.2 10.4,12.63 10.4,14.4C10.4,16.17 8.97,17.6 7.2,17.6C5.43,17.6 4,16.17 4,14.4C4,12.63 5.43,11.2 7.2,11.2M14.8,16A2,2 0 0,1 16.8,18A2,2 0 0,1 14.8,20A2,2 0 0,1 12.8,18A2,2 0 0,1 14.8,16M15.2,4A4.8,4.8 0 0,1 20,8.8C20,11.45 17.85,13.6 15.2,13.6A4.8,4.8 0 0,1 10.4,8.8C10.4,6.15 12.55,4 15.2,4Z" /></g><g id="chart-gantt"><path d="M2,5H10V2H12V22H10V18H6V15H10V13H4V10H10V8H2V5M14,5H17V8H14V5M14,10H19V13H14V10M14,15H22V18H14V15Z" /></g><g id="chart-histogram"><path d="M3,3H5V13H9V7H13V11H17V15H21V21H3V3Z" /></g><g id="chart-line"><path d="M16,11.78L20.24,4.45L21.97,5.45L16.74,14.5L10.23,10.75L5.46,19H22V21H2V3H4V17.54L9.5,8L16,11.78Z" /></g><g id="chart-pie"><path d="M21,11H13V3A8,8 0 0,1 21,11M19,13C19,15.78 17.58,18.23 15.43,19.67L11.58,13H19M11,21C8.22,21 5.77,19.58 4.33,17.43L10.82,13.68L14.56,20.17C13.5,20.7 12.28,21 11,21M3,13A8,8 0 0,1 11,5V12.42L3.83,16.56C3.3,15.5 3,14.28 3,13Z" /></g><g id="chart-scatterplot-hexbin"><path d="M2,2H4V20H22V22H2V2M14,14.5L12,18H7.94L5.92,14.5L7.94,11H12L14,14.5M14.08,6.5L12.06,10H8L6,6.5L8,3H12.06L14.08,6.5M21.25,10.5L19.23,14H15.19L13.17,10.5L15.19,7H19.23L21.25,10.5Z" /></g><g id="chart-timeline"><path d="M2,2H4V20H22V22H2V2M7,10H17V13H7V10M11,15H21V18H11V15M6,4H22V8H20V6H8V8H6V4Z" /></g><g id="check"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z" /></g><g id="check-all"><path d="M0.41,13.41L6,19L7.41,17.58L1.83,12M22.24,5.58L11.66,16.17L7.5,12L6.07,13.41L11.66,19L23.66,7M18,7L16.59,5.58L10.24,11.93L11.66,13.34L18,7Z" /></g><g id="check-circle"><path d="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M11,16.5L18,9.5L16.59,8.09L11,13.67L7.91,10.59L6.5,12L11,16.5Z" /></g><g id="check-circle-outline"><path d="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4M11,16.5L6.5,12L7.91,10.59L11,13.67L16.59,8.09L18,9.5L11,16.5Z" /></g><g id="checkbox-blank"><path d="M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z" /></g><g id="checkbox-blank-circle"><path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" /></g><g id="checkbox-blank-circle-outline"><path d="M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" /></g><g id="checkbox-blank-outline"><path d="M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3M19,5V19H5V5H19Z" /></g><g id="checkbox-marked"><path d="M10,17L5,12L6.41,10.58L10,14.17L17.59,6.58L19,8M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z" /></g><g id="checkbox-marked-circle"><path d="M10,17L5,12L6.41,10.58L10,14.17L17.59,6.58L19,8M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" /></g><g id="checkbox-marked-circle-outline"><path d="M20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4C12.76,4 13.5,4.11 14.2,4.31L15.77,2.74C14.61,2.26 13.34,2 12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12M7.91,10.08L6.5,11.5L11,16L21,6L19.59,4.58L11,13.17L7.91,10.08Z" /></g><g id="checkbox-marked-outline"><path d="M19,19H5V5H15V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V11H19M7.91,10.08L6.5,11.5L11,16L21,6L19.59,4.58L11,13.17L7.91,10.08Z" /></g><g id="checkbox-multiple-blank"><path d="M22,16A2,2 0 0,1 20,18H8C6.89,18 6,17.1 6,16V4C6,2.89 6.89,2 8,2H20A2,2 0 0,1 22,4V16M16,20V22H4A2,2 0 0,1 2,20V7H4V20H16Z" /></g><g id="checkbox-multiple-blank-circle"><path d="M14,2A8,8 0 0,0 6,10A8,8 0 0,0 14,18A8,8 0 0,0 22,10A8,8 0 0,0 14,2M4.93,5.82C3.08,7.34 2,9.61 2,12A8,8 0 0,0 10,20C10.64,20 11.27,19.92 11.88,19.77C10.12,19.38 8.5,18.5 7.17,17.29C5.22,16.25 4,14.21 4,12C4,11.7 4.03,11.41 4.07,11.11C4.03,10.74 4,10.37 4,10C4,8.56 4.32,7.13 4.93,5.82Z" /></g><g id="checkbox-multiple-blank-circle-outline"><path d="M14,2A8,8 0 0,0 6,10A8,8 0 0,0 14,18A8,8 0 0,0 22,10A8,8 0 0,0 14,2M14,4C17.32,4 20,6.69 20,10C20,13.32 17.32,16 14,16A6,6 0 0,1 8,10A6,6 0 0,1 14,4M4.93,5.82C3.08,7.34 2,9.61 2,12A8,8 0 0,0 10,20C10.64,20 11.27,19.92 11.88,19.77C10.12,19.38 8.5,18.5 7.17,17.29C5.22,16.25 4,14.21 4,12C4,11.7 4.03,11.41 4.07,11.11C4.03,10.74 4,10.37 4,10C4,8.56 4.32,7.13 4.93,5.82Z" /></g><g id="checkbox-multiple-blank-outline"><path d="M20,16V4H8V16H20M22,16A2,2 0 0,1 20,18H8C6.89,18 6,17.1 6,16V4C6,2.89 6.89,2 8,2H20A2,2 0 0,1 22,4V16M16,20V22H4A2,2 0 0,1 2,20V7H4V20H16Z" /></g><g id="checkbox-multiple-marked"><path d="M22,16A2,2 0 0,1 20,18H8C6.89,18 6,17.1 6,16V4C6,2.89 6.89,2 8,2H20A2,2 0 0,1 22,4V16M16,20V22H4A2,2 0 0,1 2,20V7H4V20H16M13,14L20,7L18.59,5.59L13,11.17L9.91,8.09L8.5,9.5L13,14Z" /></g><g id="checkbox-multiple-marked-circle"><path d="M14,2A8,8 0 0,0 6,10A8,8 0 0,0 14,18A8,8 0 0,0 22,10A8,8 0 0,0 14,2M4.93,5.82C3.08,7.34 2,9.61 2,12A8,8 0 0,0 10,20C10.64,20 11.27,19.92 11.88,19.77C10.12,19.38 8.5,18.5 7.17,17.29C5.22,16.25 4,14.21 4,12C4,11.7 4.03,11.41 4.07,11.11C4.03,10.74 4,10.37 4,10C4,8.56 4.32,7.13 4.93,5.82M18.09,6.08L19.5,7.5L13,14L9.21,10.21L10.63,8.79L13,11.17" /></g><g id="checkbox-multiple-marked-circle-outline"><path d="M14,2A8,8 0 0,0 6,10A8,8 0 0,0 14,18A8,8 0 0,0 22,10H20C20,13.32 17.32,16 14,16A6,6 0 0,1 8,10A6,6 0 0,1 14,4C14.43,4 14.86,4.05 15.27,4.14L16.88,2.54C15.96,2.18 15,2 14,2M20.59,3.58L14,10.17L11.62,7.79L10.21,9.21L14,13L22,5M4.93,5.82C3.08,7.34 2,9.61 2,12A8,8 0 0,0 10,20C10.64,20 11.27,19.92 11.88,19.77C10.12,19.38 8.5,18.5 7.17,17.29C5.22,16.25 4,14.21 4,12C4,11.7 4.03,11.41 4.07,11.11C4.03,10.74 4,10.37 4,10C4,8.56 4.32,7.13 4.93,5.82Z" /></g><g id="checkbox-multiple-marked-outline"><path d="M20,16V10H22V16A2,2 0 0,1 20,18H8C6.89,18 6,17.1 6,16V4C6,2.89 6.89,2 8,2H16V4H8V16H20M10.91,7.08L14,10.17L20.59,3.58L22,5L14,13L9.5,8.5L10.91,7.08M16,20V22H4A2,2 0 0,1 2,20V7H4V20H16Z" /></g><g id="checkerboard"><path d="M3,3H21V21H3V3M5,5V12H12V19H19V12H12V5H5Z" /></g><g id="chemical-weapon"><path d="M11,7.83C9.83,7.42 9,6.3 9,5A3,3 0 0,1 12,2A3,3 0 0,1 15,5C15,6.31 14.16,7.42 13,7.83V10.64C12.68,10.55 12.35,10.5 12,10.5C11.65,10.5 11.32,10.55 11,10.64V7.83M18.3,21.1C17.16,20.45 16.62,19.18 16.84,17.96L14.4,16.55C14.88,16.09 15.24,15.5 15.4,14.82L17.84,16.23C18.78,15.42 20.16,15.26 21.29,15.91C22.73,16.74 23.22,18.57 22.39,20C21.56,21.44 19.73,21.93 18.3,21.1M2.7,15.9C3.83,15.25 5.21,15.42 6.15,16.22L8.6,14.81C8.76,15.5 9.11,16.08 9.6,16.54L7.15,17.95C7.38,19.17 6.83,20.45 5.7,21.1C4.26,21.93 2.43,21.44 1.6,20C0.77,18.57 1.26,16.73 2.7,15.9M14,14A2,2 0 0,1 12,16C10.89,16 10,15.1 10,14A2,2 0 0,1 12,12C13.11,12 14,12.9 14,14M17,14L16.97,14.57L15.5,13.71C15.4,12.64 14.83,11.71 14,11.12V9.41C15.77,10.19 17,11.95 17,14M14.97,18.03C14.14,18.64 13.11,19 12,19C10.89,19 9.86,18.64 9.03,18L10.5,17.17C10.96,17.38 11.47,17.5 12,17.5C12.53,17.5 13.03,17.38 13.5,17.17L14.97,18.03M7.03,14.56L7,14C7,11.95 8.23,10.19 10,9.42V11.13C9.17,11.71 8.6,12.64 8.5,13.7L7.03,14.56Z" /></g><g id="chevron-double-down"><path d="M16.59,5.59L18,7L12,13L6,7L7.41,5.59L12,10.17L16.59,5.59M16.59,11.59L18,13L12,19L6,13L7.41,11.59L12,16.17L16.59,11.59Z" /></g><g id="chevron-double-left"><path d="M18.41,7.41L17,6L11,12L17,18L18.41,16.59L13.83,12L18.41,7.41M12.41,7.41L11,6L5,12L11,18L12.41,16.59L7.83,12L12.41,7.41Z" /></g><g id="chevron-double-right"><path d="M5.59,7.41L7,6L13,12L7,18L5.59,16.59L10.17,12L5.59,7.41M11.59,7.41L13,6L19,12L13,18L11.59,16.59L16.17,12L11.59,7.41Z" /></g><g id="chevron-double-up"><path d="M7.41,18.41L6,17L12,11L18,17L16.59,18.41L12,13.83L7.41,18.41M7.41,12.41L6,11L12,5L18,11L16.59,12.41L12,7.83L7.41,12.41Z" /></g><g id="chevron-down"><path d="M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z" /></g><g id="chevron-left"><path d="M15.41,16.58L10.83,12L15.41,7.41L14,6L8,12L14,18L15.41,16.58Z" /></g><g id="chevron-right"><path d="M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z" /></g><g id="chevron-up"><path d="M7.41,15.41L12,10.83L16.59,15.41L18,14L12,8L6,14L7.41,15.41Z" /></g><g id="chip"><path d="M6,4H18V5H21V7H18V9H21V11H18V13H21V15H18V17H21V19H18V20H6V19H3V17H6V15H3V13H6V11H3V9H6V7H3V5H6V4M11,15V18H12V15H11M13,15V18H14V15H13M15,15V18H16V15H15Z" /></g><g id="church"><path d="M11,2H13V4H15V6H13V9.4L22,13V15L20,14.2V22H14V17A2,2 0 0,0 12,15A2,2 0 0,0 10,17V22H4V14.2L2,15V13L11,9.4V6H9V4H11V2M6,20H8V15L7,14L6,15V20M16,20H18V15L17,14L16,15V20Z" /></g><g id="cisco-webex"><path d="M12,3A9,9 0 0,1 21,12A9,9 0 0,1 12,21A9,9 0 0,1 3,12A9,9 0 0,1 12,3M5.94,8.5C4,11.85 5.15,16.13 8.5,18.06C11.85,20 18.85,7.87 15.5,5.94C12.15,4 7.87,5.15 5.94,8.5Z" /></g><g id="city"><path d="M19,15H17V13H19M19,19H17V17H19M13,7H11V5H13M13,11H11V9H13M13,15H11V13H13M13,19H11V17H13M7,11H5V9H7M7,15H5V13H7M7,19H5V17H7M15,11V5L12,2L9,5V7H3V21H21V11H15Z" /></g><g id="clipboard"><path d="M9,4A3,3 0 0,1 12,1A3,3 0 0,1 15,4H19A2,2 0 0,1 21,6V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V6A2,2 0 0,1 5,4H9M12,3A1,1 0 0,0 11,4A1,1 0 0,0 12,5A1,1 0 0,0 13,4A1,1 0 0,0 12,3Z" /></g><g id="clipboard-account"><path d="M18,19H6V17.6C6,15.6 10,14.5 12,14.5C14,14.5 18,15.6 18,17.6M12,7A3,3 0 0,1 15,10A3,3 0 0,1 12,13A3,3 0 0,1 9,10A3,3 0 0,1 12,7M12,3A1,1 0 0,1 13,4A1,1 0 0,1 12,5A1,1 0 0,1 11,4A1,1 0 0,1 12,3M19,3H14.82C14.4,1.84 13.3,1 12,1C10.7,1 9.6,1.84 9.18,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" /></g><g id="clipboard-alert"><path d="M12,5A1,1 0 0,1 11,4A1,1 0 0,1 12,3A1,1 0 0,1 13,4A1,1 0 0,1 12,5M13,14H11V8H13M13,18H11V16H13M19,3H14.82C14.4,1.84 13.3,1 12,1C10.7,1 9.6,1.84 9.18,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" /></g><g id="clipboard-arrow-down"><path d="M12,18L7,13H10V9H14V13H17M12,3A1,1 0 0,1 13,4A1,1 0 0,1 12,5A1,1 0 0,1 11,4A1,1 0 0,1 12,3M19,3H14.82C14.4,1.84 13.3,1 12,1C10.7,1 9.6,1.84 9.18,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" /></g><g id="clipboard-arrow-left"><path d="M16,15H12V18L7,13L12,8V11H16M12,3A1,1 0 0,1 13,4A1,1 0 0,1 12,5A1,1 0 0,1 11,4A1,1 0 0,1 12,3M19,3H14.82C14.4,1.84 13.3,1 12,1C10.7,1 9.6,1.84 9.18,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" /></g><g id="clipboard-check"><path d="M10,17L6,13L7.41,11.59L10,14.17L16.59,7.58L18,9M12,3A1,1 0 0,1 13,4A1,1 0 0,1 12,5A1,1 0 0,1 11,4A1,1 0 0,1 12,3M19,3H14.82C14.4,1.84 13.3,1 12,1C10.7,1 9.6,1.84 9.18,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" /></g><g id="clipboard-outline"><path d="M7,8V6H5V19H19V6H17V8H7M9,4A3,3 0 0,1 12,1A3,3 0 0,1 15,4H19A2,2 0 0,1 21,6V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V6A2,2 0 0,1 5,4H9M12,3A1,1 0 0,0 11,4A1,1 0 0,0 12,5A1,1 0 0,0 13,4A1,1 0 0,0 12,3Z" /></g><g id="clipboard-text"><path d="M17,9H7V7H17M17,13H7V11H17M14,17H7V15H14M12,3A1,1 0 0,1 13,4A1,1 0 0,1 12,5A1,1 0 0,1 11,4A1,1 0 0,1 12,3M19,3H14.82C14.4,1.84 13.3,1 12,1C10.7,1 9.6,1.84 9.18,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" /></g><g id="clippy"><path d="M15,15.5A2.5,2.5 0 0,1 12.5,18A2.5,2.5 0 0,1 10,15.5V13.75A0.75,0.75 0 0,1 10.75,13A0.75,0.75 0 0,1 11.5,13.75V15.5A1,1 0 0,0 12.5,16.5A1,1 0 0,0 13.5,15.5V11.89C12.63,11.61 12,10.87 12,10C12,8.9 13,8 14.25,8C15.5,8 16.5,8.9 16.5,10C16.5,10.87 15.87,11.61 15,11.89V15.5M8.25,8C9.5,8 10.5,8.9 10.5,10C10.5,10.87 9.87,11.61 9,11.89V17.25A3.25,3.25 0 0,0 12.25,20.5A3.25,3.25 0 0,0 15.5,17.25V13.75A0.75,0.75 0 0,1 16.25,13A0.75,0.75 0 0,1 17,13.75V17.25A4.75,4.75 0 0,1 12.25,22A4.75,4.75 0 0,1 7.5,17.25V11.89C6.63,11.61 6,10.87 6,10C6,8.9 7,8 8.25,8M10.06,6.13L9.63,7.59C9.22,7.37 8.75,7.25 8.25,7.25C7.34,7.25 6.53,7.65 6.03,8.27L4.83,7.37C5.46,6.57 6.41,6 7.5,5.81V5.75A3.75,3.75 0 0,1 11.25,2A3.75,3.75 0 0,1 15,5.75V5.81C16.09,6 17.04,6.57 17.67,7.37L16.47,8.27C15.97,7.65 15.16,7.25 14.25,7.25C13.75,7.25 13.28,7.37 12.87,7.59L12.44,6.13C12.77,6 13.13,5.87 13.5,5.81V5.75C13.5,4.5 12.5,3.5 11.25,3.5C10,3.5 9,4.5 9,5.75V5.81C9.37,5.87 9.73,6 10.06,6.13M14.25,9.25C13.7,9.25 13.25,9.59 13.25,10C13.25,10.41 13.7,10.75 14.25,10.75C14.8,10.75 15.25,10.41 15.25,10C15.25,9.59 14.8,9.25 14.25,9.25M8.25,9.25C7.7,9.25 7.25,9.59 7.25,10C7.25,10.41 7.7,10.75 8.25,10.75C8.8,10.75 9.25,10.41 9.25,10C9.25,9.59 8.8,9.25 8.25,9.25Z" /></g><g id="clock"><path d="M12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22C6.47,22 2,17.5 2,12A10,10 0 0,1 12,2M12.5,7V12.25L17,14.92L16.25,16.15L11,13V7H12.5Z" /></g><g id="clock-alert"><path d="M12,2C6.47,2 2,6.5 2,12A10,10 0 0,0 12,22C14.25,22 16.33,21.24 18,20V17.28C16.53,18.94 14.39,20 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4C15.36,4 18.23,6.07 19.41,9H21.54C20.27,4.94 16.5,2 12,2M11,7V13L16.25,16.15L17,14.92L12.5,12.25V7H11M20,11V18H22V11H20M20,20V22H22V20H20Z" /></g><g id="clock-end"><path d="M12,1C8.14,1 5,4.14 5,8A7,7 0 0,0 12,15C15.86,15 19,11.87 19,8C19,4.14 15.86,1 12,1M12,3.15C14.67,3.15 16.85,5.32 16.85,8C16.85,10.68 14.67,12.85 12,12.85A4.85,4.85 0 0,1 7.15,8A4.85,4.85 0 0,1 12,3.15M11,5V8.69L14.19,10.53L14.94,9.23L12.5,7.82V5M15,16V19H3V21H15V24L19,20M19,20V24H21V16H19" /></g><g id="clock-fast"><path d="M15,4A8,8 0 0,1 23,12A8,8 0 0,1 15,20A8,8 0 0,1 7,12A8,8 0 0,1 15,4M15,6A6,6 0 0,0 9,12A6,6 0 0,0 15,18A6,6 0 0,0 21,12A6,6 0 0,0 15,6M14,8H15.5V11.78L17.83,14.11L16.77,15.17L14,12.4V8M2,18A1,1 0 0,1 1,17A1,1 0 0,1 2,16H5.83C6.14,16.71 6.54,17.38 7,18H2M3,13A1,1 0 0,1 2,12A1,1 0 0,1 3,11H5.05L5,12L5.05,13H3M4,8A1,1 0 0,1 3,7A1,1 0 0,1 4,6H7C6.54,6.62 6.14,7.29 5.83,8H4Z" /></g><g id="clock-in"><path d="M2.21,0.79L0.79,2.21L4.8,6.21L3,8H8V3L6.21,4.8M12,8C8.14,8 5,11.13 5,15A7,7 0 0,0 12,22C15.86,22 19,18.87 19,15A7,7 0 0,0 12,8M12,10.15C14.67,10.15 16.85,12.32 16.85,15A4.85,4.85 0 0,1 12,19.85C9.32,19.85 7.15,17.68 7.15,15A4.85,4.85 0 0,1 12,10.15M11,12V15.69L14.19,17.53L14.94,16.23L12.5,14.82V12" /></g><g id="clock-out"><path d="M18,1L19.8,2.79L15.79,6.79L17.21,8.21L21.21,4.21L23,6V1M12,8C8.14,8 5,11.13 5,15A7,7 0 0,0 12,22C15.86,22 19,18.87 19,15A7,7 0 0,0 12,8M12,10.15C14.67,10.15 16.85,12.32 16.85,15A4.85,4.85 0 0,1 12,19.85C9.32,19.85 7.15,17.68 7.15,15A4.85,4.85 0 0,1 12,10.15M11,12V15.69L14.19,17.53L14.94,16.23L12.5,14.82V12" /></g><g id="clock-start"><path d="M12,1C8.14,1 5,4.14 5,8A7,7 0 0,0 12,15C15.86,15 19,11.87 19,8C19,4.14 15.86,1 12,1M12,3.15C14.67,3.15 16.85,5.32 16.85,8C16.85,10.68 14.67,12.85 12,12.85A4.85,4.85 0 0,1 7.15,8A4.85,4.85 0 0,1 12,3.15M11,5V8.69L14.19,10.53L14.94,9.23L12.5,7.82V5M4,16V24H6V21H18V24L22,20L18,16V19H6V16" /></g><g id="close"><path d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" /></g><g id="close-box"><path d="M19,3H16.3H7.7H5A2,2 0 0,0 3,5V7.7V16.4V19A2,2 0 0,0 5,21H7.7H16.4H19A2,2 0 0,0 21,19V16.3V7.7V5A2,2 0 0,0 19,3M15.6,17L12,13.4L8.4,17L7,15.6L10.6,12L7,8.4L8.4,7L12,10.6L15.6,7L17,8.4L13.4,12L17,15.6L15.6,17Z" /></g><g id="close-box-outline"><path d="M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M19,19H5V5H19V19M17,8.4L13.4,12L17,15.6L15.6,17L12,13.4L8.4,17L7,15.6L10.6,12L7,8.4L8.4,7L12,10.6L15.6,7L17,8.4Z" /></g><g id="close-circle"><path d="M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z" /></g><g id="close-circle-outline"><path d="M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2C6.47,2 2,6.47 2,12C2,17.53 6.47,22 12,22C17.53,22 22,17.53 22,12C22,6.47 17.53,2 12,2M14.59,8L12,10.59L9.41,8L8,9.41L10.59,12L8,14.59L9.41,16L12,13.41L14.59,16L16,14.59L13.41,12L16,9.41L14.59,8Z" /></g><g id="close-network"><path d="M14.59,6L12,8.59L9.41,6L8,7.41L10.59,10L8,12.59L9.41,14L12,11.41L14.59,14L16,12.59L13.41,10L16,7.41L14.59,6M17,3A2,2 0 0,1 19,5V15A2,2 0 0,1 17,17H13V19H14A1,1 0 0,1 15,20H22V22H15A1,1 0 0,1 14,23H10A1,1 0 0,1 9,22H2V20H9A1,1 0 0,1 10,19H11V17H7C5.89,17 5,16.1 5,15V5A2,2 0 0,1 7,3H17Z" /></g><g id="close-octagon"><path d="M8.27,3L3,8.27V15.73L8.27,21H15.73L21,15.73V8.27L15.73,3M8.41,7L12,10.59L15.59,7L17,8.41L13.41,12L17,15.59L15.59,17L12,13.41L8.41,17L7,15.59L10.59,12L7,8.41" /></g><g id="close-octagon-outline"><path d="M8.27,3L3,8.27V15.73L8.27,21H15.73C17.5,19.24 21,15.73 21,15.73V8.27L15.73,3M9.1,5H14.9L19,9.1V14.9L14.9,19H9.1L5,14.9V9.1M9.12,7.71L7.71,9.12L10.59,12L7.71,14.88L9.12,16.29L12,13.41L14.88,16.29L16.29,14.88L13.41,12L16.29,9.12L14.88,7.71L12,10.59" /></g><g id="closed-caption"><path d="M18,11H16.5V10.5H14.5V13.5H16.5V13H18V14A1,1 0 0,1 17,15H14A1,1 0 0,1 13,14V10A1,1 0 0,1 14,9H17A1,1 0 0,1 18,10M11,11H9.5V10.5H7.5V13.5H9.5V13H11V14A1,1 0 0,1 10,15H7A1,1 0 0,1 6,14V10A1,1 0 0,1 7,9H10A1,1 0 0,1 11,10M19,4H5C3.89,4 3,4.89 3,6V18A2,2 0 0,0 5,20H19A2,2 0 0,0 21,18V6C21,4.89 20.1,4 19,4Z" /></g><g id="cloud"><path d="M19.35,10.03C18.67,6.59 15.64,4 12,4C9.11,4 6.6,5.64 5.35,8.03C2.34,8.36 0,10.9 0,14A6,6 0 0,0 6,20H19A5,5 0 0,0 24,15C24,12.36 21.95,10.22 19.35,10.03Z" /></g><g id="cloud-check"><path d="M10,17L6.5,13.5L7.91,12.08L10,14.17L15.18,9L16.59,10.41M19.35,10.03C18.67,6.59 15.64,4 12,4C9.11,4 6.6,5.64 5.35,8.03C2.34,8.36 0,10.9 0,14A6,6 0 0,0 6,20H19A5,5 0 0,0 24,15C24,12.36 21.95,10.22 19.35,10.03Z" /></g><g id="cloud-circle"><path d="M16.5,16H8A3,3 0 0,1 5,13A3,3 0 0,1 8,10C8.05,10 8.09,10 8.14,10C8.58,8.28 10.13,7 12,7A4,4 0 0,1 16,11H16.5A2.5,2.5 0 0,1 19,13.5A2.5,2.5 0 0,1 16.5,16M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" /></g><g id="cloud-download"><path d="M17,13L12,18L7,13H10V9H14V13M19.35,10.03C18.67,6.59 15.64,4 12,4C9.11,4 6.6,5.64 5.35,8.03C2.34,8.36 0,10.9 0,14A6,6 0 0,0 6,20H19A5,5 0 0,0 24,15C24,12.36 21.95,10.22 19.35,10.03Z" /></g><g id="cloud-outline"><path d="M19,18H6A4,4 0 0,1 2,14A4,4 0 0,1 6,10H6.71C7.37,7.69 9.5,6 12,6A5.5,5.5 0 0,1 17.5,11.5V12H19A3,3 0 0,1 22,15A3,3 0 0,1 19,18M19.35,10.03C18.67,6.59 15.64,4 12,4C9.11,4 6.6,5.64 5.35,8.03C2.34,8.36 0,10.9 0,14A6,6 0 0,0 6,20H19A5,5 0 0,0 24,15C24,12.36 21.95,10.22 19.35,10.03Z" /></g><g id="cloud-outline-off"><path d="M7.73,10L15.73,18H6A4,4 0 0,1 2,14A4,4 0 0,1 6,10M3,5.27L5.75,8C2.56,8.15 0,10.77 0,14A6,6 0 0,0 6,20H17.73L19.73,22L21,20.73L4.27,4M19.35,10.03C18.67,6.59 15.64,4 12,4C10.5,4 9.15,4.43 8,5.17L9.45,6.63C10.21,6.23 11.08,6 12,6A5.5,5.5 0 0,1 17.5,11.5V12H19A3,3 0 0,1 22,15C22,16.13 21.36,17.11 20.44,17.62L21.89,19.07C23.16,18.16 24,16.68 24,15C24,12.36 21.95,10.22 19.35,10.03Z" /></g><g id="cloud-print"><path d="M12,2C9.11,2 6.6,3.64 5.35,6.04C2.34,6.36 0,8.91 0,12A6,6 0 0,0 6,18V22H18V18H19A5,5 0 0,0 24,13C24,10.36 21.95,8.22 19.35,8.04C18.67,4.59 15.64,2 12,2M8,13H16V20H8V13M9,14V15H15V14H9M9,16V17H15V16H9M9,18V19H15V18H9Z" /></g><g id="cloud-print-outline"><path d="M19,16A3,3 0 0,0 22,13A3,3 0 0,0 19,10H17.5V9.5A5.5,5.5 0 0,0 12,4C9.5,4 7.37,5.69 6.71,8H6A4,4 0 0,0 2,12A4,4 0 0,0 6,16V11H18V16H19M19.36,8.04C21.95,8.22 24,10.36 24,13A5,5 0 0,1 19,18H18V22H6V18A6,6 0 0,1 0,12C0,8.91 2.34,6.36 5.35,6.04C6.6,3.64 9.11,2 12,2C15.64,2 18.67,4.6 19.36,8.04M8,13V20H16V13H8M9,18H15V19H9V18M15,17H9V16H15V17M9,14H15V15H9V14Z" /></g><g id="cloud-sync"><path d="M12,4C15.64,4 18.67,6.59 19.35,10.04C21.95,10.22 24,12.36 24,15A5,5 0 0,1 19,20H6A6,6 0 0,1 0,14C0,10.91 2.34,8.36 5.35,8.04C6.6,5.64 9.11,4 12,4M7.5,9.69C6.06,11.5 6.2,14.06 7.82,15.68C8.66,16.5 9.81,17 11,17V18.86L13.83,16.04L11,13.21V15C10.34,15 9.7,14.74 9.23,14.27C8.39,13.43 8.26,12.11 8.92,11.12L7.5,9.69M9.17,8.97L10.62,10.42L12,11.79V10C12.66,10 13.3,10.26 13.77,10.73C14.61,11.57 14.74,12.89 14.08,13.88L15.5,15.31C16.94,13.5 16.8,10.94 15.18,9.32C14.34,8.5 13.19,8 12,8V6.14L9.17,8.97Z" /></g><g id="cloud-upload"><path d="M14,13V17H10V13H7L12,8L17,13M19.35,10.03C18.67,6.59 15.64,4 12,4C9.11,4 6.6,5.64 5.35,8.03C2.34,8.36 0,10.9 0,14A6,6 0 0,0 6,20H19A5,5 0 0,0 24,15C24,12.36 21.95,10.22 19.35,10.03Z" /></g><g id="code-array"><path d="M3,5A2,2 0 0,1 5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5C3.89,21 3,20.1 3,19V5M6,6V18H10V16H8V8H10V6H6M16,16H14V18H18V6H14V8H16V16Z" /></g><g id="code-braces"><path d="M8,3A2,2 0 0,0 6,5V9A2,2 0 0,1 4,11H3V13H4A2,2 0 0,1 6,15V19A2,2 0 0,0 8,21H10V19H8V14A2,2 0 0,0 6,12A2,2 0 0,0 8,10V5H10V3M16,3A2,2 0 0,1 18,5V9A2,2 0 0,0 20,11H21V13H20A2,2 0 0,0 18,15V19A2,2 0 0,1 16,21H14V19H16V14A2,2 0 0,1 18,12A2,2 0 0,1 16,10V5H14V3H16Z" /></g><g id="code-brackets"><path d="M15,4V6H18V18H15V20H20V4M4,4V20H9V18H6V6H9V4H4Z" /></g><g id="code-equal"><path d="M6,13H11V15H6M13,13H18V15H13M13,9H18V11H13M6,9H11V11H6M5,3C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3H5Z" /></g><g id="code-greater-than"><path d="M10.41,7.41L15,12L10.41,16.6L9,15.18L12.18,12L9,8.82M5,3C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3H5Z" /></g><g id="code-greater-than-or-equal"><path d="M13,13H18V15H13M13,9H18V11H13M6.91,7.41L11.5,12L6.91,16.6L5.5,15.18L8.68,12L5.5,8.82M5,3C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3H5Z" /></g><g id="code-less-than"><path d="M13.59,7.41L9,12L13.59,16.6L15,15.18L11.82,12L15,8.82M19,3C20.11,3 21,3.9 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3H19Z" /></g><g id="code-less-than-or-equal"><path d="M13,13H18V15H13M13,9H18V11H13M10.09,7.41L11.5,8.82L8.32,12L11.5,15.18L10.09,16.6L5.5,12M5,3C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3H5Z" /></g><g id="code-not-equal"><path d="M6,15H8V17H6M11,13H18V15H11M11,9H18V11H11M6,7H8V13H6M5,3C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3H5Z" /></g><g id="code-not-equal-variant"><path d="M11,6.5V9.33L8.33,12L11,14.67V17.5L5.5,12M13,6.43L18.57,12L13,17.57V14.74L15.74,12L13,9.26M5,3C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3H5Z" /></g><g id="code-parentheses"><path d="M17.62,3C19.13,5.27 20,8.55 20,12C20,15.44 19.13,18.72 17.62,21L16,19.96C17.26,18.07 18,15.13 18,12C18,8.87 17.26,5.92 16,4.03L17.62,3M6.38,3L8,4.04C6.74,5.92 6,8.87 6,12C6,15.13 6.74,18.08 8,19.96L6.38,21C4.87,18.73 4,15.45 4,12C4,8.55 4.87,5.27 6.38,3Z" /></g><g id="code-string"><path d="M3,5A2,2 0 0,1 5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5C3.89,21 3,20.1 3,19V5M12.5,11H11.5A1.5,1.5 0 0,1 10,9.5A1.5,1.5 0 0,1 11.5,8H12.5A1.5,1.5 0 0,1 14,9.5H16A3.5,3.5 0 0,0 12.5,6H11.5A3.5,3.5 0 0,0 8,9.5A3.5,3.5 0 0,0 11.5,13H12.5A1.5,1.5 0 0,1 14,14.5A1.5,1.5 0 0,1 12.5,16H11.5A1.5,1.5 0 0,1 10,14.5H8A3.5,3.5 0 0,0 11.5,18H12.5A3.5,3.5 0 0,0 16,14.5A3.5,3.5 0 0,0 12.5,11Z" /></g><g id="code-tags"><path d="M14.6,16.6L19.2,12L14.6,7.4L16,6L22,12L16,18L14.6,16.6M9.4,16.6L4.8,12L9.4,7.4L8,6L2,12L8,18L9.4,16.6Z" /></g><g id="code-tags-check"><path d="M6.59,3.41L2,8L6.59,12.6L8,11.18L4.82,8L8,4.82L6.59,3.41M12.41,3.41L11,4.82L14.18,8L11,11.18L12.41,12.6L17,8L12.41,3.41M21.59,11.59L13.5,19.68L9.83,16L8.42,17.41L13.5,22.5L23,13L21.59,11.59Z" /></g><g id="codepen"><path d="M19.45,13.29L17.5,12L19.45,10.71M12.77,18.78V15.17L16.13,12.93L18.83,14.74M12,13.83L9.26,12L12,10.17L14.74,12M11.23,18.78L5.17,14.74L7.87,12.93L11.23,15.17M4.55,10.71L6.5,12L4.55,13.29M11.23,5.22V8.83L7.87,11.07L5.17,9.26M12.77,5.22L18.83,9.26L16.13,11.07L12.77,8.83M21,9.16C21,9.15 21,9.13 21,9.12C21,9.1 21,9.08 20.97,9.06C20.97,9.05 20.97,9.03 20.96,9C20.96,9 20.95,9 20.94,8.96C20.94,8.95 20.93,8.94 20.92,8.93C20.92,8.91 20.91,8.89 20.9,8.88C20.89,8.86 20.88,8.85 20.88,8.84C20.87,8.82 20.85,8.81 20.84,8.79C20.83,8.78 20.83,8.77 20.82,8.76A0.04,0.04 0 0,0 20.78,8.72C20.77,8.71 20.76,8.7 20.75,8.69C20.73,8.67 20.72,8.66 20.7,8.65C20.69,8.64 20.68,8.63 20.67,8.62C20.66,8.62 20.66,8.62 20.66,8.61L12.43,3.13C12.17,2.96 11.83,2.96 11.57,3.13L3.34,8.61C3.34,8.62 3.34,8.62 3.33,8.62C3.32,8.63 3.31,8.64 3.3,8.65C3.28,8.66 3.27,8.67 3.25,8.69C3.24,8.7 3.23,8.71 3.22,8.72C3.21,8.73 3.2,8.74 3.18,8.76C3.17,8.77 3.17,8.78 3.16,8.79C3.15,8.81 3.13,8.82 3.12,8.84C3.12,8.85 3.11,8.86 3.1,8.88C3.09,8.89 3.08,8.91 3.08,8.93C3.07,8.94 3.06,8.95 3.06,8.96C3.05,9 3.05,9 3.04,9C3.03,9.03 3.03,9.05 3.03,9.06C3,9.08 3,9.1 3,9.12C3,9.13 3,9.15 3,9.16C3,9.19 3,9.22 3,9.26V14.74C3,14.78 3,14.81 3,14.84C3,14.85 3,14.87 3,14.88C3,14.9 3,14.92 3.03,14.94C3.03,14.95 3.03,14.97 3.04,15C3.05,15 3.05,15 3.06,15.04C3.06,15.05 3.07,15.06 3.08,15.07C3.08,15.09 3.09,15.11 3.1,15.12C3.11,15.14 3.12,15.15 3.12,15.16C3.13,15.18 3.15,15.19 3.16,15.21C3.17,15.22 3.17,15.23 3.18,15.24C3.2,15.25 3.21,15.27 3.22,15.28C3.23,15.29 3.24,15.3 3.25,15.31C3.27,15.33 3.28,15.34 3.3,15.35C3.31,15.36 3.32,15.37 3.33,15.38C3.34,15.38 3.34,15.38 3.34,15.39L11.57,20.87C11.7,20.96 11.85,21 12,21C12.15,21 12.3,20.96 12.43,20.87L20.66,15.39C20.66,15.38 20.66,15.38 20.67,15.38C20.68,15.37 20.69,15.36 20.7,15.35C20.72,15.34 20.73,15.33 20.75,15.31C20.76,15.3 20.77,15.29 20.78,15.28C20.79,15.27 20.8,15.25 20.82,15.24C20.83,15.23 20.83,15.22 20.84,15.21C20.85,15.19 20.87,15.18 20.88,15.16C20.88,15.15 20.89,15.14 20.9,15.12C20.91,15.11 20.92,15.09 20.92,15.07C20.93,15.06 20.94,15.05 20.94,15.04C20.95,15 20.96,15 20.96,15C20.97,14.97 20.97,14.95 20.97,14.94C21,14.92 21,14.9 21,14.88C21,14.87 21,14.85 21,14.84C21,14.81 21,14.78 21,14.74V9.26C21,9.22 21,9.19 21,9.16Z" /></g><g id="coffee"><path d="M2,21H20V19H2M20,8H18V5H20M20,3H4V13A4,4 0 0,0 8,17H14A4,4 0 0,0 18,13V10H20A2,2 0 0,0 22,8V5C22,3.89 21.1,3 20,3Z" /></g><g id="coffee-to-go"><path d="M3,19V17H17L15.26,15.24L16.67,13.83L20.84,18L16.67,22.17L15.26,20.76L17,19H3M17,8V5H15V8H17M17,3C18.11,3 19,3.9 19,5V8C19,9.11 18.11,10 17,10H15V11A4,4 0 0,1 11,15H7A4,4 0 0,1 3,11V3H17Z" /></g><g id="coin"><path d="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4M11,17V16H9V14H13V13H10A1,1 0 0,1 9,12V9A1,1 0 0,1 10,8H11V7H13V8H15V10H11V11H14A1,1 0 0,1 15,12V15A1,1 0 0,1 14,16H13V17H11Z" /></g><g id="coins"><path d="M15,4A8,8 0 0,1 23,12A8,8 0 0,1 15,20A8,8 0 0,1 7,12A8,8 0 0,1 15,4M15,18A6,6 0 0,0 21,12A6,6 0 0,0 15,6A6,6 0 0,0 9,12A6,6 0 0,0 15,18M3,12C3,14.61 4.67,16.83 7,17.65V19.74C3.55,18.85 1,15.73 1,12C1,8.27 3.55,5.15 7,4.26V6.35C4.67,7.17 3,9.39 3,12Z" /></g><g id="collage"><path d="M5,3C3.89,3 3,3.89 3,5V19C3,20.11 3.89,21 5,21H11V3M13,3V11H21V5C21,3.89 20.11,3 19,3M13,13V21H19C20.11,21 21,20.11 21,19V13" /></g><g id="color-helper"><path d="M0,24H24V20H0V24Z" /></g><g id="comment"><path d="M9,22A1,1 0 0,1 8,21V18H4A2,2 0 0,1 2,16V4C2,2.89 2.9,2 4,2H20A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H13.9L10.2,21.71C10,21.9 9.75,22 9.5,22V22H9Z" /></g><g id="comment-account"><path d="M9,22A1,1 0 0,1 8,21V18H4A2,2 0 0,1 2,16V4C2,2.89 2.9,2 4,2H20A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H13.9L10.2,21.71C10,21.9 9.75,22 9.5,22V22H9M16,14V13C16,11.67 13.33,11 12,11C10.67,11 8,11.67 8,13V14H16M12,6A2,2 0 0,0 10,8A2,2 0 0,0 12,10A2,2 0 0,0 14,8A2,2 0 0,0 12,6Z" /></g><g id="comment-account-outline"><path d="M9,22A1,1 0 0,1 8,21V18H4A2,2 0 0,1 2,16V4C2,2.89 2.9,2 4,2H20A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H13.9L10.2,21.71C10,21.9 9.75,22 9.5,22V22H9M10,16V19.08L13.08,16H20V4H4V16H10M16,14H8V13C8,11.67 10.67,11 12,11C13.33,11 16,11.67 16,13V14M12,6A2,2 0 0,1 14,8A2,2 0 0,1 12,10A2,2 0 0,1 10,8A2,2 0 0,1 12,6Z" /></g><g id="comment-alert"><path d="M9,22A1,1 0 0,1 8,21V18H4A2,2 0 0,1 2,16V4C2,2.89 2.9,2 4,2H20A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H13.9L10.2,21.71C10,21.9 9.75,22 9.5,22V22H9M13,10V6H11V10H13M13,14V12H11V14H13Z" /></g><g id="comment-alert-outline"><path d="M9,22A1,1 0 0,1 8,21V18H4A2,2 0 0,1 2,16V4C2,2.89 2.9,2 4,2H20A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H13.9L10.2,21.71C10,21.9 9.75,22 9.5,22V22H9M10,16V19.08L13.08,16H20V4H4V16H10M13,10H11V6H13V10M13,14H11V12H13V14Z" /></g><g id="comment-check"><path d="M9,22A1,1 0 0,1 8,21V18H4A2,2 0 0,1 2,16V4C2,2.89 2.9,2 4,2H20A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H13.9L10.2,21.71C10,21.9 9.75,22 9.5,22V22H9M10,15L18,7L16.59,5.58L10,12.17L7.41,9.59L6,11L10,15Z" /></g><g id="comment-check-outline"><path d="M9,22A1,1 0 0,1 8,21V18H4A2,2 0 0,1 2,16V4C2,2.89 2.9,2 4,2H20A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H13.9L10.2,21.71C10,21.9 9.75,22 9.5,22V22H9M10,16V19.08L13.08,16H20V4H4V16H10M16.5,8L11,13.5L7.5,10L8.91,8.59L11,10.67L15.09,6.59L16.5,8Z" /></g><g id="comment-multiple-outline"><path d="M12,23A1,1 0 0,1 11,22V19H7A2,2 0 0,1 5,17V7C5,5.89 5.9,5 7,5H21A2,2 0 0,1 23,7V17A2,2 0 0,1 21,19H16.9L13.2,22.71C13,22.9 12.75,23 12.5,23V23H12M13,17V20.08L16.08,17H21V7H7V17H13M3,15H1V3A2,2 0 0,1 3,1H19V3H3V15Z" /></g><g id="comment-outline"><path d="M9,22A1,1 0 0,1 8,21V18H4A2,2 0 0,1 2,16V4C2,2.89 2.9,2 4,2H20A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H13.9L10.2,21.71C10,21.9 9.75,22 9.5,22V22H9M10,16V19.08L13.08,16H20V4H4V16H10Z" /></g><g id="comment-plus-outline"><path d="M9,22A1,1 0 0,1 8,21V18H4A2,2 0 0,1 2,16V4C2,2.89 2.9,2 4,2H20A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H13.9L10.2,21.71C10,21.9 9.75,22 9.5,22V22H9M10,16V19.08L13.08,16H20V4H4V16H10M11,6H13V9H16V11H13V14H11V11H8V9H11V6Z" /></g><g id="comment-processing"><path d="M9,22A1,1 0 0,1 8,21V18H4A2,2 0 0,1 2,16V4C2,2.89 2.9,2 4,2H20A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H13.9L10.2,21.71C10,21.9 9.75,22 9.5,22V22H9M17,11V9H15V11H17M13,11V9H11V11H13M9,11V9H7V11H9Z" /></g><g id="comment-processing-outline"><path d="M9,22A1,1 0 0,1 8,21V18H4A2,2 0 0,1 2,16V4C2,2.89 2.9,2 4,2H20A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H13.9L10.2,21.71C10,21.9 9.75,22 9.5,22V22H9M10,16V19.08L13.08,16H20V4H4V16H10M17,11H15V9H17V11M13,11H11V9H13V11M9,11H7V9H9V11Z" /></g><g id="comment-question-outline"><path d="M4,2A2,2 0 0,0 2,4V16A2,2 0 0,0 4,18H8V21A1,1 0 0,0 9,22H9.5V22C9.75,22 10,21.9 10.2,21.71L13.9,18H20A2,2 0 0,0 22,16V4C22,2.89 21.1,2 20,2H4M4,4H20V16H13.08L10,19.08V16H4V4M12.19,5.5C11.3,5.5 10.59,5.68 10.05,6.04C9.5,6.4 9.22,7 9.27,7.69C0.21,7.69 6.57,7.69 11.24,7.69C11.24,7.41 11.34,7.2 11.5,7.06C11.7,6.92 11.92,6.85 12.19,6.85C12.5,6.85 12.77,6.93 12.95,7.11C13.13,7.28 13.22,7.5 13.22,7.8C13.22,8.08 13.14,8.33 13,8.54C12.83,8.76 12.62,8.94 12.36,9.08C11.84,9.4 11.5,9.68 11.29,9.92C11.1,10.16 11,10.5 11,11H13C13,10.72 13.05,10.5 13.14,10.32C13.23,10.15 13.4,10 13.66,9.85C14.12,9.64 14.5,9.36 14.79,9C15.08,8.63 15.23,8.24 15.23,7.8C15.23,7.1 14.96,6.54 14.42,6.12C13.88,5.71 13.13,5.5 12.19,5.5M11,12V14H13V12H11Z" /></g><g id="comment-remove-outline"><path d="M9,22A1,1 0 0,1 8,21V18H4A2,2 0 0,1 2,16V4C2,2.89 2.9,2 4,2H20A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H13.9L10.2,21.71C10,21.9 9.75,22 9.5,22V22H9M10,16V19.08L13.08,16H20V4H4V16H10M9.41,6L12,8.59L14.59,6L16,7.41L13.41,10L16,12.59L14.59,14L12,11.41L9.41,14L8,12.59L10.59,10L8,7.41L9.41,6Z" /></g><g id="comment-text"><path d="M9,22A1,1 0 0,1 8,21V18H4A2,2 0 0,1 2,16V4C2,2.89 2.9,2 4,2H20A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H13.9L10.2,21.71C10,21.9 9.75,22 9.5,22V22H9M5,5V7H19V5H5M5,9V11H13V9H5M5,13V15H15V13H5Z" /></g><g id="comment-text-outline"><path d="M9,22A1,1 0 0,1 8,21V18H4A2,2 0 0,1 2,16V4C2,2.89 2.9,2 4,2H20A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H13.9L10.2,21.71C10,21.9 9.75,22 9.5,22V22H9M10,16V19.08L13.08,16H20V4H4V16H10M6,7H18V9H6V7M6,11H15V13H6V11Z" /></g><g id="compare"><path d="M19,3H14V5H19V18L14,12V21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3M10,18H5L10,12M10,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H10V23H12V1H10V3Z" /></g><g id="compass"><path d="M14.19,14.19L6,18L9.81,9.81L18,6M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,10.9A1.1,1.1 0 0,0 10.9,12A1.1,1.1 0 0,0 12,13.1A1.1,1.1 0 0,0 13.1,12A1.1,1.1 0 0,0 12,10.9Z" /></g><g id="compass-outline"><path d="M7,17L10.2,10.2L17,7L13.8,13.8L7,17M12,11.1A0.9,0.9 0 0,0 11.1,12A0.9,0.9 0 0,0 12,12.9A0.9,0.9 0 0,0 12.9,12A0.9,0.9 0 0,0 12,11.1M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z" /></g><g id="console"><path d="M20,19V7H4V19H20M20,3A2,2 0 0,1 22,5V19A2,2 0 0,1 20,21H4A2,2 0 0,1 2,19V5C2,3.89 2.9,3 4,3H20M13,17V15H18V17H13M9.58,13L5.57,9H8.4L11.7,12.3C12.09,12.69 12.09,13.33 11.7,13.72L8.42,17H5.59L9.58,13Z" /></g><g id="contact-mail"><path d="M21,8V7L18,9L15,7V8L18,10M22,3H2A2,2 0 0,0 0,5V19A2,2 0 0,0 2,21H22A2,2 0 0,0 24,19V5A2,2 0 0,0 22,3M8,6A3,3 0 0,1 11,9A3,3 0 0,1 8,12A3,3 0 0,1 5,9A3,3 0 0,1 8,6M14,18H2V17C2,15 6,13.9 8,13.9C10,13.9 14,15 14,17M22,12H14V6H22" /></g><g id="contacts"><path d="M20,0H4V2H20V0M4,24H20V22H4V24M20,4H4A2,2 0 0,0 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V6A2,2 0 0,0 20,4M12,6.75A2.25,2.25 0 0,1 14.25,9A2.25,2.25 0 0,1 12,11.25A2.25,2.25 0 0,1 9.75,9A2.25,2.25 0 0,1 12,6.75M17,17H7V15.5C7,13.83 10.33,13 12,13C13.67,13 17,13.83 17,15.5V17Z" /></g><g id="content-copy"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" /></g><g id="content-cut"><path d="M19,3L13,9L15,11L22,4V3M12,12.5A0.5,0.5 0 0,1 11.5,12A0.5,0.5 0 0,1 12,11.5A0.5,0.5 0 0,1 12.5,12A0.5,0.5 0 0,1 12,12.5M6,20A2,2 0 0,1 4,18C4,16.89 4.9,16 6,16A2,2 0 0,1 8,18C8,19.11 7.1,20 6,20M6,8A2,2 0 0,1 4,6C4,4.89 4.9,4 6,4A2,2 0 0,1 8,6C8,7.11 7.1,8 6,8M9.64,7.64C9.87,7.14 10,6.59 10,6A4,4 0 0,0 6,2A4,4 0 0,0 2,6A4,4 0 0,0 6,10C6.59,10 7.14,9.87 7.64,9.64L10,12L7.64,14.36C7.14,14.13 6.59,14 6,14A4,4 0 0,0 2,18A4,4 0 0,0 6,22A4,4 0 0,0 10,18C10,17.41 9.87,16.86 9.64,16.36L12,14L19,21H22V20L9.64,7.64Z" /></g><g id="content-duplicate"><path d="M11,17H4A2,2 0 0,1 2,15V3A2,2 0 0,1 4,1H16V3H4V15H11V13L15,16L11,19V17M19,21V7H8V13H6V7A2,2 0 0,1 8,5H19A2,2 0 0,1 21,7V21A2,2 0 0,1 19,23H8A2,2 0 0,1 6,21V19H8V21H19Z" /></g><g id="content-paste"><path d="M19,20H5V4H7V7H17V4H19M12,2A1,1 0 0,1 13,3A1,1 0 0,1 12,4A1,1 0 0,1 11,3A1,1 0 0,1 12,2M19,2H14.82C14.4,0.84 13.3,0 12,0C10.7,0 9.6,0.84 9.18,2H5A2,2 0 0,0 3,4V20A2,2 0 0,0 5,22H19A2,2 0 0,0 21,20V4A2,2 0 0,0 19,2Z" /></g><g id="content-save"><path d="M15,9H5V5H15M12,19A3,3 0 0,1 9,16A3,3 0 0,1 12,13A3,3 0 0,1 15,16A3,3 0 0,1 12,19M17,3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V7L17,3Z" /></g><g id="content-save-all"><path d="M17,7V3H7V7H17M14,17A3,3 0 0,0 17,14A3,3 0 0,0 14,11A3,3 0 0,0 11,14A3,3 0 0,0 14,17M19,1L23,5V17A2,2 0 0,1 21,19H7C5.89,19 5,18.1 5,17V3A2,2 0 0,1 7,1H19M1,7H3V21H17V23H3A2,2 0 0,1 1,21V7Z" /></g><g id="content-save-settings"><path d="M15,8V4H5V8H15M12,18A3,3 0 0,0 15,15A3,3 0 0,0 12,12A3,3 0 0,0 9,15A3,3 0 0,0 12,18M17,2L21,6V18A2,2 0 0,1 19,20H5C3.89,20 3,19.1 3,18V4A2,2 0 0,1 5,2H17M11,22H13V24H11V22M7,22H9V24H7V22M15,22H17V24H15V22Z" /></g><g id="contrast"><path d="M4.38,20.9C3.78,20.71 3.3,20.23 3.1,19.63L19.63,3.1C20.23,3.3 20.71,3.78 20.9,4.38L4.38,20.9M20,16V18H13V16H20M3,6H6V3H8V6H11V8H8V11H6V8H3V6Z" /></g><g id="contrast-box"><path d="M17,15.5H12V17H17M19,19H5L19,5M5.5,7.5H7.5V5.5H9V7.5H11V9H9V11H7.5V9H5.5M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z" /></g><g id="contrast-circle"><path d="M12,20C9.79,20 7.79,19.1 6.34,17.66L17.66,6.34C19.1,7.79 20,9.79 20,12A8,8 0 0,1 12,20M6,8H8V6H9.5V8H11.5V9.5H9.5V11.5H8V9.5H6M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,16H17V14.5H12V16Z" /></g><g id="cookie"><path d="M12,3A9,9 0 0,0 3,12A9,9 0 0,0 12,21A9,9 0 0,0 21,12C21,11.5 20.96,11 20.87,10.5C20.6,10 20,10 20,10H18V9C18,8 17,8 17,8H15V7C15,6 14,6 14,6H13V4C13,3 12,3 12,3M9.5,6A1.5,1.5 0 0,1 11,7.5A1.5,1.5 0 0,1 9.5,9A1.5,1.5 0 0,1 8,7.5A1.5,1.5 0 0,1 9.5,6M6.5,10A1.5,1.5 0 0,1 8,11.5A1.5,1.5 0 0,1 6.5,13A1.5,1.5 0 0,1 5,11.5A1.5,1.5 0 0,1 6.5,10M11.5,11A1.5,1.5 0 0,1 13,12.5A1.5,1.5 0 0,1 11.5,14A1.5,1.5 0 0,1 10,12.5A1.5,1.5 0 0,1 11.5,11M16.5,13A1.5,1.5 0 0,1 18,14.5A1.5,1.5 0 0,1 16.5,16H16.5A1.5,1.5 0 0,1 15,14.5H15A1.5,1.5 0 0,1 16.5,13M11,16A1.5,1.5 0 0,1 12.5,17.5A1.5,1.5 0 0,1 11,19A1.5,1.5 0 0,1 9.5,17.5A1.5,1.5 0 0,1 11,16Z" /></g><g id="copyright"><path d="M10.08,10.86C10.13,10.53 10.24,10.24 10.38,10C10.5,9.74 10.72,9.53 10.97,9.37C11.21,9.22 11.5,9.15 11.88,9.14C12.11,9.15 12.32,9.19 12.5,9.27C12.71,9.36 12.89,9.5 13.03,9.63C13.17,9.78 13.28,9.96 13.37,10.16C13.46,10.36 13.5,10.58 13.5,10.8H15.3C15.28,10.33 15.19,9.9 15,9.5C14.85,9.12 14.62,8.78 14.32,8.5C14,8.22 13.66,8 13.24,7.84C12.82,7.68 12.36,7.61 11.85,7.61C11.2,7.61 10.63,7.72 10.15,7.95C9.67,8.18 9.27,8.5 8.95,8.87C8.63,9.26 8.39,9.71 8.24,10.23C8.09,10.75 8,11.29 8,11.87V12.14C8,12.72 8.08,13.26 8.23,13.78C8.38,14.3 8.62,14.75 8.94,15.13C9.26,15.5 9.66,15.82 10.14,16.04C10.62,16.26 11.19,16.38 11.84,16.38C12.31,16.38 12.75,16.3 13.16,16.15C13.57,16 13.93,15.79 14.24,15.5C14.55,15.25 14.8,14.94 15,14.58C15.16,14.22 15.27,13.84 15.28,13.43H13.5C13.5,13.64 13.43,13.83 13.34,14C13.25,14.19 13.13,14.34 13,14.47C12.83,14.6 12.66,14.7 12.46,14.77C12.27,14.84 12.07,14.86 11.86,14.87C11.5,14.86 11.2,14.79 10.97,14.64C10.72,14.5 10.5,14.27 10.38,14C10.24,13.77 10.13,13.47 10.08,13.14C10.03,12.81 10,12.47 10,12.14V11.87C10,11.5 10.03,11.19 10.08,10.86M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20Z" /></g><g id="counter"><path d="M4,4H20A2,2 0 0,1 22,6V18A2,2 0 0,1 20,20H4A2,2 0 0,1 2,18V6A2,2 0 0,1 4,4M4,6V18H11V6H4M20,18V6H18.76C19,6.54 18.95,7.07 18.95,7.13C18.88,7.8 18.41,8.5 18.24,8.75L15.91,11.3L19.23,11.28L19.24,12.5L14.04,12.47L14,11.47C14,11.47 17.05,8.24 17.2,7.95C17.34,7.67 17.91,6 16.5,6C15.27,6.05 15.41,7.3 15.41,7.3L13.87,7.31C13.87,7.31 13.88,6.65 14.25,6H13V18H15.58L15.57,17.14L16.54,17.13C16.54,17.13 17.45,16.97 17.46,16.08C17.5,15.08 16.65,15.08 16.5,15.08C16.37,15.08 15.43,15.13 15.43,15.95H13.91C13.91,15.95 13.95,13.89 16.5,13.89C19.1,13.89 18.96,15.91 18.96,15.91C18.96,15.91 19,17.16 17.85,17.63L18.37,18H20M8.92,16H7.42V10.2L5.62,10.76V9.53L8.76,8.41H8.92V16Z" /></g><g id="cow"><path d="M10.5,18A0.5,0.5 0 0,1 11,18.5A0.5,0.5 0 0,1 10.5,19A0.5,0.5 0 0,1 10,18.5A0.5,0.5 0 0,1 10.5,18M13.5,18A0.5,0.5 0 0,1 14,18.5A0.5,0.5 0 0,1 13.5,19A0.5,0.5 0 0,1 13,18.5A0.5,0.5 0 0,1 13.5,18M10,11A1,1 0 0,1 11,12A1,1 0 0,1 10,13A1,1 0 0,1 9,12A1,1 0 0,1 10,11M14,11A1,1 0 0,1 15,12A1,1 0 0,1 14,13A1,1 0 0,1 13,12A1,1 0 0,1 14,11M18,18C18,20.21 15.31,22 12,22C8.69,22 6,20.21 6,18C6,17.1 6.45,16.27 7.2,15.6C6.45,14.6 6,13.35 6,12L6.12,10.78C5.58,10.93 4.93,10.93 4.4,10.78C3.38,10.5 1.84,9.35 2.07,8.55C2.3,7.75 4.21,7.6 5.23,7.9C5.82,8.07 6.45,8.5 6.82,8.96L7.39,8.15C6.79,7.05 7,4 10,3L9.91,3.14V3.14C9.63,3.58 8.91,4.97 9.67,6.47C10.39,6.17 11.17,6 12,6C12.83,6 13.61,6.17 14.33,6.47C15.09,4.97 14.37,3.58 14.09,3.14L14,3C17,4 17.21,7.05 16.61,8.15L17.18,8.96C17.55,8.5 18.18,8.07 18.77,7.9C19.79,7.6 21.7,7.75 21.93,8.55C22.16,9.35 20.62,10.5 19.6,10.78C19.07,10.93 18.42,10.93 17.88,10.78L18,12C18,13.35 17.55,14.6 16.8,15.6C17.55,16.27 18,17.1 18,18M12,16C9.79,16 8,16.9 8,18C8,19.1 9.79,20 12,20C14.21,20 16,19.1 16,18C16,16.9 14.21,16 12,16M12,14C13.12,14 14.17,14.21 15.07,14.56C15.65,13.87 16,13 16,12A4,4 0 0,0 12,8A4,4 0 0,0 8,12C8,13 8.35,13.87 8.93,14.56C9.83,14.21 10.88,14 12,14M14.09,3.14V3.14Z" /></g><g id="creation"><path d="M19,1L17.74,3.75L15,5L17.74,6.26L19,9L20.25,6.26L23,5L20.25,3.75M9,4L6.5,9.5L1,12L6.5,14.5L9,20L11.5,14.5L17,12L11.5,9.5M19,15L17.74,17.74L15,19L17.74,20.25L19,23L20.25,20.25L23,19L20.25,17.74" /></g><g id="credit-card"><path d="M20,8H4V6H20M20,18H4V12H20M20,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V6C22,4.89 21.1,4 20,4Z" /></g><g id="credit-card-multiple"><path d="M21,8V6H7V8H21M21,16V11H7V16H21M21,4A2,2 0 0,1 23,6V16A2,2 0 0,1 21,18H7C5.89,18 5,17.1 5,16V6C5,4.89 5.89,4 7,4H21M3,20H18V22H3A2,2 0 0,1 1,20V9H3V20Z" /></g><g id="credit-card-off"><path d="M0.93,4.2L2.21,2.93L20,20.72L18.73,22L16.73,20H4C2.89,20 2,19.1 2,18V6C2,5.78 2.04,5.57 2.11,5.38L0.93,4.2M20,8V6H7.82L5.82,4H20A2,2 0 0,1 22,6V18C22,18.6 21.74,19.13 21.32,19.5L19.82,18H20V12H13.82L9.82,8H20M4,8H4.73L4,7.27V8M4,12V18H14.73L8.73,12H4Z" /></g><g id="credit-card-plus"><path d="M21,18H24V20H21V23H19V20H16V18H19V15H21V18M19,8V6H3V8H19M19,12H3V18H14V20H3C1.89,20 1,19.1 1,18V6C1,4.89 1.89,4 3,4H19A2,2 0 0,1 21,6V13H19V12Z" /></g><g id="credit-card-scan"><path d="M2,4H6V2H2A2,2 0 0,0 0,4V8H2V4M22,2H18V4H22V8H24V4A2,2 0 0,0 22,2M2,16H0V20A2,2 0 0,0 2,22H6V20H2V16M22,20H18V22H22A2,2 0 0,0 24,20V16H22V20M4,8V16A2,2 0 0,0 6,18H18A2,2 0 0,0 20,16V8A2,2 0 0,0 18,6H6A2,2 0 0,0 4,8M6,16V12H18V16H6M18,8V10H6V8H18Z" /></g><g id="crop"><path d="M7,17V1H5V5H1V7H5V17A2,2 0 0,0 7,19H17V23H19V19H23V17M17,15H19V7C19,5.89 18.1,5 17,5H9V7H17V15Z" /></g><g id="crop-free"><path d="M19,3H15V5H19V9H21V5C21,3.89 20.1,3 19,3M19,19H15V21H19A2,2 0 0,0 21,19V15H19M5,15H3V19A2,2 0 0,0 5,21H9V19H5M3,5V9H5V5H9V3H5A2,2 0 0,0 3,5Z" /></g><g id="crop-landscape"><path d="M19,17H5V7H19M19,5H5A2,2 0 0,0 3,7V17A2,2 0 0,0 5,19H19A2,2 0 0,0 21,17V7C21,5.89 20.1,5 19,5Z" /></g><g id="crop-portrait"><path d="M17,19H7V5H17M17,3H7A2,2 0 0,0 5,5V19A2,2 0 0,0 7,21H17A2,2 0 0,0 19,19V5C19,3.89 18.1,3 17,3Z" /></g><g id="crop-rotate"><path d="M7.47,21.5C4.2,19.93 1.86,16.76 1.5,13H0C0.5,19.16 5.66,24 11.95,24C12.18,24 12.39,24 12.61,23.97L8.8,20.15L7.47,21.5M12.05,0C11.82,0 11.61,0 11.39,0.04L15.2,3.85L16.53,2.5C19.8,4.07 22.14,7.24 22.5,11H24C23.5,4.84 18.34,0 12.05,0M16,14H18V8C18,6.89 17.1,6 16,6H10V8H16V14M8,16V4H6V6H4V8H6V16A2,2 0 0,0 8,18H16V20H18V18H20V16H8Z" /></g><g id="crop-square"><path d="M18,18H6V6H18M18,4H6A2,2 0 0,0 4,6V18A2,2 0 0,0 6,20H18A2,2 0 0,0 20,18V6C20,4.89 19.1,4 18,4Z" /></g><g id="crosshairs"><path d="M3.05,13H1V11H3.05C3.5,6.83 6.83,3.5 11,3.05V1H13V3.05C17.17,3.5 20.5,6.83 20.95,11H23V13H20.95C20.5,17.17 17.17,20.5 13,20.95V23H11V20.95C6.83,20.5 3.5,17.17 3.05,13M12,5A7,7 0 0,0 5,12A7,7 0 0,0 12,19A7,7 0 0,0 19,12A7,7 0 0,0 12,5Z" /></g><g id="crosshairs-gps"><path d="M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8M3.05,13H1V11H3.05C3.5,6.83 6.83,3.5 11,3.05V1H13V3.05C17.17,3.5 20.5,6.83 20.95,11H23V13H20.95C20.5,17.17 17.17,20.5 13,20.95V23H11V20.95C6.83,20.5 3.5,17.17 3.05,13M12,5A7,7 0 0,0 5,12A7,7 0 0,0 12,19A7,7 0 0,0 19,12A7,7 0 0,0 12,5Z" /></g><g id="crown"><path d="M5,16L3,5L8.5,12L12,5L15.5,12L21,5L19,16H5M19,19A1,1 0 0,1 18,20H6A1,1 0 0,1 5,19V18H19V19Z" /></g><g id="cube"><path d="M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L6.04,7.5L12,10.85L17.96,7.5L12,4.15Z" /></g><g id="cube-outline"><path d="M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L6.04,7.5L12,10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V9.21L13,12.58V19.29L19,15.91Z" /></g><g id="cube-send"><path d="M16,4L9,8.04V15.96L16,20L23,15.96V8.04M16,6.31L19.8,8.5L16,10.69L12.21,8.5M0,7V9H7V7M11,10.11L15,12.42V17.11L11,14.81M21,10.11V14.81L17,17.11V12.42M2,11V13H7V11M4,15V17H7V15" /></g><g id="cube-unfolded"><path d="M6,9V4H13V9H23V16H18V21H11V16H1V9H6M16,16H13V19H16V16M8,9H11V6H8V9M6,14V11H3V14H6M18,11V14H21V11H18M13,11V14H16V11H13M8,11V14H11V11H8Z" /></g><g id="cup"><path d="M18.32,8H5.67L5.23,4H18.77M3,2L5,20.23C5.13,21.23 5.97,22 7,22H17C18,22 18.87,21.23 19,20.23L21,2H3Z" /></g><g id="cup-off"><path d="M1,4.27L2.28,3L21,21.72L19.73,23L18.27,21.54C17.93,21.83 17.5,22 17,22H7C5.97,22 5.13,21.23 5,20.23L3.53,6.8L1,4.27M18.32,8L18.77,4H5.82L3.82,2H21L19.29,17.47L9.82,8H18.32Z" /></g><g id="cup-water"><path d="M18.32,8H5.67L5.23,4H18.77M12,19A3,3 0 0,1 9,16C9,14 12,10.6 12,10.6C12,10.6 15,14 15,16A3,3 0 0,1 12,19M3,2L5,20.23C5.13,21.23 5.97,22 7,22H17C18,22 18.87,21.23 19,20.23L21,2H3Z" /></g><g id="currency-btc"><path d="M4.5,5H8V2H10V5H11.5V2H13.5V5C19,5 19,11 16,11.25C20,11 21,19 13.5,19V22H11.5V19H10V22H8V19H4.5L5,17H6A1,1 0 0,0 7,16V8A1,1 0 0,0 6,7H4.5V5M10,7V11C10,11 14.5,11.25 14.5,9C14.5,6.75 10,7 10,7M10,12.5V17C10,17 15.5,17 15.5,14.75C15.5,12.5 10,12.5 10,12.5Z" /></g><g id="currency-eur"><path d="M7.07,11L7,12L7.07,13H17.35L16.5,15H7.67C8.8,17.36 11.21,19 14,19C16.23,19 18.22,17.96 19.5,16.33V19.12C18,20.3 16.07,21 14,21C10.08,21 6.75,18.5 5.5,15H2L3,13H5.05L5,12L5.05,11H2L3,9H5.5C6.75,5.5 10.08,3 14,3C16.5,3 18.8,4.04 20.43,5.71L19.57,7.75C18.29,6.08 16.27,5 14,5C11.21,5 8.8,6.64 7.67,9H19.04L18.19,11H7.07Z" /></g><g id="currency-gbp"><path d="M6.5,21V19.75C7.44,19.29 8.24,18.57 8.81,17.7C9.38,16.83 9.67,15.85 9.68,14.75L9.66,13.77L9.57,13H7V11H9.4C9.25,10.17 9.16,9.31 9.13,8.25C9.16,6.61 9.64,5.33 10.58,4.41C11.5,3.5 12.77,3 14.32,3C15.03,3 15.64,3.07 16.13,3.2C16.63,3.32 17,3.47 17.31,3.65L16.76,5.39C16.5,5.25 16.19,5.12 15.79,5C15.38,4.9 14.89,4.85 14.32,4.84C13.25,4.86 12.5,5.19 12,5.83C11.5,6.46 11.29,7.28 11.3,8.28L11.4,9.77L11.6,11H15.5V13H11.79C11.88,14 11.84,15 11.65,15.96C11.35,17.16 10.74,18.18 9.83,19H18V21H6.5Z" /></g><g id="currency-inr"><path d="M8,3H18L17,5H13.74C14.22,5.58 14.58,6.26 14.79,7H18L17,9H15C14.75,11.57 12.74,13.63 10.2,13.96V14H9.5L15.5,21H13L7,14V12H9.5V12C11.26,12 12.72,10.7 12.96,9H7L8,7H12.66C12.1,5.82 10.9,5 9.5,5H7L8,3Z" /></g><g id="currency-ngn"><path d="M4,9H6V3H8L11.42,9H16V3H18V9H20V11H18V13H20V15H18V21H16L12.57,15H8V21H6V15H4V13H6V11H4V9M8,9H9.13L8,7.03V9M8,11V13H11.42L10.28,11H8M16,17V15H14.85L16,17M12.56,11L13.71,13H16V11H12.56Z" /></g><g id="currency-rub"><path d="M6,10H7V3H14.5C17,3 19,5 19,7.5C19,10 17,12 14.5,12H9V14H15V16H9V21H7V16H6V14H7V12H6V10M14.5,5H9V10H14.5A2.5,2.5 0 0,0 17,7.5A2.5,2.5 0 0,0 14.5,5Z" /></g><g id="currency-try"><path d="M19,12A9,9 0 0,1 10,21H8V12.77L5,13.87V11.74L8,10.64V8.87L5,9.96V7.84L8,6.74V3H10V6L15,4.2V6.32L10,8.14V9.92L15,8.1V10.23L10,12.05V19A7,7 0 0,0 17,12H19Z" /></g><g id="currency-usd"><path d="M11.8,10.9C9.53,10.31 8.8,9.7 8.8,8.75C8.8,7.66 9.81,6.9 11.5,6.9C13.28,6.9 13.94,7.75 14,9H16.21C16.14,7.28 15.09,5.7 13,5.19V3H10V5.16C8.06,5.58 6.5,6.84 6.5,8.77C6.5,11.08 8.41,12.23 11.2,12.9C13.7,13.5 14.2,14.38 14.2,15.31C14.2,16 13.71,17.1 11.5,17.1C9.44,17.1 8.63,16.18 8.5,15H6.32C6.44,17.19 8.08,18.42 10,18.83V21H13V18.85C14.95,18.5 16.5,17.35 16.5,15.3C16.5,12.46 14.07,11.5 11.8,10.9Z" /></g><g id="currency-usd-off"><path d="M12.5,6.9C14.28,6.9 14.94,7.75 15,9H17.21C17.14,7.28 16.09,5.7 14,5.19V3H11V5.16C10.47,5.28 9.97,5.46 9.5,5.7L11,7.17C11.4,7 11.9,6.9 12.5,6.9M5.33,4.06L4.06,5.33L7.5,8.77C7.5,10.85 9.06,12 11.41,12.68L14.92,16.19C14.58,16.67 13.87,17.1 12.5,17.1C10.44,17.1 9.63,16.18 9.5,15H7.32C7.44,17.19 9.08,18.42 11,18.83V21H14V18.85C14.96,18.67 15.82,18.3 16.45,17.73L18.67,19.95L19.94,18.68L5.33,4.06Z" /></g><g id="cursor-default"><path d="M13.64,21.97C13.14,22.21 12.54,22 12.31,21.5L10.13,16.76L7.62,18.78C7.45,18.92 7.24,19 7,19A1,1 0 0,1 6,18V3A1,1 0 0,1 7,2C7.24,2 7.47,2.09 7.64,2.23L7.65,2.22L19.14,11.86C19.57,12.22 19.62,12.85 19.27,13.27C19.12,13.45 18.91,13.57 18.7,13.61L15.54,14.23L17.74,18.96C18,19.46 17.76,20.05 17.26,20.28L13.64,21.97Z" /></g><g id="cursor-default-outline"><path d="M10.07,14.27C10.57,14.03 11.16,14.25 11.4,14.75L13.7,19.74L15.5,18.89L13.19,13.91C12.95,13.41 13.17,12.81 13.67,12.58L13.95,12.5L16.25,12.05L8,5.12V15.9L9.82,14.43L10.07,14.27M13.64,21.97C13.14,22.21 12.54,22 12.31,21.5L10.13,16.76L7.62,18.78C7.45,18.92 7.24,19 7,19A1,1 0 0,1 6,18V3A1,1 0 0,1 7,2C7.24,2 7.47,2.09 7.64,2.23L7.65,2.22L19.14,11.86C19.57,12.22 19.62,12.85 19.27,13.27C19.12,13.45 18.91,13.57 18.7,13.61L15.54,14.23L17.74,18.96C18,19.46 17.76,20.05 17.26,20.28L13.64,21.97Z" /></g><g id="cursor-move"><path d="M13,6V11H18V7.75L22.25,12L18,16.25V13H13V18H16.25L12,22.25L7.75,18H11V13H6V16.25L1.75,12L6,7.75V11H11V6H7.75L12,1.75L16.25,6H13Z" /></g><g id="cursor-pointer"><path d="M10,2A2,2 0 0,1 12,4V8.5C12,8.5 14,8.25 14,9.25C14,9.25 16,9 16,10C16,10 18,9.75 18,10.75C18,10.75 20,10.5 20,11.5V15C20,16 17,21 17,22H9C9,22 7,15 4,13C4,13 3,7 8,12V4A2,2 0 0,1 10,2Z" /></g><g id="cursor-text"><path d="M13,19A1,1 0 0,0 14,20H16V22H13.5C12.95,22 12,21.55 12,21C12,21.55 11.05,22 10.5,22H8V20H10A1,1 0 0,0 11,19V5A1,1 0 0,0 10,4H8V2H10.5C11.05,2 12,2.45 12,3C12,2.45 12.95,2 13.5,2H16V4H14A1,1 0 0,0 13,5V19Z" /></g><g id="database"><path d="M12,3C7.58,3 4,4.79 4,7C4,9.21 7.58,11 12,11C16.42,11 20,9.21 20,7C20,4.79 16.42,3 12,3M4,9V12C4,14.21 7.58,16 12,16C16.42,16 20,14.21 20,12V9C20,11.21 16.42,13 12,13C7.58,13 4,11.21 4,9M4,14V17C4,19.21 7.58,21 12,21C16.42,21 20,19.21 20,17V14C20,16.21 16.42,18 12,18C7.58,18 4,16.21 4,14Z" /></g><g id="database-minus"><path d="M9,3C4.58,3 1,4.79 1,7C1,9.21 4.58,11 9,11C13.42,11 17,9.21 17,7C17,4.79 13.42,3 9,3M1,9V12C1,14.21 4.58,16 9,16C13.42,16 17,14.21 17,12V9C17,11.21 13.42,13 9,13C4.58,13 1,11.21 1,9M1,14V17C1,19.21 4.58,21 9,21C10.41,21 11.79,20.81 13,20.46V17.46C11.79,17.81 10.41,18 9,18C4.58,18 1,16.21 1,14M15,17V19H23V17" /></g><g id="database-plus"><path d="M9,3C4.58,3 1,4.79 1,7C1,9.21 4.58,11 9,11C13.42,11 17,9.21 17,7C17,4.79 13.42,3 9,3M1,9V12C1,14.21 4.58,16 9,16C13.42,16 17,14.21 17,12V9C17,11.21 13.42,13 9,13C4.58,13 1,11.21 1,9M1,14V17C1,19.21 4.58,21 9,21C10.41,21 11.79,20.81 13,20.46V17.46C11.79,17.81 10.41,18 9,18C4.58,18 1,16.21 1,14M18,14V17H15V19H18V22H20V19H23V17H20V14" /></g><g id="debug-step-into"><path d="M12,22A2,2 0 0,1 10,20A2,2 0 0,1 12,18A2,2 0 0,1 14,20A2,2 0 0,1 12,22M13,2V13L17.5,8.5L18.92,9.92L12,16.84L5.08,9.92L6.5,8.5L11,13V2H13Z" /></g><g id="debug-step-out"><path d="M12,22A2,2 0 0,1 10,20A2,2 0 0,1 12,18A2,2 0 0,1 14,20A2,2 0 0,1 12,22M13,16H11V6L6.5,10.5L5.08,9.08L12,2.16L18.92,9.08L17.5,10.5L13,6V16Z" /></g><g id="debug-step-over"><path d="M12,14A2,2 0 0,1 14,16A2,2 0 0,1 12,18A2,2 0 0,1 10,16A2,2 0 0,1 12,14M23.46,8.86L21.87,15.75L15,14.16L18.8,11.78C17.39,9.5 14.87,8 12,8C8.05,8 4.77,10.86 4.12,14.63L2.15,14.28C2.96,9.58 7.06,6 12,6C15.58,6 18.73,7.89 20.5,10.72L23.46,8.86Z" /></g><g id="decimal-decrease"><path d="M12,17L15,20V18H21V16H15V14L12,17M9,5A3,3 0 0,1 12,8V11A3,3 0 0,1 9,14A3,3 0 0,1 6,11V8A3,3 0 0,1 9,5M9,7A1,1 0 0,0 8,8V11A1,1 0 0,0 9,12A1,1 0 0,0 10,11V8A1,1 0 0,0 9,7M4,12A1,1 0 0,1 5,13A1,1 0 0,1 4,14A1,1 0 0,1 3,13A1,1 0 0,1 4,12Z" /></g><g id="decimal-increase"><path d="M22,17L19,20V18H13V16H19V14L22,17M9,5A3,3 0 0,1 12,8V11A3,3 0 0,1 9,14A3,3 0 0,1 6,11V8A3,3 0 0,1 9,5M9,7A1,1 0 0,0 8,8V11A1,1 0 0,0 9,12A1,1 0 0,0 10,11V8A1,1 0 0,0 9,7M16,5A3,3 0 0,1 19,8V11A3,3 0 0,1 16,14A3,3 0 0,1 13,11V8A3,3 0 0,1 16,5M16,7A1,1 0 0,0 15,8V11A1,1 0 0,0 16,12A1,1 0 0,0 17,11V8A1,1 0 0,0 16,7M4,12A1,1 0 0,1 5,13A1,1 0 0,1 4,14A1,1 0 0,1 3,13A1,1 0 0,1 4,12Z" /></g><g id="delete"><path d="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z" /></g><g id="delete-circle"><path d="M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M17,7H14.5L13.5,6H10.5L9.5,7H7V9H17V7M9,18H15A1,1 0 0,0 16,17V10H8V17A1,1 0 0,0 9,18Z" /></g><g id="delete-forever"><path d="M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19M8.46,11.88L9.87,10.47L12,12.59L14.12,10.47L15.53,11.88L13.41,14L15.53,16.12L14.12,17.53L12,15.41L9.88,17.53L8.47,16.12L10.59,14L8.46,11.88M15.5,4L14.5,3H9.5L8.5,4H5V6H19V4H15.5Z" /></g><g id="delete-sweep"><path d="M15,16H19V18H15V16M15,8H22V10H15V8M15,12H21V14H15V12M3,18A2,2 0 0,0 5,20H11A2,2 0 0,0 13,18V8H3V18M14,5H11L10,4H6L5,5H2V7H14V5Z" /></g><g id="delete-variant"><path d="M21.03,3L18,20.31C17.83,21.27 17,22 16,22H8C7,22 6.17,21.27 6,20.31L2.97,3H21.03M5.36,5L8,20H16L18.64,5H5.36M9,18V14H13V18H9M13,13.18L9.82,10L13,6.82L16.18,10L13,13.18Z" /></g><g id="delta"><path d="M12,7.77L18.39,18H5.61L12,7.77M12,4L2,20H22" /></g><g id="deskphone"><path d="M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M15,5V19H19V5H15M5,5V9H13V5H5M5,11V13H7V11H5M8,11V13H10V11H8M11,11V13H13V11H11M5,14V16H7V14H5M8,14V16H10V14H8M11,14V16H13V14H11M11,17V19H13V17H11M8,17V19H10V17H8M5,17V19H7V17H5Z" /></g><g id="desktop-mac"><path d="M21,14H3V4H21M21,2H3C1.89,2 1,2.89 1,4V16A2,2 0 0,0 3,18H10L8,21V22H16V21L14,18H21A2,2 0 0,0 23,16V4C23,2.89 22.1,2 21,2Z" /></g><g id="desktop-tower"><path d="M8,2H16A2,2 0 0,1 18,4V20A2,2 0 0,1 16,22H8A2,2 0 0,1 6,20V4A2,2 0 0,1 8,2M8,4V6H16V4H8M16,8H8V10H16V8M16,18H14V20H16V18Z" /></g><g id="details"><path d="M6.38,6H17.63L12,16L6.38,6M3,4L12,20L21,4H3Z" /></g><g id="developer-board"><path d="M22,9V7H20V5A2,2 0 0,0 18,3H4A2,2 0 0,0 2,5V19A2,2 0 0,0 4,21H18A2,2 0 0,0 20,19V17H22V15H20V13H22V11H20V9H22M18,19H4V5H18V19M6,13H11V17H6V13M12,7H16V10H12V7M6,7H11V12H6V7M12,11H16V17H12V11Z" /></g><g id="deviantart"><path d="M6,6H12L14,2H18V6L14.5,13H18V18H12L10,22H6V18L9.5,11H6V6Z" /></g><g id="dialpad"><path d="M12,19A2,2 0 0,0 10,21A2,2 0 0,0 12,23A2,2 0 0,0 14,21A2,2 0 0,0 12,19M6,1A2,2 0 0,0 4,3A2,2 0 0,0 6,5A2,2 0 0,0 8,3A2,2 0 0,0 6,1M6,7A2,2 0 0,0 4,9A2,2 0 0,0 6,11A2,2 0 0,0 8,9A2,2 0 0,0 6,7M6,13A2,2 0 0,0 4,15A2,2 0 0,0 6,17A2,2 0 0,0 8,15A2,2 0 0,0 6,13M18,5A2,2 0 0,0 20,3A2,2 0 0,0 18,1A2,2 0 0,0 16,3A2,2 0 0,0 18,5M12,13A2,2 0 0,0 10,15A2,2 0 0,0 12,17A2,2 0 0,0 14,15A2,2 0 0,0 12,13M18,13A2,2 0 0,0 16,15A2,2 0 0,0 18,17A2,2 0 0,0 20,15A2,2 0 0,0 18,13M18,7A2,2 0 0,0 16,9A2,2 0 0,0 18,11A2,2 0 0,0 20,9A2,2 0 0,0 18,7M12,7A2,2 0 0,0 10,9A2,2 0 0,0 12,11A2,2 0 0,0 14,9A2,2 0 0,0 12,7M12,1A2,2 0 0,0 10,3A2,2 0 0,0 12,5A2,2 0 0,0 14,3A2,2 0 0,0 12,1Z" /></g><g id="diamond"><path d="M16,9H19L14,16M10,9H14L12,17M5,9H8L10,16M15,4H17L19,7H16M11,4H13L14,7H10M7,4H9L8,7H5M6,2L2,8L12,22L22,8L18,2H6Z" /></g><g id="dice-1"><path d="M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10Z" /></g><g id="dice-2"><path d="M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M7,5A2,2 0 0,0 5,7A2,2 0 0,0 7,9A2,2 0 0,0 9,7A2,2 0 0,0 7,5M17,15A2,2 0 0,0 15,17A2,2 0 0,0 17,19A2,2 0 0,0 19,17A2,2 0 0,0 17,15Z" /></g><g id="dice-3"><path d="M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10M7,5A2,2 0 0,0 5,7A2,2 0 0,0 7,9A2,2 0 0,0 9,7A2,2 0 0,0 7,5M17,15A2,2 0 0,0 15,17A2,2 0 0,0 17,19A2,2 0 0,0 19,17A2,2 0 0,0 17,15Z" /></g><g id="dice-4"><path d="M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M7,5A2,2 0 0,0 5,7A2,2 0 0,0 7,9A2,2 0 0,0 9,7A2,2 0 0,0 7,5M17,15A2,2 0 0,0 15,17A2,2 0 0,0 17,19A2,2 0 0,0 19,17A2,2 0 0,0 17,15M17,5A2,2 0 0,0 15,7A2,2 0 0,0 17,9A2,2 0 0,0 19,7A2,2 0 0,0 17,5M7,15A2,2 0 0,0 5,17A2,2 0 0,0 7,19A2,2 0 0,0 9,17A2,2 0 0,0 7,15Z" /></g><g id="dice-5"><path d="M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M7,5A2,2 0 0,0 5,7A2,2 0 0,0 7,9A2,2 0 0,0 9,7A2,2 0 0,0 7,5M17,15A2,2 0 0,0 15,17A2,2 0 0,0 17,19A2,2 0 0,0 19,17A2,2 0 0,0 17,15M17,5A2,2 0 0,0 15,7A2,2 0 0,0 17,9A2,2 0 0,0 19,7A2,2 0 0,0 17,5M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10M7,15A2,2 0 0,0 5,17A2,2 0 0,0 7,19A2,2 0 0,0 9,17A2,2 0 0,0 7,15Z" /></g><g id="dice-6"><path d="M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M7,5A2,2 0 0,0 5,7A2,2 0 0,0 7,9A2,2 0 0,0 9,7A2,2 0 0,0 7,5M17,15A2,2 0 0,0 15,17A2,2 0 0,0 17,19A2,2 0 0,0 19,17A2,2 0 0,0 17,15M17,10A2,2 0 0,0 15,12A2,2 0 0,0 17,14A2,2 0 0,0 19,12A2,2 0 0,0 17,10M17,5A2,2 0 0,0 15,7A2,2 0 0,0 17,9A2,2 0 0,0 19,7A2,2 0 0,0 17,5M7,10A2,2 0 0,0 5,12A2,2 0 0,0 7,14A2,2 0 0,0 9,12A2,2 0 0,0 7,10M7,15A2,2 0 0,0 5,17A2,2 0 0,0 7,19A2,2 0 0,0 9,17A2,2 0 0,0 7,15Z" /></g><g id="dice-d20"><path d="M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L5,8.09V15.91L12,19.85L19,15.91V8.09L12,4.15M14.93,8.27A2.57,2.57 0 0,1 17.5,10.84V13.5C17.5,14.9 16.35,16.05 14.93,16.05C13.5,16.05 12.36,14.9 12.36,13.5V10.84A2.57,2.57 0 0,1 14.93,8.27M14.92,9.71C14.34,9.71 13.86,10.18 13.86,10.77V13.53C13.86,14.12 14.34,14.6 14.92,14.6C15.5,14.6 16,14.12 16,13.53V10.77C16,10.18 15.5,9.71 14.92,9.71M11.45,14.76V15.96L6.31,15.93V14.91C6.31,14.91 9.74,11.58 9.75,10.57C9.75,9.33 8.73,9.46 8.73,9.46C8.73,9.46 7.75,9.5 7.64,10.71L6.14,10.76C6.14,10.76 6.18,8.26 8.83,8.26C11.2,8.26 11.23,10.04 11.23,10.5C11.23,12.18 8.15,14.77 8.15,14.77L11.45,14.76Z" /></g><g id="dice-d4"><path d="M13.43,15.15H14.29V16.36H13.43V18H11.92V16.36H8.82L8.75,15.41L11.91,10.42H13.43V15.15M10.25,15.15H11.92V12.47L10.25,15.15M22,21H2C1.64,21 1.31,20.81 1.13,20.5C0.95,20.18 0.96,19.79 1.15,19.5L11.15,3C11.5,2.38 12.5,2.38 12.86,3L22.86,19.5C23.04,19.79 23.05,20.18 22.87,20.5C22.69,20.81 22.36,21 22,21M3.78,19H20.23L12,5.43L3.78,19Z" /></g><g id="dice-d6"><path d="M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M5,5V19H19V5H5M13.39,9.53C10.89,9.5 10.86,11.53 10.86,11.53C10.86,11.53 11.41,10.87 12.53,10.87C13.19,10.87 14.5,11.45 14.55,13.41C14.61,15.47 12.77,16 12.77,16C12.77,16 9.27,16.86 9.3,12.66C9.33,7.94 13.39,8.33 13.39,8.33V9.53M11.95,12.1C11.21,12 10.83,12.78 10.83,12.78L10.85,13.5C10.85,14.27 11.39,14.83 12,14.83C12.61,14.83 13.05,14.27 13.05,13.5C13.05,12.73 12.56,12.1 11.95,12.1Z" /></g><g id="dice-d8"><path d="M12,23C11.67,23 11.37,22.84 11.18,22.57L4.18,12.57C3.94,12.23 3.94,11.77 4.18,11.43L11.18,1.43C11.55,0.89 12.45,0.89 12.82,1.43L19.82,11.43C20.06,11.77 20.06,12.23 19.82,12.57L12.82,22.57C12.63,22.84 12.33,23 12,23M6.22,12L12,20.26L17.78,12L12,3.74L6.22,12M12,8.25C13.31,8.25 14.38,9.2 14.38,10.38C14.38,11.07 14,11.68 13.44,12.07C14.14,12.46 14.6,13.13 14.6,13.9C14.6,15.12 13.44,16.1 12,16.1C10.56,16.1 9.4,15.12 9.4,13.9C9.4,13.13 9.86,12.46 10.56,12.07C10,11.68 9.63,11.07 9.63,10.38C9.63,9.2 10.69,8.25 12,8.25M12,12.65A1.1,1.1 0 0,0 10.9,13.75A1.1,1.1 0 0,0 12,14.85A1.1,1.1 0 0,0 13.1,13.75A1.1,1.1 0 0,0 12,12.65M12,9.5C11.5,9.5 11.1,9.95 11.1,10.5C11.1,11.05 11.5,11.5 12,11.5C12.5,11.5 12.9,11.05 12.9,10.5C12.9,9.95 12.5,9.5 12,9.5Z" /></g><g id="dictionary"><path d="M5.81,2C4.83,2.09 4,3 4,4V20C4,21.05 4.95,22 6,22H18C19.05,22 20,21.05 20,20V4C20,2.89 19.1,2 18,2H12V9L9.5,7.5L7,9V2H6C5.94,2 5.87,2 5.81,2M12,13H13A1,1 0 0,1 14,14V18H13V16H12V18H11V14A1,1 0 0,1 12,13M12,14V15H13V14H12M15,15H18V16L16,19H18V20H15V19L17,16H15V15Z" /></g><g id="directions"><path d="M14,14.5V12H10V15H8V11A1,1 0 0,1 9,10H14V7.5L17.5,11M21.71,11.29L12.71,2.29H12.7C12.31,1.9 11.68,1.9 11.29,2.29L2.29,11.29C1.9,11.68 1.9,12.32 2.29,12.71L11.29,21.71C11.68,22.09 12.31,22.1 12.71,21.71L21.71,12.71C22.1,12.32 22.1,11.68 21.71,11.29Z" /></g><g id="directions-fork"><path d="M3,4V12.5L6,9.5L9,13C10,14 10,15 10,15V21H14V14C14,14 14,13 13.47,12C12.94,11 12,10 12,10L9,6.58L11.5,4M18,4L13.54,8.47L14,9C14,9 14.93,10 15.47,11C15.68,11.4 15.8,11.79 15.87,12.13L21,7" /></g><g id="discord"><path d="M22,24L16.75,19L17.38,21H4.5A2.5,2.5 0 0,1 2,18.5V3.5A2.5,2.5 0 0,1 4.5,1H19.5A2.5,2.5 0 0,1 22,3.5V24M12,6.8C9.32,6.8 7.44,7.95 7.44,7.95C8.47,7.03 10.27,6.5 10.27,6.5L10.1,6.33C8.41,6.36 6.88,7.53 6.88,7.53C5.16,11.12 5.27,14.22 5.27,14.22C6.67,16.03 8.75,15.9 8.75,15.9L9.46,15C8.21,14.73 7.42,13.62 7.42,13.62C7.42,13.62 9.3,14.9 12,14.9C14.7,14.9 16.58,13.62 16.58,13.62C16.58,13.62 15.79,14.73 14.54,15L15.25,15.9C15.25,15.9 17.33,16.03 18.73,14.22C18.73,14.22 18.84,11.12 17.12,7.53C17.12,7.53 15.59,6.36 13.9,6.33L13.73,6.5C13.73,6.5 15.53,7.03 16.56,7.95C16.56,7.95 14.68,6.8 12,6.8M9.93,10.59C10.58,10.59 11.11,11.16 11.1,11.86C11.1,12.55 10.58,13.13 9.93,13.13C9.29,13.13 8.77,12.55 8.77,11.86C8.77,11.16 9.28,10.59 9.93,10.59M14.1,10.59C14.75,10.59 15.27,11.16 15.27,11.86C15.27,12.55 14.75,13.13 14.1,13.13C13.46,13.13 12.94,12.55 12.94,11.86C12.94,11.16 13.45,10.59 14.1,10.59Z" /></g><g id="disk"><path d="M12,14C10.89,14 10,13.1 10,12C10,10.89 10.89,10 12,10C13.11,10 14,10.89 14,12A2,2 0 0,1 12,14M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z" /></g><g id="disk-alert"><path d="M10,14C8.89,14 8,13.1 8,12C8,10.89 8.89,10 10,10A2,2 0 0,1 12,12A2,2 0 0,1 10,14M10,4A8,8 0 0,0 2,12A8,8 0 0,0 10,20A8,8 0 0,0 18,12A8,8 0 0,0 10,4M20,12H22V7H20M20,16H22V14H20V16Z" /></g><g id="disqus"><path d="M12.08,22C9.63,22 7.39,21.11 5.66,19.63L1.41,20.21L3.05,16.15C2.5,14.88 2.16,13.5 2.16,12C2.16,6.5 6.6,2 12.08,2C17.56,2 22,6.5 22,12C22,17.5 17.56,22 12.08,22M17.5,11.97V11.94C17.5,9.06 15.46,7 11.95,7H8.16V17H11.9C15.43,17 17.5,14.86 17.5,11.97M12,14.54H10.89V9.46H12C13.62,9.46 14.7,10.39 14.7,12V12C14.7,13.63 13.62,14.54 12,14.54Z" /></g><g id="disqus-outline"><path d="M11.9,14.5H10.8V9.5H11.9C13.5,9.5 14.6,10.4 14.6,12C14.6,13.6 13.5,14.5 11.9,14.5M11.9,7H8.1V17H11.8C15.3,17 17.4,14.9 17.4,12V12C17.4,9.1 15.4,7 11.9,7M12,20C10.1,20 8.3,19.3 6.9,18.1L6.2,17.5L4.5,17.7L5.2,16.1L4.9,15.3C4.4,14.2 4.2,13.1 4.2,11.9C4.2,7.5 7.8,3.9 12.1,3.9C16.4,3.9 19.9,7.6 19.9,12C19.9,16.4 16.3,20 12,20M12,2C6.5,2 2.1,6.5 2.1,12C2.1,13.5 2.4,14.9 3,16.2L1.4,20.3L5.7,19.7C7.4,21.2 9.7,22.1 12.1,22.1C17.6,22.1 22,17.6 22,12.1C22,6.6 17.5,2 12,2Z" /></g><g id="division"><path d="M19,13H5V11H19V13M12,5A2,2 0 0,1 14,7A2,2 0 0,1 12,9A2,2 0 0,1 10,7A2,2 0 0,1 12,5M12,15A2,2 0 0,1 14,17A2,2 0 0,1 12,19A2,2 0 0,1 10,17A2,2 0 0,1 12,15Z" /></g><g id="division-box"><path d="M17,13V11H7V13H17M19,3A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5C3.89,21 3,20.1 3,19V5C3,3.89 3.89,3 5,3H19M12,7A1,1 0 0,0 11,8A1,1 0 0,0 12,9A1,1 0 0,0 13,8A1,1 0 0,0 12,7M12,15A1,1 0 0,0 11,16A1,1 0 0,0 12,17A1,1 0 0,0 13,16A1,1 0 0,0 12,15Z" /></g><g id="dna"><path d="M4,2H6V4C6,5.44 6.68,6.61 7.88,7.78C8.74,8.61 9.89,9.41 11.09,10.2L9.26,11.39C8.27,10.72 7.31,10 6.5,9.21C5.07,7.82 4,6.1 4,4V2M18,2H20V4C20,6.1 18.93,7.82 17.5,9.21C16.09,10.59 14.29,11.73 12.54,12.84C10.79,13.96 9.09,15.05 7.88,16.22C6.68,17.39 6,18.56 6,20V22H4V20C4,17.9 5.07,16.18 6.5,14.79C7.91,13.41 9.71,12.27 11.46,11.16C13.21,10.04 14.91,8.95 16.12,7.78C17.32,6.61 18,5.44 18,4V2M14.74,12.61C15.73,13.28 16.69,14 17.5,14.79C18.93,16.18 20,17.9 20,20V22H18V20C18,18.56 17.32,17.39 16.12,16.22C15.26,15.39 14.11,14.59 12.91,13.8L14.74,12.61M7,3H17V4L16.94,4.5H7.06L7,4V3M7.68,6H16.32C16.08,6.34 15.8,6.69 15.42,7.06L14.91,7.5H9.07L8.58,7.06C8.2,6.69 7.92,6.34 7.68,6M9.09,16.5H14.93L15.42,16.94C15.8,17.31 16.08,17.66 16.32,18H7.68C7.92,17.66 8.2,17.31 8.58,16.94L9.09,16.5M7.06,19.5H16.94L17,20V21H7V20L7.06,19.5Z" /></g><g id="dns"><path d="M7,9A2,2 0 0,1 5,7A2,2 0 0,1 7,5A2,2 0 0,1 9,7A2,2 0 0,1 7,9M20,3H4A1,1 0 0,0 3,4V10A1,1 0 0,0 4,11H20A1,1 0 0,0 21,10V4A1,1 0 0,0 20,3M7,19A2,2 0 0,1 5,17A2,2 0 0,1 7,15A2,2 0 0,1 9,17A2,2 0 0,1 7,19M20,13H4A1,1 0 0,0 3,14V20A1,1 0 0,0 4,21H20A1,1 0 0,0 21,20V14A1,1 0 0,0 20,13Z" /></g><g id="do-not-disturb"><path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M17,13H7V11H17V13Z" /></g><g id="do-not-disturb-off"><path d="M17,11V13H15.54L20.22,17.68C21.34,16.07 22,14.11 22,12A10,10 0 0,0 12,2C9.89,2 7.93,2.66 6.32,3.78L13.54,11H17M2.27,2.27L1,3.54L3.78,6.32C2.66,7.93 2,9.89 2,12A10,10 0 0,0 12,22C14.11,22 16.07,21.34 17.68,20.22L20.46,23L21.73,21.73L2.27,2.27M7,13V11H8.46L10.46,13H7Z" /></g><g id="dolby"><path d="M2,5V19H22V5H2M6,17H4V7H6C8.86,7.09 11.1,9.33 11,12C11.1,14.67 8.86,16.91 6,17M20,17H18C15.14,16.91 12.9,14.67 13,12C12.9,9.33 15.14,7.09 18,7H20V17Z" /></g><g id="domain"><path d="M18,15H16V17H18M18,11H16V13H18M20,19H12V17H14V15H12V13H14V11H12V9H20M10,7H8V5H10M10,11H8V9H10M10,15H8V13H10M10,19H8V17H10M6,7H4V5H6M6,11H4V9H6M6,15H4V13H6M6,19H4V17H6M12,7V3H2V21H22V7H12Z" /></g><g id="dots-horizontal"><path d="M16,12A2,2 0 0,1 18,10A2,2 0 0,1 20,12A2,2 0 0,1 18,14A2,2 0 0,1 16,12M10,12A2,2 0 0,1 12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12M4,12A2,2 0 0,1 6,10A2,2 0 0,1 8,12A2,2 0 0,1 6,14A2,2 0 0,1 4,12Z" /></g><g id="dots-vertical"><path d="M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z" /></g><g id="douban"><path d="M20,6H4V4H20V6M20,18V20H4V18H7.33L6.26,14H5V8H19V14H17.74L16.67,18H20M7,12H17V10H7V12M9.4,18H14.6L15.67,14H8.33L9.4,18Z" /></g><g id="download"><path d="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z" /></g><g id="drag"><path d="M7,19V17H9V19H7M11,19V17H13V19H11M15,19V17H17V19H15M7,15V13H9V15H7M11,15V13H13V15H11M15,15V13H17V15H15M7,11V9H9V11H7M11,11V9H13V11H11M15,11V9H17V11H15M7,7V5H9V7H7M11,7V5H13V7H11M15,7V5H17V7H15Z" /></g><g id="drag-horizontal"><path d="M3,15V13H5V15H3M3,11V9H5V11H3M7,15V13H9V15H7M7,11V9H9V11H7M11,15V13H13V15H11M11,11V9H13V11H11M15,15V13H17V15H15M15,11V9H17V11H15M19,15V13H21V15H19M19,11V9H21V11H19Z" /></g><g id="drag-vertical"><path d="M9,3H11V5H9V3M13,3H15V5H13V3M9,7H11V9H9V7M13,7H15V9H13V7M9,11H11V13H9V11M13,11H15V13H13V11M9,15H11V17H9V15M13,15H15V17H13V15M9,19H11V21H9V19M13,19H15V21H13V19Z" /></g><g id="drawing"><path d="M8.5,3A5.5,5.5 0 0,1 14,8.5C14,9.83 13.53,11.05 12.74,12H21V21H12V12.74C11.05,13.53 9.83,14 8.5,14A5.5,5.5 0 0,1 3,8.5A5.5,5.5 0 0,1 8.5,3Z" /></g><g id="drawing-box"><path d="M18,18H12V12.21C11.34,12.82 10.47,13.2 9.5,13.2C7.46,13.2 5.8,11.54 5.8,9.5A3.7,3.7 0 0,1 9.5,5.8C11.54,5.8 13.2,7.46 13.2,9.5C13.2,10.47 12.82,11.34 12.21,12H18M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z" /></g><g id="dribbble"><path d="M16.42,18.42C16,16.5 15.5,14.73 15,13.17C15.5,13.1 16,13.06 16.58,13.06H16.6V13.06H16.6C17.53,13.06 18.55,13.18 19.66,13.43C19.28,15.5 18.08,17.27 16.42,18.42M12,19.8C10.26,19.8 8.66,19.23 7.36,18.26C7.64,17.81 8.23,16.94 9.18,16.04C10.14,15.11 11.5,14.15 13.23,13.58C13.82,15.25 14.36,17.15 14.77,19.29C13.91,19.62 13,19.8 12,19.8M4.2,12C4.2,11.96 4.2,11.93 4.2,11.89C4.42,11.9 4.71,11.9 5.05,11.9H5.06C6.62,11.89 9.36,11.76 12.14,10.89C12.29,11.22 12.44,11.56 12.59,11.92C10.73,12.54 9.27,13.53 8.19,14.5C7.16,15.46 6.45,16.39 6.04,17C4.9,15.66 4.2,13.91 4.2,12M8.55,5C9.1,5.65 10.18,7.06 11.34,9.25C9,9.96 6.61,10.12 5.18,10.12C5.14,10.12 5.1,10.12 5.06,10.12H5.05C4.81,10.12 4.6,10.12 4.43,10.11C5,7.87 6.5,6 8.55,5M12,4.2C13.84,4.2 15.53,4.84 16.86,5.91C15.84,7.14 14.5,8 13.03,8.65C12,6.67 11,5.25 10.34,4.38C10.88,4.27 11.43,4.2 12,4.2M18.13,7.18C19.1,8.42 19.71,9.96 19.79,11.63C18.66,11.39 17.6,11.28 16.6,11.28V11.28H16.59C15.79,11.28 15.04,11.35 14.33,11.47C14.16,11.05 14,10.65 13.81,10.26C15.39,9.57 16.9,8.58 18.13,7.18M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" /></g><g id="dribbble-box"><path d="M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M15.09,16.5C14.81,15.14 14.47,13.91 14.08,12.82L15.2,12.74H15.22V12.74C15.87,12.74 16.59,12.82 17.36,13C17.09,14.44 16.26,15.69 15.09,16.5M12,17.46C10.79,17.46 9.66,17.06 8.76,16.39C8.95,16.07 9.36,15.46 10,14.83C10.7,14.18 11.64,13.5 12.86,13.11C13.28,14.27 13.65,15.6 13.94,17.1C13.33,17.33 12.68,17.46 12,17.46M6.54,12V11.92L7.14,11.93V11.93C8.24,11.93 10.15,11.83 12.1,11.22L12.41,11.94C11.11,12.38 10.09,13.07 9.34,13.76C8.61,14.42 8.12,15.08 7.83,15.5C7.03,14.56 6.54,13.34 6.54,12M9.59,7.11C9.97,7.56 10.73,8.54 11.54,10.08C9.89,10.57 8.23,10.68 7.22,10.68H7.14V10.68H6.7C7.09,9.11 8.17,7.81 9.59,7.11M12,6.54C13.29,6.54 14.47,7 15.41,7.74C14.69,8.6 13.74,9.22 12.72,9.66C12,8.27 11.31,7.28 10.84,6.67C11.21,6.59 11.6,6.54 12,6.54M16.29,8.63C16.97,9.5 17.4,10.57 17.45,11.74C16.66,11.58 15.92,11.5 15.22,11.5V11.5C14.66,11.5 14.13,11.54 13.63,11.63L13.27,10.78C14.37,10.3 15.43,9.61 16.29,8.63M12,5A7,7 0 0,0 5,12A7,7 0 0,0 12,19A7,7 0 0,0 19,12A7,7 0 0,0 12,5Z" /></g><g id="drone"><path d="M22,11H21L20,9H13.75L16,12.5H14L10.75,9H4C3.45,9 2,8.55 2,8C2,7.45 3.5,5.5 5.5,5.5C7.5,5.5 7.67,6.5 9,7H21A1,1 0 0,1 22,8V9L22,11M10.75,6.5L14,3H16L13.75,6.5H10.75M18,11V9.5H19.75L19,11H18M3,19A1,1 0 0,1 2,18A1,1 0 0,1 3,17A4,4 0 0,1 7,21A1,1 0 0,1 6,22A1,1 0 0,1 5,21A2,2 0 0,0 3,19M11,21A1,1 0 0,1 10,22A1,1 0 0,1 9,21A6,6 0 0,0 3,15A1,1 0 0,1 2,14A1,1 0 0,1 3,13A8,8 0 0,1 11,21Z" /></g><g id="dropbox"><path d="M12,14.56L16.35,18.16L18.2,16.95V18.3L12,22L5.82,18.3V16.95L7.68,18.16L12,14.56M7.68,2.5L12,6.09L16.32,2.5L22.5,6.5L18.23,9.94L22.5,13.36L16.32,17.39L12,13.78L7.68,17.39L1.5,13.36L5.77,9.94L1.5,6.5L7.68,2.5M12,13.68L18.13,9.94L12,6.19L5.87,9.94L12,13.68Z" /></g><g id="drupal"><path d="M20.47,14.65C20.47,15.29 20.25,16.36 19.83,17.1C19.4,17.85 19.08,18.06 18.44,18.06C17.7,17.95 16.31,15.82 15.36,15.72C14.18,15.72 11.73,18.17 9.71,18.17C8.54,18.17 8.11,17.95 7.79,17.74C7.15,17.31 6.94,16.67 6.94,15.82C6.94,14.22 8.43,12.84 10.24,12.84C12.59,12.84 14.18,15.18 15.36,15.08C16.31,15.08 18.23,13.16 19.19,13.16C20.15,12.95 20.47,14 20.47,14.65M16.63,5.28C15.57,4.64 14.61,4.32 13.54,3.68C12.91,3.25 12.05,2.3 11.31,1.44C11,2.83 10.78,3.36 10.24,3.79C9.18,4.53 8.64,4.85 7.69,5.28C6.94,5.7 3,8.05 3,13.16C3,18.27 7.37,22 12.05,22C16.85,22 21,18.5 21,13.27C21.21,8.05 17.27,5.7 16.63,5.28Z" /></g><g id="duck"><path d="M8.5,5A1.5,1.5 0 0,0 7,6.5A1.5,1.5 0 0,0 8.5,8A1.5,1.5 0 0,0 10,6.5A1.5,1.5 0 0,0 8.5,5M10,2A5,5 0 0,1 15,7C15,8.7 14.15,10.2 12.86,11.1C14.44,11.25 16.22,11.61 18,12.5C21,14 22,12 22,12C22,12 21,21 15,21H9C9,21 4,21 4,16C4,13 7,12 6,10C2,10 2,6.5 2,6.5C3,7 4.24,7 5,6.65C5.19,4.05 7.36,2 10,2Z" /></g><g id="dumbbell"><path d="M4.22,14.12L3.5,13.41C2.73,12.63 2.73,11.37 3.5,10.59C4.3,9.8 5.56,9.8 6.34,10.59L8.92,13.16L13.16,8.92L10.59,6.34C9.8,5.56 9.8,4.3 10.59,3.5C11.37,2.73 12.63,2.73 13.41,3.5L14.12,4.22L19.78,9.88L20.5,10.59C21.27,11.37 21.27,12.63 20.5,13.41C19.7,14.2 18.44,14.2 17.66,13.41L15.08,10.84L10.84,15.08L13.41,17.66C14.2,18.44 14.2,19.7 13.41,20.5C12.63,21.27 11.37,21.27 10.59,20.5L9.88,19.78L4.22,14.12M3.16,19.42L4.22,18.36L2.81,16.95C2.42,16.56 2.42,15.93 2.81,15.54C3.2,15.15 3.83,15.15 4.22,15.54L8.46,19.78C8.85,20.17 8.85,20.8 8.46,21.19C8.07,21.58 7.44,21.58 7.05,21.19L5.64,19.78L4.58,20.84L3.16,19.42M19.42,3.16L20.84,4.58L19.78,5.64L21.19,7.05C21.58,7.44 21.58,8.07 21.19,8.46C20.8,8.86 20.17,8.86 19.78,8.46L15.54,4.22C15.15,3.83 15.15,3.2 15.54,2.81C15.93,2.42 16.56,2.42 16.95,2.81L18.36,4.22L19.42,3.16Z" /></g><g id="earth"><path d="M17.9,17.39C17.64,16.59 16.89,16 16,16H15V13A1,1 0 0,0 14,12H8V10H10A1,1 0 0,0 11,9V7H13A2,2 0 0,0 15,5V4.59C17.93,5.77 20,8.64 20,12C20,14.08 19.2,15.97 17.9,17.39M11,19.93C7.05,19.44 4,16.08 4,12C4,11.38 4.08,10.78 4.21,10.21L9,15V16A2,2 0 0,0 11,18M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" /></g><g id="earth-off"><path d="M22,5.27L20.5,6.75C21.46,8.28 22,10.07 22,12A10,10 0 0,1 12,22C10.08,22 8.28,21.46 6.75,20.5L5.27,22L4,20.72L20.72,4L22,5.27M17.9,17.39C19.2,15.97 20,14.08 20,12C20,10.63 19.66,9.34 19.05,8.22L14.83,12.44C14.94,12.6 15,12.79 15,13V16H16C16.89,16 17.64,16.59 17.9,17.39M11,19.93V18C10.5,18 10.07,17.83 9.73,17.54L8.22,19.05C9.07,19.5 10,19.8 11,19.93M15,4.59V5A2,2 0 0,1 13,7H11V9A1,1 0 0,1 10,10H8V12H10.18L8.09,14.09L4.21,10.21C4.08,10.78 4,11.38 4,12C4,13.74 4.56,15.36 5.5,16.67L4.08,18.1C2.77,16.41 2,14.3 2,12A10,10 0 0,1 12,2C14.3,2 16.41,2.77 18.1,4.08L16.67,5.5C16.16,5.14 15.6,4.83 15,4.59Z" /></g><g id="edge"><path d="M2.74,10.81C3.83,-1.36 22.5,-1.36 21.2,13.56H8.61C8.61,17.85 14.42,19.21 19.54,16.31V20.53C13.25,23.88 5,21.43 5,14.09C5,8.58 9.97,6.81 9.97,6.81C9.97,6.81 8.58,8.58 8.54,10.05H15.7C15.7,2.93 5.9,5.57 2.74,10.81Z" /></g><g id="eject"><path d="M12,5L5.33,15H18.67M5,17H19V19H5V17Z" /></g><g id="elevation-decline"><path d="M21,21H3V11.25L9.45,15L13.22,12.8L21,17.29V21M3,8.94V6.75L9.45,10.5L13.22,8.3L21,12.79V15L13.22,10.5L9.45,12.67L3,8.94Z" /></g><g id="elevation-rise"><path d="M3,21V17.29L10.78,12.8L14.55,15L21,11.25V21H3M21,8.94L14.55,12.67L10.78,10.5L3,15V12.79L10.78,8.3L14.55,10.5L21,6.75V8.94Z" /></g><g id="elevator"><path d="M7,2L11,6H8V10H6V6H3L7,2M17,10L13,6H16V2H18V6H21L17,10M7,12H17A2,2 0 0,1 19,14V20A2,2 0 0,1 17,22H7A2,2 0 0,1 5,20V14A2,2 0 0,1 7,12M7,14V20H17V14H7Z" /></g><g id="email"><path d="M20,8L12,13L4,8V6L12,11L20,6M20,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V6C22,4.89 21.1,4 20,4Z" /></g><g id="email-open"><path d="M4,8L12,13L20,8V8L12,3L4,8V8M22,8V18A2,2 0 0,1 20,20H4A2,2 0 0,1 2,18V8C2,7.27 2.39,6.64 2.97,6.29L12,0.64L21.03,6.29C21.61,6.64 22,7.27 22,8Z" /></g><g id="email-open-outline"><path d="M12,15.36L4,10.36V18H20V10.36L12,15.36M4,8L12,13L20,8V8L12,3L4,8V8M22,8V18A2,2 0 0,1 20,20H4A2,2 0 0,1 2,18V8C2,7.27 2.39,6.64 2.97,6.29L12,0.64L21.03,6.29C21.61,6.64 22,7.27 22,8Z" /></g><g id="email-outline"><path d="M20,4H4A2,2 0 0,0 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V6A2,2 0 0,0 20,4M20,18H4V8L12,13L20,8V18M20,6L12,11L4,6V6H20V6Z" /></g><g id="email-secure"><path d="M20.5,0A2.5,2.5 0 0,1 23,2.5V3A1,1 0 0,1 24,4V8A1,1 0 0,1 23,9H18A1,1 0 0,1 17,8V4A1,1 0 0,1 18,3V2.5A2.5,2.5 0 0,1 20.5,0M12,11L4,6V8L12,13L16.18,10.39C16.69,10.77 17.32,11 18,11H22V18A2,2 0 0,1 20,20H4A2,2 0 0,1 2,18V6A2,2 0 0,1 4,4H15V8C15,8.36 15.06,8.7 15.18,9L12,11M20.5,1A1.5,1.5 0 0,0 19,2.5V3H22V2.5A1.5,1.5 0 0,0 20.5,1Z" /></g><g id="email-variant"><path d="M12,13L2,6.76V6C2,4.89 2.89,4 4,4H20A2,2 0 0,1 22,6V6.75L12,13M22,18A2,2 0 0,1 20,20H4C2.89,20 2,19.1 2,18V9.11L4,10.36V18H20V10.36L22,9.11V18Z" /></g><g id="emby"><path d="M11,2L6,7L7,8L2,13L7,18L8,17L13,22L18,17L17,16L22,11L17,6L16,7L11,2M10,8.5L16,12L10,15.5V8.5Z" /></g><g id="emoticon"><path d="M12,17.5C14.33,17.5 16.3,16.04 17.11,14H6.89C7.69,16.04 9.67,17.5 12,17.5M8.5,11A1.5,1.5 0 0,0 10,9.5A1.5,1.5 0 0,0 8.5,8A1.5,1.5 0 0,0 7,9.5A1.5,1.5 0 0,0 8.5,11M15.5,11A1.5,1.5 0 0,0 17,9.5A1.5,1.5 0 0,0 15.5,8A1.5,1.5 0 0,0 14,9.5A1.5,1.5 0 0,0 15.5,11M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20M12,2C6.47,2 2,6.5 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" /></g><g id="emoticon-cool"><path d="M19,10C19,11.38 16.88,12.5 15.5,12.5C14.12,12.5 12.75,11.38 12.75,10H11.25C11.25,11.38 9.88,12.5 8.5,12.5C7.12,12.5 5,11.38 5,10H4.25C4.09,10.64 4,11.31 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12C20,11.31 19.91,10.64 19.75,10H19M12,4C9.04,4 6.45,5.61 5.07,8H18.93C17.55,5.61 14.96,4 12,4M22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12M12,17.23C10.25,17.23 8.71,16.5 7.81,15.42L9.23,14C9.68,14.72 10.75,15.23 12,15.23C13.25,15.23 14.32,14.72 14.77,14L16.19,15.42C15.29,16.5 13.75,17.23 12,17.23Z" /></g><g id="emoticon-dead"><path d="M12,2C6.47,2 2,6.47 2,12C2,17.53 6.47,22 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20M16.18,7.76L15.12,8.82L14.06,7.76L13,8.82L14.06,9.88L13,10.94L14.06,12L15.12,10.94L16.18,12L17.24,10.94L16.18,9.88L17.24,8.82L16.18,7.76M7.82,12L8.88,10.94L9.94,12L11,10.94L9.94,9.88L11,8.82L9.94,7.76L8.88,8.82L7.82,7.76L6.76,8.82L7.82,9.88L6.76,10.94L7.82,12M12,14C9.67,14 7.69,15.46 6.89,17.5H17.11C16.31,15.46 14.33,14 12,14Z" /></g><g id="emoticon-devil"><path d="M1.5,2.09C2.4,3 3.87,3.73 5.69,4.25C7.41,2.84 9.61,2 12,2C14.39,2 16.59,2.84 18.31,4.25C20.13,3.73 21.6,3 22.5,2.09C22.47,3.72 21.65,5.21 20.28,6.4C21.37,8 22,9.92 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12C2,9.92 2.63,8 3.72,6.4C2.35,5.21 1.53,3.72 1.5,2.09M20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12M10.5,10C10.5,10.8 9.8,11.5 9,11.5C8.2,11.5 7.5,10.8 7.5,10V8.5L10.5,10M16.5,10C16.5,10.8 15.8,11.5 15,11.5C14.2,11.5 13.5,10.8 13.5,10L16.5,8.5V10M12,17.23C10.25,17.23 8.71,16.5 7.81,15.42L9.23,14C9.68,14.72 10.75,15.23 12,15.23C13.25,15.23 14.32,14.72 14.77,14L16.19,15.42C15.29,16.5 13.75,17.23 12,17.23Z" /></g><g id="emoticon-excited"><path d="M12,2C6.47,2 2,6.47 2,12C2,17.53 6.47,22 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20M13,9.94L14.06,11L15.12,9.94L16.18,11L17.24,9.94L15.12,7.82L13,9.94M8.88,9.94L9.94,11L11,9.94L8.88,7.82L6.76,9.94L7.82,11L8.88,9.94M12,17.5C14.33,17.5 16.31,16.04 17.11,14H6.89C7.69,16.04 9.67,17.5 12,17.5Z" /></g><g id="emoticon-happy"><path d="M20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12M22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12M10,9.5C10,10.3 9.3,11 8.5,11C7.7,11 7,10.3 7,9.5C7,8.7 7.7,8 8.5,8C9.3,8 10,8.7 10,9.5M17,9.5C17,10.3 16.3,11 15.5,11C14.7,11 14,10.3 14,9.5C14,8.7 14.7,8 15.5,8C16.3,8 17,8.7 17,9.5M12,17.23C10.25,17.23 8.71,16.5 7.81,15.42L9.23,14C9.68,14.72 10.75,15.23 12,15.23C13.25,15.23 14.32,14.72 14.77,14L16.19,15.42C15.29,16.5 13.75,17.23 12,17.23Z" /></g><g id="emoticon-neutral"><path d="M8.5,11A1.5,1.5 0 0,1 7,9.5A1.5,1.5 0 0,1 8.5,8A1.5,1.5 0 0,1 10,9.5A1.5,1.5 0 0,1 8.5,11M15.5,11A1.5,1.5 0 0,1 14,9.5A1.5,1.5 0 0,1 15.5,8A1.5,1.5 0 0,1 17,9.5A1.5,1.5 0 0,1 15.5,11M12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22C6.47,22 2,17.5 2,12A10,10 0 0,1 12,2M9,14H15A1,1 0 0,1 16,15A1,1 0 0,1 15,16H9A1,1 0 0,1 8,15A1,1 0 0,1 9,14Z" /></g><g id="emoticon-poop"><path d="M9,11C9.55,11 10,11.9 10,13C10,14.1 9.55,15 9,15C8.45,15 8,14.1 8,13C8,11.9 8.45,11 9,11M15,11C15.55,11 16,11.9 16,13C16,14.1 15.55,15 15,15C14.45,15 14,14.1 14,13C14,11.9 14.45,11 15,11M9.75,1.75C9.75,1.75 16,4 15,8C15,8 19,8 17.25,11.5C17.25,11.5 21.46,11.94 20.28,15.34C19,16.53 18.7,16.88 17.5,17.75L20.31,16.14C21.35,16.65 24.37,18.47 21,21C17,24 11,21.25 9,21.25C7,21.25 5,22 4,22C3,22 2,21 2,19C2,17 4,16 5,16C5,16 2,13 7,11C7,11 5,8 9,7C9,7 8,6 9,5C10,4 9.75,2.75 9.75,1.75M8,17C9.33,18.17 10.67,19.33 12,19.33C13.33,19.33 14.67,18.17 16,17H8M9,10C7.9,10 7,11.34 7,13C7,14.66 7.9,16 9,16C10.1,16 11,14.66 11,13C11,11.34 10.1,10 9,10M15,10C13.9,10 13,11.34 13,13C13,14.66 13.9,16 15,16C16.1,16 17,14.66 17,13C17,11.34 16.1,10 15,10Z" /></g><g id="emoticon-sad"><path d="M20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12M22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12M15.5,8C16.3,8 17,8.7 17,9.5C17,10.3 16.3,11 15.5,11C14.7,11 14,10.3 14,9.5C14,8.7 14.7,8 15.5,8M10,9.5C10,10.3 9.3,11 8.5,11C7.7,11 7,10.3 7,9.5C7,8.7 7.7,8 8.5,8C9.3,8 10,8.7 10,9.5M12,14C13.75,14 15.29,14.72 16.19,15.81L14.77,17.23C14.32,16.5 13.25,16 12,16C10.75,16 9.68,16.5 9.23,17.23L7.81,15.81C8.71,14.72 10.25,14 12,14Z" /></g><g id="emoticon-tongue"><path d="M9,8A2,2 0 0,1 11,10C11,10.36 10.9,10.71 10.73,11C10.39,10.4 9.74,10 9,10C8.26,10 7.61,10.4 7.27,11C7.1,10.71 7,10.36 7,10A2,2 0 0,1 9,8M15,8A2,2 0 0,1 17,10C17,10.36 16.9,10.71 16.73,11C16.39,10.4 15.74,10 15,10C14.26,10 13.61,10.4 13.27,11C13.1,10.71 13,10.36 13,10A2,2 0 0,1 15,8M12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22C6.47,22 2,17.5 2,12A10,10 0 0,1 12,2M9,13H15A1,1 0 0,1 16,14A1,1 0 0,1 15,15C15,17 14.1,18 13,18C11.9,18 11,17 11,15H9A1,1 0 0,1 8,14A1,1 0 0,1 9,13Z" /></g><g id="engine"><path d="M7,4V6H10V8H7L5,10V13H3V10H1V18H3V15H5V18H8L10,20H18V16H20V19H23V9H20V12H18V8H12V6H15V4H7Z" /></g><g id="engine-outline"><path d="M8,10H16V18H11L9,16H7V11M7,4V6H10V8H7L5,10V13H3V10H1V18H3V15H5V18H8L10,20H18V16H20V19H23V9H20V12H18V8H12V6H15V4H7Z" /></g><g id="equal"><path d="M19,10H5V8H19V10M19,16H5V14H19V16Z" /></g><g id="equal-box"><path d="M17,16V14H7V16H17M19,3A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5C3.89,21 3,20.1 3,19V5C3,3.89 3.89,3 5,3H19M17,10V8H7V10H17Z" /></g><g id="eraser"><path d="M16.24,3.56L21.19,8.5C21.97,9.29 21.97,10.55 21.19,11.34L12,20.53C10.44,22.09 7.91,22.09 6.34,20.53L2.81,17C2.03,16.21 2.03,14.95 2.81,14.16L13.41,3.56C14.2,2.78 15.46,2.78 16.24,3.56M4.22,15.58L7.76,19.11C8.54,19.9 9.8,19.9 10.59,19.11L14.12,15.58L9.17,10.63L4.22,15.58Z" /></g><g id="eraser-variant"><path d="M15.14,3C14.63,3 14.12,3.2 13.73,3.59L2.59,14.73C1.81,15.5 1.81,16.77 2.59,17.56L5.03,20H12.69L21.41,11.27C22.2,10.5 22.2,9.23 21.41,8.44L16.56,3.59C16.17,3.2 15.65,3 15.14,3M17,18L15,20H22V18" /></g><g id="escalator"><path d="M20,8H18.95L6.95,20H4A2,2 0 0,1 2,18A2,2 0 0,1 4,16H5.29L7,14.29V10A1,1 0 0,1 8,9H9A1,1 0 0,1 10,10V11.29L17.29,4H20A2,2 0 0,1 22,6A2,2 0 0,1 20,8M8.5,5A1.5,1.5 0 0,1 10,6.5A1.5,1.5 0 0,1 8.5,8A1.5,1.5 0 0,1 7,6.5A1.5,1.5 0 0,1 8.5,5Z" /></g><g id="ethernet"><path d="M7,15H9V18H11V15H13V18H15V15H17V18H19V9H15V6H9V9H5V18H7V15M4.38,3H19.63C20.94,3 22,4.06 22,5.38V19.63A2.37,2.37 0 0,1 19.63,22H4.38C3.06,22 2,20.94 2,19.63V5.38C2,4.06 3.06,3 4.38,3Z" /></g><g id="ethernet-cable"><path d="M11,3V7H13V3H11M8,4V11H16V4H14V8H10V4H8M10,12V22H14V12H10Z" /></g><g id="ethernet-cable-off"><path d="M11,3H13V7H11V3M8,4H10V8H14V4H16V11H12.82L8,6.18V4M20,20.72L18.73,22L14,17.27V22H10V13.27L2,5.27L3.28,4L20,20.72Z" /></g><g id="etsy"><path d="M6.72,20.78C8.23,20.71 10.07,20.78 11.87,20.78C13.72,20.78 15.62,20.66 17.12,20.78C17.72,20.83 18.28,21.19 18.77,20.87C19.16,20.38 18.87,19.71 18.96,19.05C19.12,17.78 20.28,16.27 18.59,15.95C17.87,16.61 18.35,17.23 17.95,18.05C17.45,19.03 15.68,19.37 14,19.5C12.54,19.62 10,19.76 9.5,18.77C9.04,17.94 9.29,16.65 9.29,15.58C9.29,14.38 9.16,13.22 9.5,12.3C11.32,12.43 13.7,11.69 15,12.5C15.87,13 15.37,14.06 16.38,14.4C17.07,14.21 16.7,13.32 16.66,12.5C16.63,11.94 16.63,11.19 16.66,10.57C16.69,9.73 17,8.76 16.1,8.74C15.39,9.3 15.93,10.23 15.18,10.75C14.95,10.92 14.43,11 14.08,11C12.7,11.17 10.54,11.05 9.38,10.84C9.23,9.16 9.24,6.87 9.38,5.19C10,4.57 11.45,4.54 12.42,4.55C14.13,4.55 16.79,4.7 17.3,5.55C17.58,6 17.36,7 17.85,7.1C18.85,7.33 18.36,5.55 18.41,4.73C18.44,4.11 18.71,3.72 18.59,3.27C18.27,2.83 17.79,3.05 17.5,3.09C14.35,3.5 9.6,3.27 6.26,3.27C5.86,3.27 5.16,3.07 4.88,3.54C4.68,4.6 6.12,4.16 6.62,4.73C6.79,4.91 7.03,5.73 7.08,6.28C7.23,7.74 7.08,9.97 7.08,12.12C7.08,14.38 7.26,16.67 7.08,18.05C7,18.53 6.73,19.3 6.62,19.41C6,20.04 4.34,19.35 4.5,20.69C5.09,21.08 5.93,20.82 6.72,20.78Z" /></g><g id="ev-station"><path d="M19.77,7.23L19.78,7.22L16.06,3.5L15,4.56L17.11,6.67C16.17,7.03 15.5,7.93 15.5,9A2.5,2.5 0 0,0 18,11.5C18.36,11.5 18.69,11.42 19,11.29V18.5A1,1 0 0,1 18,19.5A1,1 0 0,1 17,18.5V14A2,2 0 0,0 15,12H14V5A2,2 0 0,0 12,3H6A2,2 0 0,0 4,5V21H14V13.5H15.5V18.5A2.5,2.5 0 0,0 18,21A2.5,2.5 0 0,0 20.5,18.5V9C20.5,8.31 20.22,7.68 19.77,7.23M18,10A1,1 0 0,1 17,9A1,1 0 0,1 18,8A1,1 0 0,1 19,9A1,1 0 0,1 18,10M8,18V13.5H6L10,6V11H12L8,18Z" /></g><g id="evernote"><path d="M15.09,11.63C15.09,11.63 15.28,10.35 16,10.35C16.76,10.35 17.78,12.06 17.78,12.06C17.78,12.06 15.46,11.63 15.09,11.63M19,4.69C18.64,4.09 16.83,3.41 15.89,3.41C14.96,3.41 13.5,3.41 13.5,3.41C13.5,3.41 12.7,2 10.88,2C9.05,2 9.17,2.81 9.17,3.5V6.32L8.34,7.19H4.5C4.5,7.19 3.44,7.91 3.44,9.44C3.44,11 3.92,16.35 7.13,16.85C10.93,17.43 11.58,15.67 11.58,15.46C11.58,14.56 11.6,13.21 11.6,13.21C11.6,13.21 12.71,15.33 14.39,15.33C16.07,15.33 17.04,16.3 17.04,17.29C17.04,18.28 17.04,19.13 17.04,19.13C17.04,19.13 17,20.28 16,20.28C15,20.28 13.89,20.28 13.89,20.28C13.89,20.28 13.2,19.74 13.2,19C13.2,18.25 13.53,18.05 13.93,18.05C14.32,18.05 14.65,18.09 14.65,18.09V16.53C14.65,16.53 11.47,16.5 11.47,18.94C11.47,21.37 13.13,22 14.46,22C15.8,22 16.63,22 16.63,22C16.63,22 20.56,21.5 20.56,13.75C20.56,6 19.33,5.28 19,4.69M7.5,6.31H4.26L8.32,2.22V5.5L7.5,6.31Z" /></g><g id="exclamation"><path d="M11,4.5H13V15.5H11V4.5M13,17.5V19.5H11V17.5H13Z" /></g><g id="exit-to-app"><path d="M19,3H5C3.89,3 3,3.89 3,5V9H5V5H19V19H5V15H3V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3M10.08,15.58L11.5,17L16.5,12L11.5,7L10.08,8.41L12.67,11H3V13H12.67L10.08,15.58Z" /></g><g id="export"><path d="M23,12L19,8V11H10V13H19V16M1,18V6C1,4.89 1.9,4 3,4H15A2,2 0 0,1 17,6V9H15V6H3V18H15V15H17V18A2,2 0 0,1 15,20H3A2,2 0 0,1 1,18Z" /></g><g id="eye"><path d="M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9M12,17A5,5 0 0,1 7,12A5,5 0 0,1 12,7A5,5 0 0,1 17,12A5,5 0 0,1 12,17M12,4.5C7,4.5 2.73,7.61 1,12C2.73,16.39 7,19.5 12,19.5C17,19.5 21.27,16.39 23,12C21.27,7.61 17,4.5 12,4.5Z" /></g><g id="eye-off"><path d="M11.83,9L15,12.16C15,12.11 15,12.05 15,12A3,3 0 0,0 12,9C11.94,9 11.89,9 11.83,9M7.53,9.8L9.08,11.35C9.03,11.56 9,11.77 9,12A3,3 0 0,0 12,15C12.22,15 12.44,14.97 12.65,14.92L14.2,16.47C13.53,16.8 12.79,17 12,17A5,5 0 0,1 7,12C7,11.21 7.2,10.47 7.53,9.8M2,4.27L4.28,6.55L4.73,7C3.08,8.3 1.78,10 1,12C2.73,16.39 7,19.5 12,19.5C13.55,19.5 15.03,19.2 16.38,18.66L16.81,19.08L19.73,22L21,20.73L3.27,3M12,7A5,5 0 0,1 17,12C17,12.64 16.87,13.26 16.64,13.82L19.57,16.75C21.07,15.5 22.27,13.86 23,12C21.27,7.61 17,4.5 12,4.5C10.6,4.5 9.26,4.75 8,5.2L10.17,7.35C10.74,7.13 11.35,7 12,7Z" /></g><g id="eyedropper"><path d="M19.35,11.72L17.22,13.85L15.81,12.43L8.1,20.14L3.5,22L2,20.5L3.86,15.9L11.57,8.19L10.15,6.78L12.28,4.65L19.35,11.72M16.76,3C17.93,1.83 19.83,1.83 21,3C22.17,4.17 22.17,6.07 21,7.24L19.08,9.16L14.84,4.92L16.76,3M5.56,17.03L4.5,19.5L6.97,18.44L14.4,11L13,9.6L5.56,17.03Z" /></g><g id="eyedropper-variant"><path d="M6.92,19L5,17.08L13.06,9L15,10.94M20.71,5.63L18.37,3.29C18,2.9 17.35,2.9 16.96,3.29L13.84,6.41L11.91,4.5L10.5,5.91L11.92,7.33L3,16.25V21H7.75L16.67,12.08L18.09,13.5L19.5,12.09L17.58,10.17L20.7,7.05C21.1,6.65 21.1,6 20.71,5.63Z" /></g><g id="face"><path d="M9,11.75A1.25,1.25 0 0,0 7.75,13A1.25,1.25 0 0,0 9,14.25A1.25,1.25 0 0,0 10.25,13A1.25,1.25 0 0,0 9,11.75M15,11.75A1.25,1.25 0 0,0 13.75,13A1.25,1.25 0 0,0 15,14.25A1.25,1.25 0 0,0 16.25,13A1.25,1.25 0 0,0 15,11.75M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,20C7.59,20 4,16.41 4,12C4,11.71 4,11.42 4.05,11.14C6.41,10.09 8.28,8.16 9.26,5.77C11.07,8.33 14.05,10 17.42,10C18.2,10 18.95,9.91 19.67,9.74C19.88,10.45 20,11.21 20,12C20,16.41 16.41,20 12,20Z" /></g><g id="face-profile"><path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,8.39C13.57,9.4 15.42,10 17.42,10C18.2,10 18.95,9.91 19.67,9.74C19.88,10.45 20,11.21 20,12C20,16.41 16.41,20 12,20C9,20 6.39,18.34 5,15.89L6.75,14V13A1.25,1.25 0 0,1 8,11.75A1.25,1.25 0 0,1 9.25,13V14H12M16,11.75A1.25,1.25 0 0,0 14.75,13A1.25,1.25 0 0,0 16,14.25A1.25,1.25 0 0,0 17.25,13A1.25,1.25 0 0,0 16,11.75Z" /></g><g id="facebook"><path d="M17,2V2H17V6H15C14.31,6 14,6.81 14,7.5V10H14L17,10V14H14V22H10V14H7V10H10V6A4,4 0 0,1 14,2H17Z" /></g><g id="facebook-box"><path d="M19,4V7H17A1,1 0 0,0 16,8V10H19V13H16V20H13V13H11V10H13V7.5C13,5.56 14.57,4 16.5,4M20,2H4A2,2 0 0,0 2,4V20A2,2 0 0,0 4,22H20A2,2 0 0,0 22,20V4C22,2.89 21.1,2 20,2Z" /></g><g id="facebook-messenger"><path d="M12,2C6.5,2 2,6.14 2,11.25C2,14.13 3.42,16.7 5.65,18.4L5.71,22L9.16,20.12L9.13,20.11C10.04,20.36 11,20.5 12,20.5C17.5,20.5 22,16.36 22,11.25C22,6.14 17.5,2 12,2M13.03,14.41L10.54,11.78L5.5,14.41L10.88,8.78L13.46,11.25L18.31,8.78L13.03,14.41Z" /></g><g id="factory"><path d="M4,18V20H8V18H4M4,14V16H14V14H4M10,18V20H14V18H10M16,14V16H20V14H16M16,18V20H20V18H16M2,22V8L7,12V8L12,12V8L17,12L18,2H21L22,12V22H2Z" /></g><g id="fan"><path d="M12,11A1,1 0 0,0 11,12A1,1 0 0,0 12,13A1,1 0 0,0 13,12A1,1 0 0,0 12,11M12.5,2C17,2 17.11,5.57 14.75,6.75C13.76,7.24 13.32,8.29 13.13,9.22C13.61,9.42 14.03,9.73 14.35,10.13C18.05,8.13 22.03,8.92 22.03,12.5C22.03,17 18.46,17.1 17.28,14.73C16.78,13.74 15.72,13.3 14.79,13.11C14.59,13.59 14.28,14 13.88,14.34C15.87,18.03 15.08,22 11.5,22C7,22 6.91,18.42 9.27,17.24C10.25,16.75 10.69,15.71 10.89,14.79C10.4,14.59 9.97,14.27 9.65,13.87C5.96,15.85 2,15.07 2,11.5C2,7 5.56,6.89 6.74,9.26C7.24,10.25 8.29,10.68 9.22,10.87C9.41,10.39 9.73,9.97 10.14,9.65C8.15,5.96 8.94,2 12.5,2Z" /></g><g id="fast-forward"><path d="M13,6V18L21.5,12M4,18L12.5,12L4,6V18Z" /></g><g id="fax"><path d="M6,2A1,1 0 0,0 5,3V7H6V5H8V4H6V3H8V2H6M11,2A1,1 0 0,0 10,3V7H11V5H12V7H13V3A1,1 0 0,0 12,2H11M15,2L16.42,4.5L15,7H16.13L17,5.5L17.87,7H19L17.58,4.5L19,2H17.87L17,3.5L16.13,2H15M11,3H12V4H11V3M5,9A3,3 0 0,0 2,12V18H6V22H18V18H22V12A3,3 0 0,0 19,9H5M19,11A1,1 0 0,1 20,12A1,1 0 0,1 19,13A1,1 0 0,1 18,12A1,1 0 0,1 19,11M8,15H16V20H8V15Z" /></g><g id="ferry"><path d="M6,6H18V9.96L12,8L6,9.96M3.94,19H4C5.6,19 7,18.12 8,17C9,18.12 10.4,19 12,19C13.6,19 15,18.12 16,17C17,18.12 18.4,19 20,19H20.05L21.95,12.31C22.03,12.06 22,11.78 21.89,11.54C21.76,11.3 21.55,11.12 21.29,11.04L20,10.62V6C20,4.89 19.1,4 18,4H15V1H9V4H6A2,2 0 0,0 4,6V10.62L2.71,11.04C2.45,11.12 2.24,11.3 2.11,11.54C2,11.78 1.97,12.06 2.05,12.31M20,21C18.61,21 17.22,20.53 16,19.67C13.56,21.38 10.44,21.38 8,19.67C6.78,20.53 5.39,21 4,21H2V23H4C5.37,23 6.74,22.65 8,22C10.5,23.3 13.5,23.3 16,22C17.26,22.65 18.62,23 20,23H22V21H20Z" /></g><g id="file"><path d="M13,9V3.5L18.5,9M6,2C4.89,2 4,2.89 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2H6Z" /></g><g id="file-chart"><path d="M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M7,20H9V14H7V20M11,20H13V12H11V20M15,20H17V16H15V20Z" /></g><g id="file-check"><path d="M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M10.45,18.46L15.2,13.71L14.03,12.3L10.45,15.88L8.86,14.3L7.7,15.46L10.45,18.46Z" /></g><g id="file-cloud"><path d="M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M15.68,15C15.34,13.3 13.82,12 12,12C10.55,12 9.3,12.82 8.68,14C7.17,14.18 6,15.45 6,17A3,3 0 0,0 9,20H15.5A2.5,2.5 0 0,0 18,17.5C18,16.18 16.97,15.11 15.68,15Z" /></g><g id="file-delimited"><path d="M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M14,15V11H10V15H12.3C12.6,17 12,18 9.7,19.38L10.85,20.2C13,19 14,16 14,15Z" /></g><g id="file-document"><path d="M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M15,18V16H6V18H15M18,14V12H6V14H18Z" /></g><g id="file-document-box"><path d="M14,17H7V15H14M17,13H7V11H17M17,9H7V7H17M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z" /></g><g id="file-excel"><path d="M6,2H14L20,8V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V4A2,2 0 0,1 6,2M13,3.5V9H18.5L13,3.5M17,11H13V13H14L12,14.67L10,13H11V11H7V13H8L11,15.5L8,18H7V20H11V18H10L12,16.33L14,18H13V20H17V18H16L13,15.5L16,13H17V11Z" /></g><g id="file-excel-box"><path d="M16.2,17H14.2L12,13.2L9.8,17H7.8L11,12L7.8,7H9.8L12,10.8L14.2,7H16.2L13,12M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z" /></g><g id="file-export"><path d="M6,2C4.89,2 4,2.9 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M13,3.5L18.5,9H13M8.93,12.22H16V19.29L13.88,17.17L11.05,20L8.22,17.17L11.05,14.35" /></g><g id="file-find"><path d="M9,13A3,3 0 0,0 12,16A3,3 0 0,0 15,13A3,3 0 0,0 12,10A3,3 0 0,0 9,13M20,19.59V8L14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18C18.45,22 18.85,21.85 19.19,21.6L14.76,17.17C13.96,17.69 13,18 12,18A5,5 0 0,1 7,13A5,5 0 0,1 12,8A5,5 0 0,1 17,13C17,14 16.69,14.96 16.17,15.75L20,19.59Z" /></g><g id="file-hidden"><path d="M13,9H14V11H11V7H13V9M18.5,9L16.38,6.88L17.63,5.63L20,8V10H18V11H15V9H18.5M13,3.5V2H12V4H13V6H11V4H9V2H8V4H6V5H4V4C4,2.89 4.89,2 6,2H14L16.36,4.36L15.11,5.61L13,3.5M20,20A2,2 0 0,1 18,22H16V20H18V19H20V20M18,15H20V18H18V15M12,22V20H15V22H12M8,22V20H11V22H8M6,22C4.89,22 4,21.1 4,20V18H6V20H7V22H6M4,14H6V17H4V14M4,10H6V13H4V10M18,11H20V14H18V11M4,6H6V9H4V6Z" /></g><g id="file-image"><path d="M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M6,20H15L18,20V12L14,16L12,14L6,20M8,9A2,2 0 0,0 6,11A2,2 0 0,0 8,13A2,2 0 0,0 10,11A2,2 0 0,0 8,9Z" /></g><g id="file-import"><path d="M6,2C4.89,2 4,2.9 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M13,3.5L18.5,9H13M10.05,11.22L12.88,14.05L15,11.93V19H7.93L10.05,16.88L7.22,14.05" /></g><g id="file-lock"><path d="M6,2C4.89,2 4,2.9 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2H6M13,3.5L18.5,9H13V3.5M12,11A3,3 0 0,1 15,14V15H16V19H8V15H9V14C9,12.36 10.34,11 12,11M12,13A1,1 0 0,0 11,14V15H13V14C13,13.47 12.55,13 12,13Z" /></g><g id="file-multiple"><path d="M15,7H20.5L15,1.5V7M8,0H16L22,6V18A2,2 0 0,1 20,20H8C6.89,20 6,19.1 6,18V2A2,2 0 0,1 8,0M4,4V22H20V24H4A2,2 0 0,1 2,22V4H4Z" /></g><g id="file-music"><path d="M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M9,16A2,2 0 0,0 7,18A2,2 0 0,0 9,20A2,2 0 0,0 11,18V13H14V11H10V16.27C9.71,16.1 9.36,16 9,16Z" /></g><g id="file-outline"><path d="M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M11,4H6V20H11L18,20V11H11V4Z" /></g><g id="file-pdf"><path d="M14,9H19.5L14,3.5V9M7,2H15L21,8V20A2,2 0 0,1 19,22H7C5.89,22 5,21.1 5,20V4A2,2 0 0,1 7,2M11.93,12.44C12.34,13.34 12.86,14.08 13.46,14.59L13.87,14.91C13,15.07 11.8,15.35 10.53,15.84V15.84L10.42,15.88L10.92,14.84C11.37,13.97 11.7,13.18 11.93,12.44M18.41,16.25C18.59,16.07 18.68,15.84 18.69,15.59C18.72,15.39 18.67,15.2 18.57,15.04C18.28,14.57 17.53,14.35 16.29,14.35L15,14.42L14.13,13.84C13.5,13.32 12.93,12.41 12.53,11.28L12.57,11.14C12.9,9.81 13.21,8.2 12.55,7.54C12.39,7.38 12.17,7.3 11.94,7.3H11.7C11.33,7.3 11,7.69 10.91,8.07C10.54,9.4 10.76,10.13 11.13,11.34V11.35C10.88,12.23 10.56,13.25 10.05,14.28L9.09,16.08L8.2,16.57C7,17.32 6.43,18.16 6.32,18.69C6.28,18.88 6.3,19.05 6.37,19.23L6.4,19.28L6.88,19.59L7.32,19.7C8.13,19.7 9.05,18.75 10.29,16.63L10.47,16.56C11.5,16.23 12.78,16 14.5,15.81C15.53,16.32 16.74,16.55 17.5,16.55C17.94,16.55 18.24,16.44 18.41,16.25M18,15.54L18.09,15.65C18.08,15.75 18.05,15.76 18,15.78H17.96L17.77,15.8C17.31,15.8 16.6,15.61 15.87,15.29C15.96,15.19 16,15.19 16.1,15.19C17.5,15.19 17.9,15.44 18,15.54M8.83,17C8.18,18.19 7.59,18.85 7.14,19C7.19,18.62 7.64,17.96 8.35,17.31L8.83,17M11.85,10.09C11.62,9.19 11.61,8.46 11.78,8.04L11.85,7.92L12,7.97C12.17,8.21 12.19,8.53 12.09,9.07L12.06,9.23L11.9,10.05L11.85,10.09Z" /></g><g id="file-pdf-box"><path d="M11.43,10.94C11.2,11.68 10.87,12.47 10.42,13.34C10.22,13.72 10,14.08 9.92,14.38L10.03,14.34V14.34C11.3,13.85 12.5,13.57 13.37,13.41C13.22,13.31 13.08,13.2 12.96,13.09C12.36,12.58 11.84,11.84 11.43,10.94M17.91,14.75C17.74,14.94 17.44,15.05 17,15.05C16.24,15.05 15,14.82 14,14.31C12.28,14.5 11,14.73 9.97,15.06C9.92,15.08 9.86,15.1 9.79,15.13C8.55,17.25 7.63,18.2 6.82,18.2C6.66,18.2 6.5,18.16 6.38,18.09L5.9,17.78L5.87,17.73C5.8,17.55 5.78,17.38 5.82,17.19C5.93,16.66 6.5,15.82 7.7,15.07C7.89,14.93 8.19,14.77 8.59,14.58C8.89,14.06 9.21,13.45 9.55,12.78C10.06,11.75 10.38,10.73 10.63,9.85V9.84C10.26,8.63 10.04,7.9 10.41,6.57C10.5,6.19 10.83,5.8 11.2,5.8H11.44C11.67,5.8 11.89,5.88 12.05,6.04C12.71,6.7 12.4,8.31 12.07,9.64C12.05,9.7 12.04,9.75 12.03,9.78C12.43,10.91 13,11.82 13.63,12.34C13.89,12.54 14.18,12.74 14.5,12.92C14.95,12.87 15.38,12.85 15.79,12.85C17.03,12.85 17.78,13.07 18.07,13.54C18.17,13.7 18.22,13.89 18.19,14.09C18.18,14.34 18.09,14.57 17.91,14.75M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3M17.5,14.04C17.4,13.94 17,13.69 15.6,13.69C15.53,13.69 15.46,13.69 15.37,13.79C16.1,14.11 16.81,14.3 17.27,14.3C17.34,14.3 17.4,14.29 17.46,14.28H17.5C17.55,14.26 17.58,14.25 17.59,14.15C17.57,14.12 17.55,14.08 17.5,14.04M8.33,15.5C8.12,15.62 7.95,15.73 7.85,15.81C7.14,16.46 6.69,17.12 6.64,17.5C7.09,17.35 7.68,16.69 8.33,15.5M11.35,8.59L11.4,8.55C11.47,8.23 11.5,7.95 11.56,7.73L11.59,7.57C11.69,7 11.67,6.71 11.5,6.47L11.35,6.42C11.33,6.45 11.3,6.5 11.28,6.54C11.11,6.96 11.12,7.69 11.35,8.59Z" /></g><g id="file-powerpoint"><path d="M6,2H14L20,8V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V4A2,2 0 0,1 6,2M13,3.5V9H18.5L13,3.5M8,11V13H9V19H8V20H12V19H11V17H13A3,3 0 0,0 16,14A3,3 0 0,0 13,11H8M13,13A1,1 0 0,1 14,14A1,1 0 0,1 13,15H11V13H13Z" /></g><g id="file-powerpoint-box"><path d="M9.8,13.4H12.3C13.8,13.4 14.46,13.12 15.1,12.58C15.74,12.03 16,11.25 16,10.23C16,9.26 15.75,8.5 15.1,7.88C14.45,7.29 13.83,7 12.3,7H8V17H9.8V13.4M19,3A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5C3,3.89 3.9,3 5,3H19M9.8,12V8.4H12.1C12.76,8.4 13.27,8.65 13.6,9C13.93,9.35 14.1,9.72 14.1,10.24C14.1,10.8 13.92,11.19 13.6,11.5C13.28,11.81 12.9,12 12.22,12H9.8Z" /></g><g id="file-presentation-box"><path d="M19,16H5V8H19M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z" /></g><g id="file-restore"><path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M12,18C9.95,18 8.19,16.76 7.42,15H9.13C9.76,15.9 10.81,16.5 12,16.5A3.5,3.5 0 0,0 15.5,13A3.5,3.5 0 0,0 12,9.5C10.65,9.5 9.5,10.28 8.9,11.4L10.5,13H6.5V9L7.8,10.3C8.69,8.92 10.23,8 12,8A5,5 0 0,1 17,13A5,5 0 0,1 12,18Z" /></g><g id="file-send"><path d="M14,2H6C4.89,2 4,2.89 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M12.54,19.37V17.37H8.54V15.38H12.54V13.38L15.54,16.38L12.54,19.37M13,9V3.5L18.5,9H13Z" /></g><g id="file-tree"><path d="M3,3H9V7H3V3M15,10H21V14H15V10M15,17H21V21H15V17M13,13H7V18H13V20H7L5,20V9H7V11H13V13Z" /></g><g id="file-video"><path d="M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M17,19V13L14,15.2V13H7V19H14V16.8L17,19Z" /></g><g id="file-word"><path d="M6,2H14L20,8V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V4A2,2 0 0,1 6,2M13,3.5V9H18.5L13,3.5M7,13L8.5,20H10.5L12,17L13.5,20H15.5L17,13H18V11H14V13H15L14.1,17.2L13,15V15H11V15L9.9,17.2L9,13H10V11H6V13H7Z" /></g><g id="file-word-box"><path d="M15.5,17H14L12,9.5L10,17H8.5L6.1,7H7.8L9.34,14.5L11.3,7H12.7L14.67,14.5L16.2,7H17.9M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z" /></g><g id="file-xml"><path d="M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M6.12,15.5L9.86,19.24L11.28,17.83L8.95,15.5L11.28,13.17L9.86,11.76L6.12,15.5M17.28,15.5L13.54,11.76L12.12,13.17L14.45,15.5L12.12,17.83L13.54,19.24L17.28,15.5Z" /></g><g id="film"><path d="M3.5,3H5V1.8C5,1.36 5.36,1 5.8,1H10.2C10.64,1 11,1.36 11,1.8V3H12.5A1.5,1.5 0 0,1 14,4.5V5H22V20H14V20.5A1.5,1.5 0 0,1 12.5,22H3.5A1.5,1.5 0 0,1 2,20.5V4.5A1.5,1.5 0 0,1 3.5,3M18,7V9H20V7H18M14,7V9H16V7H14M10,7V9H12V7H10M14,16V18H16V16H14M18,16V18H20V16H18M10,16V18H12V16H10Z" /></g><g id="filmstrip"><path d="M18,9H16V7H18M18,13H16V11H18M18,17H16V15H18M8,9H6V7H8M8,13H6V11H8M8,17H6V15H8M18,3V5H16V3H8V5H6V3H4V21H6V19H8V21H16V19H18V21H20V3H18Z" /></g><g id="filmstrip-off"><path d="M1,4.27L2.28,3L21,21.72L19.73,23L16,19.27V21H8V19H6V21H4V7.27L1,4.27M18,9V7H16V9H18M18,13V11H16V13H18M18,15H16.82L6.82,5H8V3H16V5H18V3H20V18.18L18,16.18V15M8,13V11.27L7.73,11H6V13H8M8,17V15H6V17H8M6,3V4.18L4.82,3H6Z" /></g><g id="filter"><path d="M3,2H21V2H21V4H20.92L14,10.92V22.91L10,18.91V10.91L3.09,4H3V2Z" /></g><g id="filter-outline"><path d="M3,2H21V2H21V4H20.92L15,9.92V22.91L9,16.91V9.91L3.09,4H3V2M11,16.08L13,18.08V9H13.09L18.09,4H5.92L10.92,9H11V16.08Z" /></g><g id="filter-remove"><path d="M14.76,20.83L17.6,18L14.76,15.17L16.17,13.76L19,16.57L21.83,13.76L23.24,15.17L20.43,18L23.24,20.83L21.83,22.24L19,19.4L16.17,22.24L14.76,20.83M2,2H20V2H20V4H19.92L13,10.92V22.91L9,18.91V10.91L2.09,4H2V2Z" /></g><g id="filter-remove-outline"><path d="M14.73,20.83L17.58,18L14.73,15.17L16.15,13.76L19,16.57L21.8,13.76L23.22,15.17L20.41,18L23.22,20.83L21.8,22.24L19,19.4L16.15,22.24L14.73,20.83M2,2H20V2H20V4H19.92L14,9.92V22.91L8,16.91V9.91L2.09,4H2V2M10,16.08L12,18.08V9H12.09L17.09,4H4.92L9.92,9H10V16.08Z" /></g><g id="filter-variant"><path d="M6,13H18V11H6M3,6V8H21V6M10,18H14V16H10V18Z" /></g><g id="fingerprint"><path d="M11.83,1.73C8.43,1.79 6.23,3.32 6.23,3.32C5.95,3.5 5.88,3.91 6.07,4.19C6.27,4.5 6.66,4.55 6.96,4.34C6.96,4.34 11.27,1.15 17.46,4.38C17.75,4.55 18.14,4.45 18.31,4.15C18.5,3.85 18.37,3.47 18.03,3.28C16.36,2.4 14.78,1.96 13.36,1.8C12.83,1.74 12.32,1.72 11.83,1.73M12.22,4.34C6.26,4.26 3.41,9.05 3.41,9.05C3.22,9.34 3.3,9.72 3.58,9.91C3.87,10.1 4.26,10 4.5,9.68C4.5,9.68 6.92,5.5 12.2,5.59C17.5,5.66 19.82,9.65 19.82,9.65C20,9.94 20.38,10.04 20.68,9.87C21,9.69 21.07,9.31 20.9,9C20.9,9 18.15,4.42 12.22,4.34M11.5,6.82C9.82,6.94 8.21,7.55 7,8.56C4.62,10.53 3.1,14.14 4.77,19C4.88,19.33 5.24,19.5 5.57,19.39C5.89,19.28 6.07,18.92 5.95,18.6V18.6C4.41,14.13 5.78,11.2 7.8,9.5C9.77,7.89 13.25,7.5 15.84,9.1C17.11,9.9 18.1,11.28 18.6,12.64C19.11,14 19.08,15.32 18.67,15.94C18.25,16.59 17.4,16.83 16.65,16.64C15.9,16.45 15.29,15.91 15.26,14.77C15.23,13.06 13.89,12 12.5,11.84C11.16,11.68 9.61,12.4 9.21,14C8.45,16.92 10.36,21.07 14.78,22.45C15.11,22.55 15.46,22.37 15.57,22.04C15.67,21.71 15.5,21.35 15.15,21.25C11.32,20.06 9.87,16.43 10.42,14.29C10.66,13.33 11.5,13 12.38,13.08C13.25,13.18 14,13.7 14,14.79C14.05,16.43 15.12,17.54 16.34,17.85C17.56,18.16 18.97,17.77 19.72,16.62C20.5,15.45 20.37,13.8 19.78,12.21C19.18,10.61 18.07,9.03 16.5,8.04C14.96,7.08 13.19,6.7 11.5,6.82M11.86,9.25V9.26C10.08,9.32 8.3,10.24 7.28,12.18C5.96,14.67 6.56,17.21 7.44,19.04C8.33,20.88 9.54,22.1 9.54,22.1C9.78,22.35 10.17,22.35 10.42,22.11C10.67,21.87 10.67,21.5 10.43,21.23C10.43,21.23 9.36,20.13 8.57,18.5C7.78,16.87 7.3,14.81 8.38,12.77C9.5,10.67 11.5,10.16 13.26,10.67C15.04,11.19 16.53,12.74 16.5,15.03C16.46,15.38 16.71,15.68 17.06,15.7C17.4,15.73 17.7,15.47 17.73,15.06C17.79,12.2 15.87,10.13 13.61,9.47C13.04,9.31 12.45,9.23 11.86,9.25M12.08,14.25C11.73,14.26 11.46,14.55 11.47,14.89C11.47,14.89 11.5,16.37 12.31,17.8C13.15,19.23 14.93,20.59 18.03,20.3C18.37,20.28 18.64,20 18.62,19.64C18.6,19.29 18.3,19.03 17.91,19.06C15.19,19.31 14.04,18.28 13.39,17.17C12.74,16.07 12.72,14.88 12.72,14.88C12.72,14.53 12.44,14.25 12.08,14.25Z" /></g><g id="fire"><path d="M11.71,19C9.93,19 8.5,17.59 8.5,15.86C8.5,14.24 9.53,13.1 11.3,12.74C13.07,12.38 14.9,11.53 15.92,10.16C16.31,11.45 16.5,12.81 16.5,14.2C16.5,16.84 14.36,19 11.71,19M13.5,0.67C13.5,0.67 14.24,3.32 14.24,5.47C14.24,7.53 12.89,9.2 10.83,9.2C8.76,9.2 7.2,7.53 7.2,5.47L7.23,5.1C5.21,7.5 4,10.61 4,14A8,8 0 0,0 12,22A8,8 0 0,0 20,14C20,8.6 17.41,3.8 13.5,0.67Z" /></g><g id="firefox"><path d="M21,11.7C21,11.3 20.9,10.7 20.8,10.3C20.5,8.6 19.6,7.1 18.5,5.9C18.3,5.6 17.9,5.3 17.5,5C16.4,4.1 15.1,3.5 13.6,3.2C10.6,2.7 7.6,3.7 5.6,5.8C5.6,5.8 5.6,5.7 5.6,5.7C5.5,5.5 5.5,5.5 5.4,5.5C5.4,5.5 5.4,5.5 5.4,5.5C5.3,5.3 5.3,5.2 5.2,5.1C5.2,5.1 5.2,5.1 5.2,5.1C5.2,4.9 5.1,4.7 5.1,4.5C4.8,4.6 4.8,4.9 4.7,5.1C4.7,5.1 4.7,5.1 4.7,5.1C4.5,5.3 4.3,5.6 4.3,5.9C4.3,5.9 4.3,5.9 4.3,5.9C4.2,6.1 4,7 4.2,7.1C4.2,7.1 4.2,7.1 4.3,7.1C4.3,7.2 4.3,7.3 4.3,7.4C4.1,7.6 4,7.7 4,7.8C3.7,8.4 3.4,8.9 3.3,9.5C3.3,9.7 3.3,9.8 3.3,9.9C3.3,9.9 3.3,9.9 3.3,9.9C3.3,9.9 3.3,9.8 3.3,9.8C3.1,10 3.1,10.3 3,10.5C3.1,10.5 3.1,10.4 3.2,10.4C2.7,13 3.4,15.7 5,17.7C7.4,20.6 11.5,21.8 15.1,20.5C18.6,19.2 21,15.8 21,12C21,11.9 21,11.8 21,11.7M13.5,4.1C15,4.4 16.4,5.1 17.5,6.1C17.6,6.2 17.7,6.4 17.7,6.4C17.4,6.1 16.7,5.6 16.3,5.8C16.4,6.1 17.6,7.6 17.7,7.7C17.7,7.7 18,9 18.1,9.1C18.1,9.1 18.1,9.1 18.1,9.1C18.1,9.1 18.1,9.1 18.1,9.1C18.1,9.3 17.4,11.9 17.4,12.3C17.4,12.4 16.5,14.2 16.6,14.2C16.3,14.9 16,14.9 15.9,15C15.8,15 15.2,15.2 14.5,15.4C13.9,15.5 13.2,15.7 12.7,15.6C12.4,15.6 12,15.6 11.7,15.4C11.6,15.3 10.8,14.9 10.6,14.8C10.3,14.7 10.1,14.5 9.9,14.3C10.2,14.3 10.8,14.3 11,14.3C11.6,14.2 14.2,13.3 14.1,12.9C14.1,12.6 13.6,12.4 13.4,12.2C13.1,12.1 11.9,12.3 11.4,12.5C11.4,12.5 9.5,12 9,11.6C9,11.5 8.9,10.9 8.9,10.8C8.8,10.7 9.2,10.4 9.2,10.4C9.2,10.4 10.2,9.4 10.2,9.3C10.4,9.3 10.6,9.1 10.7,9C10.6,9 10.8,8.9 11.1,8.7C11.1,8.7 11.1,8.7 11.1,8.7C11.4,8.5 11.6,8.5 11.6,8.2C11.6,8.2 12.1,7.3 11.5,7.4C11.5,7.4 10.6,7.3 10.3,7.3C10,7.5 9.9,7.4 9.6,7.3C9.6,7.3 9.4,7.1 9.4,7C9.5,6.8 10.2,5.3 10.5,5.2C10.3,4.8 9.3,5.1 9.1,5.4C9.1,5.4 8.3,6 7.9,6.1C7.9,6.1 7.9,6.1 7.9,6.1C7.9,6 7.4,5.9 6.9,5.9C8.7,4.4 11.1,3.7 13.5,4.1Z" /></g><g id="fish"><path d="M12,20L12.76,17C9.5,16.79 6.59,15.4 5.75,13.58C5.66,14.06 5.53,14.5 5.33,14.83C4.67,16 3.33,16 2,16C3.1,16 3.5,14.43 3.5,12.5C3.5,10.57 3.1,9 2,9C3.33,9 4.67,9 5.33,10.17C5.53,10.5 5.66,10.94 5.75,11.42C6.4,10 8.32,8.85 10.66,8.32L9,5C11,5 13,5 14.33,5.67C15.46,6.23 16.11,7.27 16.69,8.38C19.61,9.08 22,10.66 22,12.5C22,14.38 19.5,16 16.5,16.66C15.67,17.76 14.86,18.78 14.17,19.33C13.33,20 12.67,20 12,20M17,11A1,1 0 0,0 16,12A1,1 0 0,0 17,13A1,1 0 0,0 18,12A1,1 0 0,0 17,11Z" /></g><g id="flag"><path d="M14.4,6L14,4H5V21H7V14H12.6L13,16H20V6H14.4Z" /></g><g id="flag-checkered"><path d="M14.4,6H20V16H13L12.6,14H7V21H5V4H14L14.4,6M14,14H16V12H18V10H16V8H14V10L13,8V6H11V8H9V6H7V8H9V10H7V12H9V10H11V12H13V10L14,12V14M11,10V8H13V10H11M14,10H16V12H14V10Z" /></g><g id="flag-outline"><path d="M14.5,6H20V16H13L12.5,14H7V21H5V4H14L14.5,6M7,6V12H13L13.5,14H18V8H14L13.5,6H7Z" /></g><g id="flag-outline-variant"><path d="M6,3A1,1 0 0,1 7,4V4.88C8.06,4.44 9.5,4 11,4C14,4 14,6 16,6C19,6 20,4 20,4V12C20,12 19,14 16,14C13,14 13,12 11,12C8,12 7,14 7,14V21H5V4A1,1 0 0,1 6,3M7,7.25V11.5C7,11.5 9,10 11,10C13,10 14,12 16,12C18,12 18,11 18,11V7.5C18,7.5 17,8 16,8C14,8 13,6 11,6C9,6 7,7.25 7,7.25Z" /></g><g id="flag-triangle"><path d="M7,2H9V22H7V2M19,9L11,14.6V3.4L19,9Z" /></g><g id="flag-variant"><path d="M6,3A1,1 0 0,1 7,4V4.88C8.06,4.44 9.5,4 11,4C14,4 14,6 16,6C19,6 20,4 20,4V12C20,12 19,14 16,14C13,14 13,12 11,12C8,12 7,14 7,14V21H5V4A1,1 0 0,1 6,3Z" /></g><g id="flash"><path d="M7,2V13H10V22L17,10H13L17,2H7Z" /></g><g id="flash-auto"><path d="M16.85,7.65L18,4L19.15,7.65M19,2H17L13.8,11H15.7L16.4,9H19.6L20.3,11H22.2M3,2V14H6V23L13,11H9L13,2H3Z" /></g><g id="flash-off"><path d="M17,10H13L17,2H7V4.18L15.46,12.64M3.27,3L2,4.27L7,9.27V13H10V22L13.58,15.86L17.73,20L19,18.73L3.27,3Z" /></g><g id="flash-red-eye"><path d="M16,5C15.44,5 15,5.44 15,6C15,6.56 15.44,7 16,7C16.56,7 17,6.56 17,6C17,5.44 16.56,5 16,5M16,2C13.27,2 10.94,3.66 10,6C10.94,8.34 13.27,10 16,10C18.73,10 21.06,8.34 22,6C21.06,3.66 18.73,2 16,2M16,3.5A2.5,2.5 0 0,1 18.5,6A2.5,2.5 0 0,1 16,8.5A2.5,2.5 0 0,1 13.5,6A2.5,2.5 0 0,1 16,3.5M3,2V14H6V23L13,11H9L10.12,8.5C9.44,7.76 8.88,6.93 8.5,6C9.19,4.29 10.5,2.88 12.11,2H3Z" /></g><g id="flashlight"><path d="M9,10L6,5H18L15,10H9M18,4H6V2H18V4M9,22V11H15V22H9M12,13A1,1 0 0,0 11,14A1,1 0 0,0 12,15A1,1 0 0,0 13,14A1,1 0 0,0 12,13Z" /></g><g id="flashlight-off"><path d="M2,5.27L3.28,4L20,20.72L18.73,22L15,18.27V22H9V12.27L2,5.27M18,5L15,10H11.82L6.82,5H18M18,4H6V2H18V4M15,11V13.18L12.82,11H15Z" /></g><g id="flask"><path d="M6,22A3,3 0 0,1 3,19C3,18.4 3.18,17.84 3.5,17.37L9,7.81V6A1,1 0 0,1 8,5V4A2,2 0 0,1 10,2H14A2,2 0 0,1 16,4V5A1,1 0 0,1 15,6V7.81L20.5,17.37C20.82,17.84 21,18.4 21,19A3,3 0 0,1 18,22H6M5,19A1,1 0 0,0 6,20H18A1,1 0 0,0 19,19C19,18.79 18.93,18.59 18.82,18.43L16.53,14.47L14,17L8.93,11.93L5.18,18.43C5.07,18.59 5,18.79 5,19M13,10A1,1 0 0,0 12,11A1,1 0 0,0 13,12A1,1 0 0,0 14,11A1,1 0 0,0 13,10Z" /></g><g id="flask-empty"><path d="M6,22A3,3 0 0,1 3,19C3,18.4 3.18,17.84 3.5,17.37L9,7.81V6A1,1 0 0,1 8,5V4A2,2 0 0,1 10,2H14A2,2 0 0,1 16,4V5A1,1 0 0,1 15,6V7.81L20.5,17.37C20.82,17.84 21,18.4 21,19A3,3 0 0,1 18,22H6Z" /></g><g id="flask-empty-outline"><path d="M5,19A1,1 0 0,0 6,20H18A1,1 0 0,0 19,19C19,18.79 18.93,18.59 18.82,18.43L13,8.35V4H11V8.35L5.18,18.43C5.07,18.59 5,18.79 5,19M6,22A3,3 0 0,1 3,19C3,18.4 3.18,17.84 3.5,17.37L9,7.81V6A1,1 0 0,1 8,5V4A2,2 0 0,1 10,2H14A2,2 0 0,1 16,4V5A1,1 0 0,1 15,6V7.81L20.5,17.37C20.82,17.84 21,18.4 21,19A3,3 0 0,1 18,22H6Z" /></g><g id="flask-outline"><path d="M5,19A1,1 0 0,0 6,20H18A1,1 0 0,0 19,19C19,18.79 18.93,18.59 18.82,18.43L13,8.35V4H11V8.35L5.18,18.43C5.07,18.59 5,18.79 5,19M6,22A3,3 0 0,1 3,19C3,18.4 3.18,17.84 3.5,17.37L9,7.81V6A1,1 0 0,1 8,5V4A2,2 0 0,1 10,2H14A2,2 0 0,1 16,4V5A1,1 0 0,1 15,6V7.81L20.5,17.37C20.82,17.84 21,18.4 21,19A3,3 0 0,1 18,22H6M13,16L14.34,14.66L16.27,18H7.73L10.39,13.39L13,16M12.5,12A0.5,0.5 0 0,1 13,12.5A0.5,0.5 0 0,1 12.5,13A0.5,0.5 0 0,1 12,12.5A0.5,0.5 0 0,1 12.5,12Z" /></g><g id="flattr"><path d="M21,9V15A6,6 0 0,1 15,21H4.41L11.07,14.35C11.38,14.04 11.69,13.73 11.84,13.75C12,13.78 12,14.14 12,14.5V17H14A3,3 0 0,0 17,14V8.41L21,4.41V9M3,15V9A6,6 0 0,1 9,3H19.59L12.93,9.65C12.62,9.96 12.31,10.27 12.16,10.25C12,10.22 12,9.86 12,9.5V7H10A3,3 0 0,0 7,10V15.59L3,19.59V15Z" /></g><g id="flip-to-back"><path d="M15,17H17V15H15M15,5H17V3H15M5,7H3V19A2,2 0 0,0 5,21H17V19H5M19,17A2,2 0 0,0 21,15H19M19,9H21V7H19M19,13H21V11H19M9,17V15H7A2,2 0 0,0 9,17M13,3H11V5H13M19,3V5H21C21,3.89 20.1,3 19,3M13,15H11V17H13M9,3C7.89,3 7,3.89 7,5H9M9,11H7V13H9M9,7H7V9H9V7Z" /></g><g id="flip-to-front"><path d="M7,21H9V19H7M11,21H13V19H11M19,15H9V5H19M19,3H9C7.89,3 7,3.89 7,5V15A2,2 0 0,0 9,17H14L18,17H19A2,2 0 0,0 21,15V5C21,3.89 20.1,3 19,3M15,21H17V19H15M3,9H5V7H3M5,21V19H3A2,2 0 0,0 5,21M3,17H5V15H3M3,13H5V11H3V13Z" /></g><g id="floppy"><path d="M4.5,22L2,19.5V4A2,2 0 0,1 4,2H20A2,2 0 0,1 22,4V20A2,2 0 0,1 20,22H17V15A1,1 0 0,0 16,14H7A1,1 0 0,0 6,15V22H4.5M5,4V10A1,1 0 0,0 6,11H18A1,1 0 0,0 19,10V4H5M8,16H11V20H8V16M20,4V5H21V4H20Z" /></g><g id="flower"><path d="M3,13A9,9 0 0,0 12,22C12,17 7.97,13 3,13M12,5.5A2.5,2.5 0 0,1 14.5,8A2.5,2.5 0 0,1 12,10.5A2.5,2.5 0 0,1 9.5,8A2.5,2.5 0 0,1 12,5.5M5.6,10.25A2.5,2.5 0 0,0 8.1,12.75C8.63,12.75 9.12,12.58 9.5,12.31C9.5,12.37 9.5,12.43 9.5,12.5A2.5,2.5 0 0,0 12,15A2.5,2.5 0 0,0 14.5,12.5C14.5,12.43 14.5,12.37 14.5,12.31C14.88,12.58 15.37,12.75 15.9,12.75C17.28,12.75 18.4,11.63 18.4,10.25C18.4,9.25 17.81,8.4 16.97,8C17.81,7.6 18.4,6.74 18.4,5.75C18.4,4.37 17.28,3.25 15.9,3.25C15.37,3.25 14.88,3.41 14.5,3.69C14.5,3.63 14.5,3.56 14.5,3.5A2.5,2.5 0 0,0 12,1A2.5,2.5 0 0,0 9.5,3.5C9.5,3.56 9.5,3.63 9.5,3.69C9.12,3.41 8.63,3.25 8.1,3.25A2.5,2.5 0 0,0 5.6,5.75C5.6,6.74 6.19,7.6 7.03,8C6.19,8.4 5.6,9.25 5.6,10.25M12,22A9,9 0 0,0 21,13C16,13 12,17 12,22Z" /></g><g id="folder"><path d="M10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6H12L10,4Z" /></g><g id="folder-account"><path d="M19,17H11V16C11,14.67 13.67,14 15,14C16.33,14 19,14.67 19,16M15,9A2,2 0 0,1 17,11A2,2 0 0,1 15,13A2,2 0 0,1 13,11C13,9.89 13.9,9 15,9M20,6H12L10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6Z" /></g><g id="folder-download"><path d="M20,6A2,2 0 0,1 22,8V18A2,2 0 0,1 20,20H4C2.89,20 2,19.1 2,18V6C2,4.89 2.89,4 4,4H10L12,6H20M19.25,13H16V9H14V13H10.75L15,17.25" /></g><g id="folder-google-drive"><path d="M13.75,9H16.14L19,14H16.05L13.5,9.46M18.3,17H12.75L14.15,14.5H19.27L19.53,14.96M11.5,17L10.4,14.86L13.24,9.9L14.74,12.56L12.25,17M20,6H12L10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6Z" /></g><g id="folder-image"><path d="M5,17L9.5,11L13,15.5L15.5,12.5L19,17M20,6H12L10,4H4A2,2 0 0,0 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8A2,2 0 0,0 20,6Z" /></g><g id="folder-lock"><path d="M20,6A2,2 0 0,1 22,8V18A2,2 0 0,1 20,20H4C2.89,20 2,19.1 2,18V6C2,4.89 2.89,4 4,4H10L12,6H20M19,17V13H18V12A3,3 0 0,0 15,9A3,3 0 0,0 12,12V13H11V17H19M15,11A1,1 0 0,1 16,12V13H14V12A1,1 0 0,1 15,11Z" /></g><g id="folder-lock-open"><path d="M20,6A2,2 0 0,1 22,8V18A2,2 0 0,1 20,20H4C2.89,20 2,19.1 2,18V6C2,4.89 2.89,4 4,4H10L12,6H20M19,17V13H18L16,13H14V11A1,1 0 0,1 15,10A1,1 0 0,1 16,11H18A3,3 0 0,0 15,8A3,3 0 0,0 12,11V13H11V17H19Z" /></g><g id="folder-move"><path d="M9,18V15H5V11H9V8L14,13M20,6H12L10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6Z" /></g><g id="folder-multiple"><path d="M22,4H14L12,2H6A2,2 0 0,0 4,4V16A2,2 0 0,0 6,18H22A2,2 0 0,0 24,16V6A2,2 0 0,0 22,4M2,6H0V11H0V20A2,2 0 0,0 2,22H20V20H2V6Z" /></g><g id="folder-multiple-image"><path d="M7,15L11.5,9L15,13.5L17.5,10.5L21,15M22,4H14L12,2H6A2,2 0 0,0 4,4V16A2,2 0 0,0 6,18H22A2,2 0 0,0 24,16V6A2,2 0 0,0 22,4M2,6H0V11H0V20A2,2 0 0,0 2,22H20V20H2V6Z" /></g><g id="folder-multiple-outline"><path d="M22,4A2,2 0 0,1 24,6V16A2,2 0 0,1 22,18H6A2,2 0 0,1 4,16V4A2,2 0 0,1 6,2H12L14,4H22M2,6V20H20V22H2A2,2 0 0,1 0,20V11H0V6H2M6,6V16H22V6H6Z" /></g><g id="folder-outline"><path d="M20,18H4V8H20M20,6H12L10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6Z" /></g><g id="folder-plus"><path d="M10,4L12,6H20A2,2 0 0,1 22,8V18A2,2 0 0,1 20,20H4C2.89,20 2,19.1 2,18V6C2,4.89 2.89,4 4,4H10M15,9V12H12V14H15V17H17V14H20V12H17V9H15Z" /></g><g id="folder-remove"><path d="M10,4L12,6H20A2,2 0 0,1 22,8V18A2,2 0 0,1 20,20H4C2.89,20 2,19.1 2,18V6C2,4.89 2.89,4 4,4H10M12.46,10.88L14.59,13L12.46,15.12L13.88,16.54L16,14.41L18.12,16.54L19.54,15.12L17.41,13L19.54,10.88L18.12,9.46L16,11.59L13.88,9.46L12.46,10.88Z" /></g><g id="folder-star"><path d="M20,6H12L10,4H4A2,2 0 0,0 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8A2,2 0 0,0 20,6M17.94,17L15,15.28L12.06,17L12.84,13.67L10.25,11.43L13.66,11.14L15,8L16.34,11.14L19.75,11.43L17.16,13.67L17.94,17Z" /></g><g id="folder-upload"><path d="M20,6A2,2 0 0,1 22,8V18A2,2 0 0,1 20,20H4A2,2 0 0,1 2,18V6A2,2 0 0,1 4,4H10L12,6H20M10.75,13H14V17H16V13H19.25L15,8.75" /></g><g id="food"><path d="M15.5,21L14,8H16.23L15.1,3.46L16.84,3L18.09,8H22L20.5,21H15.5M5,11H10A3,3 0 0,1 13,14H2A3,3 0 0,1 5,11M13,18A3,3 0 0,1 10,21H5A3,3 0 0,1 2,18H13M3,15H8L9.5,16.5L11,15H12A1,1 0 0,1 13,16A1,1 0 0,1 12,17H3A1,1 0 0,1 2,16A1,1 0 0,1 3,15Z" /></g><g id="food-apple"><path d="M20,10C22,13 17,22 15,22C13,22 13,21 12,21C11,21 11,22 9,22C7,22 2,13 4,10C6,7 9,7 11,8V5C5.38,8.07 4.11,3.78 4.11,3.78C4.11,3.78 6.77,0.19 11,5V3H13V8C15,7 18,7 20,10Z" /></g><g id="food-fork-drink"><path d="M3,3A1,1 0 0,0 2,4V8L2,9.5C2,11.19 3.03,12.63 4.5,13.22V19.5A1.5,1.5 0 0,0 6,21A1.5,1.5 0 0,0 7.5,19.5V13.22C8.97,12.63 10,11.19 10,9.5V8L10,4A1,1 0 0,0 9,3A1,1 0 0,0 8,4V8A0.5,0.5 0 0,1 7.5,8.5A0.5,0.5 0 0,1 7,8V4A1,1 0 0,0 6,3A1,1 0 0,0 5,4V8A0.5,0.5 0 0,1 4.5,8.5A0.5,0.5 0 0,1 4,8V4A1,1 0 0,0 3,3M19.88,3C19.75,3 19.62,3.09 19.5,3.16L16,5.25V9H12V11H13L14,21H20L21,11H22V9H18V6.34L20.5,4.84C21,4.56 21.13,4 20.84,3.5C20.63,3.14 20.26,2.95 19.88,3Z" /></g><g id="food-off"><path d="M2,5.27L3.28,4L21,21.72L19.73,23L17.73,21H15.5L15.21,18.5L12.97,16.24C12.86,16.68 12.47,17 12,17H3A1,1 0 0,1 2,16A1,1 0 0,1 3,15H8L9.5,16.5L11,15H11.73L10.73,14H2A3,3 0 0,1 5,11H7.73L2,5.27M14,8H16.23L15.1,3.46L16.84,3L18.09,8H22L20.74,18.92L14.54,12.72L14,8M13,18A3,3 0 0,1 10,21H5A3,3 0 0,1 2,18H13Z" /></g><g id="food-variant"><path d="M22,18A4,4 0 0,1 18,22H15A4,4 0 0,1 11,18V16H17.79L20.55,11.23L22.11,12.13L19.87,16H22V18M9,22H2C2,19 2,16 2.33,12.83C2.6,10.3 3.08,7.66 3.6,5H3V3H4L7,3H8V5H7.4C7.92,7.66 8.4,10.3 8.67,12.83C9,16 9,19 9,22Z" /></g><g id="football"><path d="M7.5,7.5C9.17,5.87 11.29,4.69 13.37,4.18C15.46,3.67 17.5,3.83 18.6,4C19.71,4.15 19.87,4.31 20.03,5.41C20.18,6.5 20.33,8.55 19.82,10.63C19.31,12.71 18.13,14.83 16.5,16.5C14.83,18.13 12.71,19.31 10.63,19.82C8.55,20.33 6.5,20.18 5.41,20.03C4.31,19.87 4.15,19.71 4,18.6C3.83,17.5 3.67,15.46 4.18,13.37C4.69,11.29 5.87,9.17 7.5,7.5M7.3,15.79L8.21,16.7L9.42,15.5L10.63,16.7L11.54,15.79L10.34,14.58L12,12.91L13.21,14.12L14.12,13.21L12.91,12L14.58,10.34L15.79,11.54L16.7,10.63L15.5,9.42L16.7,8.21L15.79,7.3L14.58,8.5L13.37,7.3L12.46,8.21L13.66,9.42L12,11.09L10.79,9.88L9.88,10.79L11.09,12L9.42,13.66L8.21,12.46L7.3,13.37L8.5,14.58L7.3,15.79Z" /></g><g id="football-australian"><path d="M7.5,7.5C9.17,5.87 11.29,4.69 13.37,4.18C18,3 21,6 19.82,10.63C19.31,12.71 18.13,14.83 16.5,16.5C14.83,18.13 12.71,19.31 10.63,19.82C6,21 3,18 4.18,13.37C4.69,11.29 5.87,9.17 7.5,7.5M10.62,11.26L10.26,11.62L12.38,13.74L12.74,13.38L10.62,11.26M11.62,10.26L11.26,10.62L13.38,12.74L13.74,12.38L11.62,10.26M9.62,12.26L9.26,12.62L11.38,14.74L11.74,14.38L9.62,12.26M12.63,9.28L12.28,9.63L14.4,11.75L14.75,11.4L12.63,9.28M8.63,13.28L8.28,13.63L10.4,15.75L10.75,15.4L8.63,13.28M13.63,8.28L13.28,8.63L15.4,10.75L15.75,10.4L13.63,8.28Z" /></g><g id="football-helmet"><path d="M13.5,12A1.5,1.5 0 0,0 12,13.5A1.5,1.5 0 0,0 13.5,15A1.5,1.5 0 0,0 15,13.5A1.5,1.5 0 0,0 13.5,12M13.5,3C18.19,3 22,6.58 22,11C22,12.62 22,14 21.09,16C17,16 16,20 12.5,20C10.32,20 9.27,18.28 9.05,16H9L8.24,16L6.96,20.3C6.81,20.79 6.33,21.08 5.84,21H3A1,1 0 0,1 2,20A1,1 0 0,1 3,19V16A1,1 0 0,1 2,15A1,1 0 0,1 3,14H6.75L7.23,12.39C6.72,12.14 6.13,12 5.5,12H5.07L5,11C5,6.58 8.81,3 13.5,3M5,16V19H5.26L6.15,16H5Z" /></g><g id="format-align-center"><path d="M3,3H21V5H3V3M7,7H17V9H7V7M3,11H21V13H3V11M7,15H17V17H7V15M3,19H21V21H3V19Z" /></g><g id="format-align-justify"><path d="M3,3H21V5H3V3M3,7H21V9H3V7M3,11H21V13H3V11M3,15H21V17H3V15M3,19H21V21H3V19Z" /></g><g id="format-align-left"><path d="M3,3H21V5H3V3M3,7H15V9H3V7M3,11H21V13H3V11M3,15H15V17H3V15M3,19H21V21H3V19Z" /></g><g id="format-align-right"><path d="M3,3H21V5H3V3M9,7H21V9H9V7M3,11H21V13H3V11M9,15H21V17H9V15M3,19H21V21H3V19Z" /></g><g id="format-annotation-plus"><path d="M8.5,7H10.5L16,21H13.6L12.5,18H6.3L5.2,21H3L8.5,7M7.1,16H11.9L9.5,9.7L7.1,16M22,5V7H19V10H17V7H14V5H17V2H19V5H22Z" /></g><g id="format-bold"><path d="M13.5,15.5H10V12.5H13.5A1.5,1.5 0 0,1 15,14A1.5,1.5 0 0,1 13.5,15.5M10,6.5H13A1.5,1.5 0 0,1 14.5,8A1.5,1.5 0 0,1 13,9.5H10M15.6,10.79C16.57,10.11 17.25,9 17.25,8C17.25,5.74 15.5,4 13.25,4H7V18H14.04C16.14,18 17.75,16.3 17.75,14.21C17.75,12.69 16.89,11.39 15.6,10.79Z" /></g><g id="format-clear"><path d="M6,5V5.18L8.82,8H11.22L10.5,9.68L12.6,11.78L14.21,8H20V5H6M3.27,5L2,6.27L8.97,13.24L6.5,19H9.5L11.07,15.34L16.73,21L18,19.73L3.55,5.27L3.27,5Z" /></g><g id="format-color-fill"><path d="M19,11.5C19,11.5 17,13.67 17,15A2,2 0 0,0 19,17A2,2 0 0,0 21,15C21,13.67 19,11.5 19,11.5M5.21,10L10,5.21L14.79,10M16.56,8.94L7.62,0L6.21,1.41L8.59,3.79L3.44,8.94C2.85,9.5 2.85,10.47 3.44,11.06L8.94,16.56C9.23,16.85 9.62,17 10,17C10.38,17 10.77,16.85 11.06,16.56L16.56,11.06C17.15,10.47 17.15,9.5 16.56,8.94Z" /></g><g id="format-color-text"><path d="M9.62,12L12,5.67L14.37,12M11,3L5.5,17H7.75L8.87,14H15.12L16.25,17H18.5L13,3H11Z" /></g><g id="format-float-center"><path d="M9,7H15V13H9V7M3,3H21V5H3V3M3,15H21V17H3V15M3,19H17V21H3V19Z" /></g><g id="format-float-left"><path d="M3,7H9V13H3V7M3,3H21V5H3V3M21,7V9H11V7H21M21,11V13H11V11H21M3,15H17V17H3V15M3,19H21V21H3V19Z" /></g><g id="format-float-none"><path d="M3,7H9V13H3V7M3,3H21V5H3V3M21,11V13H11V11H21M3,15H17V17H3V15M3,19H21V21H3V19Z" /></g><g id="format-float-right"><path d="M15,7H21V13H15V7M3,3H21V5H3V3M13,7V9H3V7H13M9,11V13H3V11H9M3,15H17V17H3V15M3,19H21V21H3V19Z" /></g><g id="format-header-1"><path d="M3,4H5V10H9V4H11V18H9V12H5V18H3V4M14,18V16H16V6.31L13.5,7.75V5.44L16,4H18V16H20V18H14Z" /></g><g id="format-header-2"><path d="M3,4H5V10H9V4H11V18H9V12H5V18H3V4M21,18H15A2,2 0 0,1 13,16C13,15.47 13.2,15 13.54,14.64L18.41,9.41C18.78,9.05 19,8.55 19,8A2,2 0 0,0 17,6A2,2 0 0,0 15,8H13A4,4 0 0,1 17,4A4,4 0 0,1 21,8C21,9.1 20.55,10.1 19.83,10.83L15,16H21V18Z" /></g><g id="format-header-3"><path d="M3,4H5V10H9V4H11V18H9V12H5V18H3V4M15,4H19A2,2 0 0,1 21,6V16A2,2 0 0,1 19,18H15A2,2 0 0,1 13,16V15H15V16H19V12H15V10H19V6H15V7H13V6A2,2 0 0,1 15,4Z" /></g><g id="format-header-4"><path d="M3,4H5V10H9V4H11V18H9V12H5V18H3V4M18,18V13H13V11L18,4H20V11H21V13H20V18H18M18,11V7.42L15.45,11H18Z" /></g><g id="format-header-5"><path d="M3,4H5V10H9V4H11V18H9V12H5V18H3V4M15,4H20V6H15V10H17A4,4 0 0,1 21,14A4,4 0 0,1 17,18H15A2,2 0 0,1 13,16V15H15V16H17A2,2 0 0,0 19,14A2,2 0 0,0 17,12H15A2,2 0 0,1 13,10V6A2,2 0 0,1 15,4Z" /></g><g id="format-header-6"><path d="M3,4H5V10H9V4H11V18H9V12H5V18H3V4M15,4H19A2,2 0 0,1 21,6V7H19V6H15V10H19A2,2 0 0,1 21,12V16A2,2 0 0,1 19,18H15A2,2 0 0,1 13,16V6A2,2 0 0,1 15,4M15,12V16H19V12H15Z" /></g><g id="format-header-decrease"><path d="M4,4H6V10H10V4H12V18H10V12H6V18H4V4M20.42,7.41L16.83,11L20.42,14.59L19,16L14,11L19,6L20.42,7.41Z" /></g><g id="format-header-equal"><path d="M4,4H6V10H10V4H12V18H10V12H6V18H4V4M14,10V8H21V10H14M14,12H21V14H14V12Z" /></g><g id="format-header-increase"><path d="M4,4H6V10H10V4H12V18H10V12H6V18H4V4M14.59,7.41L18.17,11L14.59,14.59L16,16L21,11L16,6L14.59,7.41Z" /></g><g id="format-header-pound"><path d="M3,4H5V10H9V4H11V18H9V12H5V18H3V4M13,8H15.31L15.63,5H17.63L17.31,8H19.31L19.63,5H21.63L21.31,8H23V10H21.1L20.9,12H23V14H20.69L20.37,17H18.37L18.69,14H16.69L16.37,17H14.37L14.69,14H13V12H14.9L15.1,10H13V8M17.1,10L16.9,12H18.9L19.1,10H17.1Z" /></g><g id="format-horizontal-align-center"><path d="M19,16V13H23V11H19V8L15,12L19,16M5,8V11H1V13H5V16L9,12L5,8M11,20H13V4H11V20Z" /></g><g id="format-horizontal-align-left"><path d="M11,16V13H21V11H11V8L7,12L11,16M3,20H5V4H3V20Z" /></g><g id="format-horizontal-align-right"><path d="M13,8V11H3V13H13V16L17,12L13,8M19,20H21V4H19V20Z" /></g><g id="format-indent-decrease"><path d="M11,13H21V11H11M11,9H21V7H11M3,3V5H21V3M3,21H21V19H3M3,12L7,16V8M11,17H21V15H11V17Z" /></g><g id="format-indent-increase"><path d="M11,13H21V11H11M11,9H21V7H11M3,3V5H21V3M11,17H21V15H11M3,8V16L7,12M3,21H21V19H3V21Z" /></g><g id="format-italic"><path d="M10,4V7H12.21L8.79,15H6V18H14V15H11.79L15.21,7H18V4H10Z" /></g><g id="format-line-spacing"><path d="M10,13H22V11H10M10,19H22V17H10M10,7H22V5H10M6,7H8.5L5,3.5L1.5,7H4V17H1.5L5,20.5L8.5,17H6V7Z" /></g><g id="format-line-style"><path d="M3,16H8V14H3V16M9.5,16H14.5V14H9.5V16M16,16H21V14H16V16M3,20H5V18H3V20M7,20H9V18H7V20M11,20H13V18H11V20M15,20H17V18H15V20M19,20H21V18H19V20M3,12H11V10H3V12M13,12H21V10H13V12M3,4V8H21V4H3Z" /></g><g id="format-line-weight"><path d="M3,17H21V15H3V17M3,20H21V19H3V20M3,13H21V10H3V13M3,4V8H21V4H3Z" /></g><g id="format-list-bulleted"><path d="M7,5H21V7H7V5M7,13V11H21V13H7M4,4.5A1.5,1.5 0 0,1 5.5,6A1.5,1.5 0 0,1 4,7.5A1.5,1.5 0 0,1 2.5,6A1.5,1.5 0 0,1 4,4.5M4,10.5A1.5,1.5 0 0,1 5.5,12A1.5,1.5 0 0,1 4,13.5A1.5,1.5 0 0,1 2.5,12A1.5,1.5 0 0,1 4,10.5M7,19V17H21V19H7M4,16.5A1.5,1.5 0 0,1 5.5,18A1.5,1.5 0 0,1 4,19.5A1.5,1.5 0 0,1 2.5,18A1.5,1.5 0 0,1 4,16.5Z" /></g><g id="format-list-bulleted-type"><path d="M5,9.5L7.5,14H2.5L5,9.5M3,4H7V8H3V4M5,20A2,2 0 0,0 7,18A2,2 0 0,0 5,16A2,2 0 0,0 3,18A2,2 0 0,0 5,20M9,5V7H21V5H9M9,19H21V17H9V19M9,13H21V11H9V13Z" /></g><g id="format-list-numbers"><path d="M7,13H21V11H7M7,19H21V17H7M7,7H21V5H7M2,11H3.8L2,13.1V14H5V13H3.2L5,10.9V10H2M3,8H4V4H2V5H3M2,17H4V17.5H3V18.5H4V19H2V20H5V16H2V17Z" /></g><g id="format-paint"><path d="M18,4V3A1,1 0 0,0 17,2H5A1,1 0 0,0 4,3V7A1,1 0 0,0 5,8H17A1,1 0 0,0 18,7V6H19V10H9V21A1,1 0 0,0 10,22H12A1,1 0 0,0 13,21V12H21V4H18Z" /></g><g id="format-paragraph"><path d="M13,4A4,4 0 0,1 17,8A4,4 0 0,1 13,12H11V18H9V4H13M13,10A2,2 0 0,0 15,8A2,2 0 0,0 13,6H11V10H13Z" /></g><g id="format-quote"><path d="M14,17H17L19,13V7H13V13H16M6,17H9L11,13V7H5V13H8L6,17Z" /></g><g id="format-section"><path d="M15.67,4.42C14.7,3.84 13.58,3.54 12.45,3.56C10.87,3.56 9.66,4.34 9.66,5.56C9.66,6.96 11,7.47 13,8.14C15.5,8.95 17.4,9.97 17.4,12.38C17.36,13.69 16.69,14.89 15.6,15.61C16.25,16.22 16.61,17.08 16.6,17.97C16.6,20.79 14,21.97 11.5,21.97C10.04,22.03 8.59,21.64 7.35,20.87L8,19.34C9.04,20.05 10.27,20.43 11.53,20.44C13.25,20.44 14.53,19.66 14.53,18.24C14.53,17 13.75,16.31 11.25,15.45C8.5,14.5 6.6,13.5 6.6,11.21C6.67,9.89 7.43,8.69 8.6,8.07C7.97,7.5 7.61,6.67 7.6,5.81C7.6,3.45 9.77,2 12.53,2C13.82,2 15.09,2.29 16.23,2.89L15.67,4.42M11.35,13.42C12.41,13.75 13.44,14.18 14.41,14.71C15.06,14.22 15.43,13.45 15.41,12.64C15.41,11.64 14.77,10.76 13,10.14C11.89,9.77 10.78,9.31 9.72,8.77C8.97,9.22 8.5,10.03 8.5,10.91C8.5,11.88 9.23,12.68 11.35,13.42Z" /></g><g id="format-size"><path d="M3,12H6V19H9V12H12V9H3M9,4V7H14V19H17V7H22V4H9Z" /></g><g id="format-strikethrough"><path d="M3,14H21V12H3M5,4V7H10V10H14V7H19V4M10,19H14V16H10V19Z" /></g><g id="format-strikethrough-variant"><path d="M23,12V14H18.61C19.61,16.14 19.56,22 12.38,22C4.05,22.05 4.37,15.5 4.37,15.5L8.34,15.55C8.37,18.92 11.5,18.92 12.12,18.88C12.76,18.83 15.15,18.84 15.34,16.5C15.42,15.41 14.32,14.58 13.12,14H1V12H23M19.41,7.89L15.43,7.86C15.43,7.86 15.6,5.09 12.15,5.08C8.7,5.06 9,7.28 9,7.56C9.04,7.84 9.34,9.22 12,9.88H5.71C5.71,9.88 2.22,3.15 10.74,2C19.45,0.8 19.43,7.91 19.41,7.89Z" /></g><g id="format-subscript"><path d="M16,7.41L11.41,12L16,16.59L14.59,18L10,13.41L5.41,18L4,16.59L8.59,12L4,7.41L5.41,6L10,10.59L14.59,6L16,7.41M21.85,21.03H16.97V20.03L17.86,19.23C18.62,18.58 19.18,18.04 19.56,17.6C19.93,17.16 20.12,16.75 20.13,16.36C20.14,16.08 20.05,15.85 19.86,15.66C19.68,15.5 19.39,15.38 19,15.38C18.69,15.38 18.42,15.44 18.16,15.56L17.5,15.94L17.05,14.77C17.32,14.56 17.64,14.38 18.03,14.24C18.42,14.1 18.85,14 19.32,14C20.1,14.04 20.7,14.25 21.1,14.66C21.5,15.07 21.72,15.59 21.72,16.23C21.71,16.79 21.53,17.31 21.18,17.78C20.84,18.25 20.42,18.7 19.91,19.14L19.27,19.66V19.68H21.85V21.03Z" /></g><g id="format-superscript"><path d="M16,7.41L11.41,12L16,16.59L14.59,18L10,13.41L5.41,18L4,16.59L8.59,12L4,7.41L5.41,6L10,10.59L14.59,6L16,7.41M21.85,9H16.97V8L17.86,7.18C18.62,6.54 19.18,6 19.56,5.55C19.93,5.11 20.12,4.7 20.13,4.32C20.14,4.04 20.05,3.8 19.86,3.62C19.68,3.43 19.39,3.34 19,3.33C18.69,3.34 18.42,3.4 18.16,3.5L17.5,3.89L17.05,2.72C17.32,2.5 17.64,2.33 18.03,2.19C18.42,2.05 18.85,2 19.32,2C20.1,2 20.7,2.2 21.1,2.61C21.5,3 21.72,3.54 21.72,4.18C21.71,4.74 21.53,5.26 21.18,5.73C20.84,6.21 20.42,6.66 19.91,7.09L19.27,7.61V7.63H21.85V9Z" /></g><g id="format-text"><path d="M18.5,4L19.66,8.35L18.7,8.61C18.25,7.74 17.79,6.87 17.26,6.43C16.73,6 16.11,6 15.5,6H13V16.5C13,17 13,17.5 13.33,17.75C13.67,18 14.33,18 15,18V19H9V18C9.67,18 10.33,18 10.67,17.75C11,17.5 11,17 11,16.5V6H8.5C7.89,6 7.27,6 6.74,6.43C6.21,6.87 5.75,7.74 5.3,8.61L4.34,8.35L5.5,4H18.5Z" /></g><g id="format-textdirection-l-to-r"><path d="M21,18L17,14V17H5V19H17V22M9,10V15H11V4H13V15H15V4H17V2H9A4,4 0 0,0 5,6A4,4 0 0,0 9,10Z" /></g><g id="format-textdirection-r-to-l"><path d="M8,17V14L4,18L8,22V19H20V17M10,10V15H12V4H14V15H16V4H18V2H10A4,4 0 0,0 6,6A4,4 0 0,0 10,10Z" /></g><g id="format-title"><path d="M5,4V7H10.5V19H13.5V7H19V4H5Z" /></g><g id="format-underline"><path d="M5,21H19V19H5V21M12,17A6,6 0 0,0 18,11V3H15.5V11A3.5,3.5 0 0,1 12,14.5A3.5,3.5 0 0,1 8.5,11V3H6V11A6,6 0 0,0 12,17Z" /></g><g id="format-vertical-align-bottom"><path d="M16,13H13V3H11V13H8L12,17L16,13M4,19V21H20V19H4Z" /></g><g id="format-vertical-align-center"><path d="M8,19H11V23H13V19H16L12,15L8,19M16,5H13V1H11V5H8L12,9L16,5M4,11V13H20V11H4Z" /></g><g id="format-vertical-align-top"><path d="M8,11H11V21H13V11H16L12,7L8,11M4,3V5H20V3H4Z" /></g><g id="format-wrap-inline"><path d="M8,7L13,17H3L8,7M3,3H21V5H3V3M21,15V17H14V15H21M3,19H21V21H3V19Z" /></g><g id="format-wrap-square"><path d="M12,7L17,17H7L12,7M3,3H21V5H3V3M3,7H6V9H3V7M21,7V9H18V7H21M3,11H6V13H3V11M21,11V13H18V11H21M3,15H6V17H3V15M21,15V17H18V15H21M3,19H21V21H3V19Z" /></g><g id="format-wrap-tight"><path d="M12,7L17,17H7L12,7M3,3H21V5H3V3M3,7H9V9H3V7M21,7V9H15V7H21M3,11H7V13H3V11M21,11V13H17V11H21M3,15H6V17H3V15M21,15V17H18V15H21M3,19H21V21H3V19Z" /></g><g id="format-wrap-top-bottom"><path d="M12,7L17,17H7L12,7M3,3H21V5H3V3M3,19H21V21H3V19Z" /></g><g id="forum"><path d="M17,12V3A1,1 0 0,0 16,2H3A1,1 0 0,0 2,3V17L6,13H16A1,1 0 0,0 17,12M21,6H19V15H6V17A1,1 0 0,0 7,18H18L22,22V7A1,1 0 0,0 21,6Z" /></g><g id="forward"><path d="M12,8V4L20,12L12,20V16H4V8H12Z" /></g><g id="foursquare"><path d="M17,5L16.57,7.5C16.5,7.73 16.2,8 15.91,8C15.61,8 12,8 12,8C11.53,8 10.95,8.32 10.95,8.79V9.2C10.95,9.67 11.53,10 12,10C12,10 14.95,10 15.28,10C15.61,10 15.93,10.36 15.86,10.71C15.79,11.07 14.94,13.28 14.9,13.5C14.86,13.67 14.64,14 14.25,14C13.92,14 11.37,14 11.37,14C10.85,14 10.69,14.07 10.34,14.5C10,14.94 7.27,18.1 7.27,18.1C7.24,18.13 7,18.04 7,18V5C7,4.7 7.61,4 8,4C8,4 16.17,4 16.5,4C16.82,4 17.08,4.61 17,5M17,14.45C17.11,13.97 18.78,6.72 19.22,4.55M17.58,2C17.58,2 8.38,2 6.91,2C5.43,2 5,3.11 5,3.8C5,4.5 5,20.76 5,20.76C5,21.54 5.42,21.84 5.66,21.93C5.9,22.03 6.55,22.11 6.94,21.66C6.94,21.66 11.65,16.22 11.74,16.13C11.87,16 11.87,16 12,16C12.26,16 14.2,16 15.26,16C16.63,16 16.85,15 17,14.45C17.11,13.97 18.78,6.72 19.22,4.55C19.56,2.89 19.14,2 17.58,2Z" /></g><g id="fridge"><path d="M9,21V22H7V21A2,2 0 0,1 5,19V4A2,2 0 0,1 7,2H17A2,2 0 0,1 19,4V19A2,2 0 0,1 17,21V22H15V21H9M7,4V9H17V4H7M7,19H17V11H7V19M8,12H10V15H8V12M8,6H10V8H8V6Z" /></g><g id="fridge-filled"><path d="M7,2H17A2,2 0 0,1 19,4V9H5V4A2,2 0 0,1 7,2M19,19A2,2 0 0,1 17,21V22H15V21H9V22H7V21A2,2 0 0,1 5,19V10H19V19M8,5V7H10V5H8M8,12V15H10V12H8Z" /></g><g id="fridge-filled-bottom"><path d="M8,8V6H10V8H8M7,2H17A2,2 0 0,1 19,4V19A2,2 0 0,1 17,21V22H15V21H9V22H7V21A2,2 0 0,1 5,19V4A2,2 0 0,1 7,2M7,4V9H17V4H7M8,12V15H10V12H8Z" /></g><g id="fridge-filled-top"><path d="M7,2A2,2 0 0,0 5,4V19A2,2 0 0,0 7,21V22H9V21H15V22H17V21A2,2 0 0,0 19,19V4A2,2 0 0,0 17,2H7M8,6H10V8H8V6M7,11H17V19H7V11M8,12V15H10V12H8Z" /></g><g id="fullscreen"><path d="M5,5H10V7H7V10H5V5M14,5H19V10H17V7H14V5M17,14H19V19H14V17H17V14M10,17V19H5V14H7V17H10Z" /></g><g id="fullscreen-exit"><path d="M14,14H19V16H16V19H14V14M5,14H10V19H8V16H5V14M8,5H10V10H5V8H8V5M19,8V10H14V5H16V8H19Z" /></g><g id="function"><path d="M15.6,5.29C14.5,5.19 13.53,6 13.43,7.11L13.18,10H16V12H13L12.56,17.07C12.37,19.27 10.43,20.9 8.23,20.7C6.92,20.59 5.82,19.86 5.17,18.83L6.67,17.33C6.91,18.07 7.57,18.64 8.4,18.71C9.5,18.81 10.47,18 10.57,16.89L11,12H8V10H11.17L11.44,6.93C11.63,4.73 13.57,3.1 15.77,3.3C17.08,3.41 18.18,4.14 18.83,5.17L17.33,6.67C17.09,5.93 16.43,5.36 15.6,5.29Z" /></g><g id="gamepad"><path d="M16.5,9L13.5,12L16.5,15H22V9M9,16.5V22H15V16.5L12,13.5M7.5,9H2V15H7.5L10.5,12M15,7.5V2H9V7.5L12,10.5L15,7.5Z" /></g><g id="gamepad-variant"><path d="M7,6H17A6,6 0 0,1 23,12A6,6 0 0,1 17,18C15.22,18 13.63,17.23 12.53,16H11.47C10.37,17.23 8.78,18 7,18A6,6 0 0,1 1,12A6,6 0 0,1 7,6M6,9V11H4V13H6V15H8V13H10V11H8V9H6M15.5,12A1.5,1.5 0 0,0 14,13.5A1.5,1.5 0 0,0 15.5,15A1.5,1.5 0 0,0 17,13.5A1.5,1.5 0 0,0 15.5,12M18.5,9A1.5,1.5 0 0,0 17,10.5A1.5,1.5 0 0,0 18.5,12A1.5,1.5 0 0,0 20,10.5A1.5,1.5 0 0,0 18.5,9Z" /></g><g id="gas-cylinder"><path d="M16,9V14L16,20A2,2 0 0,1 14,22H10A2,2 0 0,1 8,20V14L8,9C8,7.14 9.27,5.57 11,5.13V4H9V2H15V4H13V5.13C14.73,5.57 16,7.14 16,9Z" /></g><g id="gas-station"><path d="M18,10A1,1 0 0,1 17,9A1,1 0 0,1 18,8A1,1 0 0,1 19,9A1,1 0 0,1 18,10M12,10H6V5H12M19.77,7.23L19.78,7.22L16.06,3.5L15,4.56L17.11,6.67C16.17,7 15.5,7.93 15.5,9A2.5,2.5 0 0,0 18,11.5C18.36,11.5 18.69,11.42 19,11.29V18.5A1,1 0 0,1 18,19.5A1,1 0 0,1 17,18.5V14C17,12.89 16.1,12 15,12H14V5C14,3.89 13.1,3 12,3H6C4.89,3 4,3.89 4,5V21H14V13.5H15.5V18.5A2.5,2.5 0 0,0 18,21A2.5,2.5 0 0,0 20.5,18.5V9C20.5,8.31 20.22,7.68 19.77,7.23Z" /></g><g id="gate"><path d="M9,5V10H7V6H5V10H3V8H1V20H3V18H5V20H7V18H9V20H11V18H13V20H15V18H17V20H19V18H21V20H23V8H21V10H19V6H17V10H15V5H13V10H11V5H9M3,12H5V16H3V12M7,12H9V16H7V12M11,12H13V16H11V12M15,12H17V16H15V12M19,12H21V16H19V12Z" /></g><g id="gauge"><path d="M17.3,18C19,16.5 20,14.4 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12C4,14.4 5,16.5 6.7,18C8.2,16.7 10,16 12,16C14,16 15.9,16.7 17.3,18M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M7,9A1,1 0 0,1 8,10A1,1 0 0,1 7,11A1,1 0 0,1 6,10A1,1 0 0,1 7,9M10,6A1,1 0 0,1 11,7A1,1 0 0,1 10,8A1,1 0 0,1 9,7A1,1 0 0,1 10,6M17,9A1,1 0 0,1 18,10A1,1 0 0,1 17,11A1,1 0 0,1 16,10A1,1 0 0,1 17,9M14.4,6.1C14.9,6.3 15.1,6.9 15,7.4L13.6,10.8C13.8,11.1 14,11.5 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12C10,11 10.7,10.1 11.7,10L13.1,6.7C13.3,6.1 13.9,5.9 14.4,6.1Z" /></g><g id="gavel"><path d="M2.3,20.28L11.9,10.68L10.5,9.26L9.78,9.97C9.39,10.36 8.76,10.36 8.37,9.97L7.66,9.26C7.27,8.87 7.27,8.24 7.66,7.85L13.32,2.19C13.71,1.8 14.34,1.8 14.73,2.19L15.44,2.9C15.83,3.29 15.83,3.92 15.44,4.31L14.73,5L16.15,6.43C16.54,6.04 17.17,6.04 17.56,6.43C17.95,6.82 17.95,7.46 17.56,7.85L18.97,9.26L19.68,8.55C20.07,8.16 20.71,8.16 21.1,8.55L21.8,9.26C22.19,9.65 22.19,10.29 21.8,10.68L16.15,16.33C15.76,16.72 15.12,16.72 14.73,16.33L14.03,15.63C13.63,15.24 13.63,14.6 14.03,14.21L14.73,13.5L13.32,12.09L3.71,21.7C3.32,22.09 2.69,22.09 2.3,21.7C1.91,21.31 1.91,20.67 2.3,20.28M20,19A2,2 0 0,1 22,21V22H12V21A2,2 0 0,1 14,19H20Z" /></g><g id="gender-female"><path d="M12,4A6,6 0 0,1 18,10C18,12.97 15.84,15.44 13,15.92V18H15V20H13V22H11V20H9V18H11V15.92C8.16,15.44 6,12.97 6,10A6,6 0 0,1 12,4M12,6A4,4 0 0,0 8,10A4,4 0 0,0 12,14A4,4 0 0,0 16,10A4,4 0 0,0 12,6Z" /></g><g id="gender-male"><path d="M9,9C10.29,9 11.5,9.41 12.47,10.11L17.58,5H13V3H21V11H19V6.41L13.89,11.5C14.59,12.5 15,13.7 15,15A6,6 0 0,1 9,21A6,6 0 0,1 3,15A6,6 0 0,1 9,9M9,11A4,4 0 0,0 5,15A4,4 0 0,0 9,19A4,4 0 0,0 13,15A4,4 0 0,0 9,11Z" /></g><g id="gender-male-female"><path d="M17.58,4H14V2H21V9H19V5.41L15.17,9.24C15.69,10.03 16,11 16,12C16,14.42 14.28,16.44 12,16.9V19H14V21H12V23H10V21H8V19H10V16.9C7.72,16.44 6,14.42 6,12A5,5 0 0,1 11,7C12,7 12.96,7.3 13.75,7.83L17.58,4M11,9A3,3 0 0,0 8,12A3,3 0 0,0 11,15A3,3 0 0,0 14,12A3,3 0 0,0 11,9Z" /></g><g id="gender-transgender"><path d="M19.58,3H15V1H23V9H21V4.41L16.17,9.24C16.69,10.03 17,11 17,12C17,14.42 15.28,16.44 13,16.9V19H15V21H13V23H11V21H9V19H11V16.9C8.72,16.44 7,14.42 7,12C7,11 7.3,10.04 7.82,9.26L6.64,8.07L5.24,9.46L3.83,8.04L5.23,6.65L3,4.42V8H1V1H8V3H4.41L6.64,5.24L8.08,3.81L9.5,5.23L8.06,6.66L9.23,7.84C10,7.31 11,7 12,7C13,7 13.96,7.3 14.75,7.83L19.58,3M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9Z" /></g><g id="ghost"><path d="M12,2A9,9 0 0,0 3,11V22L6,19L9,22L12,19L15,22L18,19L21,22V11A9,9 0 0,0 12,2M9,8A2,2 0 0,1 11,10A2,2 0 0,1 9,12A2,2 0 0,1 7,10A2,2 0 0,1 9,8M15,8A2,2 0 0,1 17,10A2,2 0 0,1 15,12A2,2 0 0,1 13,10A2,2 0 0,1 15,8Z" /></g><g id="gift"><path d="M22,12V20A2,2 0 0,1 20,22H4A2,2 0 0,1 2,20V12A1,1 0 0,1 1,11V8A2,2 0 0,1 3,6H6.17C6.06,5.69 6,5.35 6,5A3,3 0 0,1 9,2C10,2 10.88,2.5 11.43,3.24V3.23L12,4L12.57,3.23V3.24C13.12,2.5 14,2 15,2A3,3 0 0,1 18,5C18,5.35 17.94,5.69 17.83,6H21A2,2 0 0,1 23,8V11A1,1 0 0,1 22,12M4,20H11V12H4V20M20,20V12H13V20H20M9,4A1,1 0 0,0 8,5A1,1 0 0,0 9,6A1,1 0 0,0 10,5A1,1 0 0,0 9,4M15,4A1,1 0 0,0 14,5A1,1 0 0,0 15,6A1,1 0 0,0 16,5A1,1 0 0,0 15,4M3,8V10H11V8H3M13,8V10H21V8H13Z" /></g><g id="git"><path d="M2.6,10.59L8.38,4.8L10.07,6.5C9.83,7.35 10.22,8.28 11,8.73V14.27C10.4,14.61 10,15.26 10,16A2,2 0 0,0 12,18A2,2 0 0,0 14,16C14,15.26 13.6,14.61 13,14.27V9.41L15.07,11.5C15,11.65 15,11.82 15,12A2,2 0 0,0 17,14A2,2 0 0,0 19,12A2,2 0 0,0 17,10C16.82,10 16.65,10 16.5,10.07L13.93,7.5C14.19,6.57 13.71,5.55 12.78,5.16C12.35,5 11.9,4.96 11.5,5.07L9.8,3.38L10.59,2.6C11.37,1.81 12.63,1.81 13.41,2.6L21.4,10.59C22.19,11.37 22.19,12.63 21.4,13.41L13.41,21.4C12.63,22.19 11.37,22.19 10.59,21.4L2.6,13.41C1.81,12.63 1.81,11.37 2.6,10.59Z" /></g><g id="github-box"><path d="M4,2H20A2,2 0 0,1 22,4V20A2,2 0 0,1 20,22H14.85C14.5,21.92 14.5,21.24 14.5,21V18.26C14.5,17.33 14.17,16.72 13.81,16.41C16.04,16.16 18.38,15.32 18.38,11.5C18.38,10.39 18,9.5 17.35,8.79C17.45,8.54 17.8,7.5 17.25,6.15C17.25,6.15 16.41,5.88 14.5,7.17C13.71,6.95 12.85,6.84 12,6.84C11.15,6.84 10.29,6.95 9.5,7.17C7.59,5.88 6.75,6.15 6.75,6.15C6.2,7.5 6.55,8.54 6.65,8.79C6,9.5 5.62,10.39 5.62,11.5C5.62,15.31 7.95,16.17 10.17,16.42C9.89,16.67 9.63,17.11 9.54,17.76C8.97,18 7.5,18.45 6.63,16.93C6.63,16.93 6.1,15.97 5.1,15.9C5.1,15.9 4.12,15.88 5,16.5C5,16.5 5.68,16.81 6.14,17.97C6.14,17.97 6.73,19.91 9.5,19.31V21C9.5,21.24 9.5,21.92 9.14,22H4A2,2 0 0,1 2,20V4A2,2 0 0,1 4,2Z" /></g><g id="github-circle"><path d="M12,2A10,10 0 0,0 2,12C2,16.42 4.87,20.17 8.84,21.5C9.34,21.58 9.5,21.27 9.5,21C9.5,20.77 9.5,20.14 9.5,19.31C6.73,19.91 6.14,17.97 6.14,17.97C5.68,16.81 5.03,16.5 5.03,16.5C4.12,15.88 5.1,15.9 5.1,15.9C6.1,15.97 6.63,16.93 6.63,16.93C7.5,18.45 8.97,18 9.54,17.76C9.63,17.11 9.89,16.67 10.17,16.42C7.95,16.17 5.62,15.31 5.62,11.5C5.62,10.39 6,9.5 6.65,8.79C6.55,8.54 6.2,7.5 6.75,6.15C6.75,6.15 7.59,5.88 9.5,7.17C10.29,6.95 11.15,6.84 12,6.84C12.85,6.84 13.71,6.95 14.5,7.17C16.41,5.88 17.25,6.15 17.25,6.15C17.8,7.5 17.45,8.54 17.35,8.79C18,9.5 18.38,10.39 18.38,11.5C18.38,15.32 16.04,16.16 13.81,16.41C14.17,16.72 14.5,17.33 14.5,18.26C14.5,19.6 14.5,20.68 14.5,21C14.5,21.27 14.66,21.59 15.17,21.5C19.14,20.16 22,16.42 22,12A10,10 0 0,0 12,2Z" /></g><g id="glass-flute"><path d="M8,2H16C15.67,5 15.33,8 14.75,9.83C14.17,11.67 13.33,12.33 12.92,14.08C12.5,15.83 12.5,18.67 13.08,20C13.67,21.33 14.83,21.17 15.42,21.25C16,21.33 16,21.67 16,22H8C8,21.67 8,21.33 8.58,21.25C9.17,21.17 10.33,21.33 10.92,20C11.5,18.67 11.5,15.83 11.08,14.08C10.67,12.33 9.83,11.67 9.25,9.83C8.67,8 8.33,5 8,2M10,4C10.07,5.03 10.15,6.07 10.24,7H13.76C13.85,6.07 13.93,5.03 14,4H10Z" /></g><g id="glass-mug"><path d="M10,4V7H18V4H10M8,2H20L21,2V3L20,4V20L21,21V22H20L8,22H7V21L8,20V18.6L4.2,16.83C3.5,16.5 3,15.82 3,15V8A2,2 0 0,1 5,6H8V4L7,3V2H8M5,15L8,16.39V8H5V15Z" /></g><g id="glass-stange"><path d="M8,2H16V22H8V2M10,4V7H14V4H10Z" /></g><g id="glass-tulip"><path d="M8,2H16C15.67,2.67 15.33,3.33 15.58,5C15.83,6.67 16.67,9.33 16.25,10.74C15.83,12.14 14.17,12.28 13.33,13.86C12.5,15.44 12.5,18.47 13.08,19.9C13.67,21.33 14.83,21.17 15.42,21.25C16,21.33 16,21.67 16,22H8C8,21.67 8,21.33 8.58,21.25C9.17,21.17 10.33,21.33 10.92,19.9C11.5,18.47 11.5,15.44 10.67,13.86C9.83,12.28 8.17,12.14 7.75,10.74C7.33,9.33 8.17,6.67 8.42,5C8.67,3.33 8.33,2.67 8,2M10,4C10,5.19 9.83,6.17 9.64,7H14.27C14.13,6.17 14,5.19 14,4H10Z" /></g><g id="glassdoor"><path d="M18,6H16V15C16,16 15.82,16.64 15,16.95L9.5,19V6C9.5,5.3 9.74,4.1 11,4.24L18,5V3.79L9,2.11C8.64,2.04 8.36,2 8,2C6.72,2 6,2.78 6,4V20.37C6,21.95 7.37,22.26 8,22L17,18.32C18,17.91 18,17 18,16V6Z" /></g><g id="glasses"><path d="M3,10C2.76,10 2.55,10.09 2.41,10.25C2.27,10.4 2.21,10.62 2.24,10.86L2.74,13.85C2.82,14.5 3.4,15 4,15H7C7.64,15 8.36,14.44 8.5,13.82L9.56,10.63C9.6,10.5 9.57,10.31 9.5,10.19C9.39,10.07 9.22,10 9,10H3M7,17H4C2.38,17 0.96,15.74 0.76,14.14L0.26,11.15C0.15,10.3 0.39,9.5 0.91,8.92C1.43,8.34 2.19,8 3,8H9C9.83,8 10.58,8.35 11.06,8.96C11.17,9.11 11.27,9.27 11.35,9.45C11.78,9.36 12.22,9.36 12.64,9.45C12.72,9.27 12.82,9.11 12.94,8.96C13.41,8.35 14.16,8 15,8H21C21.81,8 22.57,8.34 23.09,8.92C23.6,9.5 23.84,10.3 23.74,11.11L23.23,14.18C23.04,15.74 21.61,17 20,17H17C15.44,17 13.92,15.81 13.54,14.3L12.64,11.59C12.26,11.31 11.73,11.31 11.35,11.59L10.43,14.37C10.07,15.82 8.56,17 7,17M15,10C14.78,10 14.61,10.07 14.5,10.19C14.42,10.31 14.4,10.5 14.45,10.7L15.46,13.75C15.64,14.44 16.36,15 17,15H20C20.59,15 21.18,14.5 21.25,13.89L21.76,10.82C21.79,10.62 21.73,10.4 21.59,10.25C21.45,10.09 21.24,10 21,10H15Z" /></g><g id="gmail"><path d="M20,18H18V9.25L12,13L6,9.25V18H4V6H5.2L12,10.25L18.8,6H20M20,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V6C22,4.89 21.1,4 20,4Z" /></g><g id="gnome"><path d="M18.42,2C14.26,2 13.5,7.93 15.82,7.93C18.16,7.93 22.58,2 18.42,2M12,2.73C11.92,2.73 11.85,2.73 11.78,2.74C9.44,3.04 10.26,7.12 11.5,7.19C12.72,7.27 14.04,2.73 12,2.73M7.93,4.34C7.81,4.34 7.67,4.37 7.53,4.43C5.65,5.21 7.24,8.41 8.3,8.2C9.27,8 9.39,4.3 7.93,4.34M4.93,6.85C4.77,6.84 4.59,6.9 4.41,7.03C2.9,8.07 4.91,10.58 5.8,10.19C6.57,9.85 6.08,6.89 4.93,6.85M13.29,8.77C10.1,8.8 6.03,10.42 5.32,13.59C4.53,17.11 8.56,22 12.76,22C14.83,22 17.21,20.13 17.66,17.77C18,15.97 13.65,16.69 13.81,17.88C14,19.31 12.76,20 11.55,19.1C7.69,16.16 17.93,14.7 17.25,10.69C17.03,9.39 15.34,8.76 13.29,8.77Z" /></g><g id="gondola"><path d="M18,10H13V7.59L22.12,6.07L21.88,4.59L16.41,5.5C16.46,5.35 16.5,5.18 16.5,5A1.5,1.5 0 0,0 15,3.5A1.5,1.5 0 0,0 13.5,5C13.5,5.35 13.63,5.68 13.84,5.93L13,6.07V5H11V6.41L10.41,6.5C10.46,6.35 10.5,6.18 10.5,6A1.5,1.5 0 0,0 9,4.5A1.5,1.5 0 0,0 7.5,6C7.5,6.36 7.63,6.68 7.83,6.93L1.88,7.93L2.12,9.41L11,7.93V10H6C4.89,10 4,10.9 4,12V18A2,2 0 0,0 6,20H18A2,2 0 0,0 20,18V12A2,2 0 0,0 18,10M6,12H8.25V16H6V12M9.75,16V12H14.25V16H9.75M18,16H15.75V12H18V16Z" /></g><g id="google"><path d="M21.35,11.1H12.18V13.83H18.69C18.36,17.64 15.19,19.27 12.19,19.27C8.36,19.27 5,16.25 5,12C5,7.9 8.2,4.73 12.2,4.73C15.29,4.73 17.1,6.7 17.1,6.7L19,4.72C19,4.72 16.56,2 12.1,2C6.42,2 2.03,6.8 2.03,12C2.03,17.05 6.16,22 12.25,22C17.6,22 21.5,18.33 21.5,12.91C21.5,11.76 21.35,11.1 21.35,11.1V11.1Z" /></g><g id="google-cardboard"><path d="M20.74,6H3.2C2.55,6 2,6.57 2,7.27V17.73C2,18.43 2.55,19 3.23,19H8C8.54,19 9,18.68 9.16,18.21L10.55,14.74C10.79,14.16 11.35,13.75 12,13.75C12.65,13.75 13.21,14.16 13.45,14.74L14.84,18.21C15.03,18.68 15.46,19 15.95,19H20.74C21.45,19 22,18.43 22,17.73V7.27C22,6.57 21.45,6 20.74,6M7.22,14.58C6,14.58 5,13.55 5,12.29C5,11 6,10 7.22,10C8.44,10 9.43,11 9.43,12.29C9.43,13.55 8.44,14.58 7.22,14.58M16.78,14.58C15.56,14.58 14.57,13.55 14.57,12.29C14.57,11.03 15.56,10 16.78,10C18,10 19,11.03 19,12.29C19,13.55 18,14.58 16.78,14.58Z" /></g><g id="google-chrome"><path d="M12,20L15.46,14H15.45C15.79,13.4 16,12.73 16,12C16,10.8 15.46,9.73 14.62,9H19.41C19.79,9.93 20,10.94 20,12A8,8 0 0,1 12,20M4,12C4,10.54 4.39,9.18 5.07,8L8.54,14H8.55C9.24,15.19 10.5,16 12,16C12.45,16 12.88,15.91 13.29,15.77L10.89,19.91C7,19.37 4,16.04 4,12M15,12A3,3 0 0,1 12,15A3,3 0 0,1 9,12A3,3 0 0,1 12,9A3,3 0 0,1 15,12M12,4C14.96,4 17.54,5.61 18.92,8H12C10.06,8 8.45,9.38 8.08,11.21L5.7,7.08C7.16,5.21 9.44,4 12,4M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" /></g><g id="google-circles"><path d="M16.66,15H17C18,15 19,14.8 19.87,14.46C19.17,18.73 15.47,22 11,22C6,22 2,17.97 2,13C2,8.53 5.27,4.83 9.54,4.13C9.2,5 9,6 9,7V7.34C6.68,8.16 5,10.38 5,13A6,6 0 0,0 11,19C13.62,19 15.84,17.32 16.66,15M17,10A3,3 0 0,0 20,7A3,3 0 0,0 17,4A3,3 0 0,0 14,7A3,3 0 0,0 17,10M17,1A6,6 0 0,1 23,7A6,6 0 0,1 17,13A6,6 0 0,1 11,7C11,3.68 13.69,1 17,1Z" /></g><g id="google-circles-communities"><path d="M15,12C13.89,12 13,12.89 13,14A2,2 0 0,0 15,16A2,2 0 0,0 17,14C17,12.89 16.1,12 15,12M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M14,9C14,7.89 13.1,7 12,7C10.89,7 10,7.89 10,9A2,2 0 0,0 12,11A2,2 0 0,0 14,9M9,12A2,2 0 0,0 7,14A2,2 0 0,0 9,16A2,2 0 0,0 11,14C11,12.89 10.1,12 9,12Z" /></g><g id="google-circles-extended"><path d="M18,19C16.89,19 16,18.1 16,17C16,15.89 16.89,15 18,15A2,2 0 0,1 20,17A2,2 0 0,1 18,19M18,13A4,4 0 0,0 14,17A4,4 0 0,0 18,21A4,4 0 0,0 22,17A4,4 0 0,0 18,13M12,11.1A1.9,1.9 0 0,0 10.1,13A1.9,1.9 0 0,0 12,14.9A1.9,1.9 0 0,0 13.9,13A1.9,1.9 0 0,0 12,11.1M6,19C4.89,19 4,18.1 4,17C4,15.89 4.89,15 6,15A2,2 0 0,1 8,17A2,2 0 0,1 6,19M6,13A4,4 0 0,0 2,17A4,4 0 0,0 6,21A4,4 0 0,0 10,17A4,4 0 0,0 6,13M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8C10.89,8 10,7.1 10,6C10,4.89 10.89,4 12,4M12,10A4,4 0 0,0 16,6A4,4 0 0,0 12,2A4,4 0 0,0 8,6A4,4 0 0,0 12,10Z" /></g><g id="google-circles-group"><path d="M5,10A2,2 0 0,0 3,12C3,13.11 3.9,14 5,14C6.11,14 7,13.11 7,12A2,2 0 0,0 5,10M5,16A4,4 0 0,1 1,12A4,4 0 0,1 5,8A4,4 0 0,1 9,12A4,4 0 0,1 5,16M10.5,11H14V8L18,12L14,16V13H10.5V11M5,6C4.55,6 4.11,6.05 3.69,6.14C5.63,3.05 9.08,1 13,1C19.08,1 24,5.92 24,12C24,18.08 19.08,23 13,23C9.08,23 5.63,20.95 3.69,17.86C4.11,17.95 4.55,18 5,18C5.8,18 6.56,17.84 7.25,17.56C8.71,19.07 10.74,20 13,20A8,8 0 0,0 21,12A8,8 0 0,0 13,4C10.74,4 8.71,4.93 7.25,6.44C6.56,6.16 5.8,6 5,6Z" /></g><g id="google-controller"><path d="M7.97,16L5,19C4.67,19.3 4.23,19.5 3.75,19.5A1.75,1.75 0 0,1 2,17.75V17.5L3,10.12C3.21,7.81 5.14,6 7.5,6H16.5C18.86,6 20.79,7.81 21,10.12L22,17.5V17.75A1.75,1.75 0 0,1 20.25,19.5C19.77,19.5 19.33,19.3 19,19L16.03,16H7.97M7,8V10H5V11H7V13H8V11H10V10H8V8H7M16.5,8A0.75,0.75 0 0,0 15.75,8.75A0.75,0.75 0 0,0 16.5,9.5A0.75,0.75 0 0,0 17.25,8.75A0.75,0.75 0 0,0 16.5,8M14.75,9.75A0.75,0.75 0 0,0 14,10.5A0.75,0.75 0 0,0 14.75,11.25A0.75,0.75 0 0,0 15.5,10.5A0.75,0.75 0 0,0 14.75,9.75M18.25,9.75A0.75,0.75 0 0,0 17.5,10.5A0.75,0.75 0 0,0 18.25,11.25A0.75,0.75 0 0,0 19,10.5A0.75,0.75 0 0,0 18.25,9.75M16.5,11.5A0.75,0.75 0 0,0 15.75,12.25A0.75,0.75 0 0,0 16.5,13A0.75,0.75 0 0,0 17.25,12.25A0.75,0.75 0 0,0 16.5,11.5Z" /></g><g id="google-controller-off"><path d="M2,5.27L3.28,4L20,20.72L18.73,22L12.73,16H7.97L5,19C4.67,19.3 4.23,19.5 3.75,19.5A1.75,1.75 0 0,1 2,17.75V17.5L3,10.12C3.1,9.09 3.53,8.17 4.19,7.46L2,5.27M5,10V11H7V13H8V11.27L6.73,10H5M16.5,6C18.86,6 20.79,7.81 21,10.12L22,17.5V17.75C22,18.41 21.64,19 21.1,19.28L7.82,6H16.5M16.5,8A0.75,0.75 0 0,0 15.75,8.75A0.75,0.75 0 0,0 16.5,9.5A0.75,0.75 0 0,0 17.25,8.75A0.75,0.75 0 0,0 16.5,8M14.75,9.75A0.75,0.75 0 0,0 14,10.5A0.75,0.75 0 0,0 14.75,11.25A0.75,0.75 0 0,0 15.5,10.5A0.75,0.75 0 0,0 14.75,9.75M18.25,9.75A0.75,0.75 0 0,0 17.5,10.5A0.75,0.75 0 0,0 18.25,11.25A0.75,0.75 0 0,0 19,10.5A0.75,0.75 0 0,0 18.25,9.75M16.5,11.5A0.75,0.75 0 0,0 15.75,12.25A0.75,0.75 0 0,0 16.5,13A0.75,0.75 0 0,0 17.25,12.25A0.75,0.75 0 0,0 16.5,11.5Z" /></g><g id="google-drive"><path d="M7.71,3.5L1.15,15L4.58,21L11.13,9.5M9.73,15L6.3,21H19.42L22.85,15M22.28,14L15.42,2H8.58L8.57,2L15.43,14H22.28Z" /></g><g id="google-earth"><path d="M12.4,7.56C9.6,4.91 7.3,5.65 6.31,6.1C7.06,5.38 7.94,4.8 8.92,4.4C11.7,4.3 14.83,4.84 16.56,7.31C16.56,7.31 19,11.5 19.86,9.65C20.08,10.4 20.2,11.18 20.2,12C20.2,12.3 20.18,12.59 20.15,12.88C18.12,12.65 15.33,10.32 12.4,7.56M19.1,16.1C18.16,16.47 17,17.1 15.14,17.1C13.26,17.1 11.61,16.35 9.56,15.7C7.7,15.11 7,14.2 5.72,14.2C5.06,14.2 4.73,14.86 4.55,15.41C4.07,14.37 3.8,13.22 3.8,12C3.8,11.19 3.92,10.42 4.14,9.68C5.4,8.1 7.33,7.12 10.09,9.26C10.09,9.26 16.32,13.92 19.88,14.23C19.7,14.89 19.43,15.5 19.1,16.1M12,20.2C10.88,20.2 9.81,19.97 8.83,19.56C8.21,18.08 8.22,16.92 9.95,17.5C9.95,17.5 13.87,19 18,17.58C16.5,19.19 14.37,20.2 12,20.2M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z" /></g><g id="google-glass"><path d="M13,11V13.5H18.87C18.26,17 15.5,19.5 12,19.5A7.5,7.5 0 0,1 4.5,12A7.5,7.5 0 0,1 12,4.5C14.09,4.5 15.9,5.39 17.16,6.84L18.93,5.06C17.24,3.18 14.83,2 12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22C17.5,22 21.5,17.5 21.5,12V11H13Z" /></g><g id="google-maps"><path d="M5,4A2,2 0 0,0 3,6V16.29L11.18,8.11C11.06,7.59 11,7.07 11,6.53C11,5.62 11.2,4.76 11.59,4H5M18,21A2,2 0 0,0 20,19V11.86C19.24,13 18.31,14.21 17.29,15.5L16.5,16.5L15.72,15.5C14.39,13.85 13.22,12.32 12.39,10.91C12.05,10.33 11.76,9.76 11.53,9.18L7.46,13.25L15.21,21H18M3,19A2,2 0 0,0 5,21H13.79L6.75,13.96L3,17.71V19M16.5,15C19.11,11.63 21,9.1 21,6.57C21,4.05 19,2 16.5,2C14,2 12,4.05 12,6.57C12,9.1 13.87,11.63 16.5,15M18.5,6.5A2,2 0 0,1 16.5,8.5A2,2 0 0,1 14.5,6.5A2,2 0 0,1 16.5,4.5A2,2 0 0,1 18.5,6.5Z" /></g><g id="google-nearby"><path d="M4.2,3C3.57,3 3.05,3.5 3,4.11C3,8.66 3,13.24 3,17.8C3,18.46 3.54,19 4.2,19C4.31,19 4.42,19 4.53,18.95C8.5,16.84 12.56,14.38 16.5,12.08C16.94,11.89 17.21,11.46 17.21,11C17.21,10.57 17,10.17 16.6,9.96C12.5,7.56 8.21,5.07 4.53,3.05C4.42,3 4.31,3 4.2,3M19.87,6C19.76,6 19.65,6 19.54,6.05C18.6,6.57 17.53,7.18 16.5,7.75C16.85,7.95 17.19,8.14 17.5,8.33C18.5,8.88 19.07,9.9 19.07,11V11C19.07,12.18 18.38,13.27 17.32,13.77C15.92,14.59 12.92,16.36 11.32,17.29C14.07,18.89 16.82,20.5 19.54,21.95C19.65,22 19.76,22 19.87,22C20.54,22 21.07,21.46 21.07,20.8C21.07,16.24 21.08,11.66 21.07,7.11C21,6.5 20.5,6 19.87,6Z" /></g><g id="google-pages"><path d="M19,3H13V8L17,7L16,11H21V5C21,3.89 20.1,3 19,3M17,17L13,16V21H19A2,2 0 0,0 21,19V13H16M8,13H3V19A2,2 0 0,0 5,21H11V16L7,17M3,5V11H8L7,7L11,8V3H5C3.89,3 3,3.89 3,5Z" /></g><g id="google-physical-web"><path d="M12,1.5A9,9 0 0,1 21,10.5C21,13.11 19.89,15.47 18.11,17.11L17.05,16.05C18.55,14.68 19.5,12.7 19.5,10.5A7.5,7.5 0 0,0 12,3A7.5,7.5 0 0,0 4.5,10.5C4.5,12.7 5.45,14.68 6.95,16.05L5.89,17.11C4.11,15.47 3,13.11 3,10.5A9,9 0 0,1 12,1.5M12,4.5A6,6 0 0,1 18,10.5C18,12.28 17.22,13.89 16,15L14.92,13.92C15.89,13.1 16.5,11.87 16.5,10.5C16.5,8 14.5,6 12,6C9.5,6 7.5,8 7.5,10.5C7.5,11.87 8.11,13.1 9.08,13.92L8,15C6.78,13.89 6,12.28 6,10.5A6,6 0 0,1 12,4.5M8.11,17.65L11.29,14.46C11.68,14.07 12.32,14.07 12.71,14.46L15.89,17.65C16.28,18.04 16.28,18.67 15.89,19.06L12.71,22.24C12.32,22.63 11.68,22.63 11.29,22.24L8.11,19.06C7.72,18.67 7.72,18.04 8.11,17.65Z" /></g><g id="google-play"><path d="M3,20.5V3.5C3,2.91 3.34,2.39 3.84,2.15L13.69,12L3.84,21.85C3.34,21.6 3,21.09 3,20.5M16.81,15.12L6.05,21.34L14.54,12.85L16.81,15.12M20.16,10.81C20.5,11.08 20.75,11.5 20.75,12C20.75,12.5 20.53,12.9 20.18,13.18L17.89,14.5L15.39,12L17.89,9.5L20.16,10.81M6.05,2.66L16.81,8.88L14.54,11.15L6.05,2.66Z" /></g><g id="google-plus"><path d="M23,11H21V9H19V11H17V13H19V15H21V13H23M8,11V13.4H12C11.8,14.4 10.8,16.4 8,16.4C5.6,16.4 3.7,14.4 3.7,12C3.7,9.6 5.6,7.6 8,7.6C9.4,7.6 10.3,8.2 10.8,8.7L12.7,6.9C11.5,5.7 9.9,5 8,5C4.1,5 1,8.1 1,12C1,15.9 4.1,19 8,19C12,19 14.7,16.2 14.7,12.2C14.7,11.7 14.7,11.4 14.6,11H8Z" /></g><g id="google-plus-box"><path d="M20,2A2,2 0 0,1 22,4V20A2,2 0 0,1 20,22H4A2,2 0 0,1 2,20V4C2,2.89 2.9,2 4,2H20M20,12H18V10H17V12H15V13H17V15H18V13H20V12M9,11.29V13H11.86C11.71,13.71 11,15.14 9,15.14C7.29,15.14 5.93,13.71 5.93,12C5.93,10.29 7.29,8.86 9,8.86C10,8.86 10.64,9.29 11,9.64L12.36,8.36C11.5,7.5 10.36,7 9,7C6.21,7 4,9.21 4,12C4,14.79 6.21,17 9,17C11.86,17 13.79,15 13.79,12.14C13.79,11.79 13.79,11.57 13.71,11.29H9Z" /></g><g id="google-translate"><path d="M3,1C1.89,1 1,1.89 1,3V17C1,18.11 1.89,19 3,19H15L9,1H3M12.34,5L13,7H21V21H12.38L13.03,23H21C22.11,23 23,22.11 23,21V7C23,5.89 22.11,5 21,5H12.34M7.06,5.91C8.16,5.91 9.09,6.31 9.78,7L8.66,8.03C8.37,7.74 7.87,7.41 7.06,7.41C5.67,7.41 4.56,8.55 4.56,9.94C4.56,11.33 5.67,12.5 7.06,12.5C8.68,12.5 9.26,11.33 9.38,10.75H7.06V9.38H10.88C10.93,9.61 10.94,9.77 10.94,10.06C10.94,12.38 9.38,14 7.06,14C4.81,14 3,12.19 3,9.94C3,7.68 4.81,5.91 7.06,5.91M16,10V11H14.34L14.66,12H18C17.73,12.61 17.63,13.17 16.81,14.13C16.41,13.66 16.09,13.25 16,13H15C15.12,13.43 15.62,14.1 16.22,14.78C16.09,14.91 15.91,15.08 15.75,15.22L16.03,16.06C16.28,15.84 16.53,15.61 16.78,15.38C17.8,16.45 18.88,17.44 18.88,17.44L19.44,16.84C19.44,16.84 18.37,15.79 17.41,14.75C18.04,14.05 18.6,13.2 19,12H20V11H17V10H16Z" /></g><g id="google-wallet"><path d="M9.89,11.08C9.76,9.91 9.39,8.77 8.77,7.77C8.5,7.29 8.46,6.7 8.63,6.25C8.71,6 8.83,5.8 9.03,5.59C9.24,5.38 9.46,5.26 9.67,5.18C9.88,5.09 10,5.06 10.31,5.06C10.66,5.06 11,5.17 11.28,5.35L11.72,5.76L11.83,5.92C12.94,7.76 13.53,9.86 13.53,12L13.5,12.79C13.38,14.68 12.8,16.5 11.82,18.13C11.5,18.67 10.92,19 10.29,19L9.78,18.91L9.37,18.73C8.86,18.43 8.57,17.91 8.5,17.37C8.5,17.05 8.54,16.72 8.69,16.41L8.77,16.28C9.54,15 9.95,13.53 9.95,12L9.89,11.08M20.38,7.88C20.68,9.22 20.84,10.62 20.84,12C20.84,13.43 20.68,14.82 20.38,16.16L20.11,17.21C19.78,18.4 19.4,19.32 19,20C18.7,20.62 18.06,21 17.38,21C17.1,21 16.83,20.94 16.58,20.82C16,20.55 15.67,20.07 15.55,19.54L15.5,19.11C15.5,18.7 15.67,18.35 15.68,18.32C16.62,16.34 17.09,14.23 17.09,12C17.09,9.82 16.62,7.69 15.67,5.68C15.22,4.75 15.62,3.63 16.55,3.18C16.81,3.06 17.08,3 17.36,3C18.08,3 18.75,3.42 19.05,4.07C19.63,5.29 20.08,6.57 20.38,7.88M16.12,9.5C16.26,10.32 16.34,11.16 16.34,12C16.34,14 15.95,15.92 15.2,17.72C15.11,16.21 14.75,14.76 14.16,13.44L14.22,12.73L14.25,11.96C14.25,9.88 13.71,7.85 12.67,6.07C14,7.03 15.18,8.21 16.12,9.5M4,10.5C3.15,10.03 2.84,9 3.28,8.18C3.58,7.63 4.15,7.28 4.78,7.28C5.06,7.28 5.33,7.35 5.58,7.5C6.87,8.17 8.03,9.1 8.97,10.16L9.12,11.05L9.18,12C9.18,13.43 8.81,14.84 8.1,16.07C7.6,13.66 6.12,11.62 4,10.5Z" /></g><g id="gradient"><path d="M11,9H13V11H11V9M9,11H11V13H9V11M13,11H15V13H13V11M15,9H17V11H15V9M7,9H9V11H7V9M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M9,18H7V16H9V18M13,18H11V16H13V18M17,18H15V16H17V18M19,11H17V13H19V15H17V13H15V15H13V13H11V15H9V13H7V15H5V13H7V11H5V5H19V11Z" /></g><g id="grease-pencil"><path d="M18.62,1.5C18.11,1.5 17.6,1.69 17.21,2.09L10.75,8.55L14.95,12.74L21.41,6.29C22.2,5.5 22.2,4.24 21.41,3.46L20.04,2.09C19.65,1.69 19.14,1.5 18.62,1.5M9.8,9.5L3.23,16.07L3.93,16.77C3.4,17.24 2.89,17.78 2.38,18.29C1.6,19.08 1.6,20.34 2.38,21.12C3.16,21.9 4.42,21.9 5.21,21.12C5.72,20.63 6.25,20.08 6.73,19.58L7.43,20.27L14,13.7" /></g><g id="grid"><path d="M10,4V8H14V4H10M16,4V8H20V4H16M16,10V14H20V10H16M16,16V20H20V16H16M14,20V16H10V20H14M8,20V16H4V20H8M8,14V10H4V14H8M8,8V4H4V8H8M10,14H14V10H10V14M4,2H20A2,2 0 0,1 22,4V20A2,2 0 0,1 20,22H4C2.92,22 2,21.1 2,20V4A2,2 0 0,1 4,2Z" /></g><g id="grid-off"><path d="M0,2.77L1.28,1.5L22.5,22.72L21.23,24L19.23,22H4C2.92,22 2,21.1 2,20V4.77L0,2.77M10,4V7.68L8,5.68V4H6.32L4.32,2H20A2,2 0 0,1 22,4V19.7L20,17.7V16H18.32L16.32,14H20V10H16V13.68L14,11.68V10H12.32L10.32,8H14V4H10M16,4V8H20V4H16M16,20H17.23L16,18.77V20M4,8H5.23L4,6.77V8M10,14H11.23L10,12.77V14M14,20V16.77L13.23,16H10V20H14M8,20V16H4V20H8M8,14V10.77L7.23,10H4V14H8Z" /></g><g id="group"><path d="M8,8V12H13V8H8M1,1H5V2H19V1H23V5H22V19H23V23H19V22H5V23H1V19H2V5H1V1M5,19V20H19V19H20V5H19V4H5V5H4V19H5M6,6H15V10H18V18H8V14H6V6M15,14H10V16H16V12H15V14Z" /></g><g id="guitar-electric"><path d="M20.5,2L18.65,4.08L18.83,4.26L10.45,12.16C10.23,12.38 9.45,12.75 9.26,12.28C8.81,11.12 10.23,11 10,10.8C8.94,10.28 7.73,11.18 7.67,11.23C6.94,11.78 6.5,12.43 6.26,13.13C5.96,14.04 5.17,14.15 4.73,14.17C3.64,14.24 3,14.53 2.5,15.23C2.27,15.54 1.9,16 2,16.96C2.16,18 2.95,19.33 3.56,20C4.21,20.69 5.05,21.38 5.81,21.75C6.35,22 6.68,22.08 7.47,21.88C8.17,21.7 8.86,21.14 9.15,20.4C9.39,19.76 9.42,19.3 9.53,18.78C9.67,18.11 9.76,18 10.47,17.68C11.14,17.39 11.5,17.35 12.05,16.78C12.44,16.37 12.64,15.93 12.76,15.46C12.86,15.06 12.93,14.56 12.74,14.5C12.57,14.35 12.27,15.31 11.56,14.86C11.05,14.54 11.11,13.74 11.55,13.29C14.41,10.38 16.75,8 19.63,5.09L19.86,5.32L22,3.5Z" /></g><g id="guitar-pick"><path d="M19,4.1C18.1,3.3 17,2.8 15.8,2.5C15.5,2.4 13.6,2 12.2,2C12.2,2 12.1,2 12,2C12,2 11.9,2 11.8,2C10.4,2 8.4,2.4 8.1,2.5C7,2.8 5.9,3.3 5,4.1C3,5.9 3,8.7 4,11C5,13.5 6.1,15.7 7.6,17.9C8.8,19.6 10.1,22 12,22C13.9,22 15.2,19.6 16.5,17.9C18,15.8 19.1,13.5 20.1,11C21,8.7 21,5.9 19,4.1Z" /></g><g id="guitar-pick-outline"><path d="M19,4.1C18.1,3.3 17,2.8 15.8,2.5C15.5,2.4 13.6,2 12.2,2C12.2,2 12.1,2 12,2C12,2 11.9,2 11.8,2C10.4,2 8.4,2.4 8.1,2.5C7,2.8 5.9,3.3 5,4.1C3,5.9 3,8.7 4,11C5,13.5 6.1,15.7 7.6,17.9C8.8,19.6 10.1,22 12,22C13.9,22 15.2,19.6 16.5,17.9C18,15.8 19.1,13.5 20.1,11C21,8.7 21,5.9 19,4.1M18.2,10.2C17.1,12.9 16.1,14.9 14.8,16.7C14.6,16.9 14.5,17.2 14.3,17.4C13.8,18.2 12.6,20 12,20C12,20 12,20 12,20C11.3,20 10.2,18.3 9.6,17.4C9.4,17.2 9.3,16.9 9.1,16.7C7.9,14.9 6.8,12.9 5.7,10.2C5.5,9.5 4.7,7 6.3,5.5C6.8,5 7.6,4.7 8.6,4.4C9,4.4 10.7,4 11.8,4C11.8,4 12.1,4 12.1,4C13.2,4 14.9,4.3 15.3,4.4C16.3,4.7 17.1,5 17.6,5.5C19.3,7 18.5,9.5 18.2,10.2Z" /></g><g id="hackernews"><path d="M2,2H22V22H2V2M11.25,17.5H12.75V13.06L16,7H14.5L12,11.66L9.5,7H8L11.25,13.06V17.5Z" /></g><g id="hamburger"><path d="M2,16H22V18C22,19.11 21.11,20 20,20H4C2.89,20 2,19.11 2,18V16M6,4H18C20.22,4 22,5.78 22,8V10H2V8C2,5.78 3.78,4 6,4M4,11H15L17,13L19,11H20C21.11,11 22,11.89 22,13C22,14.11 21.11,15 20,15H4C2.89,15 2,14.11 2,13C2,11.89 2.89,11 4,11Z" /></g><g id="hand-pointing-right"><path d="M21,9A1,1 0 0,1 22,10A1,1 0 0,1 21,11H16.53L16.4,12.21L14.2,17.15C14,17.65 13.47,18 12.86,18H8.5C7.7,18 7,17.27 7,16.5V10C7,9.61 7.16,9.26 7.43,9L11.63,4.1L12.4,4.84C12.6,5.03 12.72,5.29 12.72,5.58L12.69,5.8L11,9H21M2,18V10H5V18H2Z" /></g><g id="hanger"><path d="M20.76,16.34H20.75C21.5,16.77 22,17.58 22,18.5A2.5,2.5 0 0,1 19.5,21H4.5A2.5,2.5 0 0,1 2,18.5C2,17.58 2.5,16.77 3.25,16.34H3.24L11,11.86C11,11.86 11,11 12,10C13,10 14,9.1 14,8A2,2 0 0,0 12,6A2,2 0 0,0 10,8H8A4,4 0 0,1 12,4A4,4 0 0,1 16,8C16,9.86 14.73,11.42 13,11.87L20.76,16.34M4.5,19V19H19.5V19C19.67,19 19.84,18.91 19.93,18.75C20.07,18.5 20,18.21 19.75,18.07L12,13.59L4.25,18.07C4,18.21 3.93,18.5 4.07,18.75C4.16,18.91 4.33,19 4.5,19Z" /></g><g id="hangouts"><path d="M15,11L14,13H12.5L13.5,11H12V8H15M11,11L10,13H8.5L9.5,11H8V8H11M11.5,2A8.5,8.5 0 0,0 3,10.5A8.5,8.5 0 0,0 11.5,19H12V22.5C16.86,20.15 20,15 20,10.5C20,5.8 16.19,2 11.5,2Z" /></g><g id="harddisk"><path d="M6,2H18A2,2 0 0,1 20,4V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V4A2,2 0 0,1 6,2M12,4A6,6 0 0,0 6,10C6,13.31 8.69,16 12.1,16L11.22,13.77C10.95,13.29 11.11,12.68 11.59,12.4L12.45,11.9C12.93,11.63 13.54,11.79 13.82,12.27L15.74,14.69C17.12,13.59 18,11.9 18,10A6,6 0 0,0 12,4M12,9A1,1 0 0,1 13,10A1,1 0 0,1 12,11A1,1 0 0,1 11,10A1,1 0 0,1 12,9M7,18A1,1 0 0,0 6,19A1,1 0 0,0 7,20A1,1 0 0,0 8,19A1,1 0 0,0 7,18M12.09,13.27L14.58,19.58L17.17,18.08L12.95,12.77L12.09,13.27Z" /></g><g id="headphones"><path d="M12,1C7,1 3,5 3,10V17A3,3 0 0,0 6,20H9V12H5V10A7,7 0 0,1 12,3A7,7 0 0,1 19,10V12H15V20H18A3,3 0 0,0 21,17V10C21,5 16.97,1 12,1Z" /></g><g id="headphones-box"><path d="M7.2,18C6.54,18 6,17.46 6,16.8V13.2L6,12A6,6 0 0,1 12,6A6,6 0 0,1 18,12V13.2L18,16.8A1.2,1.2 0 0,1 16.8,18H14V14H16V12A4,4 0 0,0 12,8A4,4 0 0,0 8,12V14H10V18M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z" /></g><g id="headphones-settings"><path d="M12,1A9,9 0 0,1 21,10V17A3,3 0 0,1 18,20H15V12H19V10A7,7 0 0,0 12,3A7,7 0 0,0 5,10V12H9V20H6A3,3 0 0,1 3,17V10A9,9 0 0,1 12,1M15,24V22H17V24H15M11,24V22H13V24H11M7,24V22H9V24H7Z" /></g><g id="headset"><path d="M12,1C7,1 3,5 3,10V17A3,3 0 0,0 6,20H9V12H5V10A7,7 0 0,1 12,3A7,7 0 0,1 19,10V12H15V20H19V21H12V23H18A3,3 0 0,0 21,20V10C21,5 16.97,1 12,1Z" /></g><g id="headset-dock"><path d="M2,18H9V6.13C7.27,6.57 6,8.14 6,10V11H8V17H6A2,2 0 0,1 4,15V10A6,6 0 0,1 10,4H11A6,6 0 0,1 17,10V12H18V9H20V12A2,2 0 0,1 18,14H17V15A2,2 0 0,1 15,17H13V11H15V10C15,8.14 13.73,6.57 12,6.13V18H22V20H2V18Z" /></g><g id="headset-off"><path d="M22.5,4.77L20.43,6.84C20.8,7.82 21,8.89 21,10V20A3,3 0 0,1 18,23H12V21H19V20H15V12.27L9,18.27V20H7.27L4.77,22.5L3.5,21.22L21.22,3.5L22.5,4.77M12,1C14.53,1 16.82,2.04 18.45,3.72L17.04,5.14C15.77,3.82 14,3 12,3A7,7 0 0,0 5,10V12H9V13.18L3.5,18.67C3.19,18.19 3,17.62 3,17V10A9,9 0 0,1 12,1M19,12V10C19,9.46 18.94,8.94 18.83,8.44L15.27,12H19Z" /></g><g id="heart"><path d="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z" /></g><g id="heart-box"><path d="M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M12,17L12.72,16.34C15.3,14 17,12.46 17,10.57C17,9.03 15.79,7.82 14.25,7.82C13.38,7.82 12.55,8.23 12,8.87C11.45,8.23 10.62,7.82 9.75,7.82C8.21,7.82 7,9.03 7,10.57C7,12.46 8.7,14 11.28,16.34L12,17Z" /></g><g id="heart-box-outline"><path d="M12,17L11.28,16.34C8.7,14 7,12.46 7,10.57C7,9.03 8.21,7.82 9.75,7.82C10.62,7.82 11.45,8.23 12,8.87C12.55,8.23 13.38,7.82 14.25,7.82C15.79,7.82 17,9.03 17,10.57C17,12.46 15.3,14 12.72,16.34L12,17M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M5,5V19H19V5H5Z" /></g><g id="heart-broken"><path d="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C8.17,3 8.82,3.12 9.44,3.33L13,9.35L9,14.35L12,21.35V21.35M16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35L11,14.35L15.5,9.35L12.85,4.27C13.87,3.47 15.17,3 16.5,3Z" /></g><g id="heart-outline"><path d="M12.1,18.55L12,18.65L11.89,18.55C7.14,14.24 4,11.39 4,8.5C4,6.5 5.5,5 7.5,5C9.04,5 10.54,6 11.07,7.36H12.93C13.46,6 14.96,5 16.5,5C18.5,5 20,6.5 20,8.5C20,11.39 16.86,14.24 12.1,18.55M16.5,3C14.76,3 13.09,3.81 12,5.08C10.91,3.81 9.24,3 7.5,3C4.42,3 2,5.41 2,8.5C2,12.27 5.4,15.36 10.55,20.03L12,21.35L13.45,20.03C18.6,15.36 22,12.27 22,8.5C22,5.41 19.58,3 16.5,3Z" /></g><g id="heart-pulse"><path d="M7.5,4A5.5,5.5 0 0,0 2,9.5C2,10 2.09,10.5 2.22,11H6.3L7.57,7.63C7.87,6.83 9.05,6.75 9.43,7.63L11.5,13L12.09,11.58C12.22,11.25 12.57,11 13,11H21.78C21.91,10.5 22,10 22,9.5A5.5,5.5 0 0,0 16.5,4C14.64,4 13,4.93 12,6.34C11,4.93 9.36,4 7.5,4V4M3,12.5A1,1 0 0,0 2,13.5A1,1 0 0,0 3,14.5H5.44L11,20C12,20.9 12,20.9 13,20L18.56,14.5H21A1,1 0 0,0 22,13.5A1,1 0 0,0 21,12.5H13.4L12.47,14.8C12.07,15.81 10.92,15.67 10.55,14.83L8.5,9.5L7.54,11.83C7.39,12.21 7.05,12.5 6.6,12.5H3Z" /></g><g id="help"><path d="M10,19H13V22H10V19M12,2C17.35,2.22 19.68,7.62 16.5,11.67C15.67,12.67 14.33,13.33 13.67,14.17C13,15 13,16 13,17H10C10,15.33 10,13.92 10.67,12.92C11.33,11.92 12.67,11.33 13.5,10.67C15.92,8.43 15.32,5.26 12,5A3,3 0 0,0 9,8H6A6,6 0 0,1 12,2Z" /></g><g id="help-circle"><path d="M15.07,11.25L14.17,12.17C13.45,12.89 13,13.5 13,15H11V14.5C11,13.39 11.45,12.39 12.17,11.67L13.41,10.41C13.78,10.05 14,9.55 14,9C14,7.89 13.1,7 12,7A2,2 0 0,0 10,9H8A4,4 0 0,1 12,5A4,4 0 0,1 16,9C16,9.88 15.64,10.67 15.07,11.25M13,19H11V17H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z" /></g><g id="help-circle-outline"><path d="M11,18H13V16H11V18M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,6A4,4 0 0,0 8,10H10A2,2 0 0,1 12,8A2,2 0 0,1 14,10C14,12 11,11.75 11,15H13C13,12.75 16,12.5 16,10A4,4 0 0,0 12,6Z" /></g><g id="hexagon"><path d="M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5Z" /></g><g id="hexagon-outline"><path d="M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L5,8.09V15.91L12,19.85L19,15.91V8.09L12,4.15Z" /></g><g id="highway"><path d="M10,2L8,8H11V2H10M13,2V8H16L14,2H13M2,9V10H4V11H6V10H18L18.06,11H20V10H22V9H2M7,11L3.34,22H11V11H7M13,11V22H20.66L17,11H13Z" /></g><g id="history"><path d="M11,7V12.11L15.71,14.9L16.5,13.62L12.5,11.25V7M12.5,2C8.97,2 5.91,3.92 4.27,6.77L2,4.5V11H8.5L5.75,8.25C6.96,5.73 9.5,4 12.5,4A7.5,7.5 0 0,1 20,11.5A7.5,7.5 0 0,1 12.5,19C9.23,19 6.47,16.91 5.44,14H3.34C4.44,18.03 8.11,21 12.5,21C17.74,21 22,16.75 22,11.5A9.5,9.5 0 0,0 12.5,2Z" /></g><g id="hololens"><path d="M12,8C12,8 22,8 22,11C22,11 22.09,14.36 21.75,14.25C21,11 12,11 12,11C12,11 3,11 2.25,14.25C1.91,14.36 2,11 2,11C2,8 12,8 12,8M12,12C20,12 20.75,14.25 20.75,14.25C19.75,17.25 19,18 15,18C12,18 13,16.5 12,16.5C11,16.5 12,18 9,18C5,18 4.25,17.25 3.25,14.25C3.25,14.25 4,12 12,12Z" /></g><g id="home"><path d="M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z" /></g><g id="home-map-marker"><path d="M12,3L2,12H5V20H19V12H22L12,3M12,7.7C14.1,7.7 15.8,9.4 15.8,11.5C15.8,14.5 12,18 12,18C12,18 8.2,14.5 8.2,11.5C8.2,9.4 9.9,7.7 12,7.7M12,10A1.5,1.5 0 0,0 10.5,11.5A1.5,1.5 0 0,0 12,13A1.5,1.5 0 0,0 13.5,11.5A1.5,1.5 0 0,0 12,10Z" /></g><g id="home-modern"><path d="M6,21V8A2,2 0 0,1 8,6L16,3V6A2,2 0 0,1 18,8V21H12V16H8V21H6M14,19H16V16H14V19M8,13H10V9H8V13M12,13H16V9H12V13Z" /></g><g id="home-outline"><path d="M9,19V13H11L13,13H15V19H18V10.91L12,4.91L6,10.91V19H9M12,2.09L21.91,12H20V21H13V15H11V21H4V12H2.09L12,2.09Z" /></g><g id="home-variant"><path d="M8,20H5V12H2L12,3L22,12H19V20H12V14H8V20M14,14V17H17V14H14Z" /></g><g id="hops"><path d="M21,12C21,12 12.5,10 12.5,2C12.5,2 21,2 21,12M3,12C3,2 11.5,2 11.5,2C11.5,10 3,12 3,12M12,6.5C12,6.5 13,8.66 15,10.5C14.76,14.16 12,16 12,16C12,16 9.24,14.16 9,10.5C11,8.66 12,6.5 12,6.5M20.75,13.25C20.75,13.25 20,17 18,19C18,19 15.53,17.36 14.33,14.81C15.05,13.58 15.5,12.12 15.75,11.13C17.13,12.18 18.75,13 20.75,13.25M15.5,18.25C14.5,20.25 12,21.75 12,21.75C12,21.75 9.5,20.25 8.5,18.25C8.5,18.25 9.59,17.34 10.35,15.8C10.82,16.35 11.36,16.79 12,17C12.64,16.79 13.18,16.35 13.65,15.8C14.41,17.34 15.5,18.25 15.5,18.25M3.25,13.25C5.25,13 6.87,12.18 8.25,11.13C8.5,12.12 8.95,13.58 9.67,14.81C8.47,17.36 6,19 6,19C4,17 3.25,13.25 3.25,13.25Z" /></g><g id="hospital"><path d="M18,14H14V18H10V14H6V10H10V6H14V10H18M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z" /></g><g id="hospital-building"><path d="M2,22V7A1,1 0 0,1 3,6H7V2H17V6H21A1,1 0 0,1 22,7V22H14V17H10V22H2M9,4V10H11V8H13V10H15V4H13V6H11V4H9M4,20H8V17H4V20M4,15H8V12H4V15M16,20H20V17H16V20M16,15H20V12H16V15M10,15H14V12H10V15Z" /></g><g id="hospital-marker"><path d="M12,2C15.86,2 19,5.13 19,9C19,14.25 12,22 12,22C12,22 5,14.25 5,9A7,7 0 0,1 12,2M9,6V12H11V10H13V12H15V6H13V8H11V6H9Z" /></g><g id="hotel"><path d="M19,7H11V14H3V5H1V20H3V17H21V20H23V11A4,4 0 0,0 19,7M7,13A3,3 0 0,0 10,10A3,3 0 0,0 7,7A3,3 0 0,0 4,10A3,3 0 0,0 7,13Z" /></g><g id="houzz"><path d="M12,24V16L5.1,20V12H5.1V4L12,0V8L5.1,12L12,16V8L18.9,4V12H18.9V20L12,24Z" /></g><g id="houzz-box"><path d="M12,4L7.41,6.69V12L12,9.3V4M12,9.3V14.7L12,20L16.59,17.31V12L16.59,6.6L12,9.3M12,14.7L7.41,12V17.4L12,14.7M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3Z" /></g><g id="human"><path d="M21,9H15V22H13V16H11V22H9V9H3V7H21M12,2A2,2 0 0,1 14,4A2,2 0 0,1 12,6C10.89,6 10,5.1 10,4C10,2.89 10.89,2 12,2Z" /></g><g id="human-child"><path d="M12,2A3,3 0 0,1 15,5A3,3 0 0,1 12,8A3,3 0 0,1 9,5A3,3 0 0,1 12,2M11,22H8V16H6V9H18V16H16V22H13V18H11V22Z" /></g><g id="human-female"><path d="M12,2A2,2 0 0,1 14,4A2,2 0 0,1 12,6A2,2 0 0,1 10,4A2,2 0 0,1 12,2M10.5,22V16H7.5L10.09,8.41C10.34,7.59 11.1,7 12,7C12.9,7 13.66,7.59 13.91,8.41L16.5,16H13.5V22H10.5Z" /></g><g id="human-greeting"><path d="M1.5,4V5.5C1.5,9.65 3.71,13.28 7,15.3V20H22V18C22,15.34 16.67,14 14,14C14,14 13.83,14 13.75,14C9,14 5,10 5,5.5V4M14,4A4,4 0 0,0 10,8A4,4 0 0,0 14,12A4,4 0 0,0 18,8A4,4 0 0,0 14,4Z" /></g><g id="human-handsdown"><path d="M12,1C10.89,1 10,1.9 10,3C10,4.11 10.89,5 12,5C13.11,5 14,4.11 14,3A2,2 0 0,0 12,1M10,6C9.73,6 9.5,6.11 9.31,6.28H9.3L4,11.59L5.42,13L9,9.41V22H11V15H13V22H15V9.41L18.58,13L20,11.59L14.7,6.28C14.5,6.11 14.27,6 14,6" /></g><g id="human-handsup"><path d="M5,1C5,3.7 6.56,6.16 9,7.32V22H11V15H13V22H15V7.31C17.44,6.16 19,3.7 19,1H17A5,5 0 0,1 12,6A5,5 0 0,1 7,1M12,1C10.89,1 10,1.89 10,3C10,4.11 10.89,5 12,5C13.11,5 14,4.11 14,3C14,1.89 13.11,1 12,1Z" /></g><g id="human-male"><path d="M12,2A2,2 0 0,1 14,4A2,2 0 0,1 12,6A2,2 0 0,1 10,4A2,2 0 0,1 12,2M10.5,7H13.5A2,2 0 0,1 15.5,9V14.5H14V22H10V14.5H8.5V9A2,2 0 0,1 10.5,7Z" /></g><g id="human-male-female"><path d="M7.5,2A2,2 0 0,1 9.5,4A2,2 0 0,1 7.5,6A2,2 0 0,1 5.5,4A2,2 0 0,1 7.5,2M6,7H9A2,2 0 0,1 11,9V14.5H9.5V22H5.5V14.5H4V9A2,2 0 0,1 6,7M16.5,2A2,2 0 0,1 18.5,4A2,2 0 0,1 16.5,6A2,2 0 0,1 14.5,4A2,2 0 0,1 16.5,2M15,22V16H12L14.59,8.41C14.84,7.59 15.6,7 16.5,7C17.4,7 18.16,7.59 18.41,8.41L21,16H18V22H15Z" /></g><g id="human-pregnant"><path d="M9,4C9,2.89 9.89,2 11,2C12.11,2 13,2.89 13,4C13,5.11 12.11,6 11,6C9.89,6 9,5.11 9,4M16,13C16,11.66 15.17,10.5 14,10A3,3 0 0,0 11,7A3,3 0 0,0 8,10V17H10V22H13V17H16V13Z" /></g><g id="image"><path d="M8.5,13.5L11,16.5L14.5,12L19,18H5M21,19V5C21,3.89 20.1,3 19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19Z" /></g><g id="image-album"><path d="M6,19L9,15.14L11.14,17.72L14.14,13.86L18,19H6M6,4H11V12L8.5,10.5L6,12M18,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V4A2,2 0 0,0 18,2Z" /></g><g id="image-area"><path d="M20,5A2,2 0 0,1 22,7V17A2,2 0 0,1 20,19H4C2.89,19 2,18.1 2,17V7C2,5.89 2.89,5 4,5H20M5,16H19L14.5,10L11,14.5L8.5,11.5L5,16Z" /></g><g id="image-area-close"><path d="M12,23L8,19H16L12,23M20,3A2,2 0 0,1 22,5V15A2,2 0 0,1 20,17H4A2,2 0 0,1 2,15V5A2,2 0 0,1 4,3H20M5,14H19L14.5,8L11,12.5L8.5,9.5L5,14Z" /></g><g id="image-broken"><path d="M19,3A2,2 0 0,1 21,5V11H19V13H19L17,13V15H15V17H13V19H11V21H5C3.89,21 3,20.1 3,19V5A2,2 0 0,1 5,3H19M21,15V19A2,2 0 0,1 19,21H19L15,21V19H17V17H19V15H21M19,8.5A0.5,0.5 0 0,0 18.5,8H5.5A0.5,0.5 0 0,0 5,8.5V15.5A0.5,0.5 0 0,0 5.5,16H11V15H13V13H15V11H17V9H19V8.5Z" /></g><g id="image-broken-variant"><path d="M21,5V11.59L18,8.58L14,12.59L10,8.59L6,12.59L3,9.58V5A2,2 0 0,1 5,3H19A2,2 0 0,1 21,5M18,11.42L21,14.43V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V12.42L6,15.41L10,11.41L14,15.41" /></g><g id="image-filter"><path d="M21,17H7V3H21M21,1H7A2,2 0 0,0 5,3V17A2,2 0 0,0 7,19H21A2,2 0 0,0 23,17V3A2,2 0 0,0 21,1M3,5H1V21A2,2 0 0,0 3,23H19V21H3M15.96,10.29L13.21,13.83L11.25,11.47L8.5,15H19.5L15.96,10.29Z" /></g><g id="image-filter-black-white"><path d="M19,19L12,11V19H5L12,11V5H19M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" /></g><g id="image-filter-center-focus"><path d="M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9M19,19H15V21H19A2,2 0 0,0 21,19V15H19M19,3H15V5H19V9H21V5A2,2 0 0,0 19,3M5,5H9V3H5A2,2 0 0,0 3,5V9H5M5,15H3V19A2,2 0 0,0 5,21H9V19H5V15Z" /></g><g id="image-filter-center-focus-weak"><path d="M5,15H3V19A2,2 0 0,0 5,21H9V19H5M5,5H9V3H5A2,2 0 0,0 3,5V9H5M19,3H15V5H19V9H21V5A2,2 0 0,0 19,3M19,19H15V21H19A2,2 0 0,0 21,19V15H19M12,8A4,4 0 0,0 8,12A4,4 0 0,0 12,16A4,4 0 0,0 16,12A4,4 0 0,0 12,8M12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14Z" /></g><g id="image-filter-drama"><path d="M19,18H6A4,4 0 0,1 2,14A4,4 0 0,1 6,10A4,4 0 0,1 10,14H12C12,11.24 10.14,8.92 7.6,8.22C8.61,6.88 10.2,6 12,6C15.03,6 17.5,8.47 17.5,11.5V12H19A3,3 0 0,1 22,15A3,3 0 0,1 19,18M19.35,10.04C18.67,6.59 15.64,4 12,4C9.11,4 6.61,5.64 5.36,8.04C2.35,8.36 0,10.9 0,14A6,6 0 0,0 6,20H19A5,5 0 0,0 24,15C24,12.36 21.95,10.22 19.35,10.04Z" /></g><g id="image-filter-frames"><path d="M18,8H6V18H18M20,20H4V6H8.5L12.04,2.5L15.5,6H20M20,4H16L12,0L8,4H4A2,2 0 0,0 2,6V20A2,2 0 0,0 4,22H20A2,2 0 0,0 22,20V6A2,2 0 0,0 20,4Z" /></g><g id="image-filter-hdr"><path d="M14,6L10.25,11L13.1,14.8L11.5,16C9.81,13.75 7,10 7,10L1,18H23L14,6Z" /></g><g id="image-filter-none"><path d="M21,17H7V3H21M21,1H7A2,2 0 0,0 5,3V17A2,2 0 0,0 7,19H21A2,2 0 0,0 23,17V3A2,2 0 0,0 21,1M3,5H1V21A2,2 0 0,0 3,23H19V21H3V5Z" /></g><g id="image-filter-tilt-shift"><path d="M5.68,19.74C7.16,20.95 9,21.75 11,21.95V19.93C9.54,19.75 8.21,19.17 7.1,18.31M13,19.93V21.95C15,21.75 16.84,20.95 18.32,19.74L16.89,18.31C15.79,19.17 14.46,19.75 13,19.93M18.31,16.9L19.74,18.33C20.95,16.85 21.75,15 21.95,13H19.93C19.75,14.46 19.17,15.79 18.31,16.9M15,12A3,3 0 0,0 12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12M4.07,13H2.05C2.25,15 3.05,16.84 4.26,18.32L5.69,16.89C4.83,15.79 4.25,14.46 4.07,13M5.69,7.1L4.26,5.68C3.05,7.16 2.25,9 2.05,11H4.07C4.25,9.54 4.83,8.21 5.69,7.1M19.93,11H21.95C21.75,9 20.95,7.16 19.74,5.68L18.31,7.1C19.17,8.21 19.75,9.54 19.93,11M18.32,4.26C16.84,3.05 15,2.25 13,2.05V4.07C14.46,4.25 15.79,4.83 16.9,5.69M11,4.07V2.05C9,2.25 7.16,3.05 5.68,4.26L7.1,5.69C8.21,4.83 9.54,4.25 11,4.07Z" /></g><g id="image-filter-vintage"><path d="M12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16M18.7,12.4C18.42,12.24 18.13,12.11 17.84,12C18.13,11.89 18.42,11.76 18.7,11.6C20.62,10.5 21.69,8.5 21.7,6.41C19.91,5.38 17.63,5.3 15.7,6.41C15.42,6.57 15.16,6.76 14.92,6.95C14.97,6.64 15,6.32 15,6C15,3.78 13.79,1.85 12,0.81C10.21,1.85 9,3.78 9,6C9,6.32 9.03,6.64 9.08,6.95C8.84,6.75 8.58,6.56 8.3,6.4C6.38,5.29 4.1,5.37 2.3,6.4C2.3,8.47 3.37,10.5 5.3,11.59C5.58,11.75 5.87,11.88 6.16,12C5.87,12.1 5.58,12.23 5.3,12.39C3.38,13.5 2.31,15.5 2.3,17.58C4.09,18.61 6.37,18.69 8.3,17.58C8.58,17.42 8.84,17.23 9.08,17.04C9.03,17.36 9,17.68 9,18C9,20.22 10.21,22.15 12,23.19C13.79,22.15 15,20.22 15,18C15,17.68 14.97,17.36 14.92,17.05C15.16,17.25 15.42,17.43 15.7,17.59C17.62,18.7 19.9,18.62 21.7,17.59C21.69,15.5 20.62,13.5 18.7,12.4Z" /></g><g id="image-multiple"><path d="M22,16V4A2,2 0 0,0 20,2H8A2,2 0 0,0 6,4V16A2,2 0 0,0 8,18H20A2,2 0 0,0 22,16M11,12L13.03,14.71L16,11L20,16H8M2,6V20A2,2 0 0,0 4,22H18V20H4V6" /></g><g id="import"><path d="M14,12L10,8V11H2V13H10V16M20,18V6C20,4.89 19.1,4 18,4H6A2,2 0 0,0 4,6V9H6V6H18V18H6V15H4V18A2,2 0 0,0 6,20H18A2,2 0 0,0 20,18Z" /></g><g id="inbox"><path d="M19,15H15A3,3 0 0,1 12,18A3,3 0 0,1 9,15H5V5H19M19,3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" /></g><g id="inbox-arrow-down"><path d="M16,10H14V7H10V10H8L12,14M19,15H15A3,3 0 0,1 12,18A3,3 0 0,1 9,15H5V5H19M19,3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" /></g><g id="inbox-arrow-up"><path d="M14,14H10V11H8L12,7L16,11H14V14M16,11M5,15V5H19V15H15A3,3 0 0,1 12,18A3,3 0 0,1 9,15H5M19,3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3" /></g><g id="incognito"><path d="M12,3C9.31,3 7.41,4.22 7.41,4.22L6,9H18L16.59,4.22C16.59,4.22 14.69,3 12,3M12,11C9.27,11 5.39,11.54 5.13,11.59C4.09,11.87 3.25,12.15 2.59,12.41C1.58,12.75 1,13 1,13H23C23,13 22.42,12.75 21.41,12.41C20.75,12.15 19.89,11.87 18.84,11.59C18.84,11.59 14.82,11 12,11M7.5,14A3.5,3.5 0 0,0 4,17.5A3.5,3.5 0 0,0 7.5,21A3.5,3.5 0 0,0 11,17.5C11,17.34 11,17.18 10.97,17.03C11.29,16.96 11.63,16.9 12,16.91C12.37,16.91 12.71,16.96 13.03,17.03C13,17.18 13,17.34 13,17.5A3.5,3.5 0 0,0 16.5,21A3.5,3.5 0 0,0 20,17.5A3.5,3.5 0 0,0 16.5,14C15.03,14 13.77,14.9 13.25,16.19C12.93,16.09 12.55,16 12,16C11.45,16 11.07,16.09 10.75,16.19C10.23,14.9 8.97,14 7.5,14M7.5,15A2.5,2.5 0 0,1 10,17.5A2.5,2.5 0 0,1 7.5,20A2.5,2.5 0 0,1 5,17.5A2.5,2.5 0 0,1 7.5,15M16.5,15A2.5,2.5 0 0,1 19,17.5A2.5,2.5 0 0,1 16.5,20A2.5,2.5 0 0,1 14,17.5A2.5,2.5 0 0,1 16.5,15Z" /></g><g id="infinity"><path d="M18.6,6.62C21.58,6.62 24,9 24,12C24,14.96 21.58,17.37 18.6,17.37C17.15,17.37 15.8,16.81 14.78,15.8L12,13.34L9.17,15.85C8.2,16.82 6.84,17.38 5.4,17.38C2.42,17.38 0,14.96 0,12C0,9.04 2.42,6.62 5.4,6.62C6.84,6.62 8.2,7.18 9.22,8.2L12,10.66L14.83,8.15C15.8,7.18 17.16,6.62 18.6,6.62M7.8,14.39L10.5,12L7.84,9.65C7.16,8.97 6.31,8.62 5.4,8.62C3.53,8.62 2,10.13 2,12C2,13.87 3.53,15.38 5.4,15.38C6.31,15.38 7.16,15.03 7.8,14.39M16.2,9.61L13.5,12L16.16,14.35C16.84,15.03 17.7,15.38 18.6,15.38C20.47,15.38 22,13.87 22,12C22,10.13 20.47,8.62 18.6,8.62C17.69,8.62 16.84,8.97 16.2,9.61Z" /></g><g id="information"><path d="M13,9H11V7H13M13,17H11V11H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" /></g><g id="information-outline"><path d="M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M11,17H13V11H11V17Z" /></g><g id="information-variant"><path d="M13.5,4A1.5,1.5 0 0,0 12,5.5A1.5,1.5 0 0,0 13.5,7A1.5,1.5 0 0,0 15,5.5A1.5,1.5 0 0,0 13.5,4M13.14,8.77C11.95,8.87 8.7,11.46 8.7,11.46C8.5,11.61 8.56,11.6 8.72,11.88C8.88,12.15 8.86,12.17 9.05,12.04C9.25,11.91 9.58,11.7 10.13,11.36C12.25,10 10.47,13.14 9.56,18.43C9.2,21.05 11.56,19.7 12.17,19.3C12.77,18.91 14.38,17.8 14.54,17.69C14.76,17.54 14.6,17.42 14.43,17.17C14.31,17 14.19,17.12 14.19,17.12C13.54,17.55 12.35,18.45 12.19,17.88C12,17.31 13.22,13.4 13.89,10.71C14,10.07 14.3,8.67 13.14,8.77Z" /></g><g id="instagram"><path d="M7.8,2H16.2C19.4,2 22,4.6 22,7.8V16.2A5.8,5.8 0 0,1 16.2,22H7.8C4.6,22 2,19.4 2,16.2V7.8A5.8,5.8 0 0,1 7.8,2M7.6,4A3.6,3.6 0 0,0 4,7.6V16.4C4,18.39 5.61,20 7.6,20H16.4A3.6,3.6 0 0,0 20,16.4V7.6C20,5.61 18.39,4 16.4,4H7.6M17.25,5.5A1.25,1.25 0 0,1 18.5,6.75A1.25,1.25 0 0,1 17.25,8A1.25,1.25 0 0,1 16,6.75A1.25,1.25 0 0,1 17.25,5.5M12,7A5,5 0 0,1 17,12A5,5 0 0,1 12,17A5,5 0 0,1 7,12A5,5 0 0,1 12,7M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9Z" /></g><g id="instapaper"><path d="M10,5A1,1 0 0,0 9,4H8V2H16V4H15A1,1 0 0,0 14,5V19A1,1 0 0,0 15,20H16V22H8V20H9A1,1 0 0,0 10,19V5Z" /></g><g id="internet-explorer"><path d="M13,3L14,3.06C16.8,1.79 19.23,1.64 20.5,2.92C21.5,3.93 21.58,5.67 20.92,7.72C21.61,9 22,10.45 22,12L21.95,13H9.08C9.45,15.28 11.06,17 13,17C14.31,17 15.47,16.21 16.2,15H21.5C20.25,18.5 16.92,21 13,21C11.72,21 10.5,20.73 9.41,20.25C6.5,21.68 3.89,21.9 2.57,20.56C1,18.96 1.68,15.57 4,12C4.93,10.54 6.14,9.06 7.57,7.65L8.38,6.88C7.21,7.57 5.71,8.62 4.19,10.17C5.03,6.08 8.66,3 13,3M13,7C11.21,7 9.69,8.47 9.18,10.5H16.82C16.31,8.47 14.79,7 13,7M20.06,4.06C19.4,3.39 18.22,3.35 16.74,3.81C18.22,4.5 19.5,5.56 20.41,6.89C20.73,5.65 20.64,4.65 20.06,4.06M3.89,20C4.72,20.84 6.4,20.69 8.44,19.76C6.59,18.67 5.17,16.94 4.47,14.88C3.27,17.15 3,19.07 3.89,20Z" /></g><g id="invert-colors"><path d="M12,19.58V19.58C10.4,19.58 8.89,18.96 7.76,17.83C6.62,16.69 6,15.19 6,13.58C6,12 6.62,10.47 7.76,9.34L12,5.1M17.66,7.93L12,2.27V2.27L6.34,7.93C3.22,11.05 3.22,16.12 6.34,19.24C7.9,20.8 9.95,21.58 12,21.58C14.05,21.58 16.1,20.8 17.66,19.24C20.78,16.12 20.78,11.05 17.66,7.93Z" /></g><g id="itunes"><path d="M7.85,17.07C7.03,17.17 3.5,17.67 4.06,20.26C4.69,23.3 9.87,22.59 9.83,19C9.81,16.57 9.83,9.2 9.83,9.2C9.83,9.2 9.76,8.53 10.43,8.39L18.19,6.79C18.19,6.79 18.83,6.65 18.83,7.29C18.83,7.89 18.83,14.2 18.83,14.2C18.83,14.2 18.9,14.83 18.12,15C17.34,15.12 13.91,15.4 14.19,18C14.5,21.07 20,20.65 20,17.07V2.61C20,2.61 20.04,1.62 18.9,1.87L9.5,3.78C9.5,3.78 8.66,3.96 8.66,4.77C8.66,5.5 8.66,16.11 8.66,16.11C8.66,16.11 8.66,16.96 7.85,17.07Z" /></g><g id="jeepney"><path d="M19,13V7H20V4H4V7H5V13H2C2,13.93 2.5,14.71 3.5,14.93V20A1,1 0 0,0 4.5,21H5.5A1,1 0 0,0 6.5,20V19H17.5V20A1,1 0 0,0 18.5,21H19.5A1,1 0 0,0 20.5,20V14.93C21.5,14.7 22,13.93 22,13H19M8,15A1.5,1.5 0 0,1 6.5,13.5A1.5,1.5 0 0,1 8,12A1.5,1.5 0 0,1 9.5,13.5A1.5,1.5 0 0,1 8,15M16,15A1.5,1.5 0 0,1 14.5,13.5A1.5,1.5 0 0,1 16,12A1.5,1.5 0 0,1 17.5,13.5A1.5,1.5 0 0,1 16,15M17.5,10.5C15.92,10.18 14.03,10 12,10C9.97,10 8,10.18 6.5,10.5V7H17.5V10.5Z" /></g><g id="jira"><path d="M12,2A1.58,1.58 0 0,1 13.58,3.58A1.58,1.58 0 0,1 12,5.16A1.58,1.58 0 0,1 10.42,3.58A1.58,1.58 0 0,1 12,2M7.79,3.05C8.66,3.05 9.37,3.76 9.37,4.63C9.37,5.5 8.66,6.21 7.79,6.21A1.58,1.58 0 0,1 6.21,4.63A1.58,1.58 0 0,1 7.79,3.05M16.21,3.05C17.08,3.05 17.79,3.76 17.79,4.63C17.79,5.5 17.08,6.21 16.21,6.21A1.58,1.58 0 0,1 14.63,4.63A1.58,1.58 0 0,1 16.21,3.05M11.8,10.95C9.7,8.84 10.22,7.79 10.22,7.79H13.91C13.91,9.37 11.8,10.95 11.8,10.95M13.91,21.47C13.91,21.47 13.91,19.37 9.7,15.16C5.5,10.95 4.96,9.89 4.43,6.74C4.43,6.74 4.83,6.21 5.36,6.74C5.88,7.26 7.07,7.66 8.12,7.66C8.12,7.66 9.17,10.95 12.07,13.05C12.07,13.05 15.88,9.11 15.88,7.53C15.88,7.53 17.07,7.79 18.5,6.74C18.5,6.74 19.5,6.21 19.57,6.74C19.7,7.79 18.64,11.47 14.3,15.16C14.3,15.16 17.07,18.32 16.8,21.47H13.91M9.17,16.21L11.41,18.71C10.36,19.76 10.22,22 10.22,22H7.07C7.59,17.79 9.17,16.21 9.17,16.21Z" /></g><g id="jsfiddle"><path d="M20.33,10.79C21.9,11.44 23,12.96 23,14.73C23,17.09 21.06,19 18.67,19H5.4C3,18.96 1,17 1,14.62C1,13.03 1.87,11.63 3.17,10.87C3.08,10.59 3.04,10.29 3.04,10C3.04,8.34 4.39,7 6.06,7C6.75,7 7.39,7.25 7.9,7.64C8.96,5.47 11.2,3.96 13.81,3.96C17.42,3.96 20.35,6.85 20.35,10.41C20.35,10.54 20.34,10.67 20.33,10.79M9.22,10.85C7.45,10.85 6,12.12 6,13.67C6,15.23 7.45,16.5 9.22,16.5C10.25,16.5 11.17,16.06 11.76,15.39L10.75,14.25C10.42,14.68 9.77,15 9.22,15C8.43,15 7.79,14.4 7.79,13.67C7.79,12.95 8.43,12.36 9.22,12.36C9.69,12.36 10.12,12.59 10.56,12.88C11,13.16 11.73,14.17 12.31,14.82C13.77,16.29 14.53,16.42 15.4,16.42C17.17,16.42 18.6,15.15 18.6,13.6C18.6,12.04 17.17,10.78 15.4,10.78C14.36,10.78 13.44,11.21 12.85,11.88L13.86,13C14.19,12.59 14.84,12.28 15.4,12.28C16.19,12.28 16.83,12.87 16.83,13.6C16.83,14.32 16.19,14.91 15.4,14.91C14.93,14.91 14.5,14.68 14.05,14.39C13.61,14.11 12.88,13.1 12.31,12.45C10.84,11 10.08,10.85 9.22,10.85Z" /></g><g id="json"><path d="M5,3H7V5H5V10A2,2 0 0,1 3,12A2,2 0 0,1 5,14V19H7V21H5C3.93,20.73 3,20.1 3,19V15A2,2 0 0,0 1,13H0V11H1A2,2 0 0,0 3,9V5A2,2 0 0,1 5,3M19,3A2,2 0 0,1 21,5V9A2,2 0 0,0 23,11H24V13H23A2,2 0 0,0 21,15V19A2,2 0 0,1 19,21H17V19H19V14A2,2 0 0,1 21,12A2,2 0 0,1 19,10V5H17V3H19M12,15A1,1 0 0,1 13,16A1,1 0 0,1 12,17A1,1 0 0,1 11,16A1,1 0 0,1 12,15M8,15A1,1 0 0,1 9,16A1,1 0 0,1 8,17A1,1 0 0,1 7,16A1,1 0 0,1 8,15M16,15A1,1 0 0,1 17,16A1,1 0 0,1 16,17A1,1 0 0,1 15,16A1,1 0 0,1 16,15Z" /></g><g id="keg"><path d="M5,22V20H6V16H5V14H6V11H5V7H11V3H10V2H11L13,2H14V3H13V7H19V11H18V14H19V16H18V20H19V22H5M17,9A1,1 0 0,0 16,8H14A1,1 0 0,0 13,9A1,1 0 0,0 14,10H16A1,1 0 0,0 17,9Z" /></g><g id="kettle"><path d="M12.5,3C7.81,3 4,5.69 4,9V9C4,10.19 4.5,11.34 5.44,12.33C4.53,13.5 4,14.96 4,16.5C4,17.64 4,18.83 4,20C4,21.11 4.89,22 6,22H19C20.11,22 21,21.11 21,20C21,18.85 21,17.61 21,16.5C21,15.28 20.66,14.07 20,13L22,11L19,8L16.9,10.1C15.58,9.38 14.05,9 12.5,9C10.65,9 8.95,9.53 7.55,10.41C7.19,9.97 7,9.5 7,9C7,7.21 9.46,5.75 12.5,5.75V5.75C13.93,5.75 15.3,6.08 16.33,6.67L18.35,4.65C16.77,3.59 14.68,3 12.5,3M12.5,11C12.84,11 13.17,11.04 13.5,11.09C10.39,11.57 8,14.25 8,17.5V20H6V17.5A6.5,6.5 0 0,1 12.5,11Z" /></g><g id="key"><path d="M7,14A2,2 0 0,1 5,12A2,2 0 0,1 7,10A2,2 0 0,1 9,12A2,2 0 0,1 7,14M12.65,10C11.83,7.67 9.61,6 7,6A6,6 0 0,0 1,12A6,6 0 0,0 7,18C9.61,18 11.83,16.33 12.65,14H17V18H21V14H23V10H12.65Z" /></g><g id="key-change"><path d="M6.5,2C8.46,2 10.13,3.25 10.74,5H22V8H18V11H15V8H10.74C10.13,9.75 8.46,11 6.5,11C4,11 2,9 2,6.5C2,4 4,2 6.5,2M6.5,5A1.5,1.5 0 0,0 5,6.5A1.5,1.5 0 0,0 6.5,8A1.5,1.5 0 0,0 8,6.5A1.5,1.5 0 0,0 6.5,5M6.5,13C8.46,13 10.13,14.25 10.74,16H22V19H20V22H18V19H16V22H13V19H10.74C10.13,20.75 8.46,22 6.5,22C4,22 2,20 2,17.5C2,15 4,13 6.5,13M6.5,16A1.5,1.5 0 0,0 5,17.5A1.5,1.5 0 0,0 6.5,19A1.5,1.5 0 0,0 8,17.5A1.5,1.5 0 0,0 6.5,16Z" /></g><g id="key-minus"><path d="M6.5,3C8.46,3 10.13,4.25 10.74,6H22V9H18V12H15V9H10.74C10.13,10.75 8.46,12 6.5,12C4,12 2,10 2,7.5C2,5 4,3 6.5,3M6.5,6A1.5,1.5 0 0,0 5,7.5A1.5,1.5 0 0,0 6.5,9A1.5,1.5 0 0,0 8,7.5A1.5,1.5 0 0,0 6.5,6M8,17H16V19H8V17Z" /></g><g id="key-plus"><path d="M6.5,3C8.46,3 10.13,4.25 10.74,6H22V9H18V12H15V9H10.74C10.13,10.75 8.46,12 6.5,12C4,12 2,10 2,7.5C2,5 4,3 6.5,3M6.5,6A1.5,1.5 0 0,0 5,7.5A1.5,1.5 0 0,0 6.5,9A1.5,1.5 0 0,0 8,7.5A1.5,1.5 0 0,0 6.5,6M8,17H11V14H13V17H16V19H13V22H11V19H8V17Z" /></g><g id="key-remove"><path d="M6.5,3C8.46,3 10.13,4.25 10.74,6H22V9H18V12H15V9H10.74C10.13,10.75 8.46,12 6.5,12C4,12 2,10 2,7.5C2,5 4,3 6.5,3M6.5,6A1.5,1.5 0 0,0 5,7.5A1.5,1.5 0 0,0 6.5,9A1.5,1.5 0 0,0 8,7.5A1.5,1.5 0 0,0 6.5,6M14.59,14L16,15.41L13.41,18L16,20.59L14.59,22L12,19.41L9.41,22L8,20.59L10.59,18L8,15.41L9.41,14L12,16.59L14.59,14Z" /></g><g id="key-variant"><path d="M22,18V22H18V19H15V16H12L9.74,13.74C9.19,13.91 8.61,14 8,14A6,6 0 0,1 2,8A6,6 0 0,1 8,2A6,6 0 0,1 14,8C14,8.61 13.91,9.19 13.74,9.74L22,18M7,5A2,2 0 0,0 5,7A2,2 0 0,0 7,9A2,2 0 0,0 9,7A2,2 0 0,0 7,5Z" /></g><g id="keyboard"><path d="M19,10H17V8H19M19,13H17V11H19M16,10H14V8H16M16,13H14V11H16M16,17H8V15H16M7,10H5V8H7M7,13H5V11H7M8,11H10V13H8M8,8H10V10H8M11,11H13V13H11M11,8H13V10H11M20,5H4C2.89,5 2,5.89 2,7V17A2,2 0 0,0 4,19H20A2,2 0 0,0 22,17V7C22,5.89 21.1,5 20,5Z" /></g><g id="keyboard-backspace"><path d="M21,11H6.83L10.41,7.41L9,6L3,12L9,18L10.41,16.58L6.83,13H21V11Z" /></g><g id="keyboard-caps"><path d="M6,18H18V16H6M12,8.41L16.59,13L18,11.58L12,5.58L6,11.58L7.41,13L12,8.41Z" /></g><g id="keyboard-close"><path d="M12,23L16,19H8M19,8H17V6H19M19,11H17V9H19M16,8H14V6H16M16,11H14V9H16M16,15H8V13H16M7,8H5V6H7M7,11H5V9H7M8,9H10V11H8M8,6H10V8H8M11,9H13V11H11M11,6H13V8H11M20,3H4C2.89,3 2,3.89 2,5V15A2,2 0 0,0 4,17H20A2,2 0 0,0 22,15V5C22,3.89 21.1,3 20,3Z" /></g><g id="keyboard-off"><path d="M1,4.27L2.28,3L20,20.72L18.73,22L15.73,19H4C2.89,19 2,18.1 2,17V7C2,6.5 2.18,6.07 2.46,5.73L1,4.27M19,10V8H17V10H19M19,13V11H17V13H19M16,10V8H14V10H16M16,13V11H14V12.18L11.82,10H13V8H11V9.18L9.82,8L6.82,5H20A2,2 0 0,1 22,7V17C22,17.86 21.46,18.59 20.7,18.87L14.82,13H16M8,15V17H13.73L11.73,15H8M5,10H6.73L5,8.27V10M7,13V11H5V13H7M8,13H9.73L8,11.27V13Z" /></g><g id="keyboard-return"><path d="M19,7V11H5.83L9.41,7.41L8,6L2,12L8,18L9.41,16.58L5.83,13H21V7H19Z" /></g><g id="keyboard-tab"><path d="M20,18H22V6H20M11.59,7.41L15.17,11H1V13H15.17L11.59,16.58L13,18L19,12L13,6L11.59,7.41Z" /></g><g id="keyboard-variant"><path d="M6,16H18V18H6V16M6,13V15H2V13H6M7,15V13H10V15H7M11,15V13H13V15H11M14,15V13H17V15H14M18,15V13H22V15H18M2,10H5V12H2V10M19,12V10H22V12H19M18,12H16V10H18V12M8,12H6V10H8V12M12,12H9V10H12V12M15,12H13V10H15V12M2,9V7H4V9H2M5,9V7H7V9H5M8,9V7H10V9H8M11,9V7H13V9H11M14,9V7H16V9H14M17,9V7H22V9H17Z" /></g><g id="kodi"><path d="M12.03,1C11.82,1 11.6,1.11 11.41,1.31C10.56,2.16 9.72,3 8.88,3.84C8.66,4.06 8.6,4.18 8.38,4.38C8.09,4.62 7.96,4.91 7.97,5.28C8,6.57 8,7.84 8,9.13C8,10.46 8,11.82 8,13.16C8,13.26 8,13.34 8.03,13.44C8.11,13.75 8.31,13.82 8.53,13.59C9.73,12.39 10.8,11.3 12,10.09C13.36,8.73 14.73,7.37 16.09,6C16.5,5.6 16.5,5.15 16.09,4.75C14.94,3.6 13.77,2.47 12.63,1.31C12.43,1.11 12.24,1 12.03,1M18.66,7.66C18.45,7.66 18.25,7.75 18.06,7.94C16.91,9.1 15.75,10.24 14.59,11.41C14.2,11.8 14.2,12.23 14.59,12.63C15.74,13.78 16.88,14.94 18.03,16.09C18.43,16.5 18.85,16.5 19.25,16.09C20.36,15 21.5,13.87 22.59,12.75C22.76,12.58 22.93,12.42 23,12.19V11.88C22.93,11.64 22.76,11.5 22.59,11.31C21.47,10.19 20.37,9.06 19.25,7.94C19.06,7.75 18.86,7.66 18.66,7.66M4.78,8.09C4.65,8.04 4.58,8.14 4.5,8.22C3.35,9.39 2.34,10.43 1.19,11.59C0.93,11.86 0.93,12.24 1.19,12.5C1.81,13.13 2.44,13.75 3.06,14.38C3.6,14.92 4,15.33 4.56,15.88C4.72,16.03 4.86,16 4.94,15.81C5,15.71 5,15.58 5,15.47C5,14.29 5,13.37 5,12.19C5,11 5,9.81 5,8.63C5,8.55 5,8.45 4.97,8.38C4.95,8.25 4.9,8.14 4.78,8.09M12.09,14.25C11.89,14.25 11.66,14.34 11.47,14.53C10.32,15.69 9.18,16.87 8.03,18.03C7.63,18.43 7.63,18.85 8.03,19.25C9.14,20.37 10.26,21.47 11.38,22.59C11.54,22.76 11.71,22.93 11.94,23H12.22C12.44,22.94 12.62,22.79 12.78,22.63C13.9,21.5 15.03,20.38 16.16,19.25C16.55,18.85 16.5,18.4 16.13,18C14.97,16.84 13.84,15.69 12.69,14.53C12.5,14.34 12.3,14.25 12.09,14.25Z" /></g><g id="label"><path d="M17.63,5.84C17.27,5.33 16.67,5 16,5H5A2,2 0 0,0 3,7V17A2,2 0 0,0 5,19H16C16.67,19 17.27,18.66 17.63,18.15L22,12L17.63,5.84Z" /></g><g id="label-outline"><path d="M16,17H5V7H16L19.55,12M17.63,5.84C17.27,5.33 16.67,5 16,5H5A2,2 0 0,0 3,7V17A2,2 0 0,0 5,19H16C16.67,19 17.27,18.66 17.63,18.15L22,12L17.63,5.84Z" /></g><g id="lambda"><path d="M6,20L10.16,7.91L9.34,6H8V4H10C10.42,4 10.78,4.26 10.93,4.63L16.66,18H18V20H16C15.57,20 15.21,19.73 15.07,19.36L11.33,10.65L8.12,20H6Z" /></g><g id="lamp"><path d="M8,2H16L20,14H4L8,2M11,15H13V20H18V22H6V20H11V15Z" /></g><g id="lan"><path d="M10,2C8.89,2 8,2.89 8,4V7C8,8.11 8.89,9 10,9H11V11H2V13H6V15H5C3.89,15 3,15.89 3,17V20C3,21.11 3.89,22 5,22H9C10.11,22 11,21.11 11,20V17C11,15.89 10.11,15 9,15H8V13H16V15H15C13.89,15 13,15.89 13,17V20C13,21.11 13.89,22 15,22H19C20.11,22 21,21.11 21,20V17C21,15.89 20.11,15 19,15H18V13H22V11H13V9H14C15.11,9 16,8.11 16,7V4C16,2.89 15.11,2 14,2H10M10,4H14V7H10V4M5,17H9V20H5V17M15,17H19V20H15V17Z" /></g><g id="lan-connect"><path d="M4,1C2.89,1 2,1.89 2,3V7C2,8.11 2.89,9 4,9H1V11H13V9H10C11.11,9 12,8.11 12,7V3C12,1.89 11.11,1 10,1H4M4,3H10V7H4V3M3,13V18L3,20H10V18H5V13H3M14,13C12.89,13 12,13.89 12,15V19C12,20.11 12.89,21 14,21H11V23H23V21H20C21.11,21 22,20.11 22,19V15C22,13.89 21.11,13 20,13H14M14,15H20V19H14V15Z" /></g><g id="lan-disconnect"><path d="M4,1C2.89,1 2,1.89 2,3V7C2,8.11 2.89,9 4,9H1V11H13V9H10C11.11,9 12,8.11 12,7V3C12,1.89 11.11,1 10,1H4M4,3H10V7H4V3M14,13C12.89,13 12,13.89 12,15V19C12,20.11 12.89,21 14,21H11V23H23V21H20C21.11,21 22,20.11 22,19V15C22,13.89 21.11,13 20,13H14M3.88,13.46L2.46,14.88L4.59,17L2.46,19.12L3.88,20.54L6,18.41L8.12,20.54L9.54,19.12L7.41,17L9.54,14.88L8.12,13.46L6,15.59L3.88,13.46M14,15H20V19H14V15Z" /></g><g id="lan-pending"><path d="M4,1C2.89,1 2,1.89 2,3V7C2,8.11 2.89,9 4,9H1V11H13V9H10C11.11,9 12,8.11 12,7V3C12,1.89 11.11,1 10,1H4M4,3H10V7H4V3M3,12V14H5V12H3M14,13C12.89,13 12,13.89 12,15V19C12,20.11 12.89,21 14,21H11V23H23V21H20C21.11,21 22,20.11 22,19V15C22,13.89 21.11,13 20,13H14M3,15V17H5V15H3M14,15H20V19H14V15M3,18V20H5V18H3M6,18V20H8V18H6M9,18V20H11V18H9Z" /></g><g id="language-c"><path d="M15.45,15.97L15.87,18.41C15.61,18.55 15.19,18.68 14.63,18.8C14.06,18.93 13.39,19 12.62,19C10.41,18.96 8.75,18.3 7.64,17.04C6.5,15.77 5.96,14.16 5.96,12.21C6,9.9 6.68,8.13 8,6.89C9.28,5.64 10.92,5 12.9,5C13.65,5 14.3,5.07 14.84,5.19C15.38,5.31 15.78,5.44 16.04,5.59L15.44,8.08L14.4,7.74C14,7.64 13.53,7.59 13,7.59C11.85,7.58 10.89,7.95 10.14,8.69C9.38,9.42 9,10.54 8.96,12.03C8.97,13.39 9.33,14.45 10.04,15.23C10.75,16 11.74,16.4 13.03,16.41L14.36,16.29C14.79,16.21 15.15,16.1 15.45,15.97Z" /></g><g id="language-cpp"><path d="M10.5,15.97L10.91,18.41C10.65,18.55 10.23,18.68 9.67,18.8C9.1,18.93 8.43,19 7.66,19C5.45,18.96 3.79,18.3 2.68,17.04C1.56,15.77 1,14.16 1,12.21C1.05,9.9 1.72,8.13 3,6.89C4.32,5.64 5.96,5 7.94,5C8.69,5 9.34,5.07 9.88,5.19C10.42,5.31 10.82,5.44 11.08,5.59L10.5,8.08L9.44,7.74C9.04,7.64 8.58,7.59 8.05,7.59C6.89,7.58 5.93,7.95 5.18,8.69C4.42,9.42 4.03,10.54 4,12.03C4,13.39 4.37,14.45 5.08,15.23C5.79,16 6.79,16.4 8.07,16.41L9.4,16.29C9.83,16.21 10.19,16.1 10.5,15.97M11,11H13V9H15V11H17V13H15V15H13V13H11V11M18,11H20V9H22V11H24V13H22V15H20V13H18V11Z" /></g><g id="language-csharp"><path d="M11.5,15.97L11.91,18.41C11.65,18.55 11.23,18.68 10.67,18.8C10.1,18.93 9.43,19 8.66,19C6.45,18.96 4.79,18.3 3.68,17.04C2.56,15.77 2,14.16 2,12.21C2.05,9.9 2.72,8.13 4,6.89C5.32,5.64 6.96,5 8.94,5C9.69,5 10.34,5.07 10.88,5.19C11.42,5.31 11.82,5.44 12.08,5.59L11.5,8.08L10.44,7.74C10.04,7.64 9.58,7.59 9.05,7.59C7.89,7.58 6.93,7.95 6.18,8.69C5.42,9.42 5.03,10.54 5,12.03C5,13.39 5.37,14.45 6.08,15.23C6.79,16 7.79,16.4 9.07,16.41L10.4,16.29C10.83,16.21 11.19,16.1 11.5,15.97M13.89,19L14.5,15H13L13.34,13H14.84L15.16,11H13.66L14,9H15.5L16.11,5H18.11L17.5,9H18.5L19.11,5H21.11L20.5,9H22L21.66,11H20.16L19.84,13H21.34L21,15H19.5L18.89,19H16.89L17.5,15H16.5L15.89,19H13.89M16.84,13H17.84L18.16,11H17.16L16.84,13Z" /></g><g id="language-css3"><path d="M5,3L4.35,6.34H17.94L17.5,8.5H3.92L3.26,11.83H16.85L16.09,15.64L10.61,17.45L5.86,15.64L6.19,14H2.85L2.06,18L9.91,21L18.96,18L20.16,11.97L20.4,10.76L21.94,3H5Z" /></g><g id="language-html5"><path d="M12,17.56L16.07,16.43L16.62,10.33H9.38L9.2,8.3H16.8L17,6.31H7L7.56,12.32H14.45L14.22,14.9L12,15.5L9.78,14.9L9.64,13.24H7.64L7.93,16.43L12,17.56M4.07,3H19.93L18.5,19.2L12,21L5.5,19.2L4.07,3Z" /></g><g id="language-javascript"><path d="M3,3H21V21H3V3M7.73,18.04C8.13,18.89 8.92,19.59 10.27,19.59C11.77,19.59 12.8,18.79 12.8,17.04V11.26H11.1V17C11.1,17.86 10.75,18.08 10.2,18.08C9.62,18.08 9.38,17.68 9.11,17.21L7.73,18.04M13.71,17.86C14.21,18.84 15.22,19.59 16.8,19.59C18.4,19.59 19.6,18.76 19.6,17.23C19.6,15.82 18.79,15.19 17.35,14.57L16.93,14.39C16.2,14.08 15.89,13.87 15.89,13.37C15.89,12.96 16.2,12.64 16.7,12.64C17.18,12.64 17.5,12.85 17.79,13.37L19.1,12.5C18.55,11.54 17.77,11.17 16.7,11.17C15.19,11.17 14.22,12.13 14.22,13.4C14.22,14.78 15.03,15.43 16.25,15.95L16.67,16.13C17.45,16.47 17.91,16.68 17.91,17.26C17.91,17.74 17.46,18.09 16.76,18.09C15.93,18.09 15.45,17.66 15.09,17.06L13.71,17.86Z" /></g><g id="language-php"><path d="M12,18.08C5.37,18.08 0,15.36 0,12C0,8.64 5.37,5.92 12,5.92C18.63,5.92 24,8.64 24,12C24,15.36 18.63,18.08 12,18.08M6.81,10.13C7.35,10.13 7.72,10.23 7.9,10.44C8.08,10.64 8.12,11 8.03,11.47C7.93,12 7.74,12.34 7.45,12.56C7.17,12.78 6.74,12.89 6.16,12.89H5.29L5.82,10.13H6.81M3.31,15.68H4.75L5.09,13.93H6.32C6.86,13.93 7.3,13.87 7.65,13.76C8,13.64 8.32,13.45 8.61,13.18C8.85,12.96 9.04,12.72 9.19,12.45C9.34,12.19 9.45,11.89 9.5,11.57C9.66,10.79 9.55,10.18 9.17,9.75C8.78,9.31 8.18,9.1 7.35,9.1H4.59L3.31,15.68M10.56,7.35L9.28,13.93H10.7L11.44,10.16H12.58C12.94,10.16 13.18,10.22 13.29,10.34C13.4,10.46 13.42,10.68 13.36,11L12.79,13.93H14.24L14.83,10.86C14.96,10.24 14.86,9.79 14.56,9.5C14.26,9.23 13.71,9.1 12.91,9.1H11.64L12,7.35H10.56M18,10.13C18.55,10.13 18.91,10.23 19.09,10.44C19.27,10.64 19.31,11 19.22,11.47C19.12,12 18.93,12.34 18.65,12.56C18.36,12.78 17.93,12.89 17.35,12.89H16.5L17,10.13H18M14.5,15.68H15.94L16.28,13.93H17.5C18.05,13.93 18.5,13.87 18.85,13.76C19.2,13.64 19.5,13.45 19.8,13.18C20.04,12.96 20.24,12.72 20.38,12.45C20.53,12.19 20.64,11.89 20.7,11.57C20.85,10.79 20.74,10.18 20.36,9.75C20,9.31 19.37,9.1 18.54,9.1H15.79L14.5,15.68Z" /></g><g id="language-python"><path d="M19.14,7.5A2.86,2.86 0 0,1 22,10.36V14.14A2.86,2.86 0 0,1 19.14,17H12C12,17.39 12.32,17.96 12.71,17.96H17V19.64A2.86,2.86 0 0,1 14.14,22.5H9.86A2.86,2.86 0 0,1 7,19.64V15.89C7,14.31 8.28,13.04 9.86,13.04H15.11C16.69,13.04 17.96,11.76 17.96,10.18V7.5H19.14M14.86,19.29C14.46,19.29 14.14,19.59 14.14,20.18C14.14,20.77 14.46,20.89 14.86,20.89A0.71,0.71 0 0,0 15.57,20.18C15.57,19.59 15.25,19.29 14.86,19.29M4.86,17.5C3.28,17.5 2,16.22 2,14.64V10.86C2,9.28 3.28,8 4.86,8H12C12,7.61 11.68,7.04 11.29,7.04H7V5.36C7,3.78 8.28,2.5 9.86,2.5H14.14C15.72,2.5 17,3.78 17,5.36V9.11C17,10.69 15.72,11.96 14.14,11.96H8.89C7.31,11.96 6.04,13.24 6.04,14.82V17.5H4.86M9.14,5.71C9.54,5.71 9.86,5.41 9.86,4.82C9.86,4.23 9.54,4.11 9.14,4.11C8.75,4.11 8.43,4.23 8.43,4.82C8.43,5.41 8.75,5.71 9.14,5.71Z" /></g><g id="language-python-text"><path d="M2,5.69C8.92,1.07 11.1,7 11.28,10.27C11.46,13.53 8.29,17.64 4.31,14.92V20.3L2,18.77V5.69M4.22,7.4V12.78C7.84,14.95 9.08,13.17 9.08,10.09C9.08,5.74 6.57,5.59 4.22,7.4M15.08,4.15C15.08,4.15 14.9,7.64 15.08,11.07C15.44,14.5 19.69,11.84 19.69,11.84V4.92L22,5.2V14.44C22,20.6 15.85,20.3 15.85,20.3L15.08,18C20.46,18 19.78,14.43 19.78,14.43C13.27,16.97 12.77,12.61 12.77,12.61V5.69L15.08,4.15Z" /></g><g id="language-swift"><path d="M17.09,19.72C14.73,21.08 11.5,21.22 8.23,19.82C5.59,18.7 3.4,16.74 2,14.5C2.67,15.05 3.46,15.5 4.3,15.9C7.67,17.47 11.03,17.36 13.4,15.9C10.03,13.31 7.16,9.94 5.03,7.19C4.58,6.74 4.25,6.18 3.91,5.68C12.19,11.73 11.83,13.27 6.32,4.67C11.21,9.61 15.75,12.41 15.75,12.41C15.91,12.5 16,12.57 16.11,12.63C16.21,12.38 16.3,12.12 16.37,11.85C17.16,9 16.26,5.73 14.29,3.04C18.84,5.79 21.54,10.95 20.41,15.28C20.38,15.39 20.35,15.5 20.36,15.67C22.6,18.5 22,21.45 21.71,20.89C20.5,18.5 18.23,19.24 17.09,19.72V19.72Z" /></g><g id="laptop"><path d="M4,6H20V16H4M20,18A2,2 0 0,0 22,16V6C22,4.89 21.1,4 20,4H4C2.89,4 2,4.89 2,6V16A2,2 0 0,0 4,18H0V20H24V18H20Z" /></g><g id="laptop-chromebook"><path d="M20,15H4V5H20M14,18H10V17H14M22,18V3H2V18H0V20H24V18H22Z" /></g><g id="laptop-mac"><path d="M12,19A1,1 0 0,1 11,18A1,1 0 0,1 12,17A1,1 0 0,1 13,18A1,1 0 0,1 12,19M4,5H20V16H4M20,18A2,2 0 0,0 22,16V5C22,3.89 21.1,3 20,3H4C2.89,3 2,3.89 2,5V16A2,2 0 0,0 4,18H0A2,2 0 0,0 2,20H22A2,2 0 0,0 24,18H20Z" /></g><g id="laptop-windows"><path d="M3,4H21A1,1 0 0,1 22,5V16A1,1 0 0,1 21,17H22L24,20V21H0V20L2,17H3A1,1 0 0,1 2,16V5A1,1 0 0,1 3,4M4,6V15H20V6H4Z" /></g><g id="lastfm"><path d="M18,17.93C15.92,17.92 14.81,16.9 14.04,15.09L13.82,14.6L11.92,10.23C11.29,8.69 9.72,7.64 7.96,7.64C5.57,7.64 3.63,9.59 3.63,12C3.63,14.41 5.57,16.36 7.96,16.36C9.62,16.36 11.08,15.41 11.8,14L12.57,15.81C11.5,17.15 9.82,18 7.96,18C4.67,18 2,15.32 2,12C2,8.69 4.67,6 7.96,6C10.44,6 12.45,7.34 13.47,9.7C13.54,9.89 14.54,12.24 15.42,14.24C15.96,15.5 16.42,16.31 17.91,16.36C19.38,16.41 20.39,15.5 20.39,14.37C20.39,13.26 19.62,13 18.32,12.56C16,11.79 14.79,11 14.79,9.15C14.79,7.33 16,6.12 18,6.12C19.31,6.12 20.24,6.7 20.89,7.86L19.62,8.5C19.14,7.84 18.61,7.57 17.94,7.57C17,7.57 16.33,8.23 16.33,9.1C16.33,10.34 17.43,10.53 18.97,11.03C21.04,11.71 22,12.5 22,14.42C22,16.45 20.27,17.93 18,17.93Z" /></g><g id="launch"><path d="M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z" /></g><g id="layers"><path d="M12,16L19.36,10.27L21,9L12,2L3,9L4.63,10.27M12,18.54L4.62,12.81L3,14.07L12,21.07L21,14.07L19.37,12.8L12,18.54Z" /></g><g id="layers-off"><path d="M3.27,1L2,2.27L6.22,6.5L3,9L4.63,10.27L12,16L14.1,14.37L15.53,15.8L12,18.54L4.63,12.81L3,14.07L12,21.07L16.95,17.22L20.73,21L22,19.73L3.27,1M19.36,10.27L21,9L12,2L9.09,4.27L16.96,12.15L19.36,10.27M19.81,15L21,14.07L19.57,12.64L18.38,13.56L19.81,15Z" /></g><g id="lead-pencil"><path d="M16.84,2.73C16.45,2.73 16.07,2.88 15.77,3.17L13.65,5.29L18.95,10.6L21.07,8.5C21.67,7.89 21.67,6.94 21.07,6.36L17.9,3.17C17.6,2.88 17.22,2.73 16.84,2.73M12.94,6L4.84,14.11L7.4,14.39L7.58,16.68L9.86,16.85L10.15,19.41L18.25,11.3M4.25,15.04L2.5,21.73L9.2,19.94L8.96,17.78L6.65,17.61L6.47,15.29" /></g><g id="leaf"><path d="M17,8C8,10 5.9,16.17 3.82,21.34L5.71,22L6.66,19.7C7.14,19.87 7.64,20 8,20C19,20 22,3 22,3C21,5 14,5.25 9,6.25C4,7.25 2,11.5 2,13.5C2,15.5 3.75,17.25 3.75,17.25C7,8 17,8 17,8Z" /></g><g id="led-off"><path d="M12,6A4,4 0 0,0 8,10V16H6V18H9V23H11V18H13V23H15V18H18V16H16V10A4,4 0 0,0 12,6Z" /></g><g id="led-on"><path d="M11,0V4H13V0H11M18.3,2.29L15.24,5.29L16.64,6.71L19.7,3.71L18.3,2.29M5.71,2.29L4.29,3.71L7.29,6.71L8.71,5.29L5.71,2.29M12,6A4,4 0 0,0 8,10V16H6V18H9V23H11V18H13V23H15V18H18V16H16V10A4,4 0 0,0 12,6M2,9V11H6V9H2M18,9V11H22V9H18Z" /></g><g id="led-outline"><path d="M12,6A4,4 0 0,0 8,10V16H6V18H9V23H11V18H13V23H15V18H18V16H16V10A4,4 0 0,0 12,6M12,8A2,2 0 0,1 14,10V15H10V10A2,2 0 0,1 12,8Z" /></g><g id="led-variant-off"><path d="M12,3C10.05,3 8.43,4.4 8.08,6.25L16.82,15H18V13H16V7A4,4 0 0,0 12,3M3.28,4L2,5.27L8,11.27V13H6V15H9V21H11V15H11.73L13,16.27V21H15V18.27L18.73,22L20,20.72L15,15.72L8,8.72L3.28,4Z" /></g><g id="led-variant-on"><path d="M12,3A4,4 0 0,0 8,7V13H6V15H9V21H11V15H13V21H15V15H18V13H16V7A4,4 0 0,0 12,3Z" /></g><g id="led-variant-outline"><path d="M12,3A4,4 0 0,0 8,7V13H6V15H9V21H11V15H13V21H15V15H18V13H16V7A4,4 0 0,0 12,3M12,5A2,2 0 0,1 14,7V12H10V7A2,2 0 0,1 12,5Z" /></g><g id="library"><path d="M12,8A3,3 0 0,0 15,5A3,3 0 0,0 12,2A3,3 0 0,0 9,5A3,3 0 0,0 12,8M12,11.54C9.64,9.35 6.5,8 3,8V19C6.5,19 9.64,20.35 12,22.54C14.36,20.35 17.5,19 21,19V8C17.5,8 14.36,9.35 12,11.54Z" /></g><g id="library-books"><path d="M19,7H9V5H19M15,15H9V13H15M19,11H9V9H19M20,2H8A2,2 0 0,0 6,4V16A2,2 0 0,0 8,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2M4,6H2V20A2,2 0 0,0 4,22H18V20H4V6Z" /></g><g id="library-music"><path d="M4,6H2V20A2,2 0 0,0 4,22H18V20H4M18,7H15V12.5A2.5,2.5 0 0,1 12.5,15A2.5,2.5 0 0,1 10,12.5A2.5,2.5 0 0,1 12.5,10C13.07,10 13.58,10.19 14,10.5V5H18M20,2H8A2,2 0 0,0 6,4V16A2,2 0 0,0 8,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2Z" /></g><g id="library-plus"><path d="M19,11H15V15H13V11H9V9H13V5H15V9H19M20,2H8A2,2 0 0,0 6,4V16A2,2 0 0,0 8,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2M4,6H2V20A2,2 0 0,0 4,22H18V20H4V6Z" /></g><g id="lightbulb"><path d="M12,2A7,7 0 0,0 5,9C5,11.38 6.19,13.47 8,14.74V17A1,1 0 0,0 9,18H15A1,1 0 0,0 16,17V14.74C17.81,13.47 19,11.38 19,9A7,7 0 0,0 12,2M9,21A1,1 0 0,0 10,22H14A1,1 0 0,0 15,21V20H9V21Z" /></g><g id="lightbulb-outline"><path d="M12,2A7,7 0 0,1 19,9C19,11.38 17.81,13.47 16,14.74V17A1,1 0 0,1 15,18H9A1,1 0 0,1 8,17V14.74C6.19,13.47 5,11.38 5,9A7,7 0 0,1 12,2M9,21V20H15V21A1,1 0 0,1 14,22H10A1,1 0 0,1 9,21M12,4A5,5 0 0,0 7,9C7,11.05 8.23,12.81 10,13.58V16H14V13.58C15.77,12.81 17,11.05 17,9A5,5 0 0,0 12,4Z" /></g><g id="link"><path d="M16,6H13V7.9H16C18.26,7.9 20.1,9.73 20.1,12A4.1,4.1 0 0,1 16,16.1H13V18H16A6,6 0 0,0 22,12C22,8.68 19.31,6 16,6M3.9,12C3.9,9.73 5.74,7.9 8,7.9H11V6H8A6,6 0 0,0 2,12A6,6 0 0,0 8,18H11V16.1H8C5.74,16.1 3.9,14.26 3.9,12M8,13H16V11H8V13Z" /></g><g id="link-off"><path d="M2,5.27L3.28,4L20,20.72L18.73,22L14.73,18H13V16.27L9.73,13H8V11.27L5.5,8.76C4.5,9.5 3.9,10.68 3.9,12C3.9,14.26 5.74,16.1 8,16.1H11V18H8A6,6 0 0,1 2,12C2,10.16 2.83,8.5 4.14,7.41L2,5.27M16,6A6,6 0 0,1 22,12C22,14.21 20.8,16.15 19,17.19L17.6,15.77C19.07,15.15 20.1,13.7 20.1,12C20.1,9.73 18.26,7.9 16,7.9H13V6H16M8,6H11V7.9H9.72L7.82,6H8M16,11V13H14.82L12.82,11H16Z" /></g><g id="link-variant"><path d="M10.59,13.41C11,13.8 11,14.44 10.59,14.83C10.2,15.22 9.56,15.22 9.17,14.83C7.22,12.88 7.22,9.71 9.17,7.76V7.76L12.71,4.22C14.66,2.27 17.83,2.27 19.78,4.22C21.73,6.17 21.73,9.34 19.78,11.29L18.29,12.78C18.3,11.96 18.17,11.14 17.89,10.36L18.36,9.88C19.54,8.71 19.54,6.81 18.36,5.64C17.19,4.46 15.29,4.46 14.12,5.64L10.59,9.17C9.41,10.34 9.41,12.24 10.59,13.41M13.41,9.17C13.8,8.78 14.44,8.78 14.83,9.17C16.78,11.12 16.78,14.29 14.83,16.24V16.24L11.29,19.78C9.34,21.73 6.17,21.73 4.22,19.78C2.27,17.83 2.27,14.66 4.22,12.71L5.71,11.22C5.7,12.04 5.83,12.86 6.11,13.65L5.64,14.12C4.46,15.29 4.46,17.19 5.64,18.36C6.81,19.54 8.71,19.54 9.88,18.36L13.41,14.83C14.59,13.66 14.59,11.76 13.41,10.59C13,10.2 13,9.56 13.41,9.17Z" /></g><g id="link-variant-off"><path d="M2,5.27L3.28,4L20,20.72L18.73,22L13.9,17.17L11.29,19.78C9.34,21.73 6.17,21.73 4.22,19.78C2.27,17.83 2.27,14.66 4.22,12.71L5.71,11.22C5.7,12.04 5.83,12.86 6.11,13.65L5.64,14.12C4.46,15.29 4.46,17.19 5.64,18.36C6.81,19.54 8.71,19.54 9.88,18.36L12.5,15.76L10.88,14.15C10.87,14.39 10.77,14.64 10.59,14.83C10.2,15.22 9.56,15.22 9.17,14.83C8.12,13.77 7.63,12.37 7.72,11L2,5.27M12.71,4.22C14.66,2.27 17.83,2.27 19.78,4.22C21.73,6.17 21.73,9.34 19.78,11.29L18.29,12.78C18.3,11.96 18.17,11.14 17.89,10.36L18.36,9.88C19.54,8.71 19.54,6.81 18.36,5.64C17.19,4.46 15.29,4.46 14.12,5.64L10.79,8.97L9.38,7.55L12.71,4.22M13.41,9.17C13.8,8.78 14.44,8.78 14.83,9.17C16.2,10.54 16.61,12.5 16.06,14.23L14.28,12.46C14.23,11.78 13.94,11.11 13.41,10.59C13,10.2 13,9.56 13.41,9.17Z" /></g><g id="linkedin"><path d="M21,21H17V14.25C17,13.19 15.81,12.31 14.75,12.31C13.69,12.31 13,13.19 13,14.25V21H9V9H13V11C13.66,9.93 15.36,9.24 16.5,9.24C19,9.24 21,11.28 21,13.75V21M7,21H3V9H7V21M5,3A2,2 0 0,1 7,5A2,2 0 0,1 5,7A2,2 0 0,1 3,5A2,2 0 0,1 5,3Z" /></g><g id="linkedin-box"><path d="M19,19H16V13.7A1.5,1.5 0 0,0 14.5,12.2A1.5,1.5 0 0,0 13,13.7V19H10V10H13V11.2C13.5,10.36 14.59,9.8 15.5,9.8A3.5,3.5 0 0,1 19,13.3M6.5,8.31C5.5,8.31 4.69,7.5 4.69,6.5A1.81,1.81 0 0,1 6.5,4.69C7.5,4.69 8.31,5.5 8.31,6.5A1.81,1.81 0 0,1 6.5,8.31M8,19H5V10H8M20,2H4C2.89,2 2,2.89 2,4V20A2,2 0 0,0 4,22H20A2,2 0 0,0 22,20V4C22,2.89 21.1,2 20,2Z" /></g><g id="linux"><path d="M13.18,14.5C12.53,15.26 11.47,15.26 10.82,14.5L7.44,10.5C7.16,11.28 7,12.12 7,13C7,14.67 7.57,16.18 8.5,17.27C10,17.37 11.29,17.96 11.78,19C11.85,19 11.93,19 12.22,19C12.71,18 13.95,17.44 15.46,17.33C16.41,16.24 17,14.7 17,13C17,12.12 16.84,11.28 16.56,10.5L13.18,14.5M20,20.75C20,21.3 19.3,22 18.75,22H13.25C12.7,22 12,21.3 12,20.75C12,21.3 11.3,22 10.75,22H5.25C4.7,22 4,21.3 4,20.75C4,19.45 4.94,18.31 6.3,17.65C5.5,16.34 5,14.73 5,13C4,15 2.7,15.56 2.09,15C1.5,14.44 1.79,12.83 3.1,11.41C3.84,10.6 5,9.62 5.81,9.25C6.13,8.56 6.54,7.93 7,7.38V7A5,5 0 0,1 12,2A5,5 0 0,1 17,7V7.38C17.46,7.93 17.87,8.56 18.19,9.25C19,9.62 20.16,10.6 20.9,11.41C22.21,12.83 22.5,14.44 21.91,15C21.3,15.56 20,15 19,13C19,14.75 18.5,16.37 17.67,17.69C19.05,18.33 20,19.44 20,20.75M9.88,9C9.46,9.5 9.46,10.27 9.88,10.75L11.13,12.25C11.54,12.73 12.21,12.73 12.63,12.25L13.88,10.75C14.29,10.27 14.29,9.5 13.88,9H9.88M10,5.25C9.45,5.25 9,5.9 9,7C9,8.1 9.45,8.75 10,8.75C10.55,8.75 11,8.1 11,7C11,5.9 10.55,5.25 10,5.25M14,5.25C13.45,5.25 13,5.9 13,7C13,8.1 13.45,8.75 14,8.75C14.55,8.75 15,8.1 15,7C15,5.9 14.55,5.25 14,5.25Z" /></g><g id="lock"><path d="M12,17A2,2 0 0,0 14,15C14,13.89 13.1,13 12,13A2,2 0 0,0 10,15A2,2 0 0,0 12,17M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V10C4,8.89 4.9,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z" /></g><g id="lock-open"><path d="M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V10A2,2 0 0,1 6,8H15V6A3,3 0 0,0 12,3A3,3 0 0,0 9,6H7A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,17A2,2 0 0,0 14,15A2,2 0 0,0 12,13A2,2 0 0,0 10,15A2,2 0 0,0 12,17Z" /></g><g id="lock-open-outline"><path d="M18,20V10H6V20H18M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V10A2,2 0 0,1 6,8H15V6A3,3 0 0,0 12,3A3,3 0 0,0 9,6H7A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,17A2,2 0 0,1 10,15A2,2 0 0,1 12,13A2,2 0 0,1 14,15A2,2 0 0,1 12,17Z" /></g><g id="lock-outline"><path d="M12,17C10.89,17 10,16.1 10,15C10,13.89 10.89,13 12,13A2,2 0 0,1 14,15A2,2 0 0,1 12,17M18,20V10H6V20H18M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V10C4,8.89 4.89,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z" /></g><g id="lock-plus"><path d="M18,8H17V6A5,5 0 0,0 12,1A5,5 0 0,0 7,6V8H6A2,2 0 0,0 4,10V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V10A2,2 0 0,0 18,8M8.9,6C8.9,4.29 10.29,2.9 12,2.9C13.71,2.9 15.1,4.29 15.1,6V8H8.9V6M16,16H13V19H11V16H8V14H11V11H13V14H16V16Z" /></g><g id="login"><path d="M10,17.25V14H3V10H10V6.75L15.25,12L10,17.25M8,2H17A2,2 0 0,1 19,4V20A2,2 0 0,1 17,22H8A2,2 0 0,1 6,20V16H8V20H17V4H8V8H6V4A2,2 0 0,1 8,2Z" /></g><g id="login-variant"><path d="M19,3H5C3.89,3 3,3.89 3,5V9H5V5H19V19H5V15H3V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3M10.08,15.58L11.5,17L16.5,12L11.5,7L10.08,8.41L12.67,11H3V13H12.67L10.08,15.58Z" /></g><g id="logout"><path d="M17,17.25V14H10V10H17V6.75L22.25,12L17,17.25M13,2A2,2 0 0,1 15,4V8H13V4H4V20H13V16H15V20A2,2 0 0,1 13,22H4A2,2 0 0,1 2,20V4A2,2 0 0,1 4,2H13Z" /></g><g id="logout-variant"><path d="M14.08,15.59L16.67,13H7V11H16.67L14.08,8.41L15.5,7L20.5,12L15.5,17L14.08,15.59M19,3A2,2 0 0,1 21,5V9.67L19,7.67V5H5V19H19V16.33L21,14.33V19A2,2 0 0,1 19,21H5C3.89,21 3,20.1 3,19V5C3,3.89 3.89,3 5,3H19Z" /></g><g id="looks"><path d="M12,6A11,11 0 0,0 1,17H3C3,12.04 7.04,8 12,8C16.96,8 21,12.04 21,17H23A11,11 0 0,0 12,6M12,10C8.14,10 5,13.14 5,17H7A5,5 0 0,1 12,12A5,5 0 0,1 17,17H19C19,13.14 15.86,10 12,10Z" /></g><g id="loupe"><path d="M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22H20A2,2 0 0,0 22,20V12A10,10 0 0,0 12,2M13,7H11V11H7V13H11V17H13V13H17V11H13V7Z" /></g><g id="lumx"><path d="M12.35,1.75L20.13,9.53L13.77,15.89L12.35,14.47L17.3,9.53L10.94,3.16L12.35,1.75M15.89,9.53L14.47,10.94L10.23,6.7L5.28,11.65L3.87,10.23L10.23,3.87L15.89,9.53M10.23,8.11L11.65,9.53L6.7,14.47L13.06,20.84L11.65,22.25L3.87,14.47L10.23,8.11M8.11,14.47L9.53,13.06L13.77,17.3L18.72,12.35L20.13,13.77L13.77,20.13L8.11,14.47Z" /></g><g id="magnet"><path d="M3,7V13A9,9 0 0,0 12,22A9,9 0 0,0 21,13V7H17V13A5,5 0 0,1 12,18A5,5 0 0,1 7,13V7M17,5H21V2H17M3,5H7V2H3" /></g><g id="magnet-on"><path d="M3,7V13A9,9 0 0,0 12,22A9,9 0 0,0 21,13V7H17V13A5,5 0 0,1 12,18A5,5 0 0,1 7,13V7M17,5H21V2H17M3,5H7V2H3M13,1.5L9,9H11V14.5L15,7H13V1.5Z" /></g><g id="magnify"><path d="M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z" /></g><g id="magnify-minus"><path d="M9,2A7,7 0 0,1 16,9C16,10.57 15.5,12 14.61,13.19L15.41,14H16L22,20L20,22L14,16V15.41L13.19,14.61C12,15.5 10.57,16 9,16A7,7 0 0,1 2,9A7,7 0 0,1 9,2M5,8V10H13V8H5Z" /></g><g id="magnify-plus"><path d="M9,2A7,7 0 0,1 16,9C16,10.57 15.5,12 14.61,13.19L15.41,14H16L22,20L20,22L14,16V15.41L13.19,14.61C12,15.5 10.57,16 9,16A7,7 0 0,1 2,9A7,7 0 0,1 9,2M8,5V8H5V10H8V13H10V10H13V8H10V5H8Z" /></g><g id="mail-ru"><path d="M15.45,11.91C15.34,9.7 13.7,8.37 11.72,8.37H11.64C9.35,8.37 8.09,10.17 8.09,12.21C8.09,14.5 9.62,15.95 11.63,15.95C13.88,15.95 15.35,14.3 15.46,12.36M11.65,6.39C13.18,6.39 14.62,7.07 15.67,8.13V8.13C15.67,7.62 16,7.24 16.5,7.24H16.61C17.35,7.24 17.5,7.94 17.5,8.16V16.06C17.46,16.58 18.04,16.84 18.37,16.5C19.64,15.21 21.15,9.81 17.58,6.69C14.25,3.77 9.78,4.25 7.4,5.89C4.88,7.63 3.26,11.5 4.83,15.11C6.54,19.06 11.44,20.24 14.35,19.06C15.83,18.47 16.5,20.46 15,21.11C12.66,22.1 6.23,22 3.22,16.79C1.19,13.27 1.29,7.08 6.68,3.87C10.81,1.42 16.24,2.1 19.5,5.5C22.95,9.1 22.75,15.8 19.4,18.41C17.89,19.59 15.64,18.44 15.66,16.71L15.64,16.15C14.59,17.2 13.18,17.81 11.65,17.81C8.63,17.81 6,15.15 6,12.13C6,9.08 8.63,6.39 11.65,6.39Z" /></g><g id="map"><path d="M15,19L9,16.89V5L15,7.11M20.5,3C20.44,3 20.39,3 20.34,3L15,5.1L9,3L3.36,4.9C3.15,4.97 3,5.15 3,5.38V20.5A0.5,0.5 0 0,0 3.5,21C3.55,21 3.61,21 3.66,20.97L9,18.9L15,21L20.64,19.1C20.85,19 21,18.85 21,18.62V3.5A0.5,0.5 0 0,0 20.5,3Z" /></g><g id="map-marker"><path d="M12,11.5A2.5,2.5 0 0,1 9.5,9A2.5,2.5 0 0,1 12,6.5A2.5,2.5 0 0,1 14.5,9A2.5,2.5 0 0,1 12,11.5M12,2A7,7 0 0,0 5,9C5,14.25 12,22 12,22C12,22 19,14.25 19,9A7,7 0 0,0 12,2Z" /></g><g id="map-marker-circle"><path d="M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,12.5A1.5,1.5 0 0,1 10.5,11A1.5,1.5 0 0,1 12,9.5A1.5,1.5 0 0,1 13.5,11A1.5,1.5 0 0,1 12,12.5M12,7.2C9.9,7.2 8.2,8.9 8.2,11C8.2,14 12,17.5 12,17.5C12,17.5 15.8,14 15.8,11C15.8,8.9 14.1,7.2 12,7.2Z" /></g><g id="map-marker-minus"><path d="M9,11.5A2.5,2.5 0 0,0 11.5,9A2.5,2.5 0 0,0 9,6.5A2.5,2.5 0 0,0 6.5,9A2.5,2.5 0 0,0 9,11.5M9,2C12.86,2 16,5.13 16,9C16,14.25 9,22 9,22C9,22 2,14.25 2,9A7,7 0 0,1 9,2M15,17H23V19H15V17Z" /></g><g id="map-marker-multiple"><path d="M14,11.5A2.5,2.5 0 0,0 16.5,9A2.5,2.5 0 0,0 14,6.5A2.5,2.5 0 0,0 11.5,9A2.5,2.5 0 0,0 14,11.5M14,2C17.86,2 21,5.13 21,9C21,14.25 14,22 14,22C14,22 7,14.25 7,9A7,7 0 0,1 14,2M5,9C5,13.5 10.08,19.66 11,20.81L10,22C10,22 3,14.25 3,9C3,5.83 5.11,3.15 8,2.29C6.16,3.94 5,6.33 5,9Z" /></g><g id="map-marker-off"><path d="M16.37,16.1L11.75,11.47L11.64,11.36L3.27,3L2,4.27L5.18,7.45C5.06,7.95 5,8.46 5,9C5,14.25 12,22 12,22C12,22 13.67,20.15 15.37,17.65L18.73,21L20,19.72M12,6.5A2.5,2.5 0 0,1 14.5,9C14.5,9.73 14.17,10.39 13.67,10.85L17.3,14.5C18.28,12.62 19,10.68 19,9A7,7 0 0,0 12,2C10,2 8.24,2.82 6.96,4.14L10.15,7.33C10.61,6.82 11.26,6.5 12,6.5Z" /></g><g id="map-marker-plus"><path d="M9,11.5A2.5,2.5 0 0,0 11.5,9A2.5,2.5 0 0,0 9,6.5A2.5,2.5 0 0,0 6.5,9A2.5,2.5 0 0,0 9,11.5M9,2C12.86,2 16,5.13 16,9C16,14.25 9,22 9,22C9,22 2,14.25 2,9A7,7 0 0,1 9,2M15,17H18V14H20V17H23V19H20V22H18V19H15V17Z" /></g><g id="map-marker-radius"><path d="M12,2C15.31,2 18,4.66 18,7.95C18,12.41 12,19 12,19C12,19 6,12.41 6,7.95C6,4.66 8.69,2 12,2M12,6A2,2 0 0,0 10,8A2,2 0 0,0 12,10A2,2 0 0,0 14,8A2,2 0 0,0 12,6M20,19C20,21.21 16.42,23 12,23C7.58,23 4,21.21 4,19C4,17.71 5.22,16.56 7.11,15.83L7.75,16.74C6.67,17.19 6,17.81 6,18.5C6,19.88 8.69,21 12,21C15.31,21 18,19.88 18,18.5C18,17.81 17.33,17.19 16.25,16.74L16.89,15.83C18.78,16.56 20,17.71 20,19Z" /></g><g id="margin"><path d="M14.63,6.78L12.9,5.78L18.5,2.08L18.1,8.78L16.37,7.78L8.73,21H6.42L14.63,6.78M17.5,12C19.43,12 21,13.74 21,16.5C21,19.26 19.43,21 17.5,21C15.57,21 14,19.26 14,16.5C14,13.74 15.57,12 17.5,12M17.5,14C16.67,14 16,14.84 16,16.5C16,18.16 16.67,19 17.5,19C18.33,19 19,18.16 19,16.5C19,14.84 18.33,14 17.5,14M7.5,5C9.43,5 11,6.74 11,9.5C11,12.26 9.43,14 7.5,14C5.57,14 4,12.26 4,9.5C4,6.74 5.57,5 7.5,5M7.5,7C6.67,7 6,7.84 6,9.5C6,11.16 6.67,12 7.5,12C8.33,12 9,11.16 9,9.5C9,7.84 8.33,7 7.5,7Z" /></g><g id="markdown"><path d="M2,16V8H4L7,11L10,8H12V16H10V10.83L7,13.83L4,10.83V16H2M16,8H19V12H21.5L17.5,16.5L13.5,12H16V8Z" /></g><g id="marker"><path d="M18.5,1.15C17.97,1.15 17.46,1.34 17.07,1.73L11.26,7.55L16.91,13.2L22.73,7.39C23.5,6.61 23.5,5.35 22.73,4.56L19.89,1.73C19.5,1.34 19,1.15 18.5,1.15M10.3,8.5L4.34,14.46C3.56,15.24 3.56,16.5 4.36,17.31C3.14,18.54 1.9,19.77 0.67,21H6.33L7.19,20.14C7.97,20.9 9.22,20.89 10,20.12L15.95,14.16" /></g><g id="marker-check"><path d="M10,16L5,11L6.41,9.58L10,13.17L17.59,5.58L19,7M19,1H5C3.89,1 3,1.89 3,3V15.93C3,16.62 3.35,17.23 3.88,17.59L12,23L20.11,17.59C20.64,17.23 21,16.62 21,15.93V3C21,1.89 20.1,1 19,1Z" /></g><g id="martini"><path d="M7.5,7L5.5,5H18.5L16.5,7M11,13V19H6V21H18V19H13V13L21,5V3H3V5L11,13Z" /></g><g id="material-ui"><path d="M8,16.61V15.37L14,11.91V7.23L9,10.12L4,7.23V13L3,13.58L2,13V5L3.07,4.38L9,7.81L12.93,5.54L14.93,4.38L16,5V13.06L10.92,16L14.97,18.33L20,15.43V11L21,10.42L22,11V16.58L14.97,20.64L8,16.61M22,9.75L21,10.33L20,9.75V8.58L21,8L22,8.58V9.75Z" /></g><g id="math-compass"><path d="M13,4.2V3C13,2.4 12.6,2 12,2V4.2C9.8,4.6 9,5.7 9,7C9,7.8 9.3,8.5 9.8,9L4,19.9V22L6.2,20L11.6,10C11.7,10 11.9,10 12,10C13.7,10 15,8.7 15,7C15,5.7 14.2,4.6 13,4.2M12.9,7.5C12.7,7.8 12.4,8 12,8C11.4,8 11,7.6 11,7C11,6.8 11.1,6.7 11.1,6.5C11.3,6.2 11.6,6 12,6C12.6,6 13,6.4 13,7C13,7.2 12.9,7.3 12.9,7.5M20,19.9V22H20L17.8,20L13.4,11.8C14.1,11.6 14.7,11.3 15.2,10.9L20,19.9Z" /></g><g id="matrix"><path d="M2,2H6V4H4V20H6V22H2V2M20,4H18V2H22V22H18V20H20V4M9,5H10V10H11V11H8V10H9V6L8,6.5V5.5L9,5M15,13H16V18H17V19H14V18H15V14L14,14.5V13.5L15,13M9,13C10.1,13 11,14.34 11,16C11,17.66 10.1,19 9,19C7.9,19 7,17.66 7,16C7,14.34 7.9,13 9,13M9,14C8.45,14 8,14.9 8,16C8,17.1 8.45,18 9,18C9.55,18 10,17.1 10,16C10,14.9 9.55,14 9,14M15,5C16.1,5 17,6.34 17,8C17,9.66 16.1,11 15,11C13.9,11 13,9.66 13,8C13,6.34 13.9,5 15,5M15,6C14.45,6 14,6.9 14,8C14,9.1 14.45,10 15,10C15.55,10 16,9.1 16,8C16,6.9 15.55,6 15,6Z" /></g><g id="maxcdn"><path d="M20.6,6.69C19.73,5.61 18.38,5 16.9,5H2.95L4.62,8.57L2.39,19H6.05L8.28,8.57H11.4L9.17,19H12.83L15.06,8.57H16.9C17.3,8.57 17.63,8.7 17.82,8.94C18,9.17 18.07,9.5 18,9.9L16.04,19H19.69L21.5,10.65C21.78,9.21 21.46,7.76 20.6,6.69Z" /></g><g id="medium"><path d="M21.93,6.62L15.89,16.47L11.57,9.43L15,3.84C15.17,3.58 15.5,3.47 15.78,3.55L21.93,6.62M22,19.78C22,20.35 21.5,20.57 20.89,20.26L16.18,17.91L22,8.41V19.78M9,19.94C9,20.5 8.57,20.76 8.07,20.5L2.55,17.76C2.25,17.6 2,17.2 2,16.86V4.14C2,3.69 2.33,3.5 2.74,3.68L8.7,6.66L9,7.12V19.94M15.29,17.46L10,14.81V8.81L15.29,17.46Z" /></g><g id="memory"><path d="M17,17H7V7H17M21,11V9H19V7C19,5.89 18.1,5 17,5H15V3H13V5H11V3H9V5H7C5.89,5 5,5.89 5,7V9H3V11H5V13H3V15H5V17A2,2 0 0,0 7,19H9V21H11V19H13V21H15V19H17A2,2 0 0,0 19,17V15H21V13H19V11M13,13H11V11H13M15,9H9V15H15V9Z" /></g><g id="menu"><path d="M3,6H21V8H3V6M3,11H21V13H3V11M3,16H21V18H3V16Z" /></g><g id="menu-down"><path d="M7,10L12,15L17,10H7Z" /></g><g id="menu-down-outline"><path d="M18,9V10.5L12,16.5L6,10.5V9H18M12,13.67L14.67,11H9.33L12,13.67Z" /></g><g id="menu-left"><path d="M14,7L9,12L14,17V7Z" /></g><g id="menu-right"><path d="M10,17L15,12L10,7V17Z" /></g><g id="menu-up"><path d="M7,15L12,10L17,15H7Z" /></g><g id="menu-up-outline"><path d="M18,16V14.5L12,8.5L6,14.5V16H18M12,11.33L14.67,14H9.33L12,11.33Z" /></g><g id="message"><path d="M20,2H4A2,2 0 0,0 2,4V22L6,18H20A2,2 0 0,0 22,16V4C22,2.89 21.1,2 20,2Z" /></g><g id="message-alert"><path d="M13,10H11V6H13M13,14H11V12H13M20,2H4A2,2 0 0,0 2,4V22L6,18H20A2,2 0 0,0 22,16V4C22,2.89 21.1,2 20,2Z" /></g><g id="message-bulleted"><path d="M20,2H4A2,2 0 0,0 2,4V22L6,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2M8,14H6V12H8V14M8,11H6V9H8V11M8,8H6V6H8V8M15,14H10V12H15V14M18,11H10V9H18V11M18,8H10V6H18V8Z" /></g><g id="message-bulleted-off"><path d="M1.27,1.73L0,3L2,5V22L6,18H15L20.73,23.73L22,22.46L1.27,1.73M8,14H6V12H8V14M6,11V9L8,11H6M20,2H4.08L10,7.92V6H18V8H10.08L11.08,9H18V11H13.08L20.07,18C21.14,17.95 22,17.08 22,16V4A2,2 0 0,0 20,2Z" /></g><g id="message-draw"><path d="M18,14H10.5L12.5,12H18M6,14V11.5L12.88,4.64C13.07,4.45 13.39,4.45 13.59,4.64L15.35,6.41C15.55,6.61 15.55,6.92 15.35,7.12L8.47,14M20,2H4A2,2 0 0,0 2,4V22L6,18H20A2,2 0 0,0 22,16V4C22,2.89 21.1,2 20,2Z" /></g><g id="message-image"><path d="M5,14L8.5,9.5L11,12.5L14.5,8L19,14M20,2H4A2,2 0 0,0 2,4V22L6,18H20A2,2 0 0,0 22,16V4C22,2.89 21.1,2 20,2Z" /></g><g id="message-outline"><path d="M20,2H4A2,2 0 0,0 2,4V22L6,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2M20,16H6L4,18V4H20" /></g><g id="message-plus"><path d="M20,2A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H6L2,22V4C2,2.89 2.9,2 4,2H20M11,6V9H8V11H11V14H13V11H16V9H13V6H11Z" /></g><g id="message-processing"><path d="M17,11H15V9H17M13,11H11V9H13M9,11H7V9H9M20,2H4A2,2 0 0,0 2,4V22L6,18H20A2,2 0 0,0 22,16V4C22,2.89 21.1,2 20,2Z" /></g><g id="message-reply"><path d="M22,4C22,2.89 21.1,2 20,2H4A2,2 0 0,0 2,4V16A2,2 0 0,0 4,18H18L22,22V4Z" /></g><g id="message-reply-text"><path d="M18,8H6V6H18V8M18,11H6V9H18V11M18,14H6V12H18V14M22,4A2,2 0 0,0 20,2H4A2,2 0 0,0 2,4V16A2,2 0 0,0 4,18H18L22,22V4Z" /></g><g id="message-text"><path d="M20,2H4A2,2 0 0,0 2,4V22L6,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2M6,9H18V11H6M14,14H6V12H14M18,8H6V6H18" /></g><g id="message-text-outline"><path d="M20,2A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H6L2,22V4C2,2.89 2.9,2 4,2H20M4,4V17.17L5.17,16H20V4H4M6,7H18V9H6V7M6,11H15V13H6V11Z" /></g><g id="message-video"><path d="M18,14L14,10.8V14H6V6H14V9.2L18,6M20,2H4A2,2 0 0,0 2,4V22L6,18H20A2,2 0 0,0 22,16V4C22,2.89 21.1,2 20,2Z" /></g><g id="meteor"><path d="M2.8,3L19.67,18.82C19.67,18.82 20,19.27 19.58,19.71C19.17,20.15 18.63,19.77 18.63,19.77L2.8,3M7.81,4.59L20.91,16.64C20.91,16.64 21.23,17.08 20.82,17.5C20.4,17.97 19.86,17.59 19.86,17.59L7.81,4.59M4.29,8L17.39,20.03C17.39,20.03 17.71,20.47 17.3,20.91C16.88,21.36 16.34,21 16.34,21L4.29,8M12.05,5.96L21.2,14.37C21.2,14.37 21.42,14.68 21.13,15C20.85,15.3 20.47,15.03 20.47,15.03L12.05,5.96M5.45,11.91L14.6,20.33C14.6,20.33 14.82,20.64 14.54,20.95C14.25,21.26 13.87,21 13.87,21L5.45,11.91M16.38,7.92L20.55,11.74C20.55,11.74 20.66,11.88 20.5,12.03C20.38,12.17 20.19,12.05 20.19,12.05L16.38,7.92M7.56,16.1L11.74,19.91C11.74,19.91 11.85,20.06 11.7,20.2C11.56,20.35 11.37,20.22 11.37,20.22L7.56,16.1Z" /></g><g id="microphone"><path d="M12,2A3,3 0 0,1 15,5V11A3,3 0 0,1 12,14A3,3 0 0,1 9,11V5A3,3 0 0,1 12,2M19,11C19,14.53 16.39,17.44 13,17.93V21H11V17.93C7.61,17.44 5,14.53 5,11H7A5,5 0 0,0 12,16A5,5 0 0,0 17,11H19Z" /></g><g id="microphone-off"><path d="M19,11C19,12.19 18.66,13.3 18.1,14.28L16.87,13.05C17.14,12.43 17.3,11.74 17.3,11H19M15,11.16L9,5.18V5A3,3 0 0,1 12,2A3,3 0 0,1 15,5V11L15,11.16M4.27,3L21,19.73L19.73,21L15.54,16.81C14.77,17.27 13.91,17.58 13,17.72V21H11V17.72C7.72,17.23 5,14.41 5,11H6.7C6.7,14 9.24,16.1 12,16.1C12.81,16.1 13.6,15.91 14.31,15.58L12.65,13.92L12,14A3,3 0 0,1 9,11V10.28L3,4.27L4.27,3Z" /></g><g id="microphone-outline"><path d="M17.3,11C17.3,14 14.76,16.1 12,16.1C9.24,16.1 6.7,14 6.7,11H5C5,14.41 7.72,17.23 11,17.72V21H13V17.72C16.28,17.23 19,14.41 19,11M10.8,4.9C10.8,4.24 11.34,3.7 12,3.7C12.66,3.7 13.2,4.24 13.2,4.9L13.19,11.1C13.19,11.76 12.66,12.3 12,12.3C11.34,12.3 10.8,11.76 10.8,11.1M12,14A3,3 0 0,0 15,11V5A3,3 0 0,0 12,2A3,3 0 0,0 9,5V11A3,3 0 0,0 12,14Z" /></g><g id="microphone-settings"><path d="M19,10H17.3C17.3,13 14.76,15.1 12,15.1C9.24,15.1 6.7,13 6.7,10H5C5,13.41 7.72,16.23 11,16.72V20H13V16.72C16.28,16.23 19,13.41 19,10M15,24H17V22H15M11,24H13V22H11M12,13A3,3 0 0,0 15,10V4A3,3 0 0,0 12,1A3,3 0 0,0 9,4V10A3,3 0 0,0 12,13M7,24H9V22H7V24Z" /></g><g id="microphone-variant"><path d="M9,3A4,4 0 0,1 13,7H5A4,4 0 0,1 9,3M11.84,9.82L11,18H10V19A2,2 0 0,0 12,21A2,2 0 0,0 14,19V14A4,4 0 0,1 18,10H20L19,11L20,12H18A2,2 0 0,0 16,14V19A4,4 0 0,1 12,23A4,4 0 0,1 8,19V18H7L6.16,9.82C5.67,9.32 5.31,8.7 5.13,8H12.87C12.69,8.7 12.33,9.32 11.84,9.82M9,11A1,1 0 0,0 8,12A1,1 0 0,0 9,13A1,1 0 0,0 10,12A1,1 0 0,0 9,11Z" /></g><g id="microphone-variant-off"><path d="M2,5.27L3.28,4L20,20.72L18.73,22L16,19.26C15.86,21.35 14.12,23 12,23A4,4 0 0,1 8,19V18H7L6.16,9.82C5.82,9.47 5.53,9.06 5.33,8.6L2,5.27M9,3A4,4 0 0,1 13,7H8.82L6.08,4.26C6.81,3.5 7.85,3 9,3M11.84,9.82L11.82,10L9.82,8H12.87C12.69,8.7 12.33,9.32 11.84,9.82M11,18H10V19A2,2 0 0,0 12,21A2,2 0 0,0 14,19V17.27L11.35,14.62L11,18M18,10H20L19,11L20,12H18A2,2 0 0,0 16,14V14.18L14.3,12.5C14.9,11 16.33,10 18,10M8,12A1,1 0 0,0 9,13C9.21,13 9.4,12.94 9.56,12.83L8.17,11.44C8.06,11.6 8,11.79 8,12Z" /></g><g id="microscope"><path d="M9.46,6.28L11.05,9C8.47,9.26 6.5,11.41 6.5,14A5,5 0 0,0 11.5,19C13.55,19 15.31,17.77 16.08,16H13.5V14H21.5V16H19.25C18.84,17.57 17.97,18.96 16.79,20H19.5V22H3.5V20H6.21C4.55,18.53 3.5,16.39 3.5,14C3.5,10.37 5.96,7.2 9.46,6.28M12.74,2.07L13.5,3.37L14.36,2.87L17.86,8.93L14.39,10.93L10.89,4.87L11.76,4.37L11,3.07L12.74,2.07Z" /></g><g id="microsoft"><path d="M2,3H11V12H2V3M11,22H2V13H11V22M21,3V12H12V3H21M21,22H12V13H21V22Z" /></g><g id="minecraft"><path d="M4,2H20A2,2 0 0,1 22,4V20A2,2 0 0,1 20,22H4A2,2 0 0,1 2,20V4A2,2 0 0,1 4,2M6,6V10H10V12H8V18H10V16H14V18H16V12H14V10H18V6H14V10H10V6H6Z" /></g><g id="minus"><path d="M19,13H5V11H19V13Z" /></g><g id="minus-box"><path d="M17,13H7V11H17M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z" /></g><g id="minus-circle"><path d="M17,13H7V11H17M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" /></g><g id="minus-circle-outline"><path d="M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M7,13H17V11H7" /></g><g id="minus-network"><path d="M16,11V9H8V11H16M17,3A2,2 0 0,1 19,5V15A2,2 0 0,1 17,17H13V19H14A1,1 0 0,1 15,20H22V22H15A1,1 0 0,1 14,23H10A1,1 0 0,1 9,22H2V20H9A1,1 0 0,1 10,19H11V17H7C5.89,17 5,16.1 5,15V5A2,2 0 0,1 7,3H17Z" /></g><g id="mixcloud"><path d="M21.11,18.5C20.97,18.5 20.83,18.44 20.71,18.36C20.37,18.13 20.28,17.68 20.5,17.34C21.18,16.34 21.54,15.16 21.54,13.93C21.54,12.71 21.18,11.53 20.5,10.5C20.28,10.18 20.37,9.73 20.71,9.5C21.04,9.28 21.5,9.37 21.72,9.7C22.56,10.95 23,12.41 23,13.93C23,15.45 22.56,16.91 21.72,18.16C21.58,18.37 21.35,18.5 21.11,18.5M19,17.29C18.88,17.29 18.74,17.25 18.61,17.17C18.28,16.94 18.19,16.5 18.42,16.15C18.86,15.5 19.1,14.73 19.1,13.93C19.1,13.14 18.86,12.37 18.42,11.71C18.19,11.37 18.28,10.92 18.61,10.69C18.95,10.47 19.4,10.55 19.63,10.89C20.24,11.79 20.56,12.84 20.56,13.93C20.56,15 20.24,16.07 19.63,16.97C19.5,17.18 19.25,17.29 19,17.29M14.9,15.73C15.89,15.73 16.7,14.92 16.7,13.93C16.7,13.17 16.22,12.5 15.55,12.25C15.5,12.55 15.43,12.85 15.34,13.14C15.23,13.44 14.95,13.64 14.64,13.64C14.57,13.64 14.5,13.62 14.41,13.6C14.03,13.47 13.82,13.06 13.95,12.67C14.09,12.24 14.17,11.78 14.17,11.32C14.17,8.93 12.22,7 9.82,7C8.1,7 6.56,8 5.87,9.5C6.54,9.7 7.16,10.04 7.66,10.54C7.95,10.83 7.95,11.29 7.66,11.58C7.38,11.86 6.91,11.86 6.63,11.58C6.17,11.12 5.56,10.86 4.9,10.86C3.56,10.86 2.46,11.96 2.46,13.3C2.46,14.64 3.56,15.73 4.9,15.73H14.9M15.6,10.75C17.06,11.07 18.17,12.37 18.17,13.93C18.17,15.73 16.7,17.19 14.9,17.19H4.9C2.75,17.19 1,15.45 1,13.3C1,11.34 2.45,9.73 4.33,9.45C5.12,7.12 7.33,5.5 9.82,5.5C12.83,5.5 15.31,7.82 15.6,10.75Z" /></g><g id="monitor"><path d="M21,16H3V4H21M21,2H3C1.89,2 1,2.89 1,4V16A2,2 0 0,0 3,18H10V20H8V22H16V20H14V18H21A2,2 0 0,0 23,16V4C23,2.89 22.1,2 21,2Z" /></g><g id="monitor-multiple"><path d="M22,17V7H6V17H22M22,5A2,2 0 0,1 24,7V17C24,18.11 23.1,19 22,19H16V21H18V23H10V21H12V19H6C4.89,19 4,18.11 4,17V7A2,2 0 0,1 6,5H22M2,3V15H0V3A2,2 0 0,1 2,1H20V3H2Z" /></g><g id="more"><path d="M19,13.5A1.5,1.5 0 0,1 17.5,12A1.5,1.5 0 0,1 19,10.5A1.5,1.5 0 0,1 20.5,12A1.5,1.5 0 0,1 19,13.5M14,13.5A1.5,1.5 0 0,1 12.5,12A1.5,1.5 0 0,1 14,10.5A1.5,1.5 0 0,1 15.5,12A1.5,1.5 0 0,1 14,13.5M9,13.5A1.5,1.5 0 0,1 7.5,12A1.5,1.5 0 0,1 9,10.5A1.5,1.5 0 0,1 10.5,12A1.5,1.5 0 0,1 9,13.5M22,3H7C6.31,3 5.77,3.35 5.41,3.88L0,12L5.41,20.11C5.77,20.64 6.37,21 7.06,21H22A2,2 0 0,0 24,19V5C24,3.89 23.1,3 22,3Z" /></g><g id="motorbike"><path d="M16.36,4.27H18.55V2.13H16.36V1.07H18.22C17.89,0.43 17.13,0 16.36,0C15.16,0 14.18,0.96 14.18,2.13C14.18,3.31 15.16,4.27 16.36,4.27M10.04,9.39L13,6.93L17.45,9.6H10.25M19.53,12.05L21.05,10.56C21.93,9.71 21.93,8.43 21.05,7.57L19.2,9.39L13.96,4.27C13.64,3.73 13,3.41 12.33,3.41C11.78,3.41 11.35,3.63 11,3.95L7,7.89C6.65,8.21 6.44,8.64 6.44,9.17V9.71H5.13C4.04,9.71 3.16,10.67 3.16,11.84V12.27C3.5,12.16 3.93,12.16 4.25,12.16C7.09,12.16 9.5,14.4 9.5,17.28C9.5,17.6 9.5,18.03 9.38,18.35H14.5C14.4,18.03 14.4,17.6 14.4,17.28C14.4,14.29 16.69,12.05 19.53,12.05M4.36,19.73C2.84,19.73 1.64,18.56 1.64,17.07C1.64,15.57 2.84,14.4 4.36,14.4C5.89,14.4 7.09,15.57 7.09,17.07C7.09,18.56 5.89,19.73 4.36,19.73M4.36,12.8C1.96,12.8 0,14.72 0,17.07C0,19.41 1.96,21.33 4.36,21.33C6.76,21.33 8.73,19.41 8.73,17.07C8.73,14.72 6.76,12.8 4.36,12.8M19.64,19.73C18.11,19.73 16.91,18.56 16.91,17.07C16.91,15.57 18.11,14.4 19.64,14.4C21.16,14.4 22.36,15.57 22.36,17.07C22.36,18.56 21.16,19.73 19.64,19.73M19.64,12.8C17.24,12.8 15.27,14.72 15.27,17.07C15.27,19.41 17.24,21.33 19.64,21.33C22.04,21.33 24,19.41 24,17.07C24,14.72 22.04,12.8 19.64,12.8Z" /></g><g id="mouse"><path d="M11,1.07C7.05,1.56 4,4.92 4,9H11M4,15A8,8 0 0,0 12,23A8,8 0 0,0 20,15V11H4M13,1.07V9H20C20,4.92 16.94,1.56 13,1.07Z" /></g><g id="mouse-off"><path d="M2,5.27L3.28,4L20,20.72L18.73,22L17.5,20.79C16.08,22.16 14.14,23 12,23A8,8 0 0,1 4,15V11H7.73L5.73,9H4C4,8.46 4.05,7.93 4.15,7.42L2,5.27M11,1.07V9H10.82L5.79,3.96C7.05,2.4 8.9,1.33 11,1.07M20,11V15C20,15.95 19.83,16.86 19.53,17.71L12.82,11H20M13,1.07C16.94,1.56 20,4.92 20,9H13V1.07Z" /></g><g id="mouse-variant"><path d="M14,7H10V2.1C12.28,2.56 14,4.58 14,7M4,7C4,4.58 5.72,2.56 8,2.1V7H4M14,12C14,14.42 12.28,16.44 10,16.9V18A3,3 0 0,0 13,21A3,3 0 0,0 16,18V13A4,4 0 0,1 20,9H22L21,10L22,11H20A2,2 0 0,0 18,13H18V18A5,5 0 0,1 13,23A5,5 0 0,1 8,18V16.9C5.72,16.44 4,14.42 4,12V9H14V12Z" /></g><g id="mouse-variant-off"><path d="M2,5.27L3.28,4L20,20.72L18.73,22L17.29,20.56C16.42,22 14.82,23 13,23A5,5 0 0,1 8,18V16.9C5.72,16.44 4,14.42 4,12V9H5.73L2,5.27M14,7H10V2.1C12.28,2.56 14,4.58 14,7M8,2.1V6.18L5.38,3.55C6.07,2.83 7,2.31 8,2.1M14,12V12.17L10.82,9H14V12M10,16.9V18A3,3 0 0,0 13,21C14.28,21 15.37,20.2 15.8,19.07L12.4,15.67C11.74,16.28 10.92,16.71 10,16.9M16,13A4,4 0 0,1 20,9H22L21,10L22,11H20A2,2 0 0,0 18,13V16.18L16,14.18V13Z" /></g><g id="move-resize"><path d="M9,1V2H10V5H9V6H12V5H11V2H12V1M9,7C7.89,7 7,7.89 7,9V21C7,22.11 7.89,23 9,23H21C22.11,23 23,22.11 23,21V9C23,7.89 22.11,7 21,7M1,9V12H2V11H5V12H6V9H5V10H2V9M9,9H21V21H9M14,10V11H15V16H11V15H10V18H11V17H15V19H14V20H17V19H16V17H19V18H20V15H19V16H16V11H17V10" /></g><g id="move-resize-variant"><path d="M1.88,0.46L0.46,1.88L5.59,7H2V9H9V2H7V5.59M11,7V9H21V15H23V9A2,2 0 0,0 21,7M7,11V21A2,2 0 0,0 9,23H15V21H9V11M15.88,14.46L14.46,15.88L19.6,21H17V23H23V17H21V19.59" /></g><g id="movie"><path d="M18,4L20,8H17L15,4H13L15,8H12L10,4H8L10,8H7L5,4H4A2,2 0 0,0 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V4H18Z" /></g><g id="multiplication"><path d="M11,3H13V10.27L19.29,6.64L20.29,8.37L14,12L20.3,15.64L19.3,17.37L13,13.72V21H11V13.73L4.69,17.36L3.69,15.63L10,12L3.72,8.36L4.72,6.63L11,10.26V3Z" /></g><g id="multiplication-box"><path d="M19,3A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5C3.89,21 3,20.1 3,19V5C3,3.89 3.89,3 5,3H19M11,17H13V13.73L15.83,15.36L16.83,13.63L14,12L16.83,10.36L15.83,8.63L13,10.27V7H11V10.27L8.17,8.63L7.17,10.36L10,12L7.17,13.63L8.17,15.36L11,13.73V17Z" /></g><g id="music-box"><path d="M16,9H13V14.5A2.5,2.5 0 0,1 10.5,17A2.5,2.5 0 0,1 8,14.5A2.5,2.5 0 0,1 10.5,12C11.07,12 11.58,12.19 12,12.5V7H16M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" /></g><g id="music-box-outline"><path d="M16,9H13V14.5A2.5,2.5 0 0,1 10.5,17A2.5,2.5 0 0,1 8,14.5A2.5,2.5 0 0,1 10.5,12C11.07,12 11.58,12.19 12,12.5V7H16V9M19,3A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3H19M5,5V19H19V5H5Z" /></g><g id="music-circle"><path d="M16,9V7H12V12.5C11.58,12.19 11.07,12 10.5,12A2.5,2.5 0 0,0 8,14.5A2.5,2.5 0 0,0 10.5,17A2.5,2.5 0 0,0 13,14.5V9H16M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2Z" /></g><g id="music-note"><path d="M12,3V12.26C11.5,12.09 11,12 10.5,12C8,12 6,14 6,16.5C6,19 8,21 10.5,21C13,21 15,19 15,16.5V6H19V3H12Z" /></g><g id="music-note-bluetooth"><path d="M10,3V12.26C9.5,12.09 9,12 8.5,12C6,12 4,14 4,16.5C4,19 6,21 8.5,21C11,21 13,19 13,16.5V6H17V3H10M20,7V10.79L17.71,8.5L17,9.21L19.79,12L17,14.79L17.71,15.5L20,13.21V17H20.5L23.35,14.15L21.21,12L23.36,9.85L20.5,7H20M21,8.91L21.94,9.85L21,10.79V8.91M21,13.21L21.94,14.15L21,15.09V13.21Z" /></g><g id="music-note-bluetooth-off"><path d="M10,3V8.68L13,11.68V6H17V3H10M3.28,4.5L2,5.77L8.26,12.03C5.89,12.15 4,14.1 4,16.5C4,19 6,21 8.5,21C10.9,21 12.85,19.11 12.97,16.74L17.68,21.45L18.96,20.18L13,14.22L10,11.22L3.28,4.5M20,7V10.79L17.71,8.5L17,9.21L19.79,12L17,14.79L17.71,15.5L20,13.21V17H20.5L23.35,14.15L21.21,12L23.36,9.85L20.5,7H20M21,8.91L21.94,9.85L21,10.79V8.91M21,13.21L21.94,14.15L21,15.09V13.21Z" /></g><g id="music-note-eighth"><path d="M12,3V12.26C11.5,12.09 11,12 10.5,12C8.54,12 6.9,13.26 6.28,15H3V18H6.28C6.9,19.74 8.54,21 10.5,21C12.46,21 14.1,19.74 14.72,18H19V15H15V6H19V3H12Z" /></g><g id="music-note-half"><path d="M12,3V12.26C11.5,12.09 11,12 10.5,12C8.54,12 6.9,13.26 6.28,15H3V18H6.28C6.9,19.74 8.54,21 10.5,21C12.46,21 14.1,19.74 14.72,18H19V15H15V9L15,6V3H12M10.5,14.5A2,2 0 0,1 12.5,16.5A2,2 0 0,1 10.5,18.5A2,2 0 0,1 8.5,16.5A2,2 0 0,1 10.5,14.5Z" /></g><g id="music-note-off"><path d="M12,3V8.68L15,11.68V6H19V3H12M5.28,4.5L4,5.77L10.26,12.03C7.89,12.15 6,14.1 6,16.5C6,19 8,21 10.5,21C12.9,21 14.85,19.11 14.97,16.74L19.68,21.45L20.96,20.18L15,14.22L12,11.22L5.28,4.5Z" /></g><g id="music-note-quarter"><path d="M12,3H15V15H19V18H14.72C14.1,19.74 12.46,21 10.5,21C8.54,21 6.9,19.74 6.28,18H3V15H6.28C6.9,13.26 8.54,12 10.5,12C11,12 11.5,12.09 12,12.26V3Z" /></g><g id="music-note-sixteenth"><path d="M12,3V12.26C11.5,12.09 11,12 10.5,12C8.54,12 6.9,13.26 6.28,15H3V18H6.28C6.9,19.74 8.54,21 10.5,21C12.46,21 14.1,19.74 14.72,18H19V15H15V10H19V7H15V6H19V3H12Z" /></g><g id="music-note-whole"><path d="M10.5,12C8.6,12 6.9,13.2 6.26,15H3V18H6.26C6.9,19.8 8.6,21 10.5,21C12.4,21 14.1,19.8 14.74,18H19V15H14.74C14.1,13.2 12.4,12 10.5,12M10.5,14.5A2,2 0 0,1 12.5,16.5A2,2 0 0,1 10.5,18.5A2,2 0 0,1 8.5,16.5A2,2 0 0,1 10.5,14.5Z" /></g><g id="nature"><path d="M13,16.12C16.47,15.71 19.17,12.76 19.17,9.17C19.17,5.3 16.04,2.17 12.17,2.17A7,7 0 0,0 5.17,9.17C5.17,12.64 7.69,15.5 11,16.06V20H5V22H19V20H13V16.12Z" /></g><g id="nature-people"><path d="M4.5,11A1.5,1.5 0 0,0 6,9.5A1.5,1.5 0 0,0 4.5,8A1.5,1.5 0 0,0 3,9.5A1.5,1.5 0 0,0 4.5,11M22.17,9.17C22.17,5.3 19.04,2.17 15.17,2.17A7,7 0 0,0 8.17,9.17C8.17,12.64 10.69,15.5 14,16.06V20H6V17H7V13A1,1 0 0,0 6,12H3A1,1 0 0,0 2,13V17H3V22H19V20H16V16.12C19.47,15.71 22.17,12.76 22.17,9.17Z" /></g><g id="navigation"><path d="M12,2L4.5,20.29L5.21,21L12,18L18.79,21L19.5,20.29L12,2Z" /></g><g id="near-me"><path d="M21,3L3,10.53V11.5L9.84,14.16L12.5,21H13.46L21,3Z" /></g><g id="needle"><path d="M11.15,15.18L9.73,13.77L11.15,12.35L12.56,13.77L13.97,12.35L12.56,10.94L13.97,9.53L15.39,10.94L16.8,9.53L13.97,6.7L6.9,13.77L9.73,16.6L11.15,15.18M3.08,19L6.2,15.89L4.08,13.77L13.97,3.87L16.1,6L17.5,4.58L16.1,3.16L17.5,1.75L21.75,6L20.34,7.4L18.92,6L17.5,7.4L19.63,9.53L9.73,19.42L7.61,17.3L3.08,21.84V19Z" /></g><g id="nest-protect"><path d="M12,18A6,6 0 0,0 18,12C18,8.68 15.31,6 12,6C8.68,6 6,8.68 6,12A6,6 0 0,0 12,18M19,3A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5C3.89,21 3,20.1 3,19V5C3,3.89 3.89,3 5,3H19M8,12A4,4 0 0,1 12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12Z" /></g><g id="nest-thermostat"><path d="M16.95,16.95L14.83,14.83C15.55,14.1 16,13.1 16,12C16,11.26 15.79,10.57 15.43,10L17.6,7.81C18.5,9 19,10.43 19,12C19,13.93 18.22,15.68 16.95,16.95M12,5C13.57,5 15,5.5 16.19,6.4L14,8.56C13.43,8.21 12.74,8 12,8A4,4 0 0,0 8,12C8,13.1 8.45,14.1 9.17,14.83L7.05,16.95C5.78,15.68 5,13.93 5,12A7,7 0 0,1 12,5M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z" /></g><g id="new-box"><path d="M20,4C21.11,4 22,4.89 22,6V18C22,19.11 21.11,20 20,20H4C2.89,20 2,19.11 2,18V6C2,4.89 2.89,4 4,4H20M8.5,15V9H7.25V12.5L4.75,9H3.5V15H4.75V11.5L7.3,15H8.5M13.5,10.26V9H9.5V15H13.5V13.75H11V12.64H13.5V11.38H11V10.26H13.5M20.5,14V9H19.25V13.5H18.13V10H16.88V13.5H15.75V9H14.5V14A1,1 0 0,0 15.5,15H19.5A1,1 0 0,0 20.5,14Z" /></g><g id="newspaper"><path d="M20,11H4V8H20M20,15H13V13H20M20,19H13V17H20M11,19H4V13H11M20.33,4.67L18.67,3L17,4.67L15.33,3L13.67,4.67L12,3L10.33,4.67L8.67,3L7,4.67L5.33,3L3.67,4.67L2,3V19A2,2 0 0,0 4,21H20A2,2 0 0,0 22,19V3L20.33,4.67Z" /></g><g id="nfc"><path d="M10.59,7.66C10.59,7.66 11.19,7.39 11.57,7.82C11.95,8.26 12.92,9.94 12.92,11.62C12.92,13.3 12.5,15.09 12.05,15.68C11.62,16.28 11.19,16.28 10.86,16.06C10.54,15.85 5.5,12 5.23,11.89C4.95,11.78 4.85,12.05 5.12,13.5C5.39,15 4.95,15.41 4.57,15.47C4.2,15.5 3.06,15.2 3,12.16C2.95,9.13 3.76,8.64 4.14,8.64C4.85,8.64 10.27,13.5 10.64,13.46C10.97,13.41 11.13,11.35 10.5,9.72C9.78,7.96 10.59,7.66 10.59,7.66M19.3,4.63C21.12,8.24 21,11.66 21,12C21,12.34 21.12,15.76 19.3,19.37C19.3,19.37 18.83,19.92 18.12,19.59C17.42,19.26 17.66,18.4 17.66,18.4C17.66,18.4 19.14,15.55 19.1,12.05V12C19.14,8.5 17.66,5.6 17.66,5.6C17.66,5.6 17.42,4.74 18.12,4.41C18.83,4.08 19.3,4.63 19.3,4.63M15.77,6.25C17.26,8.96 17.16,11.66 17.14,12C17.16,12.34 17.26,14.92 15.77,17.85C15.77,17.85 15.3,18.4 14.59,18.07C13.89,17.74 14.13,16.88 14.13,16.88C14.13,16.88 15.09,15.5 15.24,12.05V12C15.14,8.53 14.13,7.23 14.13,7.23C14.13,7.23 13.89,6.36 14.59,6.04C15.3,5.71 15.77,6.25 15.77,6.25Z" /></g><g id="nfc-tap"><path d="M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M4,4H11A2,2 0 0,1 13,6V9H11V6H4V11H6V9L9,12L6,15V13H4A2,2 0 0,1 2,11V6A2,2 0 0,1 4,4M20,20H13A2,2 0 0,1 11,18V15H13V18H20V13H18V15L15,12L18,9V11H20A2,2 0 0,1 22,13V18A2,2 0 0,1 20,20Z" /></g><g id="nfc-variant"><path d="M18,6H13A2,2 0 0,0 11,8V10.28C10.41,10.62 10,11.26 10,12A2,2 0 0,0 12,14C13.11,14 14,13.1 14,12C14,11.26 13.6,10.62 13,10.28V8H16V16H8V8H10V6H8L6,6V18H18M20,20H4V4H20M20,2H4A2,2 0 0,0 2,4V20A2,2 0 0,0 4,22H20C21.11,22 22,21.1 22,20V4C22,2.89 21.11,2 20,2Z" /></g><g id="nodejs"><path d="M12,1.85C11.73,1.85 11.45,1.92 11.22,2.05L3.78,6.35C3.3,6.63 3,7.15 3,7.71V16.29C3,16.85 3.3,17.37 3.78,17.65L5.73,18.77C6.68,19.23 7,19.24 7.44,19.24C8.84,19.24 9.65,18.39 9.65,16.91V8.44C9.65,8.32 9.55,8.22 9.43,8.22H8.5C8.37,8.22 8.27,8.32 8.27,8.44V16.91C8.27,17.57 7.59,18.22 6.5,17.67L4.45,16.5C4.38,16.45 4.34,16.37 4.34,16.29V7.71C4.34,7.62 4.38,7.54 4.45,7.5L11.89,3.21C11.95,3.17 12.05,3.17 12.11,3.21L19.55,7.5C19.62,7.54 19.66,7.62 19.66,7.71V16.29C19.66,16.37 19.62,16.45 19.55,16.5L12.11,20.79C12.05,20.83 11.95,20.83 11.88,20.79L10,19.65C9.92,19.62 9.84,19.61 9.79,19.64C9.26,19.94 9.16,20 8.67,20.15C8.55,20.19 8.36,20.26 8.74,20.47L11.22,21.94C11.46,22.08 11.72,22.15 12,22.15C12.28,22.15 12.54,22.08 12.78,21.94L20.22,17.65C20.7,17.37 21,16.85 21,16.29V7.71C21,7.15 20.7,6.63 20.22,6.35L12.78,2.05C12.55,1.92 12.28,1.85 12,1.85M14,8C11.88,8 10.61,8.89 10.61,10.39C10.61,12 11.87,12.47 13.91,12.67C16.34,12.91 16.53,13.27 16.53,13.75C16.53,14.58 15.86,14.93 14.3,14.93C12.32,14.93 11.9,14.44 11.75,13.46C11.73,13.36 11.64,13.28 11.53,13.28H10.57C10.45,13.28 10.36,13.37 10.36,13.5C10.36,14.74 11.04,16.24 14.3,16.24C16.65,16.24 18,15.31 18,13.69C18,12.08 16.92,11.66 14.63,11.35C12.32,11.05 12.09,10.89 12.09,10.35C12.09,9.9 12.29,9.3 14,9.3C15.5,9.3 16.09,9.63 16.32,10.66C16.34,10.76 16.43,10.83 16.53,10.83H17.5C17.55,10.83 17.61,10.81 17.65,10.76C17.69,10.72 17.72,10.66 17.7,10.6C17.56,8.82 16.38,8 14,8Z" /></g><g id="note"><path d="M14,10V4.5L19.5,10M5,3C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V9L15,3H5Z" /></g><g id="note-multiple"><path d="M16,9H21.5L16,3.5V9M7,2H17L23,8V18A2,2 0 0,1 21,20H7C5.89,20 5,19.1 5,18V4A2,2 0 0,1 7,2M3,6V22H21V24H3A2,2 0 0,1 1,22V6H3Z" /></g><g id="note-multiple-outline"><path d="M3,6V22H21V24H3A2,2 0 0,1 1,22V6H3M16,9H21.5L16,3.5V9M7,2H17L23,8V18A2,2 0 0,1 21,20H7C5.89,20 5,19.1 5,18V4A2,2 0 0,1 7,2M7,4V18H21V11H14V4H7Z" /></g><g id="note-outline"><path d="M14,10H19.5L14,4.5V10M5,3H15L21,9V19A2,2 0 0,1 19,21H5C3.89,21 3,20.1 3,19V5C3,3.89 3.89,3 5,3M5,5V19H19V12H12V5H5Z" /></g><g id="note-plus"><path d="M14,10H19.5L14,4.5V10M5,3H15L21,9V19A2,2 0 0,1 19,21H5C3.89,21 3,20.1 3,19V5C3,3.89 3.89,3 5,3M9,18H11V15H14V13H11V10H9V13H6V15H9V18Z" /></g><g id="note-plus-outline"><path d="M15,10H20.5L15,4.5V10M4,3H16L22,9V19A2,2 0 0,1 20,21H4C2.89,21 2,20.1 2,19V5C2,3.89 2.89,3 4,3M4,5V19H20V12H13V5H4M8,17V15H6V13H8V11H10V13H12V15H10V17H8Z" /></g><g id="note-text"><path d="M14,10H19.5L14,4.5V10M5,3H15L21,9V19A2,2 0 0,1 19,21H5C3.89,21 3,20.1 3,19V5C3,3.89 3.89,3 5,3M5,12V14H19V12H5M5,16V18H14V16H5Z" /></g><g id="notification-clear-all"><path d="M5,13H19V11H5M3,17H17V15H3M7,7V9H21V7" /></g><g id="nuke"><path d="M14.04,12H10V11H5.5A3.5,3.5 0 0,1 2,7.5A3.5,3.5 0 0,1 5.5,4C6.53,4 7.45,4.44 8.09,5.15C8.5,3.35 10.08,2 12,2C13.92,2 15.5,3.35 15.91,5.15C16.55,4.44 17.47,4 18.5,4A3.5,3.5 0 0,1 22,7.5A3.5,3.5 0 0,1 18.5,11H14.04V12M10,16.9V15.76H5V13.76H19V15.76H14.04V16.92L20,19.08C20.58,19.29 21,19.84 21,20.5A1.5,1.5 0 0,1 19.5,22H4.5A1.5,1.5 0 0,1 3,20.5C3,19.84 3.42,19.29 4,19.08L10,16.9Z" /></g><g id="numeric"><path d="M4,17V9H2V7H6V17H4M22,15C22,16.11 21.1,17 20,17H16V15H20V13H18V11H20V9H16V7H20A2,2 0 0,1 22,9V10.5A1.5,1.5 0 0,1 20.5,12A1.5,1.5 0 0,1 22,13.5V15M14,15V17H8V13C8,11.89 8.9,11 10,11H12V9H8V7H12A2,2 0 0,1 14,9V11C14,12.11 13.1,13 12,13H10V15H14Z" /></g><g id="numeric-0-box"><path d="M19,3A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3H19M11,7A2,2 0 0,0 9,9V15A2,2 0 0,0 11,17H13A2,2 0 0,0 15,15V9A2,2 0 0,0 13,7H11M11,9H13V15H11V9Z" /></g><g id="numeric-0-box-multiple-outline"><path d="M21,17V3H7V17H21M21,1A2,2 0 0,1 23,3V17A2,2 0 0,1 21,19H7A2,2 0 0,1 5,17V3A2,2 0 0,1 7,1H21M3,5V21H19V23H3A2,2 0 0,1 1,21V5H3M13,5H15A2,2 0 0,1 17,7V13A2,2 0 0,1 15,15H13A2,2 0 0,1 11,13V7A2,2 0 0,1 13,5M13,7V13H15V7H13Z" /></g><g id="numeric-0-box-outline"><path d="M19,19V5H5V19H19M19,3A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3H19M11,7H13A2,2 0 0,1 15,9V15A2,2 0 0,1 13,17H11A2,2 0 0,1 9,15V9A2,2 0 0,1 11,7M11,9V15H13V9H11Z" /></g><g id="numeric-1-box"><path d="M14,17H12V9H10V7H14M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" /></g><g id="numeric-1-box-multiple-outline"><path d="M21,17H7V3H21M21,1H7A2,2 0 0,0 5,3V17A2,2 0 0,0 7,19H21A2,2 0 0,0 23,17V3A2,2 0 0,0 21,1M14,15H16V5H12V7H14M3,5H1V21A2,2 0 0,0 3,23H19V21H3V5Z" /></g><g id="numeric-1-box-outline"><path d="M19,19H5V5H19M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M12,17H14V7H10V9H12" /></g><g id="numeric-2-box"><path d="M15,11C15,12.11 14.1,13 13,13H11V15H15V17H9V13C9,11.89 9.9,11 11,11H13V9H9V7H13A2,2 0 0,1 15,9M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" /></g><g id="numeric-2-box-multiple-outline"><path d="M17,13H13V11H15A2,2 0 0,0 17,9V7C17,5.89 16.1,5 15,5H11V7H15V9H13A2,2 0 0,0 11,11V15H17M21,17H7V3H21M21,1H7A2,2 0 0,0 5,3V17A2,2 0 0,0 7,19H21A2,2 0 0,0 23,17V3A2,2 0 0,0 21,1M3,5H1V21A2,2 0 0,0 3,23H19V21H3V5Z" /></g><g id="numeric-2-box-outline"><path d="M15,15H11V13H13A2,2 0 0,0 15,11V9C15,7.89 14.1,7 13,7H9V9H13V11H11A2,2 0 0,0 9,13V17H15M19,19H5V5H19M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" /></g><g id="numeric-3-box"><path d="M15,10.5A1.5,1.5 0 0,1 13.5,12C14.34,12 15,12.67 15,13.5V15C15,16.11 14.11,17 13,17H9V15H13V13H11V11H13V9H9V7H13C14.11,7 15,7.89 15,9M19,3H5C3.91,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19C20.11,21 21,20.1 21,19V5A2,2 0 0,0 19,3Z" /></g><g id="numeric-3-box-multiple-outline"><path d="M17,13V11.5A1.5,1.5 0 0,0 15.5,10A1.5,1.5 0 0,0 17,8.5V7C17,5.89 16.1,5 15,5H11V7H15V9H13V11H15V13H11V15H15A2,2 0 0,0 17,13M3,5H1V21A2,2 0 0,0 3,23H19V21H3M21,17H7V3H21M21,1H7A2,2 0 0,0 5,3V17A2,2 0 0,0 7,19H21A2,2 0 0,0 23,17V3A2,2 0 0,0 21,1Z" /></g><g id="numeric-3-box-outline"><path d="M15,15V13.5A1.5,1.5 0 0,0 13.5,12A1.5,1.5 0 0,0 15,10.5V9C15,7.89 14.1,7 13,7H9V9H13V11H11V13H13V15H9V17H13A2,2 0 0,0 15,15M19,19H5V5H19M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" /></g><g id="numeric-4-box"><path d="M15,17H13V13H9V7H11V11H13V7H15M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" /></g><g id="numeric-4-box-multiple-outline"><path d="M21,17H7V3H21M21,1H7A2,2 0 0,0 5,3V17A2,2 0 0,0 7,19H21A2,2 0 0,0 23,17V3A2,2 0 0,0 21,1M15,15H17V5H15V9H13V5H11V11H15M3,5H1V21A2,2 0 0,0 3,23H19V21H3V5Z" /></g><g id="numeric-4-box-outline"><path d="M19,19H5V5H19M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M13,17H15V7H13V11H11V7H9V13H13" /></g><g id="numeric-5-box"><path d="M15,9H11V11H13A2,2 0 0,1 15,13V15C15,16.11 14.1,17 13,17H9V15H13V13H9V7H15M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" /></g><g id="numeric-5-box-multiple-outline"><path d="M17,13V11C17,9.89 16.1,9 15,9H13V7H17V5H11V11H15V13H11V15H15A2,2 0 0,0 17,13M3,5H1V21A2,2 0 0,0 3,23H19V21H3M21,17H7V3H21M21,1H7A2,2 0 0,0 5,3V17A2,2 0 0,0 7,19H21A2,2 0 0,0 23,17V3A2,2 0 0,0 21,1Z" /></g><g id="numeric-5-box-outline"><path d="M15,15V13C15,11.89 14.1,11 13,11H11V9H15V7H9V13H13V15H9V17H13A2,2 0 0,0 15,15M19,19H5V5H19M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" /></g><g id="numeric-6-box"><path d="M15,9H11V11H13A2,2 0 0,1 15,13V15C15,16.11 14.1,17 13,17H11A2,2 0 0,1 9,15V9C9,7.89 9.9,7 11,7H15M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M11,15H13V13H11V15Z" /></g><g id="numeric-6-box-multiple-outline"><path d="M13,11H15V13H13M13,15H15A2,2 0 0,0 17,13V11C17,9.89 16.1,9 15,9H13V7H17V5H13A2,2 0 0,0 11,7V13C11,14.11 11.9,15 13,15M21,17H7V3H21M21,1H7A2,2 0 0,0 5,3V17A2,2 0 0,0 7,19H21A2,2 0 0,0 23,17V3A2,2 0 0,0 21,1M3,5H1V21A2,2 0 0,0 3,23H19V21H3V5Z" /></g><g id="numeric-6-box-outline"><path d="M11,13H13V15H11M11,17H13A2,2 0 0,0 15,15V13C15,11.89 14.1,11 13,11H11V9H15V7H11A2,2 0 0,0 9,9V15C9,16.11 9.9,17 11,17M19,19H5V5H19M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" /></g><g id="numeric-7-box"><path d="M19,3A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3H19M11,17L15,9V7H9V9H13L9,17H11Z" /></g><g id="numeric-7-box-multiple-outline"><path d="M13,15L17,7V5H11V7H15L11,15M21,17H7V3H21M21,1H7A2,2 0 0,0 5,3V17A2,2 0 0,0 7,19H21A2,2 0 0,0 23,17V3A2,2 0 0,0 21,1M3,5H1V21A2,2 0 0,0 3,23H19V21H3V5Z" /></g><g id="numeric-7-box-outline"><path d="M11,17L15,9V7H9V9H13L9,17M19,19H5V5H19M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" /></g><g id="numeric-8-box"><path d="M19,3A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3H19M11,17H13A2,2 0 0,0 15,15V13.5A1.5,1.5 0 0,0 13.5,12A1.5,1.5 0 0,0 15,10.5V9C15,7.89 14.1,7 13,7H11A2,2 0 0,0 9,9V10.5A1.5,1.5 0 0,0 10.5,12A1.5,1.5 0 0,0 9,13.5V15C9,16.11 9.9,17 11,17M11,13H13V15H11V13M11,9H13V11H11V9Z" /></g><g id="numeric-8-box-multiple-outline"><path d="M13,11H15V13H13M13,7H15V9H13M13,15H15A2,2 0 0,0 17,13V11.5A1.5,1.5 0 0,0 15.5,10A1.5,1.5 0 0,0 17,8.5V7C17,5.89 16.1,5 15,5H13A2,2 0 0,0 11,7V8.5A1.5,1.5 0 0,0 12.5,10A1.5,1.5 0 0,0 11,11.5V13C11,14.11 11.9,15 13,15M21,17H7V3H21M21,1H7A2,2 0 0,0 5,3V17A2,2 0 0,0 7,19H21A2,2 0 0,0 23,17V3A2,2 0 0,0 21,1M3,5H1V21A2,2 0 0,0 3,23H19V21H3V5Z" /></g><g id="numeric-8-box-outline"><path d="M11,13H13V15H11M11,9H13V11H11M11,17H13A2,2 0 0,0 15,15V13.5A1.5,1.5 0 0,0 13.5,12A1.5,1.5 0 0,0 15,10.5V9C15,7.89 14.1,7 13,7H11A2,2 0 0,0 9,9V10.5A1.5,1.5 0 0,0 10.5,12A1.5,1.5 0 0,0 9,13.5V15C9,16.11 9.9,17 11,17M19,19H5V5H19M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" /></g><g id="numeric-9-box"><path d="M19,3A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3H19M13,11H11V9H13V11M13,7H11A2,2 0 0,0 9,9V11C9,12.11 9.9,13 11,13H13V15H9V17H13A2,2 0 0,0 15,15V9C15,7.89 14.1,7 13,7Z" /></g><g id="numeric-9-box-multiple-outline"><path d="M15,9H13V7H15M15,5H13A2,2 0 0,0 11,7V9C11,10.11 11.9,11 13,11H15V13H11V15H15A2,2 0 0,0 17,13V7C17,5.89 16.1,5 15,5M21,17H7V3H21M21,1H7A2,2 0 0,0 5,3V17A2,2 0 0,0 7,19H21A2,2 0 0,0 23,17V3A2,2 0 0,0 21,1M3,5H1V21A2,2 0 0,0 3,23H19V21H3V5Z" /></g><g id="numeric-9-box-outline"><path d="M13,11H11V9H13M13,7H11A2,2 0 0,0 9,9V11C9,12.11 9.9,13 11,13H13V15H9V17H13A2,2 0 0,0 15,15V9C15,7.89 14.1,7 13,7M19,19H5V5H19M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" /></g><g id="numeric-9-plus-box"><path d="M21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3H19A2,2 0 0,1 21,5M19,11H17V9H15V11H13V13H15V15H17V13H19V11M10,7H8A2,2 0 0,0 6,9V11C6,12.11 6.9,13 8,13H10V15H6V17H10A2,2 0 0,0 12,15V9C12,7.89 11.1,7 10,7M8,9H10V11H8V9Z" /></g><g id="numeric-9-plus-box-multiple-outline"><path d="M21,9H19V7H17V9H15V11H17V13H19V11H21V17H7V3H21M21,1H7A2,2 0 0,0 5,3V17A2,2 0 0,0 7,19H21A2,2 0 0,0 23,17V3A2,2 0 0,0 21,1M11,9V8H12V9M14,12V8C14,6.89 13.1,6 12,6H11A2,2 0 0,0 9,8V9C9,10.11 9.9,11 11,11H12V12H9V14H12A2,2 0 0,0 14,12M3,5H1V21A2,2 0 0,0 3,23H19V21H3V5Z" /></g><g id="numeric-9-plus-box-outline"><path d="M19,11H17V9H15V11H13V13H15V15H17V13H19V19H5V5H19M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M9,11V10H10V11M12,14V10C12,8.89 11.1,8 10,8H9A2,2 0 0,0 7,10V11C7,12.11 7.9,13 9,13H10V14H7V16H10A2,2 0 0,0 12,14Z" /></g><g id="nutrition"><path d="M22,18A4,4 0 0,1 18,22H14A4,4 0 0,1 10,18V16H22V18M4,3H14A2,2 0 0,1 16,5V14H8V19H4A2,2 0 0,1 2,17V5A2,2 0 0,1 4,3M4,6V8H6V6H4M14,8V6H8V8H14M4,10V12H6V10H4M8,10V12H14V10H8M4,14V16H6V14H4Z" /></g><g id="oar"><path d="M20.23,15.21C18.77,13.75 14.97,10.2 12.77,11.27L4.5,3L3,4.5L11.28,12.79C10.3,15 13.88,18.62 15.35,20.08C17.11,21.84 18.26,20.92 19.61,19.57C21.1,18.08 21.61,16.61 20.23,15.21Z" /></g><g id="octagon"><path d="M15.73,3H8.27L3,8.27V15.73L8.27,21H15.73L21,15.73V8.27" /></g><g id="octagon-outline"><path d="M8.27,3L3,8.27V15.73L8.27,21H15.73C17.5,19.24 21,15.73 21,15.73V8.27L15.73,3M9.1,5H14.9L19,9.1V14.9L14.9,19H9.1L5,14.9V9.1" /></g><g id="odnoklassniki"><path d="M17.83,12.74C17.55,12.17 16.76,11.69 15.71,12.5C14.28,13.64 12,13.64 12,13.64C12,13.64 9.72,13.64 8.29,12.5C7.24,11.69 6.45,12.17 6.17,12.74C5.67,13.74 6.23,14.23 7.5,15.04C8.59,15.74 10.08,16 11.04,16.1L10.24,16.9C9.1,18.03 8,19.12 7.25,19.88C6.8,20.34 6.8,21.07 7.25,21.5L7.39,21.66C7.84,22.11 8.58,22.11 9.03,21.66L12,18.68C13.15,19.81 14.24,20.9 15,21.66C15.45,22.11 16.18,22.11 16.64,21.66L16.77,21.5C17.23,21.07 17.23,20.34 16.77,19.88L13.79,16.9L13,16.09C13.95,16 15.42,15.73 16.5,15.04C17.77,14.23 18.33,13.74 17.83,12.74M12,4.57C13.38,4.57 14.5,5.69 14.5,7.06C14.5,8.44 13.38,9.55 12,9.55C10.62,9.55 9.5,8.44 9.5,7.06C9.5,5.69 10.62,4.57 12,4.57M12,12.12C14.8,12.12 17.06,9.86 17.06,7.06C17.06,4.27 14.8,2 12,2C9.2,2 6.94,4.27 6.94,7.06C6.94,9.86 9.2,12.12 12,12.12Z" /></g><g id="office"><path d="M3,18L7,16.75V7L14,5V19.5L3.5,18.25L14,22L20,20.75V3.5L13.95,2L3,5.75V18Z" /></g><g id="oil"><path d="M22,12.5C22,12.5 24,14.67 24,16A2,2 0 0,1 22,18A2,2 0 0,1 20,16C20,14.67 22,12.5 22,12.5M6,6H10A1,1 0 0,1 11,7A1,1 0 0,1 10,8H9V10H11C11.74,10 12.39,10.4 12.73,11L19.24,7.24L22.5,9.13C23,9.4 23.14,10 22.87,10.5C22.59,10.97 22,11.14 21.5,10.86L19.4,9.65L15.75,15.97C15.41,16.58 14.75,17 14,17H5A2,2 0 0,1 3,15V12A2,2 0 0,1 5,10H7V8H6A1,1 0 0,1 5,7A1,1 0 0,1 6,6M5,12V15H14L16.06,11.43L12.6,13.43L11.69,12H5M0.38,9.21L2.09,7.5C2.5,7.11 3.11,7.11 3.5,7.5C3.89,7.89 3.89,8.5 3.5,8.91L1.79,10.62C1.4,11 0.77,11 0.38,10.62C0,10.23 0,9.6 0.38,9.21Z" /></g><g id="oil-temperature"><path d="M11.5,1A1.5,1.5 0 0,0 10,2.5V14.5C9.37,14.97 9,15.71 9,16.5A2.5,2.5 0 0,0 11.5,19A2.5,2.5 0 0,0 14,16.5C14,15.71 13.63,15 13,14.5V13H17V11H13V9H17V7H13V5H17V3H13V2.5A1.5,1.5 0 0,0 11.5,1M0,15V17C0.67,17 0.79,17.21 1.29,17.71C1.79,18.21 2.67,19 4,19C5.33,19 6.21,18.21 6.71,17.71C6.82,17.59 6.91,17.5 7,17.41V15.16C6.21,15.42 5.65,15.93 5.29,16.29C4.79,16.79 4.67,17 4,17C3.33,17 3.21,16.79 2.71,16.29C2.21,15.79 1.33,15 0,15M16,15V17C16.67,17 16.79,17.21 17.29,17.71C17.79,18.21 18.67,19 20,19C21.33,19 22.21,18.21 22.71,17.71C23.21,17.21 23.33,17 24,17V15C22.67,15 21.79,15.79 21.29,16.29C20.79,16.79 20.67,17 20,17C19.33,17 19.21,16.79 18.71,16.29C18.21,15.79 17.33,15 16,15M8,20C6.67,20 5.79,20.79 5.29,21.29C4.79,21.79 4.67,22 4,22C3.33,22 3.21,21.79 2.71,21.29C2.35,20.93 1.79,20.42 1,20.16V22.41C1.09,22.5 1.18,22.59 1.29,22.71C1.79,23.21 2.67,24 4,24C5.33,24 6.21,23.21 6.71,22.71C7.21,22.21 7.33,22 8,22C8.67,22 8.79,22.21 9.29,22.71C9.73,23.14 10.44,23.8 11.5,23.96C11.66,24 11.83,24 12,24C13.33,24 14.21,23.21 14.71,22.71C15.21,22.21 15.33,22 16,22C16.67,22 16.79,22.21 17.29,22.71C17.79,23.21 18.67,24 20,24C21.33,24 22.21,23.21 22.71,22.71C22.82,22.59 22.91,22.5 23,22.41V20.16C22.21,20.42 21.65,20.93 21.29,21.29C20.79,21.79 20.67,22 20,22C19.33,22 19.21,21.79 18.71,21.29C18.21,20.79 17.33,20 16,20C14.67,20 13.79,20.79 13.29,21.29C12.79,21.79 12.67,22 12,22C11.78,22 11.63,21.97 11.5,21.92C11.22,21.82 11.05,21.63 10.71,21.29C10.21,20.79 9.33,20 8,20Z" /></g><g id="omega"><path d="M19.15,19H13.39V16.87C15.5,15.25 16.59,13.24 16.59,10.84C16.59,9.34 16.16,8.16 15.32,7.29C14.47,6.42 13.37,6 12.03,6C10.68,6 9.57,6.42 8.71,7.3C7.84,8.17 7.41,9.37 7.41,10.88C7.41,13.26 8.5,15.26 10.61,16.87V19H4.85V16.87H8.41C6.04,15.32 4.85,13.23 4.85,10.6C4.85,8.5 5.5,6.86 6.81,5.66C8.12,4.45 9.84,3.85 11.97,3.85C14.15,3.85 15.89,4.45 17.19,5.64C18.5,6.83 19.15,8.5 19.15,10.58C19.15,13.21 17.95,15.31 15.55,16.87H19.15V19Z" /></g><g id="onedrive"><path d="M20.08,13.64C21.17,13.81 22,14.75 22,15.89C22,16.78 21.5,17.55 20.75,17.92L20.58,18H9.18L9.16,18V18C7.71,18 6.54,16.81 6.54,15.36C6.54,13.9 7.72,12.72 9.18,12.72L9.4,12.73L9.39,12.53A3.3,3.3 0 0,1 12.69,9.23C13.97,9.23 15.08,9.96 15.63,11C16.08,10.73 16.62,10.55 17.21,10.55A2.88,2.88 0 0,1 20.09,13.43L20.08,13.64M8.82,12.16C7.21,12.34 5.96,13.7 5.96,15.36C5.96,16.04 6.17,16.66 6.5,17.18H4.73A2.73,2.73 0 0,1 2,14.45C2,13 3.12,11.83 4.53,11.73L4.46,11.06C4.46,9.36 5.84,8 7.54,8C8.17,8 8.77,8.18 9.26,8.5C9.95,7.11 11.4,6.15 13.07,6.15C15.27,6.15 17.08,7.83 17.3,9.97H17.21C16.73,9.97 16.27,10.07 15.84,10.25C15.12,9.25 13.96,8.64 12.69,8.64C10.67,8.64 9,10.19 8.82,12.16Z" /></g><g id="opacity"><path d="M17.66,8L12,2.35L6.34,8C4.78,9.56 4,11.64 4,13.64C4,15.64 4.78,17.75 6.34,19.31C7.9,20.87 9.95,21.66 12,21.66C14.05,21.66 16.1,20.87 17.66,19.31C19.22,17.75 20,15.64 20,13.64C20,11.64 19.22,9.56 17.66,8M6,14C6,12 6.62,10.73 7.76,9.6L12,5.27L16.24,9.65C17.38,10.77 18,12 18,14H6Z" /></g><g id="open-in-app"><path d="M12,10L8,14H11V20H13V14H16M19,4H5C3.89,4 3,4.9 3,6V18A2,2 0 0,0 5,20H9V18H5V8H19V18H15V20H19A2,2 0 0,0 21,18V6A2,2 0 0,0 19,4Z" /></g><g id="open-in-new"><path d="M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z" /></g><g id="openid"><path d="M14,2L11,3.5V19.94C7,19.5 4,17.46 4,15C4,12.75 6.5,10.85 10,10.22V8.19C4.86,8.88 1,11.66 1,15C1,18.56 5.36,21.5 11,21.94C11.03,21.94 11.06,21.94 11.09,21.94L14,20.5V2M15,8.19V10.22C16.15,10.43 17.18,10.77 18.06,11.22L16.5,12L23,13.5L22.5,9L20.5,10C19,9.12 17.12,8.47 15,8.19Z" /></g><g id="opera"><path d="M17.33,3.57C15.86,2.56 14.05,2 12,2C10.13,2 8.46,2.47 7.06,3.32C4.38,4.95 2.72,8 2.72,11.9C2.72,17.19 6.43,22 12,22C17.57,22 21.28,17.19 21.28,11.9C21.28,8.19 19.78,5.25 17.33,3.57M12,3.77C15,3.77 15.6,7.93 15.6,11.72C15.6,15.22 15.26,19.91 12.04,19.91C8.82,19.91 8.4,15.17 8.4,11.67C8.4,7.89 9,3.77 12,3.77Z" /></g><g id="ornament"><path d="M12,1A3,3 0 0,1 15,4V5A1,1 0 0,1 16,6V7.07C18.39,8.45 20,11.04 20,14A8,8 0 0,1 12,22A8,8 0 0,1 4,14C4,11.04 5.61,8.45 8,7.07V6A1,1 0 0,1 9,5V4A3,3 0 0,1 12,1M12,3A1,1 0 0,0 11,4V5H13V4A1,1 0 0,0 12,3M12,8C10.22,8 8.63,8.77 7.53,10H16.47C15.37,8.77 13.78,8 12,8M6.34,16H7.59L6,14.43C6.05,15 6.17,15.5 6.34,16M12.59,16L8.59,12H6.41L10.41,16H12.59M17.66,12H16.41L18,13.57C17.95,13 17.83,12.5 17.66,12M11.41,12L15.41,16H17.59L13.59,12H11.41M12,20C13.78,20 15.37,19.23 16.47,18H7.53C8.63,19.23 10.22,20 12,20Z" /></g><g id="ornament-variant"><path d="M12,1A3,3 0 0,1 15,4V5A1,1 0 0,1 16,6V7.07C18.39,8.45 20,11.04 20,14A8,8 0 0,1 12,22A8,8 0 0,1 4,14C4,11.04 5.61,8.45 8,7.07V6A1,1 0 0,1 9,5V4A3,3 0 0,1 12,1M12,3A1,1 0 0,0 11,4V5H13V4A1,1 0 0,0 12,3M12,8C10.22,8 8.63,8.77 7.53,10H16.47C15.37,8.77 13.78,8 12,8M12,20C13.78,20 15.37,19.23 16.47,18H7.53C8.63,19.23 10.22,20 12,20M12,12A2,2 0 0,0 10,14A2,2 0 0,0 12,16A2,2 0 0,0 14,14A2,2 0 0,0 12,12M18,14C18,13.31 17.88,12.65 17.67,12C16.72,12.19 16,13 16,14C16,15 16.72,15.81 17.67,15.97C17.88,15.35 18,14.69 18,14M6,14C6,14.69 6.12,15.35 6.33,15.97C7.28,15.81 8,15 8,14C8,13 7.28,12.19 6.33,12C6.12,12.65 6,13.31 6,14Z" /></g><g id="owl"><path d="M12,16C12.56,16.84 13.31,17.53 14.2,18L12,20.2L9.8,18C10.69,17.53 11.45,16.84 12,16M17,11.2A2,2 0 0,0 15,13.2A2,2 0 0,0 17,15.2A2,2 0 0,0 19,13.2C19,12.09 18.1,11.2 17,11.2M7,11.2A2,2 0 0,0 5,13.2A2,2 0 0,0 7,15.2A2,2 0 0,0 9,13.2C9,12.09 8.1,11.2 7,11.2M17,8.7A4,4 0 0,1 21,12.7A4,4 0 0,1 17,16.7A4,4 0 0,1 13,12.7A4,4 0 0,1 17,8.7M7,8.7A4,4 0 0,1 11,12.7A4,4 0 0,1 7,16.7A4,4 0 0,1 3,12.7A4,4 0 0,1 7,8.7M2.24,1C4,4.7 2.73,7.46 1.55,10.2C1.19,11 1,11.83 1,12.7A6,6 0 0,0 7,18.7C7.21,18.69 7.42,18.68 7.63,18.65L10.59,21.61L12,23L13.41,21.61L16.37,18.65C16.58,18.68 16.79,18.69 17,18.7A6,6 0 0,0 23,12.7C23,11.83 22.81,11 22.45,10.2C21.27,7.46 20,4.7 21.76,1C19.12,3.06 15.36,4.69 12,4.7C8.64,4.69 4.88,3.06 2.24,1Z" /></g><g id="package"><path d="M5.12,5H18.87L17.93,4H5.93L5.12,5M20.54,5.23C20.83,5.57 21,6 21,6.5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V6.5C3,6 3.17,5.57 3.46,5.23L4.84,3.55C5.12,3.21 5.53,3 6,3H18C18.47,3 18.88,3.21 19.15,3.55L20.54,5.23M6,18H12V15H6V18Z" /></g><g id="package-down"><path d="M5.12,5L5.93,4H17.93L18.87,5M12,17.5L6.5,12H10V10H14V12H17.5L12,17.5M20.54,5.23L19.15,3.55C18.88,3.21 18.47,3 18,3H6C5.53,3 5.12,3.21 4.84,3.55L3.46,5.23C3.17,5.57 3,6 3,6.5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V6.5C21,6 20.83,5.57 20.54,5.23Z" /></g><g id="package-up"><path d="M20.54,5.23C20.83,5.57 21,6 21,6.5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V6.5C3,6 3.17,5.57 3.46,5.23L4.84,3.55C5.12,3.21 5.53,3 6,3H18C18.47,3 18.88,3.21 19.15,3.55L20.54,5.23M5.12,5H18.87L17.93,4H5.93L5.12,5M12,9.5L6.5,15H10V17H14V15H17.5L12,9.5Z" /></g><g id="package-variant"><path d="M2,10.96C1.5,10.68 1.35,10.07 1.63,9.59L3.13,7C3.24,6.8 3.41,6.66 3.6,6.58L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.66,6.72 20.82,6.88 20.91,7.08L22.36,9.6C22.64,10.08 22.47,10.69 22,10.96L21,11.54V16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V10.96C2.7,11.13 2.32,11.14 2,10.96M12,4.15V4.15L12,10.85V10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V12.69L14,15.59C13.67,15.77 13.3,15.76 13,15.6V19.29L19,15.91M13.85,13.36L20.13,9.73L19.55,8.72L13.27,12.35L13.85,13.36Z" /></g><g id="package-variant-closed"><path d="M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L10.11,5.22L16,8.61L17.96,7.5L12,4.15M6.04,7.5L12,10.85L13.96,9.75L8.08,6.35L6.04,7.5M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V9.21L13,12.58V19.29L19,15.91Z" /></g><g id="page-first"><path d="M18.41,16.59L13.82,12L18.41,7.41L17,6L11,12L17,18L18.41,16.59M6,6H8V18H6V6Z" /></g><g id="page-last"><path d="M5.59,7.41L10.18,12L5.59,16.59L7,18L13,12L7,6L5.59,7.41M16,6H18V18H16V6Z" /></g><g id="palette"><path d="M17.5,12A1.5,1.5 0 0,1 16,10.5A1.5,1.5 0 0,1 17.5,9A1.5,1.5 0 0,1 19,10.5A1.5,1.5 0 0,1 17.5,12M14.5,8A1.5,1.5 0 0,1 13,6.5A1.5,1.5 0 0,1 14.5,5A1.5,1.5 0 0,1 16,6.5A1.5,1.5 0 0,1 14.5,8M9.5,8A1.5,1.5 0 0,1 8,6.5A1.5,1.5 0 0,1 9.5,5A1.5,1.5 0 0,1 11,6.5A1.5,1.5 0 0,1 9.5,8M6.5,12A1.5,1.5 0 0,1 5,10.5A1.5,1.5 0 0,1 6.5,9A1.5,1.5 0 0,1 8,10.5A1.5,1.5 0 0,1 6.5,12M12,3A9,9 0 0,0 3,12A9,9 0 0,0 12,21A1.5,1.5 0 0,0 13.5,19.5C13.5,19.11 13.35,18.76 13.11,18.5C12.88,18.23 12.73,17.88 12.73,17.5A1.5,1.5 0 0,1 14.23,16H16A5,5 0 0,0 21,11C21,6.58 16.97,3 12,3Z" /></g><g id="palette-advanced"><path d="M22,22H10V20H22V22M2,22V20H9V22H2M18,18V10H22V18H18M18,3H22V9H18V3M2,18V3H16V18H2M9,14.56A3,3 0 0,0 12,11.56C12,9.56 9,6.19 9,6.19C9,6.19 6,9.56 6,11.56A3,3 0 0,0 9,14.56Z" /></g><g id="panda"><path d="M12,3C13.74,3 15.36,3.5 16.74,4.35C17.38,3.53 18.38,3 19.5,3A3.5,3.5 0 0,1 23,6.5C23,8 22.05,9.28 20.72,9.78C20.9,10.5 21,11.23 21,12A9,9 0 0,1 12,21A9,9 0 0,1 3,12C3,11.23 3.1,10.5 3.28,9.78C1.95,9.28 1,8 1,6.5A3.5,3.5 0 0,1 4.5,3C5.62,3 6.62,3.53 7.26,4.35C8.64,3.5 10.26,3 12,3M12,5A7,7 0 0,0 5,12A7,7 0 0,0 12,19A7,7 0 0,0 19,12A7,7 0 0,0 12,5M16.19,10.3C16.55,11.63 16.08,12.91 15.15,13.16C14.21,13.42 13.17,12.54 12.81,11.2C12.45,9.87 12.92,8.59 13.85,8.34C14.79,8.09 15.83,8.96 16.19,10.3M7.81,10.3C8.17,8.96 9.21,8.09 10.15,8.34C11.08,8.59 11.55,9.87 11.19,11.2C10.83,12.54 9.79,13.42 8.85,13.16C7.92,12.91 7.45,11.63 7.81,10.3M12,14C12.6,14 13.13,14.19 13.5,14.5L12.5,15.5C12.5,15.92 12.84,16.25 13.25,16.25A0.75,0.75 0 0,0 14,15.5A0.5,0.5 0 0,1 14.5,15A0.5,0.5 0 0,1 15,15.5A1.75,1.75 0 0,1 13.25,17.25C12.76,17.25 12.32,17.05 12,16.72C11.68,17.05 11.24,17.25 10.75,17.25A1.75,1.75 0 0,1 9,15.5A0.5,0.5 0 0,1 9.5,15A0.5,0.5 0 0,1 10,15.5A0.75,0.75 0 0,0 10.75,16.25A0.75,0.75 0 0,0 11.5,15.5L10.5,14.5C10.87,14.19 11.4,14 12,14Z" /></g><g id="pandora"><path d="M16.87,7.73C16.87,9.9 15.67,11.7 13.09,11.7H10.45V3.66H13.09C15.67,3.66 16.87,5.5 16.87,7.73M10.45,15.67V13.41H13.09C17.84,13.41 20.5,10.91 20.5,7.73C20.5,4.45 17.84,2 13.09,2H3.5V2.92C6.62,2.92 7.17,3.66 7.17,8.28V15.67C7.17,20.29 6.62,21.08 3.5,21.08V22H14.1V21.08C11,21.08 10.45,20.29 10.45,15.67Z" /></g><g id="panorama"><path d="M8.5,12.5L11,15.5L14.5,11L19,17H5M23,18V6A2,2 0 0,0 21,4H3A2,2 0 0,0 1,6V18A2,2 0 0,0 3,20H21A2,2 0 0,0 23,18Z" /></g><g id="panorama-fisheye"><path d="M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2C6.47,2 2,6.47 2,12C2,17.53 6.47,22 12,22C17.53,22 22,17.53 22,12C22,6.47 17.53,2 12,2Z" /></g><g id="panorama-horizontal"><path d="M21.43,4C21.33,4 21.23,4 21.12,4.06C18.18,5.16 15.09,5.7 12,5.7C8.91,5.7 5.82,5.15 2.88,4.06C2.77,4 2.66,4 2.57,4C2.23,4 2,4.23 2,4.63V19.38C2,19.77 2.23,20 2.57,20C2.67,20 2.77,20 2.88,19.94C5.82,18.84 8.91,18.3 12,18.3C15.09,18.3 18.18,18.85 21.12,19.94C21.23,20 21.33,20 21.43,20C21.76,20 22,19.77 22,19.37V4.63C22,4.23 21.76,4 21.43,4M20,6.54V17.45C17.4,16.68 14.72,16.29 12,16.29C9.28,16.29 6.6,16.68 4,17.45V6.54C6.6,7.31 9.28,7.7 12,7.7C14.72,7.71 17.4,7.32 20,6.54Z" /></g><g id="panorama-vertical"><path d="M6.54,20C7.31,17.4 7.7,14.72 7.7,12C7.7,9.28 7.31,6.6 6.54,4H17.45C16.68,6.6 16.29,9.28 16.29,12C16.29,14.72 16.68,17.4 17.45,20M19.94,21.12C18.84,18.18 18.3,15.09 18.3,12C18.3,8.91 18.85,5.82 19.94,2.88C20,2.77 20,2.66 20,2.57C20,2.23 19.77,2 19.37,2H4.63C4.23,2 4,2.23 4,2.57C4,2.67 4,2.77 4.06,2.88C5.16,5.82 5.71,8.91 5.71,12C5.71,15.09 5.16,18.18 4.07,21.12C4,21.23 4,21.34 4,21.43C4,21.76 4.23,22 4.63,22H19.38C19.77,22 20,21.76 20,21.43C20,21.33 20,21.23 19.94,21.12Z" /></g><g id="panorama-wide-angle"><path d="M12,4C9.27,4 6.78,4.24 4.05,4.72L3.12,4.88L2.87,5.78C2.29,7.85 2,9.93 2,12C2,14.07 2.29,16.15 2.87,18.22L3.12,19.11L4.05,19.27C6.78,19.76 9.27,20 12,20C14.73,20 17.22,19.76 19.95,19.28L20.88,19.12L21.13,18.23C21.71,16.15 22,14.07 22,12C22,9.93 21.71,7.85 21.13,5.78L20.88,4.89L19.95,4.73C17.22,4.24 14.73,4 12,4M12,6C14.45,6 16.71,6.2 19.29,6.64C19.76,8.42 20,10.22 20,12C20,13.78 19.76,15.58 19.29,17.36C16.71,17.8 14.45,18 12,18C9.55,18 7.29,17.8 4.71,17.36C4.24,15.58 4,13.78 4,12C4,10.22 4.24,8.42 4.71,6.64C7.29,6.2 9.55,6 12,6Z" /></g><g id="paper-cut-vertical"><path d="M11.43,3.23L12,4L12.57,3.23V3.24C13.12,2.5 14,2 15,2A3,3 0 0,1 18,5C18,5.35 17.94,5.69 17.83,6H20A2,2 0 0,1 22,8V20A2,2 0 0,1 20,22H4A2,2 0 0,1 2,20V8A2,2 0 0,1 4,6H6.17C6.06,5.69 6,5.35 6,5A3,3 0 0,1 9,2C10,2 10.88,2.5 11.43,3.24V3.23M4,8V20H11A1,1 0 0,1 12,19A1,1 0 0,1 13,20H20V8H15L14.9,8L17,10.92L15.4,12.1L12.42,8H11.58L8.6,12.1L7,10.92L9.1,8H9L4,8M9,4A1,1 0 0,0 8,5A1,1 0 0,0 9,6A1,1 0 0,0 10,5A1,1 0 0,0 9,4M15,4A1,1 0 0,0 14,5A1,1 0 0,0 15,6A1,1 0 0,0 16,5A1,1 0 0,0 15,4M12,16A1,1 0 0,1 13,17A1,1 0 0,1 12,18A1,1 0 0,1 11,17A1,1 0 0,1 12,16M12,13A1,1 0 0,1 13,14A1,1 0 0,1 12,15A1,1 0 0,1 11,14A1,1 0 0,1 12,13M12,10A1,1 0 0,1 13,11A1,1 0 0,1 12,12A1,1 0 0,1 11,11A1,1 0 0,1 12,10Z" /></g><g id="paperclip"><path d="M16.5,6V17.5A4,4 0 0,1 12.5,21.5A4,4 0 0,1 8.5,17.5V5A2.5,2.5 0 0,1 11,2.5A2.5,2.5 0 0,1 13.5,5V15.5A1,1 0 0,1 12.5,16.5A1,1 0 0,1 11.5,15.5V6H10V15.5A2.5,2.5 0 0,0 12.5,18A2.5,2.5 0 0,0 15,15.5V5A4,4 0 0,0 11,1A4,4 0 0,0 7,5V17.5A5.5,5.5 0 0,0 12.5,23A5.5,5.5 0 0,0 18,17.5V6H16.5Z" /></g><g id="parking"><path d="M13.2,11H10V7H13.2A2,2 0 0,1 15.2,9A2,2 0 0,1 13.2,11M13,3H6V21H10V15H13A6,6 0 0,0 19,9C19,5.68 16.31,3 13,3Z" /></g><g id="pause"><path d="M14,19H18V5H14M6,19H10V5H6V19Z" /></g><g id="pause-circle"><path d="M15,16H13V8H15M11,16H9V8H11M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" /></g><g id="pause-circle-outline"><path d="M13,16V8H15V16H13M9,16V8H11V16H9M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z" /></g><g id="pause-octagon"><path d="M15.73,3L21,8.27V15.73L15.73,21H8.27L3,15.73V8.27L8.27,3H15.73M15,16V8H13V16H15M11,16V8H9V16H11Z" /></g><g id="pause-octagon-outline"><path d="M15,16H13V8H15V16M11,16H9V8H11V16M15.73,3L21,8.27V15.73L15.73,21H8.27L3,15.73V8.27L8.27,3H15.73M14.9,5H9.1L5,9.1V14.9L9.1,19H14.9L19,14.9V9.1L14.9,5Z" /></g><g id="paw"><path d="M8.35,3C9.53,2.83 10.78,4.12 11.14,5.9C11.5,7.67 10.85,9.25 9.67,9.43C8.5,9.61 7.24,8.32 6.87,6.54C6.5,4.77 7.17,3.19 8.35,3M15.5,3C16.69,3.19 17.35,4.77 17,6.54C16.62,8.32 15.37,9.61 14.19,9.43C13,9.25 12.35,7.67 12.72,5.9C13.08,4.12 14.33,2.83 15.5,3M3,7.6C4.14,7.11 5.69,8 6.5,9.55C7.26,11.13 7,12.79 5.87,13.28C4.74,13.77 3.2,12.89 2.41,11.32C1.62,9.75 1.9,8.08 3,7.6M21,7.6C22.1,8.08 22.38,9.75 21.59,11.32C20.8,12.89 19.26,13.77 18.13,13.28C17,12.79 16.74,11.13 17.5,9.55C18.31,8 19.86,7.11 21,7.6M19.33,18.38C19.37,19.32 18.65,20.36 17.79,20.75C16,21.57 13.88,19.87 11.89,19.87C9.9,19.87 7.76,21.64 6,20.75C5,20.26 4.31,18.96 4.44,17.88C4.62,16.39 6.41,15.59 7.47,14.5C8.88,13.09 9.88,10.44 11.89,10.44C13.89,10.44 14.95,13.05 16.3,14.5C17.41,15.72 19.26,16.75 19.33,18.38Z" /></g><g id="paw-off"><path d="M2,4.27L3.28,3L21.5,21.22L20.23,22.5L18.23,20.5C18.09,20.6 17.94,20.68 17.79,20.75C16,21.57 13.88,19.87 11.89,19.87C9.9,19.87 7.76,21.64 6,20.75C5,20.26 4.31,18.96 4.44,17.88C4.62,16.39 6.41,15.59 7.47,14.5C8.21,13.77 8.84,12.69 9.55,11.82L2,4.27M8.35,3C9.53,2.83 10.78,4.12 11.14,5.9C11.32,6.75 11.26,7.56 11,8.19L7.03,4.2C7.29,3.55 7.75,3.1 8.35,3M15.5,3C16.69,3.19 17.35,4.77 17,6.54C16.62,8.32 15.37,9.61 14.19,9.43C13,9.25 12.35,7.67 12.72,5.9C13.08,4.12 14.33,2.83 15.5,3M3,7.6C4.14,7.11 5.69,8 6.5,9.55C7.26,11.13 7,12.79 5.87,13.28C4.74,13.77 3.2,12.89 2.41,11.32C1.62,9.75 1.9,8.08 3,7.6M21,7.6C22.1,8.08 22.38,9.75 21.59,11.32C20.8,12.89 19.26,13.77 18.13,13.28C17,12.79 16.74,11.13 17.5,9.55C18.31,8 19.86,7.11 21,7.6Z" /></g><g id="pen"><path d="M20.71,7.04C20.37,7.38 20.04,7.71 20.03,8.04C20,8.36 20.34,8.69 20.66,9C21.14,9.5 21.61,9.95 21.59,10.44C21.57,10.93 21.06,11.44 20.55,11.94L16.42,16.08L15,14.66L19.25,10.42L18.29,9.46L16.87,10.87L13.12,7.12L16.96,3.29C17.35,2.9 18,2.9 18.37,3.29L20.71,5.63C21.1,6 21.1,6.65 20.71,7.04M3,17.25L12.56,7.68L16.31,11.43L6.75,21H3V17.25Z" /></g><g id="pencil"><path d="M20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18,2.9 17.35,2.9 16.96,3.29L15.12,5.12L18.87,8.87M3,17.25V21H6.75L17.81,9.93L14.06,6.18L3,17.25Z" /></g><g id="pencil-box"><path d="M19,3A2,2 0 0,1 21,5V19C21,20.11 20.1,21 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3H19M16.7,9.35C16.92,9.14 16.92,8.79 16.7,8.58L15.42,7.3C15.21,7.08 14.86,7.08 14.65,7.3L13.65,8.3L15.7,10.35L16.7,9.35M7,14.94V17H9.06L15.12,10.94L13.06,8.88L7,14.94Z" /></g><g id="pencil-box-outline"><path d="M19,19V5H5V19H19M19,3A2,2 0 0,1 21,5V19C21,20.11 20.1,21 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3H19M16.7,9.35L15.7,10.35L13.65,8.3L14.65,7.3C14.86,7.08 15.21,7.08 15.42,7.3L16.7,8.58C16.92,8.79 16.92,9.14 16.7,9.35M7,14.94L13.06,8.88L15.12,10.94L9.06,17H7V14.94Z" /></g><g id="pencil-lock"><path d="M5.5,2A2.5,2.5 0 0,0 3,4.5V5A1,1 0 0,0 2,6V10A1,1 0 0,0 3,11H8A1,1 0 0,0 9,10V6A1,1 0 0,0 8,5V4.5A2.5,2.5 0 0,0 5.5,2M5.5,3A1.5,1.5 0 0,1 7,4.5V5H4V4.5A1.5,1.5 0 0,1 5.5,3M19.66,3C19.4,3 19.16,3.09 18.97,3.28L17.13,5.13L20.88,8.88L22.72,7.03C23.11,6.64 23.11,6 22.72,5.63L20.38,3.28C20.18,3.09 19.91,3 19.66,3M16.06,6.19L5,17.25V21H8.75L19.81,9.94L16.06,6.19Z" /></g><g id="pencil-off"><path d="M18.66,2C18.4,2 18.16,2.09 17.97,2.28L16.13,4.13L19.88,7.88L21.72,6.03C22.11,5.64 22.11,5 21.72,4.63L19.38,2.28C19.18,2.09 18.91,2 18.66,2M3.28,4L2,5.28L8.5,11.75L4,16.25V20H7.75L12.25,15.5L18.72,22L20,20.72L13.5,14.25L9.75,10.5L3.28,4M15.06,5.19L11.03,9.22L14.78,12.97L18.81,8.94L15.06,5.19Z" /></g><g id="percent"><path d="M7,4A3,3 0 0,1 10,7A3,3 0 0,1 7,10A3,3 0 0,1 4,7A3,3 0 0,1 7,4M17,14A3,3 0 0,1 20,17A3,3 0 0,1 17,20A3,3 0 0,1 14,17A3,3 0 0,1 17,14M20,5.41L5.41,20L4,18.59L18.59,4L20,5.41Z" /></g><g id="pharmacy"><path d="M16,14H13V17H11V14H8V12H11V9H13V12H16M21,5H18.35L19.5,1.85L17.15,1L15.69,5H3V7L5,13L3,19V21H21V19L19,13L21,7V5Z" /></g><g id="phone"><path d="M6.62,10.79C8.06,13.62 10.38,15.94 13.21,17.38L15.41,15.18C15.69,14.9 16.08,14.82 16.43,14.93C17.55,15.3 18.75,15.5 20,15.5A1,1 0 0,1 21,16.5V20A1,1 0 0,1 20,21A17,17 0 0,1 3,4A1,1 0 0,1 4,3H7.5A1,1 0 0,1 8.5,4C8.5,5.25 8.7,6.45 9.07,7.57C9.18,7.92 9.1,8.31 8.82,8.59L6.62,10.79Z" /></g><g id="phone-bluetooth"><path d="M20,15.5C18.75,15.5 17.55,15.3 16.43,14.93C16.08,14.82 15.69,14.9 15.41,15.18L13.21,17.38C10.38,15.94 8.06,13.62 6.62,10.79L8.82,8.59C9.1,8.31 9.18,7.92 9.07,7.57C8.7,6.45 8.5,5.25 8.5,4A1,1 0 0,0 7.5,3H4A1,1 0 0,0 3,4A17,17 0 0,0 20,21A1,1 0 0,0 21,20V16.5A1,1 0 0,0 20,15.5M18,7.21L18.94,8.14L18,9.08M18,2.91L18.94,3.85L18,4.79M14.71,9.5L17,7.21V11H17.5L20.35,8.14L18.21,6L20.35,3.85L17.5,1H17V4.79L14.71,2.5L14,3.21L16.79,6L14,8.79L14.71,9.5Z" /></g><g id="phone-classic"><path d="M12,3C7.46,3 3.34,4.78 0.29,7.67C0.11,7.85 0,8.1 0,8.38C0,8.66 0.11,8.91 0.29,9.09L2.77,11.57C2.95,11.75 3.2,11.86 3.5,11.86C3.75,11.86 4,11.75 4.18,11.58C4.97,10.84 5.87,10.22 6.84,9.73C7.17,9.57 7.4,9.23 7.4,8.83V5.73C8.85,5.25 10.39,5 12,5C13.59,5 15.14,5.25 16.59,5.72V8.82C16.59,9.21 16.82,9.56 17.15,9.72C18.13,10.21 19,10.84 19.82,11.57C20,11.75 20.25,11.85 20.5,11.85C20.8,11.85 21.05,11.74 21.23,11.56L23.71,9.08C23.89,8.9 24,8.65 24,8.37C24,8.09 23.88,7.85 23.7,7.67C20.65,4.78 16.53,3 12,3M9,7V10C9,10 3,15 3,18V22H21V18C21,15 15,10 15,10V7H13V9H11V7H9M12,12A4,4 0 0,1 16,16A4,4 0 0,1 12,20A4,4 0 0,1 8,16A4,4 0 0,1 12,12M12,13.5A2.5,2.5 0 0,0 9.5,16A2.5,2.5 0 0,0 12,18.5A2.5,2.5 0 0,0 14.5,16A2.5,2.5 0 0,0 12,13.5Z" /></g><g id="phone-forward"><path d="M20,15.5C18.75,15.5 17.55,15.3 16.43,14.93C16.08,14.82 15.69,14.9 15.41,15.18L13.21,17.38C10.38,15.94 8.06,13.62 6.62,10.79L8.82,8.59C9.1,8.31 9.18,7.92 9.07,7.57C8.7,6.45 8.5,5.25 8.5,4A1,1 0 0,0 7.5,3H4A1,1 0 0,0 3,4A17,17 0 0,0 20,21A1,1 0 0,0 21,20V16.5A1,1 0 0,0 20,15.5M18,11L23,6L18,1V4H14V8H18V11Z" /></g><g id="phone-hangup"><path d="M12,9C10.4,9 8.85,9.25 7.4,9.72V12.82C7.4,13.22 7.17,13.56 6.84,13.72C5.86,14.21 4.97,14.84 4.17,15.57C4,15.75 3.75,15.86 3.5,15.86C3.2,15.86 2.95,15.74 2.77,15.56L0.29,13.08C0.11,12.9 0,12.65 0,12.38C0,12.1 0.11,11.85 0.29,11.67C3.34,8.77 7.46,7 12,7C16.54,7 20.66,8.77 23.71,11.67C23.89,11.85 24,12.1 24,12.38C24,12.65 23.89,12.9 23.71,13.08L21.23,15.56C21.05,15.74 20.8,15.86 20.5,15.86C20.25,15.86 20,15.75 19.82,15.57C19.03,14.84 18.14,14.21 17.16,13.72C16.83,13.56 16.6,13.22 16.6,12.82V9.72C15.15,9.25 13.6,9 12,9Z" /></g><g id="phone-in-talk"><path d="M15,12H17A5,5 0 0,0 12,7V9A3,3 0 0,1 15,12M19,12H21C21,7 16.97,3 12,3V5C15.86,5 19,8.13 19,12M20,15.5C18.75,15.5 17.55,15.3 16.43,14.93C16.08,14.82 15.69,14.9 15.41,15.18L13.21,17.38C10.38,15.94 8.06,13.62 6.62,10.79L8.82,8.59C9.1,8.31 9.18,7.92 9.07,7.57C8.7,6.45 8.5,5.25 8.5,4A1,1 0 0,0 7.5,3H4A1,1 0 0,0 3,4A17,17 0 0,0 20,21A1,1 0 0,0 21,20V16.5A1,1 0 0,0 20,15.5Z" /></g><g id="phone-incoming"><path d="M4,3A1,1 0 0,0 3,4A17,17 0 0,0 20,21A1,1 0 0,0 21,20V16.5A1,1 0 0,0 20,15.5C18.75,15.5 17.55,15.3 16.43,14.93C16.08,14.82 15.69,14.9 15.41,15.17L13.21,17.37C10.38,15.93 8.06,13.62 6.62,10.78L8.82,8.57C9.1,8.31 9.18,7.92 9.07,7.57C8.7,6.45 8.5,5.25 8.5,4A1,1 0 0,0 7.5,3H4M19,11V9.5H15.5L21,4L20,3L14.5,8.5V5H13V11H19Z" /></g><g id="phone-locked"><path d="M19.2,4H15.8V3.5C15.8,2.56 16.56,1.8 17.5,1.8C18.44,1.8 19.2,2.56 19.2,3.5M20,4V3.5A2.5,2.5 0 0,0 17.5,1A2.5,2.5 0 0,0 15,3.5V4A1,1 0 0,0 14,5V9A1,1 0 0,0 15,10H20A1,1 0 0,0 21,9V5A1,1 0 0,0 20,4M20,15.5C18.75,15.5 17.55,15.3 16.43,14.93C16.08,14.82 15.69,14.9 15.41,15.18L13.21,17.38C10.38,15.94 8.06,13.62 6.62,10.79L8.82,8.59C9.1,8.31 9.18,7.92 9.07,7.57C8.7,6.45 8.5,5.25 8.5,4A1,1 0 0,0 7.5,3H4A1,1 0 0,0 3,4A17,17 0 0,0 20,21A1,1 0 0,0 21,20V16.5A1,1 0 0,0 20,15.5Z" /></g><g id="phone-log"><path d="M20,15.5A1,1 0 0,1 21,16.5V20A1,1 0 0,1 20,21A17,17 0 0,1 3,4A1,1 0 0,1 4,3H7.5A1,1 0 0,1 8.5,4C8.5,5.24 8.7,6.45 9.07,7.57C9.18,7.92 9.1,8.31 8.82,8.58L6.62,10.79C8.06,13.62 10.38,15.94 13.21,17.38L15.41,15.18C15.69,14.9 16.08,14.82 16.43,14.93C17.55,15.3 18.75,15.5 20,15.5M12,3H14V5H12M15,3H21V5H15M12,6H14V8H12M15,6H21V8H15M12,9H14V11H12M15,9H21V11H15" /></g><g id="phone-minus"><path d="M4,3A1,1 0 0,0 3,4A17,17 0 0,0 20,21A1,1 0 0,0 21,20V16.5A1,1 0 0,0 20,15.5C18.76,15.5 17.55,15.3 16.43,14.93C16.08,14.82 15.69,14.9 15.41,15.18L13.21,17.38C10.38,15.94 8.07,13.62 6.62,10.79L8.82,8.58C9.1,8.31 9.18,7.92 9.07,7.57C8.7,6.45 8.5,5.24 8.5,4A1,1 0 0,0 7.5,3M13,6V8H21V6" /></g><g id="phone-missed"><path d="M23.71,16.67C20.66,13.77 16.54,12 12,12C7.46,12 3.34,13.77 0.29,16.67C0.11,16.85 0,17.1 0,17.38C0,17.65 0.11,17.9 0.29,18.08L2.77,20.56C2.95,20.74 3.2,20.86 3.5,20.86C3.75,20.86 4,20.75 4.18,20.57C4.97,19.83 5.86,19.21 6.84,18.72C7.17,18.56 7.4,18.22 7.4,17.82V14.72C8.85,14.25 10.39,14 12,14C13.6,14 15.15,14.25 16.6,14.72V17.82C16.6,18.22 16.83,18.56 17.16,18.72C18.14,19.21 19.03,19.83 19.82,20.57C20,20.75 20.25,20.86 20.5,20.86C20.8,20.86 21.05,20.74 21.23,20.56L23.71,18.08C23.89,17.9 24,17.65 24,17.38C24,17.1 23.89,16.85 23.71,16.67M6.5,5.5L12,11L19,4L18,3L12,9L7.5,4.5H11V3H5V9H6.5V5.5Z" /></g><g id="phone-outgoing"><path d="M4,3A1,1 0 0,0 3,4A17,17 0 0,0 20,21A1,1 0 0,0 21,20V16.5A1,1 0 0,0 20,15.5C18.75,15.5 17.55,15.3 16.43,14.93C16.08,14.82 15.69,14.9 15.41,15.17L13.21,17.37C10.38,15.93 8.06,13.62 6.62,10.78L8.82,8.57C9.1,8.31 9.18,7.92 9.07,7.57C8.7,6.45 8.5,5.25 8.5,4A1,1 0 0,0 7.5,3H4M15,3V4.5H18.5L13,10L14,11L19.5,5.5V9H21V3H15Z" /></g><g id="phone-paused"><path d="M19,10H21V3H19M20,15.5C18.75,15.5 17.55,15.3 16.43,14.93C16.08,14.82 15.69,14.9 15.41,15.18L13.21,17.38C10.38,15.94 8.06,13.62 6.62,10.79L8.82,8.59C9.1,8.31 9.18,7.92 9.07,7.57C8.7,6.45 8.5,5.25 8.5,4A1,1 0 0,0 7.5,3H4A1,1 0 0,0 3,4A17,17 0 0,0 20,21A1,1 0 0,0 21,20V16.5A1,1 0 0,0 20,15.5M17,3H15V10H17V3Z" /></g><g id="phone-plus"><path d="M4,3A1,1 0 0,0 3,4A17,17 0 0,0 20,21A1,1 0 0,0 21,20V16.5A1,1 0 0,0 20,15.5C18.76,15.5 17.55,15.3 16.43,14.93C16.08,14.82 15.69,14.9 15.41,15.18L13.21,17.38C10.38,15.94 8.07,13.62 6.62,10.79L8.82,8.58C9.1,8.31 9.18,7.92 9.07,7.57C8.7,6.45 8.5,5.24 8.5,4A1,1 0 0,0 7.5,3M16,3V6H13V8H16V11H18V8H21V6H18V3" /></g><g id="phone-settings"><path d="M19,11H21V9H19M20,15.5C18.75,15.5 17.55,15.3 16.43,14.93C16.08,14.82 15.69,14.9 15.41,15.18L13.21,17.38C10.38,15.94 8.06,13.62 6.62,10.79L8.82,8.59C9.1,8.31 9.18,7.92 9.07,7.57C8.7,6.45 8.5,5.25 8.5,4A1,1 0 0,0 7.5,3H4A1,1 0 0,0 3,4A17,17 0 0,0 20,21A1,1 0 0,0 21,20V16.5A1,1 0 0,0 20,15.5M17,9H15V11H17M13,9H11V11H13V9Z" /></g><g id="phone-voip"><path d="M13,17V19H14A1,1 0 0,1 15,20H22V22H15A1,1 0 0,1 14,23H10A1,1 0 0,1 9,22H2V20H9A1,1 0 0,1 10,19H11V17H13M23.7,7.67C23.88,7.85 24,8.09 24,8.37C24,8.65 23.89,8.9 23.71,9.08L21.23,11.56C21.05,11.74 20.8,11.85 20.5,11.85C20.25,11.85 20,11.75 19.82,11.57C19,10.84 18.13,10.21 17.15,9.72C16.82,9.56 16.59,9.21 16.59,8.82V5.72C15.14,5.25 13.59,5 12,5C10.4,5 8.85,5.25 7.4,5.73V8.83C7.4,9.23 7.17,9.57 6.84,9.73C5.87,10.22 4.97,10.84 4.18,11.58C4,11.75 3.75,11.86 3.5,11.86C3.2,11.86 2.95,11.75 2.77,11.57L0.29,9.09C0.11,8.91 0,8.66 0,8.38C0,8.1 0.11,7.85 0.29,7.67C3.34,4.78 7.46,3 12,3C16.53,3 20.65,4.78 23.7,7.67M11,10V15H10V10H11M12,10H15V13H13V15H12V10M14,12V11H13V12H14Z" /></g><g id="pi"><path d="M4,5V7H6V19H8V7H14V16A3,3 0 0,0 17,19A3,3 0 0,0 20,16H18A1,1 0 0,1 17,17A1,1 0 0,1 16,16V7H18V5" /></g><g id="pi-box"><path d="M5,3C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M6,7H17V9H15V14A1,1 0 0,0 16,15A1,1 0 0,0 17,14H19A3,3 0 0,1 16,17A3,3 0 0,1 13,14V9H10V17H8V9H6" /></g><g id="piano"><path d="M4,3H20A2,2 0 0,1 22,5V19A2,2 0 0,1 20,21H4A2,2 0 0,1 2,19V5A2,2 0 0,1 4,3M4,5V19H8V13H6.75V5H4M9,19H15V13H13.75V5H10.25V13H9V19M16,19H20V5H17.25V13H16V19Z" /></g><g id="pig"><path d="M9.5,9A1.5,1.5 0 0,0 8,10.5A1.5,1.5 0 0,0 9.5,12A1.5,1.5 0 0,0 11,10.5A1.5,1.5 0 0,0 9.5,9M14.5,9A1.5,1.5 0 0,0 13,10.5A1.5,1.5 0 0,0 14.5,12A1.5,1.5 0 0,0 16,10.5A1.5,1.5 0 0,0 14.5,9M12,4L12.68,4.03C13.62,3.24 14.82,2.59 15.72,2.35C17.59,1.85 20.88,2.23 21.31,3.83C21.62,5 20.6,6.45 19.03,7.38C20.26,8.92 21,10.87 21,13A9,9 0 0,1 12,22A9,9 0 0,1 3,13C3,10.87 3.74,8.92 4.97,7.38C3.4,6.45 2.38,5 2.69,3.83C3.12,2.23 6.41,1.85 8.28,2.35C9.18,2.59 10.38,3.24 11.32,4.03L12,4M10,16A1,1 0 0,1 11,17A1,1 0 0,1 10,18A1,1 0 0,1 9,17A1,1 0 0,1 10,16M14,16A1,1 0 0,1 15,17A1,1 0 0,1 14,18A1,1 0 0,1 13,17A1,1 0 0,1 14,16M12,13C9.24,13 7,15.34 7,17C7,18.66 9.24,20 12,20C14.76,20 17,18.66 17,17C17,15.34 14.76,13 12,13M7.76,4.28C7.31,4.16 4.59,4.35 4.59,4.35C4.59,4.35 6.8,6.1 7.24,6.22C7.69,6.34 9.77,6.43 9.91,5.9C10.06,5.36 8.2,4.4 7.76,4.28M16.24,4.28C15.8,4.4 13.94,5.36 14.09,5.9C14.23,6.43 16.31,6.34 16.76,6.22C17.2,6.1 19.41,4.35 19.41,4.35C19.41,4.35 16.69,4.16 16.24,4.28Z" /></g><g id="pill"><path d="M4.22,11.29L11.29,4.22C13.64,1.88 17.43,1.88 19.78,4.22C22.12,6.56 22.12,10.36 19.78,12.71L12.71,19.78C10.36,22.12 6.56,22.12 4.22,19.78C1.88,17.43 1.88,13.64 4.22,11.29M5.64,12.71C4.59,13.75 4.24,15.24 4.6,16.57L10.59,10.59L14.83,14.83L18.36,11.29C19.93,9.73 19.93,7.2 18.36,5.64C16.8,4.07 14.27,4.07 12.71,5.64L5.64,12.71Z" /></g><g id="pin"><path d="M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z" /></g><g id="pin-off"><path d="M2,5.27L3.28,4L20,20.72L18.73,22L12.8,16.07V22H11.2V16H6V14L8,12V11.27L2,5.27M16,12L18,14V16H17.82L8,6.18V4H7V2H17V4H16V12Z" /></g><g id="pine-tree"><path d="M10,21V18H3L8,13H5L10,8H7L12,3L17,8H14L19,13H16L21,18H14V21H10Z" /></g><g id="pine-tree-box"><path d="M4,2H20A2,2 0 0,1 22,4V20A2,2 0 0,1 20,22H4A2,2 0 0,1 2,20V4A2,2 0 0,1 4,2M11,19H13V17H18L14,13H17L13,9H16L12,5L8,9H11L7,13H10L6,17H11V19Z" /></g><g id="pinterest"><path d="M13.25,17.25C12.25,17.25 11.29,16.82 10.6,16.1L9.41,20.1L9.33,20.36L9.29,20.34C9.04,20.75 8.61,21 8.12,21C7.37,21 6.75,20.38 6.75,19.62C6.75,19.56 6.76,19.5 6.77,19.44L6.75,19.43L6.81,19.21L9.12,12.26C9.12,12.26 8.87,11.5 8.87,10.42C8.87,8.27 10.03,7.62 10.95,7.62C11.88,7.62 12.73,7.95 12.73,9.26C12.73,10.94 11.61,11.8 11.61,13C11.61,13.94 12.37,14.69 13.29,14.69C16.21,14.69 17.25,12.5 17.25,10.44C17.25,7.71 14.89,5.5 12,5.5C9.1,5.5 6.75,7.71 6.75,10.44C6.75,11.28 7,12.12 7.43,12.85C7.54,13.05 7.6,13.27 7.6,13.5A1.25,1.25 0 0,1 6.35,14.75C5.91,14.75 5.5,14.5 5.27,14.13C4.6,13 4.25,11.73 4.25,10.44C4.25,6.33 7.73,3 12,3C16.27,3 19.75,6.33 19.75,10.44C19.75,13.72 17.71,17.25 13.25,17.25Z" /></g><g id="pinterest-box"><path d="M13,16.2C12.2,16.2 11.43,15.86 10.88,15.28L9.93,18.5L9.86,18.69L9.83,18.67C9.64,19 9.29,19.2 8.9,19.2C8.29,19.2 7.8,18.71 7.8,18.1C7.8,18.05 7.81,18 7.81,17.95H7.8L7.85,17.77L9.7,12.21C9.7,12.21 9.5,11.59 9.5,10.73C9.5,9 10.42,8.5 11.16,8.5C11.91,8.5 12.58,8.76 12.58,9.81C12.58,11.15 11.69,11.84 11.69,12.81C11.69,13.55 12.29,14.16 13.03,14.16C15.37,14.16 16.2,12.4 16.2,10.75C16.2,8.57 14.32,6.8 12,6.8C9.68,6.8 7.8,8.57 7.8,10.75C7.8,11.42 8,12.09 8.34,12.68C8.43,12.84 8.5,13 8.5,13.2A1,1 0 0,1 7.5,14.2C7.13,14.2 6.79,14 6.62,13.7C6.08,12.81 5.8,11.79 5.8,10.75C5.8,7.47 8.58,4.8 12,4.8C15.42,4.8 18.2,7.47 18.2,10.75C18.2,13.37 16.57,16.2 13,16.2M20,2H4C2.89,2 2,2.89 2,4V20A2,2 0 0,0 4,22H20A2,2 0 0,0 22,20V4C22,2.89 21.1,2 20,2Z" /></g><g id="pizza"><path d="M12,15A2,2 0 0,1 10,13C10,11.89 10.9,11 12,11A2,2 0 0,1 14,13A2,2 0 0,1 12,15M7,7C7,5.89 7.89,5 9,5A2,2 0 0,1 11,7A2,2 0 0,1 9,9C7.89,9 7,8.1 7,7M12,2C8.43,2 5.23,3.54 3,6L12,22L21,6C18.78,3.54 15.57,2 12,2Z" /></g><g id="plane-shield"><path d="M12,1L3,5V11C3,16.55 6.84,21.74 12,23C17.16,21.74 21,16.55 21,11V5L12,1M12,5.68C12.5,5.68 12.95,6.11 12.95,6.63V10.11L18,13.26V14.53L12.95,12.95V16.42L14.21,17.37V18.32L12,17.68L9.79,18.32V17.37L11.05,16.42V12.95L6,14.53V13.26L11.05,10.11V6.63C11.05,6.11 11.5,5.68 12,5.68Z" /></g><g id="play"><path d="M8,5.14V19.14L19,12.14L8,5.14Z" /></g><g id="play-box-outline"><path d="M19,19H5V5H19M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3M10,8V16L15,12L10,8Z" /></g><g id="play-circle"><path d="M10,16.5V7.5L16,12M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" /></g><g id="play-circle-outline"><path d="M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M10,16.5L16,12L10,7.5V16.5Z" /></g><g id="play-pause"><path d="M3,5V19L11,12M13,19H16V5H13M18,5V19H21V5" /></g><g id="play-protected-content"><path d="M2,5V18H11V16H4V7H17V11H19V5H2M9,9V14L12.5,11.5L9,9M21.04,11.67L16.09,16.62L13.96,14.5L12.55,15.91L16.09,19.45L22.45,13.09L21.04,11.67Z" /></g><g id="playlist-check"><path d="M14,10H2V12H14V10M14,6H2V8H14V6M2,16H10V14H2V16M21.5,11.5L23,13L16,20L11.5,15.5L13,14L16,17L21.5,11.5Z" /></g><g id="playlist-minus"><path d="M2,16H10V14H2M12,14V16H22V14M14,6H2V8H14M14,10H2V12H14V10Z" /></g><g id="playlist-play"><path d="M19,9H2V11H19V9M19,5H2V7H19V5M2,15H15V13H2V15M17,13V19L22,16L17,13Z" /></g><g id="playlist-plus"><path d="M2,16H10V14H2M18,14V10H16V14H12V16H16V20H18V16H22V14M14,6H2V8H14M14,10H2V12H14V10Z" /></g><g id="playlist-remove"><path d="M2,6V8H14V6H2M2,10V12H10V10H2M14.17,10.76L12.76,12.17L15.59,15L12.76,17.83L14.17,19.24L17,16.41L19.83,19.24L21.24,17.83L18.41,15L21.24,12.17L19.83,10.76L17,13.59L14.17,10.76M2,14V16H10V14H2Z" /></g><g id="playstation"><path d="M9.5,4.27C10.88,4.53 12.9,5.14 14,5.5C16.75,6.45 17.69,7.63 17.69,10.29C17.69,12.89 16.09,13.87 14.05,12.89V8.05C14.05,7.5 13.95,6.97 13.41,6.82C13,6.69 12.76,7.07 12.76,7.63V19.73L9.5,18.69V4.27M13.37,17.62L18.62,15.75C19.22,15.54 19.31,15.24 18.83,15.08C18.34,14.92 17.47,14.97 16.87,15.18L13.37,16.41V14.45L13.58,14.38C13.58,14.38 14.59,14 16,13.87C17.43,13.71 19.17,13.89 20.53,14.4C22.07,14.89 22.25,15.61 21.86,16.1C21.46,16.6 20.5,16.95 20.5,16.95L13.37,19.5V17.62M3.5,17.42C1.93,17 1.66,16.05 2.38,15.5C3.05,15 4.18,14.65 4.18,14.65L8.86,13V14.88L5.5,16.09C4.9,16.3 4.81,16.6 5.29,16.76C5.77,16.92 6.65,16.88 7.24,16.66L8.86,16.08V17.77L8.54,17.83C6.92,18.09 5.2,18 3.5,17.42Z" /></g><g id="plex"><path d="M4,2C2.89,2 2,2.89 2,4V20C2,21.11 2.89,22 4,22H20C21.11,22 22,21.11 22,20V4C22,2.89 21.11,2 20,2H4M8.56,6H12.06L15.5,12L12.06,18H8.56L12,12L8.56,6Z" /></g><g id="plus"><path d="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" /></g><g id="plus-box"><path d="M17,13H13V17H11V13H7V11H11V7H13V11H17M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z" /></g><g id="plus-circle"><path d="M17,13H13V17H11V13H7V11H11V7H13V11H17M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" /></g><g id="plus-circle-multiple-outline"><path d="M16,8H14V11H11V13H14V16H16V13H19V11H16M2,12C2,9.21 3.64,6.8 6,5.68V3.5C2.5,4.76 0,8.09 0,12C0,15.91 2.5,19.24 6,20.5V18.32C3.64,17.2 2,14.79 2,12M15,3C10.04,3 6,7.04 6,12C6,16.96 10.04,21 15,21C19.96,21 24,16.96 24,12C24,7.04 19.96,3 15,3M15,19C11.14,19 8,15.86 8,12C8,8.14 11.14,5 15,5C18.86,5 22,8.14 22,12C22,15.86 18.86,19 15,19Z" /></g><g id="plus-circle-outline"><path d="M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M13,7H11V11H7V13H11V17H13V13H17V11H13V7Z" /></g><g id="plus-network"><path d="M16,11V9H13V6H11V9H8V11H11V14H13V11H16M17,3A2,2 0 0,1 19,5V15A2,2 0 0,1 17,17H13V19H14A1,1 0 0,1 15,20H22V22H15A1,1 0 0,1 14,23H10A1,1 0 0,1 9,22H2V20H9A1,1 0 0,1 10,19H11V17H7C5.89,17 5,16.1 5,15V5A2,2 0 0,1 7,3H17Z" /></g><g id="plus-one"><path d="M10,8V12H14V14H10V18H8V14H4V12H8V8H10M14.5,6.08L19,5V18H17V7.4L14.5,7.9V6.08Z" /></g><g id="pocket"><path d="M22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12V4.5A2.5,2.5 0 0,1 4.5,2H19.5A2.5,2.5 0 0,1 22,4.5V12M15.88,8.25L12,12.13L8.12,8.24C7.53,7.65 6.58,7.65 6,8.24C5.41,8.82 5.41,9.77 6,10.36L10.93,15.32C11.5,15.9 12.47,15.9 13.06,15.32L18,10.37C18.59,9.78 18.59,8.83 18,8.25C17.42,7.66 16.47,7.66 15.88,8.25Z" /></g><g id="pokeball"><path d="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4C7.92,4 4.55,7.05 4.06,11H8.13C8.57,9.27 10.14,8 12,8C13.86,8 15.43,9.27 15.87,11H19.94C19.45,7.05 16.08,4 12,4M12,20C16.08,20 19.45,16.95 19.94,13H15.87C15.43,14.73 13.86,16 12,16C10.14,16 8.57,14.73 8.13,13H4.06C4.55,16.95 7.92,20 12,20M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10Z" /></g><g id="polaroid"><path d="M6,3H18A2,2 0 0,1 20,5V19A2,2 0 0,1 18,21H6A2,2 0 0,1 4,19V5A2,2 0 0,1 6,3M6,5V17H18V5H6Z" /></g><g id="poll"><path d="M3,22V8H7V22H3M10,22V2H14V22H10M17,22V14H21V22H17Z" /></g><g id="poll-box"><path d="M17,17H15V13H17M13,17H11V7H13M9,17H7V10H9M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z" /></g><g id="polymer"><path d="M19,4H15L7.1,16.63L4.5,12L9,4H5L0.5,12L5,20H9L16.89,7.37L19.5,12L15,20H19L23.5,12L19,4Z" /></g><g id="pool"><path d="M2,15C3.67,14.25 5.33,13.5 7,13.17V5A3,3 0 0,1 10,2C11.31,2 12.42,2.83 12.83,4H10A1,1 0 0,0 9,5V6H14V5A3,3 0 0,1 17,2C18.31,2 19.42,2.83 19.83,4H17A1,1 0 0,0 16,5V14.94C18,14.62 20,13 22,13V15C19.78,15 17.56,17 15.33,17C13.11,17 10.89,15 8.67,15C6.44,15 4.22,16 2,17V15M14,8H9V10H14V8M14,12H9V13C10.67,13.16 12.33,14.31 14,14.79V12M2,19C4.22,18 6.44,17 8.67,17C10.89,17 13.11,19 15.33,19C17.56,19 19.78,17 22,17V19C19.78,19 17.56,21 15.33,21C13.11,21 10.89,19 8.67,19C6.44,19 4.22,20 2,21V19Z" /></g><g id="popcorn"><path d="M7,22H4.75C4.75,22 4,22 3.81,20.65L2.04,3.81L2,3.5C2,2.67 2.9,2 4,2C5.1,2 6,2.67 6,3.5C6,2.67 6.9,2 8,2C9.1,2 10,2.67 10,3.5C10,2.67 10.9,2 12,2C13.09,2 14,2.66 14,3.5V3.5C14,2.67 14.9,2 16,2C17.1,2 18,2.67 18,3.5C18,2.67 18.9,2 20,2C21.1,2 22,2.67 22,3.5L21.96,3.81L20.19,20.65C20,22 19.25,22 19.25,22H17L16.5,22H13.75L10.25,22H7.5L7,22M17.85,4.93C17.55,4.39 16.84,4 16,4C15.19,4 14.36,4.36 14,4.87L13.78,20H16.66L17.85,4.93M10,4.87C9.64,4.36 8.81,4 8,4C7.16,4 6.45,4.39 6.15,4.93L7.34,20H10.22L10,4.87Z" /></g><g id="pot"><path d="M19,19A2,2 0 0,1 17,21H7A2,2 0 0,1 5,19V13H3V10H21V13H19V19M6,6H8V8H6V6M11,6H13V8H11V6M16,6H18V8H16V6M18,3H20V5H18V3M13,3H15V5H13V3M8,3H10V5H8V3Z" /></g><g id="pot-mix"><path d="M19,19A2,2 0 0,1 17,21H7A2,2 0 0,1 5,19V13H3V10H14L18,3.07L19.73,4.07L16.31,10H21V13H19V19Z" /></g><g id="pound"><path d="M5.41,21L6.12,17H2.12L2.47,15H6.47L7.53,9H3.53L3.88,7H7.88L8.59,3H10.59L9.88,7H15.88L16.59,3H18.59L17.88,7H21.88L21.53,9H17.53L16.47,15H20.47L20.12,17H16.12L15.41,21H13.41L14.12,17H8.12L7.41,21H5.41M9.53,9L8.47,15H14.47L15.53,9H9.53Z" /></g><g id="pound-box"><path d="M3,5A2,2 0 0,1 5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5C3.89,21 3,20.1 3,19V5M7,18H9L9.35,16H13.35L13,18H15L15.35,16H17.35L17.71,14H15.71L16.41,10H18.41L18.76,8H16.76L17.12,6H15.12L14.76,8H10.76L11.12,6H9.12L8.76,8H6.76L6.41,10H8.41L7.71,14H5.71L5.35,16H7.35L7,18M10.41,10H14.41L13.71,14H9.71L10.41,10Z" /></g><g id="power"><path d="M16.56,5.44L15.11,6.89C16.84,7.94 18,9.83 18,12A6,6 0 0,1 12,18A6,6 0 0,1 6,12C6,9.83 7.16,7.94 8.88,6.88L7.44,5.44C5.36,6.88 4,9.28 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12C20,9.28 18.64,6.88 16.56,5.44M13,3H11V13H13" /></g><g id="power-plug"><path d="M16,7V3H14V7H10V3H8V7H8C7,7 6,8 6,9V14.5L9.5,18V21H14.5V18L18,14.5V9C18,8 17,7 16,7Z" /></g><g id="power-plug-off"><path d="M8,3V6.18C11.1,9.23 14.1,12.3 17.2,15.3C17.4,15 17.8,14.8 18,14.4V8.8C18,7.68 16.7,7.16 16,6.84V3H14V7H10V3H8M3.28,4C2.85,4.42 2.43,4.85 2,5.27L6,9.27V14.5C7.17,15.65 8.33,16.83 9.5,18V21H14.5V18C14.72,17.73 14.95,18.33 15.17,18.44C16.37,19.64 17.47,20.84 18.67,22.04C19.17,21.64 19.57,21.14 19.97,20.74C14.37,15.14 8.77,9.64 3.27,4.04L3.28,4Z" /></g><g id="power-settings"><path d="M15,24H17V22H15M16.56,4.44L15.11,5.89C16.84,6.94 18,8.83 18,11A6,6 0 0,1 12,17A6,6 0 0,1 6,11C6,8.83 7.16,6.94 8.88,5.88L7.44,4.44C5.36,5.88 4,8.28 4,11A8,8 0 0,0 12,19A8,8 0 0,0 20,11C20,8.28 18.64,5.88 16.56,4.44M13,2H11V12H13M11,24H13V22H11M7,24H9V22H7V24Z" /></g><g id="power-socket"><path d="M15,15H17V11H15M7,15H9V11H7M11,13H13V9H11M8.83,7H15.2L19,10.8V17H5V10.8M8,5L3,10V19H21V10L16,5H8Z" /></g><g id="presentation"><path d="M2,3H10A2,2 0 0,1 12,1A2,2 0 0,1 14,3H22V5H21V16H15.25L17,22H15L13.25,16H10.75L9,22H7L8.75,16H3V5H2V3M5,5V14H19V5H5Z" /></g><g id="presentation-play"><path d="M2,3H10A2,2 0 0,1 12,1A2,2 0 0,1 14,3H22V5H21V16H15.25L17,22H15L13.25,16H10.75L9,22H7L8.75,16H3V5H2V3M5,5V14H19V5H5M11.85,11.85C11.76,11.94 11.64,12 11.5,12A0.5,0.5 0 0,1 11,11.5V7.5A0.5,0.5 0 0,1 11.5,7C11.64,7 11.76,7.06 11.85,7.15L13.25,8.54C13.57,8.86 13.89,9.18 13.89,9.5C13.89,9.82 13.57,10.14 13.25,10.46L11.85,11.85Z" /></g><g id="printer"><path d="M18,3H6V7H18M19,12A1,1 0 0,1 18,11A1,1 0 0,1 19,10A1,1 0 0,1 20,11A1,1 0 0,1 19,12M16,19H8V14H16M19,8H5A3,3 0 0,0 2,11V17H6V21H18V17H22V11A3,3 0 0,0 19,8Z" /></g><g id="printer-3d"><path d="M19,6A1,1 0 0,0 20,5A1,1 0 0,0 19,4A1,1 0 0,0 18,5A1,1 0 0,0 19,6M19,2A3,3 0 0,1 22,5V11H18V7H6V11H2V5A3,3 0 0,1 5,2H19M18,18.25C18,18.63 17.79,18.96 17.47,19.13L12.57,21.82C12.4,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L6.53,19.13C6.21,18.96 6,18.63 6,18.25V13C6,12.62 6.21,12.29 6.53,12.12L11.43,9.68C11.59,9.56 11.79,9.5 12,9.5C12.21,9.5 12.4,9.56 12.57,9.68L17.47,12.12C17.79,12.29 18,12.62 18,13V18.25M12,11.65L9.04,13L12,14.6L14.96,13L12,11.65M8,17.66L11,19.29V16.33L8,14.71V17.66M16,17.66V14.71L13,16.33V19.29L16,17.66Z" /></g><g id="printer-alert"><path d="M14,4V8H6V4H14M15,13A1,1 0 0,0 16,12A1,1 0 0,0 15,11A1,1 0 0,0 14,12A1,1 0 0,0 15,13M13,19V15H7V19H13M15,9A3,3 0 0,1 18,12V17H15V21H5V17H2V12A3,3 0 0,1 5,9H15M22,7V12H20V7H22M22,14V16H20V14H22Z" /></g><g id="priority-high"><path d="M14,19H22V17H14V19M14,13.5H22V11.5H14V13.5M14,8H22V6H14V8M2,12.5C2,8.92 4.92,6 8.5,6H9V4L12,7L9,10V8H8.5C6,8 4,10 4,12.5C4,15 6,17 8.5,17H12V19H8.5C4.92,19 2,16.08 2,12.5Z" /></g><g id="priority-low"><path d="M14,5H22V7H14V5M14,10.5H22V12.5H14V10.5M14,16H22V18H14V16M2,11.5C2,15.08 4.92,18 8.5,18H9V20L12,17L9,14V16H8.5C6,16 4,14 4,11.5C4,9 6,7 8.5,7H12V5H8.5C4.92,5 2,7.92 2,11.5Z" /></g><g id="professional-hexagon"><path d="M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M5,9V15H6.25V13H7A2,2 0 0,0 9,11A2,2 0 0,0 7,9H5M6.25,12V10H6.75A1,1 0 0,1 7.75,11A1,1 0 0,1 6.75,12H6.25M9.75,9V15H11V13H11.75L12.41,15H13.73L12.94,12.61C13.43,12.25 13.75,11.66 13.75,11A2,2 0 0,0 11.75,9H9.75M11,12V10H11.5A1,1 0 0,1 12.5,11A1,1 0 0,1 11.5,12H11M17,9C15.62,9 14.5,10.34 14.5,12C14.5,13.66 15.62,15 17,15C18.38,15 19.5,13.66 19.5,12C19.5,10.34 18.38,9 17,9M17,10.25C17.76,10.25 18.38,11.03 18.38,12C18.38,12.97 17.76,13.75 17,13.75C16.24,13.75 15.63,12.97 15.63,12C15.63,11.03 16.24,10.25 17,10.25Z" /></g><g id="projector"><path d="M16,6C14.87,6 13.77,6.35 12.84,7H4C2.89,7 2,7.89 2,9V15C2,16.11 2.89,17 4,17H5V18A1,1 0 0,0 6,19H8A1,1 0 0,0 9,18V17H15V18A1,1 0 0,0 16,19H18A1,1 0 0,0 19,18V17H20C21.11,17 22,16.11 22,15V9C22,7.89 21.11,7 20,7H19.15C18.23,6.35 17.13,6 16,6M16,7.5A3.5,3.5 0 0,1 19.5,11A3.5,3.5 0 0,1 16,14.5A3.5,3.5 0 0,1 12.5,11A3.5,3.5 0 0,1 16,7.5M4,9H8V10H4V9M16,9A2,2 0 0,0 14,11A2,2 0 0,0 16,13A2,2 0 0,0 18,11A2,2 0 0,0 16,9M4,11H8V12H4V11M4,13H8V14H4V13Z" /></g><g id="projector-screen"><path d="M4,2A1,1 0 0,0 3,3V4A1,1 0 0,0 4,5H5V14H11V16.59L6.79,20.79L8.21,22.21L11,19.41V22H13V19.41L15.79,22.21L17.21,20.79L13,16.59V14H19V5H20A1,1 0 0,0 21,4V3A1,1 0 0,0 20,2H4Z" /></g><g id="publish"><path d="M5,4V6H19V4H5M5,14H9V20H15V14H19L12,7L5,14Z" /></g><g id="pulse"><path d="M3,13H5.79L10.1,4.79L11.28,13.75L14.5,9.66L17.83,13H21V15H17L14.67,12.67L9.92,18.73L8.94,11.31L7,15H3V13Z" /></g><g id="puzzle"><path d="M20.5,11H19V7C19,5.89 18.1,5 17,5H13V3.5A2.5,2.5 0 0,0 10.5,1A2.5,2.5 0 0,0 8,3.5V5H4A2,2 0 0,0 2,7V10.8H3.5C5,10.8 6.2,12 6.2,13.5C6.2,15 5,16.2 3.5,16.2H2V20A2,2 0 0,0 4,22H7.8V20.5C7.8,19 9,17.8 10.5,17.8C12,17.8 13.2,19 13.2,20.5V22H17A2,2 0 0,0 19,20V16H20.5A2.5,2.5 0 0,0 23,13.5A2.5,2.5 0 0,0 20.5,11Z" /></g><g id="qqchat"><path d="M3.18,13.54C3.76,12.16 4.57,11.14 5.17,10.92C5.16,10.12 5.31,9.62 5.56,9.22C5.56,9.19 5.5,8.86 5.72,8.45C5.87,4.85 8.21,2 12,2C15.79,2 18.13,4.85 18.28,8.45C18.5,8.86 18.44,9.19 18.44,9.22C18.69,9.62 18.84,10.12 18.83,10.92C19.43,11.14 20.24,12.16 20.82,13.55C21.57,15.31 21.69,17 21.09,17.3C20.68,17.5 20.03,17 19.42,16.12C19.18,17.1 18.58,18 17.73,18.71C18.63,19.04 19.21,19.58 19.21,20.19C19.21,21.19 17.63,22 15.69,22C13.93,22 12.5,21.34 12.21,20.5H11.79C11.5,21.34 10.07,22 8.31,22C6.37,22 4.79,21.19 4.79,20.19C4.79,19.58 5.37,19.04 6.27,18.71C5.42,18 4.82,17.1 4.58,16.12C3.97,17 3.32,17.5 2.91,17.3C2.31,17 2.43,15.31 3.18,13.54Z" /></g><g id="qrcode"><path d="M3,11H5V13H3V11M11,5H13V9H11V5M9,11H13V15H11V13H9V11M15,11H17V13H19V11H21V13H19V15H21V19H19V21H17V19H13V21H11V17H15V15H17V13H15V11M19,19V15H17V19H19M15,3H21V9H15V3M17,5V7H19V5H17M3,3H9V9H3V3M5,5V7H7V5H5M3,15H9V21H3V15M5,17V19H7V17H5Z" /></g><g id="qrcode-scan"><path d="M4,4H10V10H4V4M20,4V10H14V4H20M14,15H16V13H14V11H16V13H18V11H20V13H18V15H20V18H18V20H16V18H13V20H11V16H14V15M16,15V18H18V15H16M4,20V14H10V20H4M6,6V8H8V6H6M16,6V8H18V6H16M6,16V18H8V16H6M4,11H6V13H4V11M9,11H13V15H11V13H9V11M11,6H13V10H11V6M2,2V6H0V2A2,2 0 0,1 2,0H6V2H2M22,0A2,2 0 0,1 24,2V6H22V2H18V0H22M2,18V22H6V24H2A2,2 0 0,1 0,22V18H2M22,22V18H24V22A2,2 0 0,1 22,24H18V22H22Z" /></g><g id="quadcopter"><path d="M5.5,1C8,1 10,3 10,5.5C10,6.38 9.75,7.2 9.31,7.9L9.41,8H14.59L14.69,7.9C14.25,7.2 14,6.38 14,5.5C14,3 16,1 18.5,1C21,1 23,3 23,5.5C23,8 21,10 18.5,10C17.62,10 16.8,9.75 16.1,9.31L15,10.41V13.59L16.1,14.69C16.8,14.25 17.62,14 18.5,14C21,14 23,16 23,18.5C23,21 21,23 18.5,23C16,23 14,21 14,18.5C14,17.62 14.25,16.8 14.69,16.1L14.59,16H9.41L9.31,16.1C9.75,16.8 10,17.62 10,18.5C10,21 8,23 5.5,23C3,23 1,21 1,18.5C1,16 3,14 5.5,14C6.38,14 7.2,14.25 7.9,14.69L9,13.59V10.41L7.9,9.31C7.2,9.75 6.38,10 5.5,10C3,10 1,8 1,5.5C1,3 3,1 5.5,1M5.5,3A2.5,2.5 0 0,0 3,5.5A2.5,2.5 0 0,0 5.5,8A2.5,2.5 0 0,0 8,5.5A2.5,2.5 0 0,0 5.5,3M5.5,16A2.5,2.5 0 0,0 3,18.5A2.5,2.5 0 0,0 5.5,21A2.5,2.5 0 0,0 8,18.5A2.5,2.5 0 0,0 5.5,16M18.5,3A2.5,2.5 0 0,0 16,5.5A2.5,2.5 0 0,0 18.5,8A2.5,2.5 0 0,0 21,5.5A2.5,2.5 0 0,0 18.5,3M18.5,16A2.5,2.5 0 0,0 16,18.5A2.5,2.5 0 0,0 18.5,21A2.5,2.5 0 0,0 21,18.5A2.5,2.5 0 0,0 18.5,16M3.91,17.25L5.04,17.91C5.17,17.81 5.33,17.75 5.5,17.75A0.75,0.75 0 0,1 6.25,18.5L6.24,18.6L7.37,19.25L7.09,19.75L5.96,19.09C5.83,19.19 5.67,19.25 5.5,19.25A0.75,0.75 0 0,1 4.75,18.5L4.76,18.4L3.63,17.75L3.91,17.25M3.63,6.25L4.76,5.6L4.75,5.5A0.75,0.75 0 0,1 5.5,4.75C5.67,4.75 5.83,4.81 5.96,4.91L7.09,4.25L7.37,4.75L6.24,5.4L6.25,5.5A0.75,0.75 0 0,1 5.5,6.25C5.33,6.25 5.17,6.19 5.04,6.09L3.91,6.75L3.63,6.25M16.91,4.25L18.04,4.91C18.17,4.81 18.33,4.75 18.5,4.75A0.75,0.75 0 0,1 19.25,5.5L19.24,5.6L20.37,6.25L20.09,6.75L18.96,6.09C18.83,6.19 18.67,6.25 18.5,6.25A0.75,0.75 0 0,1 17.75,5.5L17.76,5.4L16.63,4.75L16.91,4.25M16.63,19.25L17.75,18.5A0.75,0.75 0 0,1 18.5,17.75C18.67,17.75 18.83,17.81 18.96,17.91L20.09,17.25L20.37,17.75L19.25,18.5A0.75,0.75 0 0,1 18.5,19.25C18.33,19.25 18.17,19.19 18.04,19.09L16.91,19.75L16.63,19.25Z" /></g><g id="quality-high"><path d="M14.5,13.5H16.5V10.5H14.5M18,14A1,1 0 0,1 17,15H16.25V16.5H14.75V15H14A1,1 0 0,1 13,14V10A1,1 0 0,1 14,9H17A1,1 0 0,1 18,10M11,15H9.5V13H7.5V15H6V9H7.5V11.5H9.5V9H11M19,4H5C3.89,4 3,4.89 3,6V18A2,2 0 0,0 5,20H19A2,2 0 0,0 21,18V6C21,4.89 20.1,4 19,4Z" /></g><g id="quicktime"><path d="M12,3A9,9 0 0,1 21,12C21,13.76 20.5,15.4 19.62,16.79L21,18.17V20A1,1 0 0,1 20,21H18.18L16.79,19.62C15.41,20.5 13.76,21 12,21A9,9 0 0,1 3,12A9,9 0 0,1 12,3M12,7A5,5 0 0,0 7,12A5,5 0 0,0 12,17C12.65,17 13.26,16.88 13.83,16.65L10.95,13.77C10.17,13 10.17,11.72 10.95,10.94C11.73,10.16 13,10.16 13.78,10.94L16.66,13.82C16.88,13.26 17,12.64 17,12A5,5 0 0,0 12,7Z" /></g><g id="radar"><path d="M19.07,4.93L17.66,6.34C19.1,7.79 20,9.79 20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12C4,7.92 7.05,4.56 11,4.07V6.09C8.16,6.57 6,9.03 6,12A6,6 0 0,0 12,18A6,6 0 0,0 18,12C18,10.34 17.33,8.84 16.24,7.76L14.83,9.17C15.55,9.9 16,10.9 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12C8,10.14 9.28,8.59 11,8.14V10.28C10.4,10.63 10,11.26 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12C14,11.26 13.6,10.62 13,10.28V2H12A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,9.24 20.88,6.74 19.07,4.93Z" /></g><g id="radiator"><path d="M7.95,3L6.53,5.19L7.95,7.4H7.94L5.95,10.5L4.22,9.6L5.64,7.39L4.22,5.19L6.22,2.09L7.95,3M13.95,2.89L12.53,5.1L13.95,7.3L13.94,7.31L11.95,10.4L10.22,9.5L11.64,7.3L10.22,5.1L12.22,2L13.95,2.89M20,2.89L18.56,5.1L20,7.3V7.31L18,10.4L16.25,9.5L17.67,7.3L16.25,5.1L18.25,2L20,2.89M2,22V14A2,2 0 0,1 4,12H20A2,2 0 0,1 22,14V22H20V20H4V22H2M6,14A1,1 0 0,0 5,15V17A1,1 0 0,0 6,18A1,1 0 0,0 7,17V15A1,1 0 0,0 6,14M10,14A1,1 0 0,0 9,15V17A1,1 0 0,0 10,18A1,1 0 0,0 11,17V15A1,1 0 0,0 10,14M14,14A1,1 0 0,0 13,15V17A1,1 0 0,0 14,18A1,1 0 0,0 15,17V15A1,1 0 0,0 14,14M18,14A1,1 0 0,0 17,15V17A1,1 0 0,0 18,18A1,1 0 0,0 19,17V15A1,1 0 0,0 18,14Z" /></g><g id="radio"><path d="M20,6A2,2 0 0,1 22,8V20A2,2 0 0,1 20,22H4A2,2 0 0,1 2,20V8C2,7.15 2.53,6.42 3.28,6.13L15.71,1L16.47,2.83L8.83,6H20M20,8H4V12H16V10H18V12H20V8M7,14A3,3 0 0,0 4,17A3,3 0 0,0 7,20A3,3 0 0,0 10,17A3,3 0 0,0 7,14Z" /></g><g id="radio-handheld"><path d="M9,2A1,1 0 0,0 8,3C8,8.67 8,14.33 8,20C8,21.11 8.89,22 10,22H15C16.11,22 17,21.11 17,20V9C17,7.89 16.11,7 15,7H10V3A1,1 0 0,0 9,2M10,9H15V13H10V9Z" /></g><g id="radio-tower"><path d="M12,10A2,2 0 0,1 14,12C14,12.5 13.82,12.94 13.53,13.29L16.7,22H14.57L12,14.93L9.43,22H7.3L10.47,13.29C10.18,12.94 10,12.5 10,12A2,2 0 0,1 12,10M12,8A4,4 0 0,0 8,12C8,12.5 8.1,13 8.28,13.46L7.4,15.86C6.53,14.81 6,13.47 6,12A6,6 0 0,1 12,6A6,6 0 0,1 18,12C18,13.47 17.47,14.81 16.6,15.86L15.72,13.46C15.9,13 16,12.5 16,12A4,4 0 0,0 12,8M12,4A8,8 0 0,0 4,12C4,14.36 5,16.5 6.64,17.94L5.92,19.94C3.54,18.11 2,15.23 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12C22,15.23 20.46,18.11 18.08,19.94L17.36,17.94C19,16.5 20,14.36 20,12A8,8 0 0,0 12,4Z" /></g><g id="radioactive"><path d="M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,22C10.05,22 8.22,21.44 6.69,20.47L10,15.47C10.6,15.81 11.28,16 12,16C12.72,16 13.4,15.81 14,15.47L17.31,20.47C15.78,21.44 13.95,22 12,22M2,12C2,7.86 4.5,4.3 8.11,2.78L10.34,8.36C8.96,9 8,10.38 8,12H2M16,12C16,10.38 15.04,9 13.66,8.36L15.89,2.78C19.5,4.3 22,7.86 22,12H16Z" /></g><g id="radiobox-blank"><path d="M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" /></g><g id="radiobox-marked"><path d="M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,7A5,5 0 0,0 7,12A5,5 0 0,0 12,17A5,5 0 0,0 17,12A5,5 0 0,0 12,7Z" /></g><g id="raspberrypi"><path d="M20,8H22V10H20V8M4,5H20A2,2 0 0,1 22,7H19V9H5V13H8V16H19V17H22A2,2 0 0,1 20,19H16V20H14V19H11V20H7V19H4A2,2 0 0,1 2,17V7A2,2 0 0,1 4,5M19,15H9V10H19V11H22V13H19V15M13,12V14H15V12H13M5,6V8H6V6H5M7,6V8H8V6H7M9,6V8H10V6H9M11,6V8H12V6H11M13,6V8H14V6H13M15,6V8H16V6H15M20,14H22V16H20V14Z" /></g><g id="ray-end"><path d="M20,9C18.69,9 17.58,9.83 17.17,11H2V13H17.17C17.58,14.17 18.69,15 20,15A3,3 0 0,0 23,12A3,3 0 0,0 20,9Z" /></g><g id="ray-end-arrow"><path d="M1,12L5,16V13H17.17C17.58,14.17 18.69,15 20,15A3,3 0 0,0 23,12A3,3 0 0,0 20,9C18.69,9 17.58,9.83 17.17,11H5V8L1,12Z" /></g><g id="ray-start"><path d="M4,9C5.31,9 6.42,9.83 6.83,11H22V13H6.83C6.42,14.17 5.31,15 4,15A3,3 0 0,1 1,12A3,3 0 0,1 4,9Z" /></g><g id="ray-start-arrow"><path d="M23,12L19,16V13H6.83C6.42,14.17 5.31,15 4,15A3,3 0 0,1 1,12A3,3 0 0,1 4,9C5.31,9 6.42,9.83 6.83,11H19V8L23,12Z" /></g><g id="ray-start-end"><path d="M4,9C5.31,9 6.42,9.83 6.83,11H17.17C17.58,9.83 18.69,9 20,9A3,3 0 0,1 23,12A3,3 0 0,1 20,15C18.69,15 17.58,14.17 17.17,13H6.83C6.42,14.17 5.31,15 4,15A3,3 0 0,1 1,12A3,3 0 0,1 4,9Z" /></g><g id="ray-vertex"><path d="M2,11H9.17C9.58,9.83 10.69,9 12,9C13.31,9 14.42,9.83 14.83,11H22V13H14.83C14.42,14.17 13.31,15 12,15C10.69,15 9.58,14.17 9.17,13H2V11Z" /></g><g id="rdio"><path d="M19.29,10.84C19.35,11.22 19.38,11.61 19.38,12C19.38,16.61 15.5,20.35 10.68,20.35C5.87,20.35 2,16.61 2,12C2,7.39 5.87,3.65 10.68,3.65C11.62,3.65 12.53,3.79 13.38,4.06V9.11C13.38,9.11 10.79,7.69 8.47,9.35C6.15,11 6.59,12.76 6.59,12.76C6.59,12.76 6.7,15.5 9.97,15.5C13.62,15.5 14.66,12.19 14.66,12.19V4.58C15.36,4.93 16,5.36 16.65,5.85C18.2,6.82 19.82,7.44 21.67,7.39C21.67,7.39 22,7.31 22,8C22,8.4 21.88,8.83 21.5,9.25C21.5,9.25 20.78,10.33 19.29,10.84Z" /></g><g id="read"><path d="M21.59,11.59L23,13L13.5,22.5L8.42,17.41L9.83,16L13.5,19.68L21.59,11.59M4,16V3H6L9,3A4,4 0 0,1 13,7C13,8.54 12.13,9.88 10.85,10.55L14,16H12L9.11,11H6V16H4M6,9H9A2,2 0 0,0 11,7A2,2 0 0,0 9,5H6V9Z" /></g><g id="readability"><path d="M12,4C15.15,4 17.81,6.38 18.69,9.65C18,10.15 17.58,10.93 17.5,11.81L17.32,13.91C15.55,13 13.78,12.17 12,12.17C10.23,12.17 8.45,13 6.68,13.91L6.5,11.77C6.42,10.89 6,10.12 5.32,9.61C6.21,6.36 8.86,4 12,4M17.05,17H6.95L6.73,14.47C8.5,13.59 10.24,12.75 12,12.75C13.76,12.75 15.5,13.59 17.28,14.47L17.05,17M5,19V18L3.72,14.5H3.5A2.5,2.5 0 0,1 1,12A2.5,2.5 0 0,1 3.5,9.5C4.82,9.5 5.89,10.5 6,11.81L6.5,18V19H5M19,19H17.5V18L18,11.81C18.11,10.5 19.18,9.5 20.5,9.5A2.5,2.5 0 0,1 23,12A2.5,2.5 0 0,1 20.5,14.5H20.28L19,18V19Z" /></g><g id="receipt"><path d="M3,22L4.5,20.5L6,22L7.5,20.5L9,22L10.5,20.5L12,22L13.5,20.5L15,22L16.5,20.5L18,22L19.5,20.5L21,22V2L19.5,3.5L18,2L16.5,3.5L15,2L13.5,3.5L12,2L10.5,3.5L9,2L7.5,3.5L6,2L4.5,3.5L3,2M18,9H6V7H18M18,13H6V11H18M18,17H6V15H18V17Z" /></g><g id="record"><path d="M19,12C19,15.86 15.86,19 12,19C8.14,19 5,15.86 5,12C5,8.14 8.14,5 12,5C15.86,5 19,8.14 19,12Z" /></g><g id="record-rec"><path d="M12.5,5A7.5,7.5 0 0,0 5,12.5A7.5,7.5 0 0,0 12.5,20A7.5,7.5 0 0,0 20,12.5A7.5,7.5 0 0,0 12.5,5M7,10H9A1,1 0 0,1 10,11V12C10,12.5 9.62,12.9 9.14,12.97L10.31,15H9.15L8,13V15H7M12,10H14V11H12V12H14V13H12V14H14V15H12A1,1 0 0,1 11,14V11A1,1 0 0,1 12,10M16,10H18V11H16V14H18V15H16A1,1 0 0,1 15,14V11A1,1 0 0,1 16,10M8,11V12H9V11" /></g><g id="recycle"><path d="M21.82,15.42L19.32,19.75C18.83,20.61 17.92,21.06 17,21H15V23L12.5,18.5L15,14V16H17.82L15.6,12.15L19.93,9.65L21.73,12.77C22.25,13.54 22.32,14.57 21.82,15.42M9.21,3.06H14.21C15.19,3.06 16.04,3.63 16.45,4.45L17.45,6.19L19.18,5.19L16.54,9.6L11.39,9.69L13.12,8.69L11.71,6.24L9.5,10.09L5.16,7.59L6.96,4.47C7.37,3.64 8.22,3.06 9.21,3.06M5.05,19.76L2.55,15.43C2.06,14.58 2.13,13.56 2.64,12.79L3.64,11.06L1.91,10.06L7.05,10.14L9.7,14.56L7.97,13.56L6.56,16H11V21H7.4C6.47,21.07 5.55,20.61 5.05,19.76Z" /></g><g id="reddit"><path d="M22,11.5C22,10.1 20.9,9 19.5,9C18.9,9 18.3,9.2 17.9,9.6C16.4,8.7 14.6,8.1 12.5,8L13.6,4L17,5A2,2 0 0,0 19,7A2,2 0 0,0 21,5A2,2 0 0,0 19,3C18.3,3 17.6,3.4 17.3,4L13.3,3C13,2.9 12.8,3.1 12.7,3.4L11.5,8C9.5,8.1 7.6,8.7 6.1,9.6C5.7,9.2 5.1,9 4.5,9C3.1,9 2,10.1 2,11.5C2,12.4 2.4,13.1 3.1,13.6L3,14.5C3,18.1 7,21 12,21C17,21 21,18.1 21,14.5L20.9,13.6C21.6,13.1 22,12.4 22,11.5M9,11.8C9.7,11.8 10.2,12.4 10.2,13C10.2,13.6 9.7,14.2 9,14.2C8.3,14.2 7.8,13.7 7.8,13C7.8,12.3 8.3,11.8 9,11.8M15.8,17.2C14,18.3 10,18.3 8.2,17.2C8,17 7.9,16.7 8.1,16.5C8.3,16.3 8.6,16.2 8.8,16.4C10,17.3 14,17.3 15.2,16.4C15.4,16.2 15.7,16.3 15.9,16.5C16.1,16.7 16,17 15.8,17.2M15,14.2C14.3,14.2 13.8,13.6 13.8,13C13.8,12.3 14.4,11.8 15,11.8C15.7,11.8 16.2,12.4 16.2,13C16.2,13.7 15.7,14.2 15,14.2Z" /></g><g id="redo"><path d="M18.4,10.6C16.55,9 14.15,8 11.5,8C6.85,8 2.92,11.03 1.54,15.22L3.9,16C4.95,12.81 7.95,10.5 11.5,10.5C13.45,10.5 15.23,11.22 16.62,12.38L13,16H22V7L18.4,10.6Z" /></g><g id="redo-variant"><path d="M10.5,7A6.5,6.5 0 0,0 4,13.5A6.5,6.5 0 0,0 10.5,20H14V18H10.5C8,18 6,16 6,13.5C6,11 8,9 10.5,9H16.17L13.09,12.09L14.5,13.5L20,8L14.5,2.5L13.08,3.91L16.17,7H10.5M18,18H16V20H18V18Z" /></g><g id="refresh"><path d="M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z" /></g><g id="regex"><path d="M16,16.92C15.67,16.97 15.34,17 15,17C14.66,17 14.33,16.97 14,16.92V13.41L11.5,15.89C11,15.5 10.5,15 10.11,14.5L12.59,12H9.08C9.03,11.67 9,11.34 9,11C9,10.66 9.03,10.33 9.08,10H12.59L10.11,7.5C10.3,7.25 10.5,7 10.76,6.76V6.76C11,6.5 11.25,6.3 11.5,6.11L14,8.59V5.08C14.33,5.03 14.66,5 15,5C15.34,5 15.67,5.03 16,5.08V8.59L18.5,6.11C19,6.5 19.5,7 19.89,7.5L17.41,10H20.92C20.97,10.33 21,10.66 21,11C21,11.34 20.97,11.67 20.92,12H17.41L19.89,14.5C19.7,14.75 19.5,15 19.24,15.24V15.24C19,15.5 18.75,15.7 18.5,15.89L16,13.41V16.92H16V16.92M5,19A2,2 0 0,1 7,17A2,2 0 0,1 9,19A2,2 0 0,1 7,21A2,2 0 0,1 5,19H5Z" /></g><g id="relative-scale"><path d="M20,18H4V6H20M20,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V6C22,4.89 21.1,4 20,4M12,10H10V12H12M8,10H6V12H8M16,14H14V16H16M16,10H14V12H16V10Z" /></g><g id="reload"><path d="M19,12H22.32L17.37,16.95L12.42,12H16.97C17,10.46 16.42,8.93 15.24,7.75C12.9,5.41 9.1,5.41 6.76,7.75C4.42,10.09 4.42,13.9 6.76,16.24C8.6,18.08 11.36,18.47 13.58,17.41L15.05,18.88C12,20.69 8,20.29 5.34,17.65C2.22,14.53 2.23,9.47 5.35,6.35C8.5,3.22 13.53,3.21 16.66,6.34C18.22,7.9 19,9.95 19,12Z" /></g><g id="remote"><path d="M12,0C8.96,0 6.21,1.23 4.22,3.22L5.63,4.63C7.26,3 9.5,2 12,2C14.5,2 16.74,3 18.36,4.64L19.77,3.23C17.79,1.23 15.04,0 12,0M7.05,6.05L8.46,7.46C9.37,6.56 10.62,6 12,6C13.38,6 14.63,6.56 15.54,7.46L16.95,6.05C15.68,4.78 13.93,4 12,4C10.07,4 8.32,4.78 7.05,6.05M12,15A2,2 0 0,1 10,13A2,2 0 0,1 12,11A2,2 0 0,1 14,13A2,2 0 0,1 12,15M15,9H9A1,1 0 0,0 8,10V22A1,1 0 0,0 9,23H15A1,1 0 0,0 16,22V10A1,1 0 0,0 15,9Z" /></g><g id="rename-box"><path d="M18,17H10.5L12.5,15H18M6,17V14.5L13.88,6.65C14.07,6.45 14.39,6.45 14.59,6.65L16.35,8.41C16.55,8.61 16.55,8.92 16.35,9.12L8.47,17M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z" /></g><g id="reorder-horizontal"><path d="M3,15H21V13H3V15M3,19H21V17H3V19M3,11H21V9H3V11M3,5V7H21V5H3Z" /></g><g id="reorder-vertical"><path d="M9,3V21H11V3H9M5,3V21H7V3H5M13,3V21H15V3H13M19,3H17V21H19V3Z" /></g><g id="repeat"><path d="M17,17H7V14L3,18L7,22V19H19V13H17M7,7H17V10L21,6L17,2V5H5V11H7V7Z" /></g><g id="repeat-off"><path d="M2,5.27L3.28,4L20,20.72L18.73,22L15.73,19H7V22L3,18L7,14V17H13.73L7,10.27V11H5V8.27L2,5.27M17,13H19V17.18L17,15.18V13M17,5V2L21,6L17,10V7H8.82L6.82,5H17Z" /></g><g id="repeat-once"><path d="M13,15V9H12L10,10V11H11.5V15M17,17H7V14L3,18L7,22V19H19V13H17M7,7H17V10L21,6L17,2V5H5V11H7V7Z" /></g><g id="replay"><path d="M12,5V1L7,6L12,11V7A6,6 0 0,1 18,13A6,6 0 0,1 12,19A6,6 0 0,1 6,13H4A8,8 0 0,0 12,21A8,8 0 0,0 20,13A8,8 0 0,0 12,5Z" /></g><g id="reply"><path d="M10,9V5L3,12L10,19V14.9C15,14.9 18.5,16.5 21,20C20,15 17,10 10,9Z" /></g><g id="reply-all"><path d="M13,9V5L6,12L13,19V14.9C18,14.9 21.5,16.5 24,20C23,15 20,10 13,9M7,8V5L0,12L7,19V16L3,12L7,8Z" /></g><g id="reproduction"><path d="M12.72,13.15L13.62,12.26C13.6,11 14.31,9.44 15.62,8.14C17.57,6.18 20.11,5.55 21.28,6.72C22.45,7.89 21.82,10.43 19.86,12.38C18.56,13.69 17,14.4 15.74,14.38L14.85,15.28C14.5,15.61 14,15.66 13.6,15.41C12.76,15.71 12,16.08 11.56,16.8C11.03,17.68 11.03,19.1 10.47,19.95C9.91,20.81 8.79,21.1 7.61,21.1C6.43,21.1 5,21 3.95,19.5L6.43,19.92C7,20 8.5,19.39 9.05,18.54C9.61,17.68 9.61,16.27 10.14,15.38C10.61,14.6 11.5,14.23 12.43,13.91C12.42,13.64 12.5,13.36 12.72,13.15M7,2A5,5 0 0,1 12,7A5,5 0 0,1 7,12A5,5 0 0,1 2,7A5,5 0 0,1 7,2M7,4A3,3 0 0,0 4,7A3,3 0 0,0 7,10A3,3 0 0,0 10,7A3,3 0 0,0 7,4Z" /></g><g id="resize-bottom-right"><path d="M22,22H20V20H22V22M22,18H20V16H22V18M18,22H16V20H18V22M18,18H16V16H18V18M14,22H12V20H14V22M22,14H20V12H22V14Z" /></g><g id="responsive"><path d="M4,6V16H9V12A2,2 0 0,1 11,10H16A2,2 0 0,1 18,12V16H20V6H4M0,20V18H4A2,2 0 0,1 2,16V6A2,2 0 0,1 4,4H20A2,2 0 0,1 22,6V16A2,2 0 0,1 20,18H24V20H18V20C18,21.11 17.1,22 16,22H11A2,2 0 0,1 9,20H9L0,20M11.5,20A0.5,0.5 0 0,0 11,20.5A0.5,0.5 0 0,0 11.5,21A0.5,0.5 0 0,0 12,20.5A0.5,0.5 0 0,0 11.5,20M15.5,20A0.5,0.5 0 0,0 15,20.5A0.5,0.5 0 0,0 15.5,21A0.5,0.5 0 0,0 16,20.5A0.5,0.5 0 0,0 15.5,20M13,20V21H14V20H13M11,12V19H16V12H11Z" /></g><g id="restore"><path d="M13,3A9,9 0 0,0 4,12H1L4.89,15.89L4.96,16.03L9,12H6A7,7 0 0,1 13,5A7,7 0 0,1 20,12A7,7 0 0,1 13,19C11.07,19 9.32,18.21 8.06,16.94L6.64,18.36C8.27,20 10.5,21 13,21A9,9 0 0,0 22,12A9,9 0 0,0 13,3M12,8V13L16.28,15.54L17,14.33L13.5,12.25V8H12Z" /></g><g id="rewind"><path d="M11.5,12L20,18V6M11,18V6L2.5,12L11,18Z" /></g><g id="ribbon"><path d="M13.41,19.31L16.59,22.5L18,21.07L14.83,17.9M15.54,11.53H15.53L12,15.07L8.47,11.53H8.46V11.53C7.56,10.63 7,9.38 7,8A5,5 0 0,1 12,3A5,5 0 0,1 17,8C17,9.38 16.44,10.63 15.54,11.53M16.9,13C18.2,11.73 19,9.96 19,8A7,7 0 0,0 12,1A7,7 0 0,0 5,8C5,9.96 5.81,11.73 7.1,13V13L10.59,16.5L6,21.07L7.41,22.5L16.9,13Z" /></g><g id="road"><path d="M11,16H13V20H11M11,10H13V14H11M11,4H13V8H11M4,22H20V2H4V22Z" /></g><g id="road-variant"><path d="M18.1,4.8C18,4.3 17.6,4 17.1,4H13L13.2,7H10.8L11,4H6.8C6.3,4 5.9,4.4 5.8,4.8L3.1,18.8C3,19.4 3.5,20 4.1,20H10L10.3,15H13.7L14,20H19.8C20.4,20 20.9,19.4 20.8,18.8L18.1,4.8M10.4,13L10.6,9H13.2L13.4,13H10.4Z" /></g><g id="robot"><path d="M12,2A2,2 0 0,1 14,4C14,4.74 13.6,5.39 13,5.73V7H14A7,7 0 0,1 21,14H22A1,1 0 0,1 23,15V18A1,1 0 0,1 22,19H21V20A2,2 0 0,1 19,22H5A2,2 0 0,1 3,20V19H2A1,1 0 0,1 1,18V15A1,1 0 0,1 2,14H3A7,7 0 0,1 10,7H11V5.73C10.4,5.39 10,4.74 10,4A2,2 0 0,1 12,2M7.5,13A2.5,2.5 0 0,0 5,15.5A2.5,2.5 0 0,0 7.5,18A2.5,2.5 0 0,0 10,15.5A2.5,2.5 0 0,0 7.5,13M16.5,13A2.5,2.5 0 0,0 14,15.5A2.5,2.5 0 0,0 16.5,18A2.5,2.5 0 0,0 19,15.5A2.5,2.5 0 0,0 16.5,13Z" /></g><g id="rocket"><path d="M2.81,14.12L5.64,11.29L8.17,10.79C11.39,6.41 17.55,4.22 19.78,4.22C19.78,6.45 17.59,12.61 13.21,15.83L12.71,18.36L9.88,21.19L9.17,17.66C7.76,17.66 7.76,17.66 7.05,16.95C6.34,16.24 6.34,16.24 6.34,14.83L2.81,14.12M5.64,16.95L7.05,18.36L4.39,21.03H2.97V19.61L5.64,16.95M4.22,15.54L5.46,15.71L3,18.16V16.74L4.22,15.54M8.29,18.54L8.46,19.78L7.26,21H5.84L8.29,18.54M13,9.5A1.5,1.5 0 0,0 11.5,11A1.5,1.5 0 0,0 13,12.5A1.5,1.5 0 0,0 14.5,11A1.5,1.5 0 0,0 13,9.5Z" /></g><g id="rotate-3d"><path d="M12,5C16.97,5 21,7.69 21,11C21,12.68 19.96,14.2 18.29,15.29C19.36,14.42 20,13.32 20,12.13C20,9.29 16.42,7 12,7V10L8,6L12,2V5M12,19C7.03,19 3,16.31 3,13C3,11.32 4.04,9.8 5.71,8.71C4.64,9.58 4,10.68 4,11.88C4,14.71 7.58,17 12,17V14L16,18L12,22V19Z" /></g><g id="rotate-90"><path d="M7.34,6.41L0.86,12.9L7.35,19.38L13.84,12.9L7.34,6.41M3.69,12.9L7.35,9.24L11,12.9L7.34,16.56L3.69,12.9M19.36,6.64C17.61,4.88 15.3,4 13,4V0.76L8.76,5L13,9.24V6C14.79,6 16.58,6.68 17.95,8.05C20.68,10.78 20.68,15.22 17.95,17.95C16.58,19.32 14.79,20 13,20C12.03,20 11.06,19.79 10.16,19.39L8.67,20.88C10,21.62 11.5,22 13,22C15.3,22 17.61,21.12 19.36,19.36C22.88,15.85 22.88,10.15 19.36,6.64Z" /></g><g id="rotate-left"><path d="M13,4.07V1L8.45,5.55L13,10V6.09C15.84,6.57 18,9.03 18,12C18,14.97 15.84,17.43 13,17.91V19.93C16.95,19.44 20,16.08 20,12C20,7.92 16.95,4.56 13,4.07M7.1,18.32C8.26,19.22 9.61,19.76 11,19.93V17.9C10.13,17.75 9.29,17.41 8.54,16.87L7.1,18.32M6.09,13H4.07C4.24,14.39 4.79,15.73 5.69,16.89L7.1,15.47C6.58,14.72 6.23,13.88 6.09,13M7.11,8.53L5.7,7.11C4.8,8.27 4.24,9.61 4.07,11H6.09C6.23,10.13 6.58,9.28 7.11,8.53Z" /></g><g id="rotate-left-variant"><path d="M4,2H7A2,2 0 0,1 9,4V20A2,2 0 0,1 7,22H4A2,2 0 0,1 2,20V4A2,2 0 0,1 4,2M20,15A2,2 0 0,1 22,17V20A2,2 0 0,1 20,22H11V15H20M14,4A8,8 0 0,1 22,12L21.94,13H19.92L20,12A6,6 0 0,0 14,6V9L10,5L14,1V4Z" /></g><g id="rotate-right"><path d="M16.89,15.5L18.31,16.89C19.21,15.73 19.76,14.39 19.93,13H17.91C17.77,13.87 17.43,14.72 16.89,15.5M13,17.9V19.92C14.39,19.75 15.74,19.21 16.9,18.31L15.46,16.87C14.71,17.41 13.87,17.76 13,17.9M19.93,11C19.76,9.61 19.21,8.27 18.31,7.11L16.89,8.53C17.43,9.28 17.77,10.13 17.91,11M15.55,5.55L11,1V4.07C7.06,4.56 4,7.92 4,12C4,16.08 7.05,19.44 11,19.93V17.91C8.16,17.43 6,14.97 6,12C6,9.03 8.16,6.57 11,6.09V10L15.55,5.55Z" /></g><g id="rotate-right-variant"><path d="M10,4V1L14,5L10,9V6A6,6 0 0,0 4,12L4.08,13H2.06L2,12A8,8 0 0,1 10,4M17,2H20A2,2 0 0,1 22,4V20A2,2 0 0,1 20,22H17A2,2 0 0,1 15,20V4A2,2 0 0,1 17,2M4,15H13V22H4A2,2 0 0,1 2,20V17A2,2 0 0,1 4,15Z" /></g><g id="rounded-corner"><path d="M19,19H21V21H19V19M19,17H21V15H19V17M3,13H5V11H3V13M3,17H5V15H3V17M3,9H5V7H3V9M3,5H5V3H3V5M7,5H9V3H7V5M15,21H17V19H15V21M11,21H13V19H11V21M15,21H17V19H15V21M7,21H9V19H7V21M3,21H5V19H3V21M21,8A5,5 0 0,0 16,3H11V5H16A3,3 0 0,1 19,8V13H21V8Z" /></g><g id="router-wireless"><path d="M4,13H20A1,1 0 0,1 21,14V18A1,1 0 0,1 20,19H4A1,1 0 0,1 3,18V14A1,1 0 0,1 4,13M9,17H10V15H9V17M5,15V17H7V15H5M19,6.93L17.6,8.34C16.15,6.89 14.15,6 11.93,6C9.72,6 7.72,6.89 6.27,8.34L4.87,6.93C6.68,5.12 9.18,4 11.93,4C14.69,4 17.19,5.12 19,6.93M16.17,9.76L14.77,11.17C14.04,10.45 13.04,10 11.93,10C10.82,10 9.82,10.45 9.1,11.17L7.7,9.76C8.78,8.67 10.28,8 11.93,8C13.58,8 15.08,8.67 16.17,9.76Z" /></g><g id="routes"><path d="M11,10H5L3,8L5,6H11V3L12,2L13,3V4H19L21,6L19,8H13V10H19L21,12L19,14H13V20A2,2 0 0,1 15,22H9A2,2 0 0,1 11,20V10Z" /></g><g id="rowing"><path d="M8.5,14.5L4,19L5.5,20.5L9,17H11L8.5,14.5M15,1A2,2 0 0,0 13,3A2,2 0 0,0 15,5A2,2 0 0,0 17,3A2,2 0 0,0 15,1M21,21L18,24L15,21V19.5L7.91,12.41C7.6,12.46 7.3,12.5 7,12.5V10.32C8.66,10.35 10.61,9.45 11.67,8.28L13.07,6.73C13.26,6.5 13.5,6.35 13.76,6.23C14.05,6.09 14.38,6 14.72,6H14.75C16,6 17,7 17,8.26V14C17,14.85 16.65,15.62 16.08,16.17L12.5,12.59V10.32C11.87,10.84 11.07,11.34 10.21,11.71L16.5,18H18L21,21Z" /></g><g id="rss"><path d="M6.18,15.64A2.18,2.18 0 0,1 8.36,17.82C8.36,19 7.38,20 6.18,20C5,20 4,19 4,17.82A2.18,2.18 0 0,1 6.18,15.64M4,4.44A15.56,15.56 0 0,1 19.56,20H16.73A12.73,12.73 0 0,0 4,7.27V4.44M4,10.1A9.9,9.9 0 0,1 13.9,20H11.07A7.07,7.07 0 0,0 4,12.93V10.1Z" /></g><g id="rss-box"><path d="M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M7.5,15A1.5,1.5 0 0,0 6,16.5A1.5,1.5 0 0,0 7.5,18A1.5,1.5 0 0,0 9,16.5A1.5,1.5 0 0,0 7.5,15M6,10V12A6,6 0 0,1 12,18H14A8,8 0 0,0 6,10M6,6V8A10,10 0 0,1 16,18H18A12,12 0 0,0 6,6Z" /></g><g id="ruler"><path d="M1.39,18.36L3.16,16.6L4.58,18L5.64,16.95L4.22,15.54L5.64,14.12L8.11,16.6L9.17,15.54L6.7,13.06L8.11,11.65L9.53,13.06L10.59,12L9.17,10.59L10.59,9.17L13.06,11.65L14.12,10.59L11.65,8.11L13.06,6.7L14.47,8.11L15.54,7.05L14.12,5.64L15.54,4.22L18,6.7L19.07,5.64L16.6,3.16L18.36,1.39L22.61,5.64L5.64,22.61L1.39,18.36Z" /></g><g id="run"><path d="M17.12,10L16.04,8.18L15.31,11.05L17.8,15.59V22H16V17L13.67,13.89L12.07,18.4L7.25,20.5L6.2,19L10.39,16.53L12.91,6.67L10.8,7.33V11H9V5.8L14.42,4.11L14.92,4.03C15.54,4.03 16.08,4.37 16.38,4.87L18.38,8.2H22V10H17.12M17,3.8C16,3.8 15.2,3 15.2,2C15.2,1 16,0.2 17,0.2C18,0.2 18.8,1 18.8,2C18.8,3 18,3.8 17,3.8M7,9V11H4A1,1 0 0,1 3,10A1,1 0 0,1 4,9H7M9.25,13L8.75,15H5A1,1 0 0,1 4,14A1,1 0 0,1 5,13H9.25M7,5V7H3A1,1 0 0,1 2,6A1,1 0 0,1 3,5H7Z" /></g><g id="sale"><path d="M18.65,2.85L19.26,6.71L22.77,8.5L21,12L22.78,15.5L19.24,17.29L18.63,21.15L14.74,20.54L11.97,23.3L9.19,20.5L5.33,21.14L4.71,17.25L1.22,15.47L3,11.97L1.23,8.5L4.74,6.69L5.35,2.86L9.22,3.5L12,0.69L14.77,3.46L18.65,2.85M9.5,7A1.5,1.5 0 0,0 8,8.5A1.5,1.5 0 0,0 9.5,10A1.5,1.5 0 0,0 11,8.5A1.5,1.5 0 0,0 9.5,7M14.5,14A1.5,1.5 0 0,0 13,15.5A1.5,1.5 0 0,0 14.5,17A1.5,1.5 0 0,0 16,15.5A1.5,1.5 0 0,0 14.5,14M8.41,17L17,8.41L15.59,7L7,15.59L8.41,17Z" /></g><g id="satellite"><path d="M5,18L8.5,13.5L11,16.5L14.5,12L19,18M5,12V10A5,5 0 0,0 10,5H12A7,7 0 0,1 5,12M5,5H8A3,3 0 0,1 5,8M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z" /></g><g id="satellite-variant"><path d="M11.62,1L17.28,6.67L15.16,8.79L13.04,6.67L11.62,8.09L13.95,10.41L12.79,11.58L13.24,12.04C14.17,11.61 15.31,11.77 16.07,12.54L12.54,16.07C11.77,15.31 11.61,14.17 12.04,13.24L11.58,12.79L10.41,13.95L8.09,11.62L6.67,13.04L8.79,15.16L6.67,17.28L1,11.62L3.14,9.5L5.26,11.62L6.67,10.21L3.84,7.38C3.06,6.6 3.06,5.33 3.84,4.55L4.55,3.84C5.33,3.06 6.6,3.06 7.38,3.84L10.21,6.67L11.62,5.26L9.5,3.14L11.62,1M18,14A4,4 0 0,1 14,18V16A2,2 0 0,0 16,14H18M22,14A8,8 0 0,1 14,22V20A6,6 0 0,0 20,14H22Z" /></g><g id="saxophone"><path d="M4,2A1,1 0 0,0 3,3A1,1 0 0,0 4,4A3,3 0 0,1 7,7V8.66L7,15.5C7,19.1 9.9,22 13.5,22C17.1,22 20,19.1 20,15.5V13A1,1 0 0,0 21,12A1,1 0 0,0 20,11H14A1,1 0 0,0 13,12A1,1 0 0,0 14,13V15A1,1 0 0,1 13,16A1,1 0 0,1 12,15V11A1,1 0 0,0 13,10A1,1 0 0,0 12,9V8A1,1 0 0,0 13,7A1,1 0 0,0 12,6V5.5A3.5,3.5 0 0,0 8.5,2H4Z" /></g><g id="scale"><path d="M8.46,15.06L7.05,16.47L5.68,15.1C4.82,16.21 4.24,17.54 4.06,19H6V21H2V20C2,15.16 5.44,11.13 10,10.2V8.2L2,5V3H22V5L14,8.2V10.2C18.56,11.13 22,15.16 22,20V21H18V19H19.94C19.76,17.54 19.18,16.21 18.32,15.1L16.95,16.47L15.54,15.06L16.91,13.68C15.8,12.82 14.46,12.24 13,12.06V14H11V12.06C9.54,12.24 8.2,12.82 7.09,13.68L8.46,15.06M12,18A2,2 0 0,1 14,20A2,2 0 0,1 12,22C11.68,22 11.38,21.93 11.12,21.79L7.27,20L11.12,18.21C11.38,18.07 11.68,18 12,18Z" /></g><g id="scale-balance"><path d="M12,3C10.73,3 9.6,3.8 9.18,5H3V7H4.95L2,14C1.53,16 3,17 5.5,17C8,17 9.56,16 9,14L6.05,7H9.17C9.5,7.85 10.15,8.5 11,8.83V20H2V22H22V20H13V8.82C13.85,8.5 14.5,7.85 14.82,7H17.95L15,14C14.53,16 16,17 18.5,17C21,17 22.56,16 22,14L19.05,7H21V5H14.83C14.4,3.8 13.27,3 12,3M12,5A1,1 0 0,1 13,6A1,1 0 0,1 12,7A1,1 0 0,1 11,6A1,1 0 0,1 12,5M5.5,10.25L7,14H4L5.5,10.25M18.5,10.25L20,14H17L18.5,10.25Z" /></g><g id="scale-bathroom"><path d="M5,2H19A2,2 0 0,1 21,4V20A2,2 0 0,1 19,22H5A2,2 0 0,1 3,20V4A2,2 0 0,1 5,2M12,4A4,4 0 0,0 8,8H11.26L10.85,5.23L12.9,8H16A4,4 0 0,0 12,4M5,10V20H19V10H5Z" /></g><g id="scanner"><path d="M19.8,10.7L4.2,5L3.5,6.9L17.6,12H5A2,2 0 0,0 3,14V18A2,2 0 0,0 5,20H19A2,2 0 0,0 21,18V12.5C21,11.7 20.5,10.9 19.8,10.7M7,17H5V15H7V17M19,17H9V15H19V17Z" /></g><g id="school"><path d="M12,3L1,9L12,15L21,10.09V17H23V9M5,13.18V17.18L12,21L19,17.18V13.18L12,17L5,13.18Z" /></g><g id="screen-rotation"><path d="M7.5,21.5C4.25,19.94 1.91,16.76 1.55,13H0.05C0.56,19.16 5.71,24 12,24L12.66,23.97L8.85,20.16M14.83,21.19L2.81,9.17L9.17,2.81L21.19,14.83M10.23,1.75C9.64,1.16 8.69,1.16 8.11,1.75L1.75,8.11C1.16,8.7 1.16,9.65 1.75,10.23L13.77,22.25C14.36,22.84 15.31,22.84 15.89,22.25L22.25,15.89C22.84,15.3 22.84,14.35 22.25,13.77L10.23,1.75M16.5,2.5C19.75,4.07 22.09,7.24 22.45,11H23.95C23.44,4.84 18.29,0 12,0L11.34,0.03L15.15,3.84L16.5,2.5Z" /></g><g id="screen-rotation-lock"><path d="M16.8,2.5C16.8,1.56 17.56,0.8 18.5,0.8C19.44,0.8 20.2,1.56 20.2,2.5V3H16.8V2.5M16,9H21A1,1 0 0,0 22,8V4A1,1 0 0,0 21,3V2.5A2.5,2.5 0 0,0 18.5,0A2.5,2.5 0 0,0 16,2.5V3A1,1 0 0,0 15,4V8A1,1 0 0,0 16,9M8.47,20.5C5.2,18.94 2.86,15.76 2.5,12H1C1.5,18.16 6.66,23 12.95,23L13.61,22.97L9.8,19.15L8.47,20.5M23.25,12.77L20.68,10.2L19.27,11.61L21.5,13.83L15.83,19.5L4.5,8.17L10.17,2.5L12.27,4.61L13.68,3.2L11.23,0.75C10.64,0.16 9.69,0.16 9.11,0.75L2.75,7.11C2.16,7.7 2.16,8.65 2.75,9.23L14.77,21.25C15.36,21.84 16.31,21.84 16.89,21.25L23.25,14.89C23.84,14.3 23.84,13.35 23.25,12.77Z" /></g><g id="screwdriver"><path d="M18,1.83C17.5,1.83 17,2 16.59,2.41C13.72,5.28 8,11 8,11L9.5,12.5L6,16H4L2,20L4,22L8,20V18L11.5,14.5L13,16C13,16 18.72,10.28 21.59,7.41C22.21,6.5 22.37,5.37 21.59,4.59L19.41,2.41C19,2 18.5,1.83 18,1.83M18,4L20,6L13,13L11,11L18,4Z" /></g><g id="script"><path d="M14,20A2,2 0 0,0 16,18V5H9A1,1 0 0,0 8,6V16H5V5A3,3 0 0,1 8,2H19A3,3 0 0,1 22,5V6H18V18L18,19A3,3 0 0,1 15,22H5A3,3 0 0,1 2,19V18H12A2,2 0 0,0 14,20Z" /></g><g id="sd"><path d="M18,8H16V4H18M15,8H13V4H15M12,8H10V4H12M18,2H10L4,8V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V4A2,2 0 0,0 18,2Z" /></g><g id="seal"><path d="M20.39,19.37L16.38,18L15,22L11.92,16L9,22L7.62,18L3.61,19.37L6.53,13.37C5.57,12.17 5,10.65 5,9A7,7 0 0,1 12,2A7,7 0 0,1 19,9C19,10.65 18.43,12.17 17.47,13.37L20.39,19.37M7,9L9.69,10.34L9.5,13.34L12,11.68L14.5,13.33L14.33,10.34L17,9L14.32,7.65L14.5,4.67L12,6.31L9.5,4.65L9.67,7.66L7,9Z" /></g><g id="seat-flat"><path d="M22,11V13H9V7H18A4,4 0 0,1 22,11M2,14V16H8V18H16V16H22V14M7.14,12.1C8.3,10.91 8.28,9 7.1,7.86C5.91,6.7 4,6.72 2.86,7.9C1.7,9.09 1.72,11 2.9,12.14C4.09,13.3 6,13.28 7.14,12.1Z" /></g><g id="seat-flat-angled"><path d="M22.25,14.29L21.56,16.18L9.2,11.71L11.28,6.05L19.84,9.14C21.94,9.9 23,12.2 22.25,14.29M1.5,12.14L8,14.5V19H16V17.37L20.5,19L21.21,17.11L2.19,10.25M7.3,10.2C8.79,9.5 9.42,7.69 8.71,6.2C8,4.71 6.2,4.08 4.7,4.8C3.21,5.5 2.58,7.3 3.3,8.8C4,10.29 5.8,10.92 7.3,10.2Z" /></g><g id="seat-individual-suite"><path d="M7,13A3,3 0 0,0 10,10A3,3 0 0,0 7,7A3,3 0 0,0 4,10A3,3 0 0,0 7,13M19,7H11V14H3V7H1V17H23V11A4,4 0 0,0 19,7Z" /></g><g id="seat-legroom-extra"><path d="M4,12V3H2V12A5,5 0 0,0 7,17H13V15H7A3,3 0 0,1 4,12M22.83,17.24C22.45,16.5 21.54,16.27 20.8,16.61L19.71,17.11L16.3,10.13C15.96,9.45 15.27,9 14.5,9H11V3H5V11A3,3 0 0,0 8,14H15L18.41,21L22.13,19.3C22.9,18.94 23.23,18 22.83,17.24Z" /></g><g id="seat-legroom-normal"><path d="M5,12V3H3V12A5,5 0 0,0 8,17H14V15H8A3,3 0 0,1 5,12M20.5,18H19V11A2,2 0 0,0 17,9H12V3H6V11A3,3 0 0,0 9,14H16V21H20.5A1.5,1.5 0 0,0 22,19.5A1.5,1.5 0 0,0 20.5,18Z" /></g><g id="seat-legroom-reduced"><path d="M19.97,19.2C20.15,20.16 19.42,21 18.5,21H14V18L15,14H9A3,3 0 0,1 6,11V3H12V9H17A2,2 0 0,1 19,11L17,18H18.44C19.17,18 19.83,18.5 19.97,19.2M5,12V3H3V12A5,5 0 0,0 8,17H12V15H8A3,3 0 0,1 5,12Z" /></g><g id="seat-recline-extra"><path d="M5.35,5.64C4.45,5 4.23,3.76 4.86,2.85C5.5,1.95 6.74,1.73 7.65,2.36C8.55,3 8.77,4.24 8.14,5.15C7.5,6.05 6.26,6.27 5.35,5.64M16,19H8.93C7.45,19 6.19,17.92 5.97,16.46L4,7H2L4,16.76C4.37,19.2 6.47,21 8.94,21H16M16.23,15H11.35L10.32,10.9C11.9,11.79 13.6,12.44 15.47,12.12V10C13.84,10.3 12.03,9.72 10.78,8.74L9.14,7.47C8.91,7.29 8.65,7.17 8.38,7.09C8.06,7 7.72,6.97 7.39,7.03H7.37C6.14,7.25 5.32,8.42 5.53,9.64L6.88,15.56C7.16,17 8.39,18 9.83,18H16.68L20.5,21L22,19.5" /></g><g id="seat-recline-normal"><path d="M7.59,5.41C6.81,4.63 6.81,3.36 7.59,2.58C8.37,1.8 9.64,1.8 10.42,2.58C11.2,3.36 11.2,4.63 10.42,5.41C9.63,6.2 8.37,6.2 7.59,5.41M6,16V7H4V16A5,5 0 0,0 9,21H15V19H9A3,3 0 0,1 6,16M20,20.07L14.93,15H11.5V11.32C12.9,12.47 15.1,13.5 17,13.5V11.32C15.34,11.34 13.39,10.45 12.33,9.28L10.93,7.73C10.74,7.5 10.5,7.35 10.24,7.23C9.95,7.09 9.62,7 9.28,7H9.25C8,7 7,8 7,9.25V15A3,3 0 0,0 10,18H15.07L18.57,21.5" /></g><g id="security"><path d="M12,12H19C18.47,16.11 15.72,19.78 12,20.92V12H5V6.3L12,3.19M12,1L3,5V11C3,16.55 6.84,21.73 12,23C17.16,21.73 21,16.55 21,11V5L12,1Z" /></g><g id="security-home"><path d="M11,13H13V16H16V11H18L12,6L6,11H8V16H11V13M12,1L21,5V11C21,16.55 17.16,21.74 12,23C6.84,21.74 3,16.55 3,11V5L12,1Z" /></g><g id="security-network"><path d="M13,18H14A1,1 0 0,1 15,19H22V21H15A1,1 0 0,1 14,22H10A1,1 0 0,1 9,21H2V19H9A1,1 0 0,1 10,18H11V16.34C8.07,15.13 6,12 6,8.67V4.67L12,2L18,4.67V8.67C18,12 15.93,15.13 13,16.34V18M12,4L8,5.69V9H12V4M12,9V15C13.91,14.53 16,12.06 16,10V9H12Z" /></g><g id="select"><path d="M4,3H5V5H3V4A1,1 0 0,1 4,3M20,3A1,1 0 0,1 21,4V5H19V3H20M15,5V3H17V5H15M11,5V3H13V5H11M7,5V3H9V5H7M21,20A1,1 0 0,1 20,21H19V19H21V20M15,21V19H17V21H15M11,21V19H13V21H11M7,21V19H9V21H7M4,21A1,1 0 0,1 3,20V19H5V21H4M3,15H5V17H3V15M21,15V17H19V15H21M3,11H5V13H3V11M21,11V13H19V11H21M3,7H5V9H3V7M21,7V9H19V7H21Z" /></g><g id="select-all"><path d="M9,9H15V15H9M7,17H17V7H7M15,5H17V3H15M15,21H17V19H15M19,17H21V15H19M19,9H21V7H19M19,21A2,2 0 0,0 21,19H19M19,13H21V11H19M11,21H13V19H11M9,3H7V5H9M3,17H5V15H3M5,21V19H3A2,2 0 0,0 5,21M19,3V5H21A2,2 0 0,0 19,3M13,3H11V5H13M3,9H5V7H3M7,21H9V19H7M3,13H5V11H3M3,5H5V3A2,2 0 0,0 3,5Z" /></g><g id="select-inverse"><path d="M5,3H7V5H9V3H11V5H13V3H15V5H17V3H19V5H21V7H19V9H21V11H19V13H21V15H19V17H21V19H19V21H17V19H15V21H13V19H11V21H9V19H7V21H5V19H3V17H5V15H3V13H5V11H3V9H5V7H3V5H5V3Z" /></g><g id="select-off"><path d="M1,4.27L2.28,3L21,21.72L19.73,23L17,20.27V21H15V19H15.73L5,8.27V9H3V7H3.73L1,4.27M20,3A1,1 0 0,1 21,4V5H19V3H20M15,5V3H17V5H15M11,5V3H13V5H11M7,5V3H9V5H7M11,21V19H13V21H11M7,21V19H9V21H7M4,21A1,1 0 0,1 3,20V19H5V21H4M3,15H5V17H3V15M21,15V17H19V15H21M3,11H5V13H3V11M21,11V13H19V11H21M21,7V9H19V7H21Z" /></g><g id="selection"><path d="M2,4V7H4V4H2M7,4H4C4,4 4,4 4,4H2C2,2.89 2.9,2 4,2H7V4M22,4V7H20V4H22M17,4H20C20,4 20,4 20,4H22C22,2.89 21.1,2 20,2H17V4M22,20V17H20V20H22M17,20H20C20,20 20,20 20,20H22C22,21.11 21.1,22 20,22H17V20M2,20V17H4V20H2M7,20H4C4,20 4,20 4,20H2C2,21.11 2.9,22 4,22H7V20M10,2H14V4H10V2M10,20H14V22H10V20M20,10H22V14H20V10M2,10H4V14H2V10Z" /></g><g id="send"><path d="M2,21L23,12L2,3V10L17,12L2,14V21Z" /></g><g id="serial-port"><path d="M7,3H17V5H19V8H16V14H8V8H5V5H7V3M17,9H19V14H17V9M11,15H13V22H11V15M5,9H7V14H5V9Z" /></g><g id="server"><path d="M4,1H20A1,1 0 0,1 21,2V6A1,1 0 0,1 20,7H4A1,1 0 0,1 3,6V2A1,1 0 0,1 4,1M4,9H20A1,1 0 0,1 21,10V14A1,1 0 0,1 20,15H4A1,1 0 0,1 3,14V10A1,1 0 0,1 4,9M4,17H20A1,1 0 0,1 21,18V22A1,1 0 0,1 20,23H4A1,1 0 0,1 3,22V18A1,1 0 0,1 4,17M9,5H10V3H9V5M9,13H10V11H9V13M9,21H10V19H9V21M5,3V5H7V3H5M5,11V13H7V11H5M5,19V21H7V19H5Z" /></g><g id="server-minus"><path d="M4,4H20A1,1 0 0,1 21,5V9A1,1 0 0,1 20,10H4A1,1 0 0,1 3,9V5A1,1 0 0,1 4,4M9,8H10V6H9V8M5,6V8H7V6H5M8,16H16V18H8V16Z" /></g><g id="server-network"><path d="M13,18H14A1,1 0 0,1 15,19H22V21H15A1,1 0 0,1 14,22H10A1,1 0 0,1 9,21H2V19H9A1,1 0 0,1 10,18H11V16H4A1,1 0 0,1 3,15V11A1,1 0 0,1 4,10H20A1,1 0 0,1 21,11V15A1,1 0 0,1 20,16H13V18M4,2H20A1,1 0 0,1 21,3V7A1,1 0 0,1 20,8H4A1,1 0 0,1 3,7V3A1,1 0 0,1 4,2M9,6H10V4H9V6M9,14H10V12H9V14M5,4V6H7V4H5M5,12V14H7V12H5Z" /></g><g id="server-network-off"><path d="M13,18H14A1,1 0 0,1 15,19H15.73L13,16.27V18M22,19V20.18L20.82,19H22M21,21.72L19.73,23L17.73,21H15A1,1 0 0,1 14,22H10A1,1 0 0,1 9,21H2V19H9A1,1 0 0,1 10,18H11V16H4A1,1 0 0,1 3,15V11A1,1 0 0,1 4,10H6.73L4.73,8H4A1,1 0 0,1 3,7V6.27L1,4.27L2.28,3L21,21.72M4,2H20A1,1 0 0,1 21,3V7A1,1 0 0,1 20,8H9.82L7,5.18V4H5.82L3.84,2C3.89,2 3.94,2 4,2M20,10A1,1 0 0,1 21,11V15A1,1 0 0,1 20,16H17.82L11.82,10H20M9,6H10V4H9V6M9,14H10V13.27L9,12.27V14M5,12V14H7V12H5Z" /></g><g id="server-off"><path d="M4,1H20A1,1 0 0,1 21,2V6A1,1 0 0,1 20,7H8.82L6.82,5H7V3H5V3.18L3.21,1.39C3.39,1.15 3.68,1 4,1M22,22.72L20.73,24L19.73,23H4A1,1 0 0,1 3,22V18A1,1 0 0,1 4,17H13.73L11.73,15H4A1,1 0 0,1 3,14V10A1,1 0 0,1 4,9H5.73L3.68,6.95C3.38,6.85 3.15,6.62 3.05,6.32L1,4.27L2.28,3L22,22.72M20,9A1,1 0 0,1 21,10V14A1,1 0 0,1 20,15H16.82L10.82,9H20M20,17A1,1 0 0,1 21,18V19.18L18.82,17H20M9,5H10V3H9V5M9,13H9.73L9,12.27V13M9,21H10V19H9V21M5,11V13H7V11H5M5,19V21H7V19H5Z" /></g><g id="server-plus"><path d="M4,4H20A1,1 0 0,1 21,5V9A1,1 0 0,1 20,10H4A1,1 0 0,1 3,9V5A1,1 0 0,1 4,4M9,8H10V6H9V8M5,6V8H7V6H5M8,16H11V13H13V16H16V18H13V21H11V18H8V16Z" /></g><g id="server-remove"><path d="M4,4H20A1,1 0 0,1 21,5V9A1,1 0 0,1 20,10H4A1,1 0 0,1 3,9V5A1,1 0 0,1 4,4M9,8H10V6H9V8M5,6V8H7V6H5M10.59,17L8,14.41L9.41,13L12,15.59L14.59,13L16,14.41L13.41,17L16,19.59L14.59,21L12,18.41L9.41,21L8,19.59L10.59,17Z" /></g><g id="server-security"><path d="M3,1H19A1,1 0 0,1 20,2V6A1,1 0 0,1 19,7H3A1,1 0 0,1 2,6V2A1,1 0 0,1 3,1M3,9H19A1,1 0 0,1 20,10V10.67L17.5,9.56L11,12.44V15H3A1,1 0 0,1 2,14V10A1,1 0 0,1 3,9M3,17H11C11.06,19.25 12,21.4 13.46,23H3A1,1 0 0,1 2,22V18A1,1 0 0,1 3,17M8,5H9V3H8V5M8,13H9V11H8V13M8,21H9V19H8V21M4,3V5H6V3H4M4,11V13H6V11H4M4,19V21H6V19H4M17.5,12L22,14V17C22,19.78 20.08,22.37 17.5,23C14.92,22.37 13,19.78 13,17V14L17.5,12M17.5,13.94L15,15.06V17.72C15,19.26 16.07,20.7 17.5,21.06V13.94Z" /></g><g id="settings"><path d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z" /></g><g id="settings-box"><path d="M17.25,12C17.25,12.23 17.23,12.46 17.2,12.68L18.68,13.84C18.81,13.95 18.85,14.13 18.76,14.29L17.36,16.71C17.27,16.86 17.09,16.92 16.93,16.86L15.19,16.16C14.83,16.44 14.43,16.67 14,16.85L13.75,18.7C13.72,18.87 13.57,19 13.4,19H10.6C10.43,19 10.28,18.87 10.25,18.7L10,16.85C9.56,16.67 9.17,16.44 8.81,16.16L7.07,16.86C6.91,16.92 6.73,16.86 6.64,16.71L5.24,14.29C5.15,14.13 5.19,13.95 5.32,13.84L6.8,12.68C6.77,12.46 6.75,12.23 6.75,12C6.75,11.77 6.77,11.54 6.8,11.32L5.32,10.16C5.19,10.05 5.15,9.86 5.24,9.71L6.64,7.29C6.73,7.13 6.91,7.07 7.07,7.13L8.81,7.84C9.17,7.56 9.56,7.32 10,7.15L10.25,5.29C10.28,5.13 10.43,5 10.6,5H13.4C13.57,5 13.72,5.13 13.75,5.29L14,7.15C14.43,7.32 14.83,7.56 15.19,7.84L16.93,7.13C17.09,7.07 17.27,7.13 17.36,7.29L18.76,9.71C18.85,9.86 18.81,10.05 18.68,10.16L17.2,11.32C17.23,11.54 17.25,11.77 17.25,12M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3M12,10C10.89,10 10,10.89 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12C14,10.89 13.1,10 12,10Z" /></g><g id="shape-circle-plus"><path d="M11,19A6,6 0 0,0 17,13H19A8,8 0 0,1 11,21A8,8 0 0,1 3,13A8,8 0 0,1 11,5V7A6,6 0 0,0 5,13A6,6 0 0,0 11,19M19,5H22V7H19V10H17V7H14V5H17V2H19V5Z" /></g><g id="shape-plus"><path d="M2,2H11V11H2V2M17.5,2C20,2 22,4 22,6.5C22,9 20,11 17.5,11C15,11 13,9 13,6.5C13,4 15,2 17.5,2M6.5,14L11,22H2L6.5,14M19,17H22V19H19V22H17V19H14V17H17V14H19V17Z" /></g><g id="shape-polygon-plus"><path d="M17,15.7V13H19V17L10,21L3,14L7,5H11V7H8.3L5.4,13.6L10.4,18.6L17,15.7M22,5V7H19V10H17V7H14V5H17V2H19V5H22Z" /></g><g id="shape-rectangle-plus"><path d="M19,6H22V8H19V11H17V8H14V6H17V3H19V6M17,17V14H19V19H3V6H11V8H5V17H17Z" /></g><g id="shape-square-plus"><path d="M19,5H22V7H19V10H17V7H14V5H17V2H19V5M17,19V13H19V21H3V5H11V7H5V19H17Z" /></g><g id="share"><path d="M21,11L14,4V8C7,9 4,14 3,19C5.5,15.5 9,13.9 14,13.9V18L21,11Z" /></g><g id="share-variant"><path d="M18,16.08C17.24,16.08 16.56,16.38 16.04,16.85L8.91,12.7C8.96,12.47 9,12.24 9,12C9,11.76 8.96,11.53 8.91,11.3L15.96,7.19C16.5,7.69 17.21,8 18,8A3,3 0 0,0 21,5A3,3 0 0,0 18,2A3,3 0 0,0 15,5C15,5.24 15.04,5.47 15.09,5.7L8.04,9.81C7.5,9.31 6.79,9 6,9A3,3 0 0,0 3,12A3,3 0 0,0 6,15C6.79,15 7.5,14.69 8.04,14.19L15.16,18.34C15.11,18.55 15.08,18.77 15.08,19C15.08,20.61 16.39,21.91 18,21.91C19.61,21.91 20.92,20.61 20.92,19A2.92,2.92 0 0,0 18,16.08Z" /></g><g id="shield"><path d="M12,1L3,5V11C3,16.55 6.84,21.74 12,23C17.16,21.74 21,16.55 21,11V5L12,1Z" /></g><g id="shield-outline"><path d="M21,11C21,16.55 17.16,21.74 12,23C6.84,21.74 3,16.55 3,11V5L12,1L21,5V11M12,21C15.75,20 19,15.54 19,11.22V6.3L12,3.18L5,6.3V11.22C5,15.54 8.25,20 12,21Z" /></g><g id="shopping"><path d="M12,13A5,5 0 0,1 7,8H9A3,3 0 0,0 12,11A3,3 0 0,0 15,8H17A5,5 0 0,1 12,13M12,3A3,3 0 0,1 15,6H9A3,3 0 0,1 12,3M19,6H17A5,5 0 0,0 12,1A5,5 0 0,0 7,6H5C3.89,6 3,6.89 3,8V20A2,2 0 0,0 5,22H19A2,2 0 0,0 21,20V8C21,6.89 20.1,6 19,6Z" /></g><g id="shopping-music"><path d="M12,3A3,3 0 0,0 9,6H15A3,3 0 0,0 12,3M19,6A2,2 0 0,1 21,8V20A2,2 0 0,1 19,22H5C3.89,22 3,21.1 3,20V8C3,6.89 3.89,6 5,6H7A5,5 0 0,1 12,1A5,5 0 0,1 17,6H19M9,19L16.5,14L9,10V19Z" /></g><g id="shredder"><path d="M6,3V7H8V5H16V7H18V3H6M5,8A3,3 0 0,0 2,11V17H5V14H19V17H22V11A3,3 0 0,0 19,8H5M18,10A1,1 0 0,1 19,11A1,1 0 0,1 18,12A1,1 0 0,1 17,11A1,1 0 0,1 18,10M7,16V21H9V16H7M11,16V20H13V16H11M15,16V21H17V16H15Z" /></g><g id="shuffle"><path d="M14.83,13.41L13.42,14.82L16.55,17.95L14.5,20H20V14.5L17.96,16.54L14.83,13.41M14.5,4L16.54,6.04L4,18.59L5.41,20L17.96,7.46L20,9.5V4M10.59,9.17L5.41,4L4,5.41L9.17,10.58L10.59,9.17Z" /></g><g id="shuffle-disabled"><path d="M16,4.5V7H5V9H16V11.5L19.5,8M16,12.5V15H5V17H16V19.5L19.5,16" /></g><g id="shuffle-variant"><path d="M17,3L22.25,7.5L17,12L22.25,16.5L17,21V18H14.26L11.44,15.18L13.56,13.06L15.5,15H17V12L17,9H15.5L6.5,18H2V15H5.26L14.26,6H17V3M2,6H6.5L9.32,8.82L7.2,10.94L5.26,9H2V6Z" /></g><g id="sigma"><path d="M5,4H18V9H17L16,6H10.06L13.65,11.13L9.54,17H16L17,15H18V20H5L10.6,12L5,4Z" /></g><g id="sigma-lower"><path d="M19,12C19,16.42 15.64,20 11.5,20C7.36,20 4,16.42 4,12C4,7.58 7.36,4 11.5,4H20V6H16.46C18,7.47 19,9.61 19,12M11.5,6C8.46,6 6,8.69 6,12C6,15.31 8.46,18 11.5,18C14.54,18 17,15.31 17,12C17,8.69 14.54,6 11.5,6Z" /></g><g id="sign-caution"><path d="M2,3H22V13H18V21H16V13H8V21H6V13H2V3M18.97,11L20,9.97V7.15L16.15,11H18.97M13.32,11L19.32,5H16.5L10.5,11H13.32M7.66,11L13.66,5H10.83L4.83,11H7.66M5.18,5L4,6.18V9L8,5H5.18Z" /></g><g id="signal"><path d="M3,21H6V18H3M8,21H11V14H8M13,21H16V9H13M18,21H21V3H18V21Z" /></g><g id="signal-variant"><path d="M4,6V4H4.1C12.9,4 20,11.1 20,19.9V20H18V19.9C18,12.2 11.8,6 4,6M4,10V8A12,12 0 0,1 16,20H14A10,10 0 0,0 4,10M4,14V12A8,8 0 0,1 12,20H10A6,6 0 0,0 4,14M4,16A4,4 0 0,1 8,20H4V16Z" /></g><g id="silverware"><path d="M8.1,13.34L3.91,9.16C2.35,7.59 2.35,5.06 3.91,3.5L10.93,10.5L8.1,13.34M14.88,11.53L13.41,13L20.29,19.88L18.88,21.29L12,14.41L5.12,21.29L3.71,19.88L13.47,10.12C12.76,8.59 13.26,6.44 14.85,4.85C16.76,2.93 19.5,2.57 20.96,4.03C22.43,5.5 22.07,8.24 20.15,10.15C18.56,11.74 16.41,12.24 14.88,11.53Z" /></g><g id="silverware-fork"><path d="M5.12,21.29L3.71,19.88L13.36,10.22L13.16,10C12.38,9.23 12.38,7.97 13.16,7.19L17.5,2.82L18.43,3.74L15.19,7L16.15,7.94L19.39,4.69L20.31,5.61L17.06,8.85L18,9.81L21.26,6.56L22.18,7.5L17.81,11.84C17.03,12.62 15.77,12.62 15,11.84L14.78,11.64L5.12,21.29Z" /></g><g id="silverware-spoon"><path d="M14.88,11.53L5.12,21.29L3.71,19.88L13.47,10.12C12.76,8.59 13.26,6.44 14.85,4.85C16.76,2.93 19.5,2.57 20.96,4.03C22.43,5.5 22.07,8.24 20.15,10.15C18.56,11.74 16.41,12.24 14.88,11.53Z" /></g><g id="silverware-variant"><path d="M8.1,13.34L3.91,9.16C2.35,7.59 2.35,5.06 3.91,3.5L10.93,10.5L8.1,13.34M13.41,13L20.29,19.88L18.88,21.29L12,14.41L5.12,21.29L3.71,19.88L13.36,10.22L13.16,10C12.38,9.23 12.38,7.97 13.16,7.19L17.5,2.82L18.43,3.74L15.19,7L16.15,7.94L19.39,4.69L20.31,5.61L17.06,8.85L18,9.81L21.26,6.56L22.18,7.5L17.81,11.84C17.03,12.62 15.77,12.62 15,11.84L14.78,11.64L13.41,13Z" /></g><g id="sim"><path d="M20,4A2,2 0 0,0 18,2H10L4,8V20A2,2 0 0,0 6,22H18C19.11,22 20,21.1 20,20V4M9,19H7V17H9V19M17,19H15V17H17V19M9,15H7V11H9V15M13,19H11V15H13V19M13,13H11V11H13V13M17,15H15V11H17V15Z" /></g><g id="sim-alert"><path d="M13,13H11V8H13M13,17H11V15H13M18,2H10L4,8V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V4A2,2 0 0,0 18,2Z" /></g><g id="sim-off"><path d="M19,5A2,2 0 0,0 17,3H10L7.66,5.34L19,16.68V5M3.65,3.88L2.38,5.15L5,7.77V19A2,2 0 0,0 7,21H17C17.36,21 17.68,20.9 17.97,20.74L19.85,22.62L21.12,21.35L3.65,3.88Z" /></g><g id="sitemap"><path d="M9,2V8H11V11H5C3.89,11 3,11.89 3,13V16H1V22H7V16H5V13H11V16H9V22H15V16H13V13H19V16H17V22H23V16H21V13C21,11.89 20.11,11 19,11H13V8H15V2H9Z" /></g><g id="skip-backward"><path d="M20,5V19L13,12M6,5V19H4V5M13,5V19L6,12" /></g><g id="skip-forward"><path d="M4,5V19L11,12M18,5V19H20V5M11,5V19L18,12" /></g><g id="skip-next"><path d="M16,18H18V6H16M6,18L14.5,12L6,6V18Z" /></g><g id="skip-next-circle"><path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M8,8L13,12L8,16M14,8H16V16H14" /></g><g id="skip-next-circle-outline"><path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4M8,8V16L13,12M14,8V16H16V8" /></g><g id="skip-previous"><path d="M6,18V6H8V18H6M9.5,12L18,6V18L9.5,12Z" /></g><g id="skip-previous-circle"><path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M8,8H10V16H8M16,8V16L11,12" /></g><g id="skip-previous-circle-outline"><path d="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4C7.59,4 4,7.59 4,12C4,16.41 7.59,20 12,20C16.41,20 20,16.41 20,12C20,7.59 16.41,4 12,4M16,8V16L11,12M10,8V16H8V8" /></g><g id="skull"><path d="M12,2A9,9 0 0,0 3,11C3,14.03 4.53,16.82 7,18.47V22H9V19H11V22H13V19H15V22H17V18.46C19.47,16.81 21,14 21,11A9,9 0 0,0 12,2M8,11A2,2 0 0,1 10,13A2,2 0 0,1 8,15A2,2 0 0,1 6,13A2,2 0 0,1 8,11M16,11A2,2 0 0,1 18,13A2,2 0 0,1 16,15A2,2 0 0,1 14,13A2,2 0 0,1 16,11M12,14L13.5,17H10.5L12,14Z" /></g><g id="skype"><path d="M18,6C20.07,8.04 20.85,10.89 20.36,13.55C20.77,14.27 21,15.11 21,16A5,5 0 0,1 16,21C15.11,21 14.27,20.77 13.55,20.36C10.89,20.85 8.04,20.07 6,18C3.93,15.96 3.15,13.11 3.64,10.45C3.23,9.73 3,8.89 3,8A5,5 0 0,1 8,3C8.89,3 9.73,3.23 10.45,3.64C13.11,3.15 15.96,3.93 18,6M12.04,17.16C14.91,17.16 16.34,15.78 16.34,13.92C16.34,12.73 15.78,11.46 13.61,10.97L11.62,10.53C10.86,10.36 10,10.13 10,9.42C10,8.7 10.6,8.2 11.7,8.2C13.93,8.2 13.72,9.73 14.83,9.73C15.41,9.73 15.91,9.39 15.91,8.8C15.91,7.43 13.72,6.4 11.86,6.4C9.85,6.4 7.7,7.26 7.7,9.54C7.7,10.64 8.09,11.81 10.25,12.35L12.94,13.03C13.75,13.23 13.95,13.68 13.95,14.1C13.95,14.78 13.27,15.45 12.04,15.45C9.63,15.45 9.96,13.6 8.67,13.6C8.09,13.6 7.67,14 7.67,14.57C7.67,15.68 9,17.16 12.04,17.16Z" /></g><g id="skype-business"><path d="M12.03,16.53C9.37,16.53 8.18,15.22 8.18,14.24C8.18,13.74 8.55,13.38 9.06,13.38C10.2,13.38 9.91,15 12.03,15C13.12,15 13.73,14.43 13.73,13.82C13.73,13.46 13.55,13.06 12.83,12.88L10.46,12.29C8.55,11.81 8.2,10.78 8.2,9.81C8.2,7.79 10.1,7.03 11.88,7.03C13.5,7.03 15.46,7.94 15.46,9.15C15.46,9.67 15,9.97 14.5,9.97C13.5,9.97 13.7,8.62 11.74,8.62C10.77,8.62 10.23,9.06 10.23,9.69C10.23,10.32 11,10.5 11.66,10.68L13.42,11.07C15.34,11.5 15.83,12.62 15.83,13.67C15.83,15.31 14.57,16.53 12.03,16.53M18,6C20.07,8.04 20.85,10.89 20.36,13.55C20.77,14.27 21,15.11 21,16A5,5 0 0,1 16,21C15.11,21 14.27,20.77 13.55,20.36C10.89,20.85 8.04,20.07 6,18C3.93,15.96 3.15,13.11 3.64,10.45C3.23,9.73 3,8.89 3,8A5,5 0 0,1 8,3C8.89,3 9.73,3.23 10.45,3.64C13.11,3.15 15.96,3.93 18,6M8,5A3,3 0 0,0 5,8C5,8.79 5.3,9.5 5.8,10.04C5.1,12.28 5.63,14.82 7.4,16.6C9.18,18.37 11.72,18.9 13.96,18.2C14.5,18.7 15.21,19 16,19A3,3 0 0,0 19,16C19,15.21 18.7,14.5 18.2,13.96C18.9,11.72 18.37,9.18 16.6,7.4C14.82,5.63 12.28,5.1 10.04,5.8C9.5,5.3 8.79,5 8,5Z" /></g><g id="slack"><path d="M10.23,11.16L12.91,10.27L13.77,12.84L11.09,13.73L10.23,11.16M17.69,13.71C18.23,13.53 18.5,12.94 18.34,12.4C18.16,11.86 17.57,11.56 17.03,11.75L15.73,12.18L14.87,9.61L16.17,9.17C16.71,9 17,8.4 16.82,7.86C16.64,7.32 16.05,7 15.5,7.21L14.21,7.64L13.76,6.3C13.58,5.76 13,5.46 12.45,5.65C11.91,5.83 11.62,6.42 11.8,6.96L12.25,8.3L9.57,9.19L9.12,7.85C8.94,7.31 8.36,7 7.81,7.2C7.27,7.38 7,7.97 7.16,8.5L7.61,9.85L6.31,10.29C5.77,10.47 5.5,11.06 5.66,11.6C5.8,12 6.19,12.3 6.61,12.31L6.97,12.25L8.27,11.82L9.13,14.39L7.83,14.83C7.29,15 7,15.6 7.18,16.14C7.32,16.56 7.71,16.84 8.13,16.85L8.5,16.79L9.79,16.36L10.24,17.7C10.38,18.13 10.77,18.4 11.19,18.41L11.55,18.35C12.09,18.17 12.38,17.59 12.2,17.04L11.75,15.7L14.43,14.81L14.88,16.15C15,16.57 15.41,16.84 15.83,16.85L16.19,16.8C16.73,16.62 17,16.03 16.84,15.5L16.39,14.15L17.69,13.71M21.17,9.25C23.23,16.12 21.62,19.1 14.75,21.17C7.88,23.23 4.9,21.62 2.83,14.75C0.77,7.88 2.38,4.9 9.25,2.83C16.12,0.77 19.1,2.38 21.17,9.25Z" /></g><g id="sleep"><path d="M23,12H17V10L20.39,6H17V4H23V6L19.62,10H23V12M15,16H9V14L12.39,10H9V8H15V10L11.62,14H15V16M7,20H1V18L4.39,14H1V12H7V14L3.62,18H7V20Z" /></g><g id="sleep-off"><path d="M2,5.27L3.28,4L20,20.72L18.73,22L12.73,16H9V14L9.79,13.06L2,5.27M23,12H17V10L20.39,6H17V4H23V6L19.62,10H23V12M9.82,8H15V10L13.54,11.72L9.82,8M7,20H1V18L4.39,14H1V12H7V14L3.62,18H7V20Z" /></g><g id="smoking"><path d="M7,19H22V15H7M2,19H5V15H2M10,4V5A3,3 0 0,1 7,8A5,5 0 0,0 2,13H4A3,3 0 0,1 7,10A5,5 0 0,0 12,5V4H10Z" /></g><g id="smoking-off"><path d="M15.82,14L19.82,18H22V14M2,18H5V14H2M3.28,4L2,5.27L4.44,7.71C2.93,8.61 2,10.24 2,12H4C4,10.76 4.77,9.64 5.93,9.2L10.73,14H7V18H14.73L18.73,22L20,20.72M10,3V4C10,5.09 9.4,6.1 8.45,6.62L9.89,8.07C11.21,7.13 12,5.62 12,4V3H10Z" /></g><g id="snapchat"><path d="M12,20.45C10.81,20.45 10.1,19.94 9.47,19.5C9,19.18 8.58,18.87 8.08,18.79C6.93,18.73 6.59,18.79 5.97,18.9C5.86,18.9 5.73,18.87 5.68,18.69C5.5,17.94 5.45,17.73 5.32,17.71C4,17.5 3.19,17.2 3.03,16.83C3,16.6 3.07,16.5 3.18,16.5C4.25,16.31 5.2,15.75 6,14.81C6.63,14.09 6.93,13.39 6.96,13.32C7.12,13 7.15,12.72 7.06,12.5C6.89,12.09 6.31,11.91 5.68,11.7C5.34,11.57 4.79,11.29 4.86,10.9C4.92,10.62 5.29,10.42 5.81,10.46C6.16,10.62 6.46,10.7 6.73,10.7C7.06,10.7 7.21,10.58 7.25,10.54C7.14,8.78 7.05,7.25 7.44,6.38C8.61,3.76 11.08,3.55 12,3.55C12.92,3.55 15.39,3.76 16.56,6.38C16.95,7.25 16.86,8.78 16.75,10.54C16.79,10.58 16.94,10.7 17.27,10.7C17.54,10.7 17.84,10.62 18.19,10.46C18.71,10.42 19.08,10.62 19.14,10.9C19.21,11.29 18.66,11.57 18.32,11.7C17.69,11.91 17.11,12.09 16.94,12.5C16.85,12.72 16.88,13 17.04,13.32C17.07,13.39 17.37,14.09 18,14.81C18.8,15.75 19.75,16.31 20.82,16.5C20.93,16.5 21,16.6 20.97,16.83C20.81,17.2 20,17.5 18.68,17.71C18.55,17.73 18.5,17.94 18.32,18.69C18.27,18.87 18.14,18.9 18.03,18.9C17.41,18.79 17.07,18.73 15.92,18.79C15.42,18.87 15,19.18 14.53,19.5C13.9,19.94 13.19,20.45 12,20.45Z" /></g><g id="snowman"><path d="M17,17A5,5 0 0,1 12,22A5,5 0 0,1 7,17C7,15.5 7.65,14.17 8.69,13.25C8.26,12.61 8,11.83 8,11C8,10.86 8,10.73 8,10.59L5.04,8.87L4.83,8.71L2.29,9.39L2.03,8.43L4.24,7.84L2.26,6.69L2.76,5.82L4.74,6.97L4.15,4.75L5.11,4.5L5.8,7.04L6.04,7.14L8.73,8.69C9.11,8.15 9.62,7.71 10.22,7.42C9.5,6.87 9,6 9,5A3,3 0 0,1 12,2A3,3 0 0,1 15,5C15,6 14.5,6.87 13.78,7.42C14.38,7.71 14.89,8.15 15.27,8.69L17.96,7.14L18.2,7.04L18.89,4.5L19.85,4.75L19.26,6.97L21.24,5.82L21.74,6.69L19.76,7.84L21.97,8.43L21.71,9.39L19.17,8.71L18.96,8.87L16,10.59V11C16,11.83 15.74,12.61 15.31,13.25C16.35,14.17 17,15.5 17,17Z" /></g><g id="soccer"><path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,3C13.76,3 15.4,3.53 16.78,4.41L16.5,5H13L12,5L10.28,4.16L10.63,3.13C11.08,3.05 11.53,3 12,3M9.53,3.38L9.19,4.41L6.63,5.69L5.38,5.94C6.5,4.73 7.92,3.84 9.53,3.38M13,6H16L18.69,9.59L17.44,12.16L14.81,12.78L11.53,8.94L13,6M6.16,6.66L7,10L5.78,13.06L3.22,13.94C3.08,13.31 3,12.67 3,12C3,10.1 3.59,8.36 4.59,6.91L6.16,6.66M20.56,9.22C20.85,10.09 21,11.03 21,12C21,13.44 20.63,14.79 20.03,16H19L18.16,12.66L19.66,9.66L20.56,9.22M8,10H11L13.81,13.28L12,16L8.84,16.78L6.53,13.69L8,10M12,17L15,19L14.13,20.72C13.44,20.88 12.73,21 12,21C10.25,21 8.63,20.5 7.25,19.63L8.41,17.91L12,17M19,17H19.5C18.5,18.5 17,19.67 15.31,20.34L16,19L19,17Z" /></g><g id="sofa"><path d="M7,6H9A2,2 0 0,1 11,8V12H5V8A2,2 0 0,1 7,6M15,6H17A2,2 0 0,1 19,8V12H13V8A2,2 0 0,1 15,6M1,9H2A1,1 0 0,1 3,10V12A2,2 0 0,0 5,14H19A2,2 0 0,0 21,12V11L21,10A1,1 0 0,1 22,9H23A1,1 0 0,1 24,10V19H21V17H3V19H0V10A1,1 0 0,1 1,9Z" /></g><g id="solid"><path d="M0,0H24V24H0" /></g><g id="sort"><path d="M10,13V11H18V13H10M10,19V17H14V19H10M10,7V5H22V7H10M6,17H8.5L5,20.5L1.5,17H4V7H1.5L5,3.5L8.5,7H6V17Z" /></g><g id="sort-alphabetical"><path d="M9.25,5L12.5,1.75L15.75,5H9.25M15.75,19L12.5,22.25L9.25,19H15.75M8.89,14.3H6L5.28,17H2.91L6,7H9L12.13,17H9.67L8.89,14.3M6.33,12.68H8.56L7.93,10.56L7.67,9.59L7.42,8.63H7.39L7.17,9.6L6.93,10.58L6.33,12.68M13.05,17V15.74L17.8,8.97V8.91H13.5V7H20.73V8.34L16.09,15V15.08H20.8V17H13.05Z" /></g><g id="sort-ascending"><path d="M10,11V13H18V11H10M10,5V7H14V5H10M10,17V19H22V17H10M6,7H8.5L5,3.5L1.5,7H4V20H6V7Z" /></g><g id="sort-descending"><path d="M10,13V11H18V13H10M10,19V17H14V19H10M10,7V5H22V7H10M6,17H8.5L5,20.5L1.5,17H4V4H6V17Z" /></g><g id="sort-numeric"><path d="M7.78,7C9.08,7.04 10,7.53 10.57,8.46C11.13,9.4 11.41,10.56 11.39,11.95C11.4,13.5 11.09,14.73 10.5,15.62C9.88,16.5 8.95,16.97 7.71,17C6.45,16.96 5.54,16.5 4.96,15.56C4.38,14.63 4.09,13.45 4.09,12C4.09,10.55 4.39,9.36 5,8.44C5.59,7.5 6.5,7.04 7.78,7M7.75,8.63C7.31,8.63 6.96,8.9 6.7,9.46C6.44,10 6.32,10.87 6.32,12C6.31,13.15 6.44,14 6.69,14.54C6.95,15.1 7.31,15.37 7.77,15.37C8.69,15.37 9.16,14.24 9.17,12C9.17,9.77 8.7,8.65 7.75,8.63M13.33,17V15.22L13.76,15.24L14.3,15.22L15.34,15.03C15.68,14.92 16,14.78 16.26,14.58C16.59,14.35 16.86,14.08 17.07,13.76C17.29,13.45 17.44,13.12 17.53,12.78L17.5,12.77C17.05,13.19 16.38,13.4 15.47,13.41C14.62,13.4 13.91,13.15 13.34,12.65C12.77,12.15 12.5,11.43 12.46,10.5C12.47,9.5 12.81,8.69 13.47,8.03C14.14,7.37 15,7.03 16.12,7C17.37,7.04 18.29,7.45 18.88,8.24C19.47,9 19.76,10 19.76,11.19C19.75,12.15 19.61,13 19.32,13.76C19.03,14.5 18.64,15.13 18.12,15.64C17.66,16.06 17.11,16.38 16.47,16.61C15.83,16.83 15.12,16.96 14.34,17H13.33M16.06,8.63C15.65,8.64 15.32,8.8 15.06,9.11C14.81,9.42 14.68,9.84 14.68,10.36C14.68,10.8 14.8,11.16 15.03,11.46C15.27,11.77 15.63,11.92 16.11,11.93C16.43,11.93 16.7,11.86 16.92,11.74C17.14,11.61 17.3,11.46 17.41,11.28C17.5,11.17 17.53,10.97 17.53,10.71C17.54,10.16 17.43,9.69 17.2,9.28C16.97,8.87 16.59,8.65 16.06,8.63M9.25,5L12.5,1.75L15.75,5H9.25M15.75,19L12.5,22.25L9.25,19H15.75Z" /></g><g id="sort-variant"><path d="M3,13H15V11H3M3,6V8H21V6M3,18H9V16H3V18Z" /></g><g id="soundcloud"><path d="M11.56,8.87V17H20.32V17C22.17,16.87 23,15.73 23,14.33C23,12.85 21.88,11.66 20.38,11.66C20,11.66 19.68,11.74 19.35,11.88C19.11,9.54 17.12,7.71 14.67,7.71C13.5,7.71 12.39,8.15 11.56,8.87M10.68,9.89C10.38,9.71 10.06,9.57 9.71,9.5V17H11.1V9.34C10.95,9.5 10.81,9.7 10.68,9.89M8.33,9.35V17H9.25V9.38C9.06,9.35 8.87,9.34 8.67,9.34C8.55,9.34 8.44,9.34 8.33,9.35M6.5,10V17H7.41V9.54C7.08,9.65 6.77,9.81 6.5,10M4.83,12.5C4.77,12.5 4.71,12.44 4.64,12.41V17H5.56V10.86C5.19,11.34 4.94,11.91 4.83,12.5M2.79,12.22V16.91C3,16.97 3.24,17 3.5,17H3.72V12.14C3.64,12.13 3.56,12.12 3.5,12.12C3.24,12.12 3,12.16 2.79,12.22M1,14.56C1,15.31 1.34,15.97 1.87,16.42V12.71C1.34,13.15 1,13.82 1,14.56Z" /></g><g id="source-branch"><path d="M13,14C9.64,14 8.54,15.35 8.18,16.24C9.25,16.7 10,17.76 10,19A3,3 0 0,1 7,22A3,3 0 0,1 4,19C4,17.69 4.83,16.58 6,16.17V7.83C4.83,7.42 4,6.31 4,5A3,3 0 0,1 7,2A3,3 0 0,1 10,5C10,6.31 9.17,7.42 8,7.83V13.12C8.88,12.47 10.16,12 12,12C14.67,12 15.56,10.66 15.85,9.77C14.77,9.32 14,8.25 14,7A3,3 0 0,1 17,4A3,3 0 0,1 20,7C20,8.34 19.12,9.5 17.91,9.86C17.65,11.29 16.68,14 13,14M7,18A1,1 0 0,0 6,19A1,1 0 0,0 7,20A1,1 0 0,0 8,19A1,1 0 0,0 7,18M7,4A1,1 0 0,0 6,5A1,1 0 0,0 7,6A1,1 0 0,0 8,5A1,1 0 0,0 7,4M17,6A1,1 0 0,0 16,7A1,1 0 0,0 17,8A1,1 0 0,0 18,7A1,1 0 0,0 17,6Z" /></g><g id="source-fork"><path d="M6,2A3,3 0 0,1 9,5C9,6.28 8.19,7.38 7.06,7.81C7.15,8.27 7.39,8.83 8,9.63C9,10.92 11,12.83 12,14.17C13,12.83 15,10.92 16,9.63C16.61,8.83 16.85,8.27 16.94,7.81C15.81,7.38 15,6.28 15,5A3,3 0 0,1 18,2A3,3 0 0,1 21,5C21,6.32 20.14,7.45 18.95,7.85C18.87,8.37 18.64,9 18,9.83C17,11.17 15,13.08 14,14.38C13.39,15.17 13.15,15.73 13.06,16.19C14.19,16.62 15,17.72 15,19A3,3 0 0,1 12,22A3,3 0 0,1 9,19C9,17.72 9.81,16.62 10.94,16.19C10.85,15.73 10.61,15.17 10,14.38C9,13.08 7,11.17 6,9.83C5.36,9 5.13,8.37 5.05,7.85C3.86,7.45 3,6.32 3,5A3,3 0 0,1 6,2M6,4A1,1 0 0,0 5,5A1,1 0 0,0 6,6A1,1 0 0,0 7,5A1,1 0 0,0 6,4M18,4A1,1 0 0,0 17,5A1,1 0 0,0 18,6A1,1 0 0,0 19,5A1,1 0 0,0 18,4M12,18A1,1 0 0,0 11,19A1,1 0 0,0 12,20A1,1 0 0,0 13,19A1,1 0 0,0 12,18Z" /></g><g id="source-merge"><path d="M7,3A3,3 0 0,1 10,6C10,7.29 9.19,8.39 8.04,8.81C8.58,13.81 13.08,14.77 15.19,14.96C15.61,13.81 16.71,13 18,13A3,3 0 0,1 21,16A3,3 0 0,1 18,19C16.69,19 15.57,18.16 15.16,17C10.91,16.8 9.44,15.19 8,13.39V15.17C9.17,15.58 10,16.69 10,18A3,3 0 0,1 7,21A3,3 0 0,1 4,18C4,16.69 4.83,15.58 6,15.17V8.83C4.83,8.42 4,7.31 4,6A3,3 0 0,1 7,3M7,5A1,1 0 0,0 6,6A1,1 0 0,0 7,7A1,1 0 0,0 8,6A1,1 0 0,0 7,5M7,17A1,1 0 0,0 6,18A1,1 0 0,0 7,19A1,1 0 0,0 8,18A1,1 0 0,0 7,17M18,15A1,1 0 0,0 17,16A1,1 0 0,0 18,17A1,1 0 0,0 19,16A1,1 0 0,0 18,15Z" /></g><g id="source-pull"><path d="M6,3A3,3 0 0,1 9,6C9,7.31 8.17,8.42 7,8.83V15.17C8.17,15.58 9,16.69 9,18A3,3 0 0,1 6,21A3,3 0 0,1 3,18C3,16.69 3.83,15.58 5,15.17V8.83C3.83,8.42 3,7.31 3,6A3,3 0 0,1 6,3M6,5A1,1 0 0,0 5,6A1,1 0 0,0 6,7A1,1 0 0,0 7,6A1,1 0 0,0 6,5M6,17A1,1 0 0,0 5,18A1,1 0 0,0 6,19A1,1 0 0,0 7,18A1,1 0 0,0 6,17M21,18A3,3 0 0,1 18,21A3,3 0 0,1 15,18C15,16.69 15.83,15.58 17,15.17V7H15V10.25L10.75,6L15,1.75V5H17A2,2 0 0,1 19,7V15.17C20.17,15.58 21,16.69 21,18M18,17A1,1 0 0,0 17,18A1,1 0 0,0 18,19A1,1 0 0,0 19,18A1,1 0 0,0 18,17Z" /></g><g id="speaker"><path d="M12,12A3,3 0 0,0 9,15A3,3 0 0,0 12,18A3,3 0 0,0 15,15A3,3 0 0,0 12,12M12,20A5,5 0 0,1 7,15A5,5 0 0,1 12,10A5,5 0 0,1 17,15A5,5 0 0,1 12,20M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8C10.89,8 10,7.1 10,6C10,4.89 10.89,4 12,4M17,2H7C5.89,2 5,2.89 5,4V20A2,2 0 0,0 7,22H17A2,2 0 0,0 19,20V4C19,2.89 18.1,2 17,2Z" /></g><g id="speaker-off"><path d="M2,5.27L3.28,4L21,21.72L19.73,23L18.27,21.54C17.93,21.83 17.5,22 17,22H7C5.89,22 5,21.1 5,20V8.27L2,5.27M12,18A3,3 0 0,1 9,15C9,14.24 9.28,13.54 9.75,13L8.33,11.6C7.5,12.5 7,13.69 7,15A5,5 0 0,0 12,20C13.31,20 14.5,19.5 15.4,18.67L14,17.25C13.45,17.72 12.76,18 12,18M17,15A5,5 0 0,0 12,10H11.82L5.12,3.3C5.41,2.54 6.14,2 7,2H17A2,2 0 0,1 19,4V17.18L17,15.17V15M12,4C10.89,4 10,4.89 10,6A2,2 0 0,0 12,8A2,2 0 0,0 14,6C14,4.89 13.1,4 12,4Z" /></g><g id="speedometer"><path d="M12,16A3,3 0 0,1 9,13C9,11.88 9.61,10.9 10.5,10.39L20.21,4.77L14.68,14.35C14.18,15.33 13.17,16 12,16M12,3C13.81,3 15.5,3.5 16.97,4.32L14.87,5.53C14,5.19 13,5 12,5A8,8 0 0,0 4,13C4,15.21 4.89,17.21 6.34,18.65H6.35C6.74,19.04 6.74,19.67 6.35,20.06C5.96,20.45 5.32,20.45 4.93,20.07V20.07C3.12,18.26 2,15.76 2,13A10,10 0 0,1 12,3M22,13C22,15.76 20.88,18.26 19.07,20.07V20.07C18.68,20.45 18.05,20.45 17.66,20.06C17.27,19.67 17.27,19.04 17.66,18.65V18.65C19.11,17.2 20,15.21 20,13C20,12 19.81,11 19.46,10.1L20.67,8C21.5,9.5 22,11.18 22,13Z" /></g><g id="spellcheck"><path d="M21.59,11.59L13.5,19.68L9.83,16L8.42,17.41L13.5,22.5L23,13M6.43,11L8.5,5.5L10.57,11M12.45,16H14.54L9.43,3H7.57L2.46,16H4.55L5.67,13H11.31L12.45,16Z" /></g><g id="spotify"><path d="M17.9,10.9C14.7,9 9.35,8.8 6.3,9.75C5.8,9.9 5.3,9.6 5.15,9.15C5,8.65 5.3,8.15 5.75,8C9.3,6.95 15.15,7.15 18.85,9.35C19.3,9.6 19.45,10.2 19.2,10.65C18.95,11 18.35,11.15 17.9,10.9M17.8,13.7C17.55,14.05 17.1,14.2 16.75,13.95C14.05,12.3 9.95,11.8 6.8,12.8C6.4,12.9 5.95,12.7 5.85,12.3C5.75,11.9 5.95,11.45 6.35,11.35C10,10.25 14.5,10.8 17.6,12.7C17.9,12.85 18.05,13.35 17.8,13.7M16.6,16.45C16.4,16.75 16.05,16.85 15.75,16.65C13.4,15.2 10.45,14.9 6.95,15.7C6.6,15.8 6.3,15.55 6.2,15.25C6.1,14.9 6.35,14.6 6.65,14.5C10.45,13.65 13.75,14 16.35,15.6C16.7,15.75 16.75,16.15 16.6,16.45M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" /></g><g id="spotlight"><path d="M2,6L7.09,8.55C6.4,9.5 6,10.71 6,12C6,13.29 6.4,14.5 7.09,15.45L2,18V6M6,3H18L15.45,7.09C14.5,6.4 13.29,6 12,6C10.71,6 9.5,6.4 8.55,7.09L6,3M22,6V18L16.91,15.45C17.6,14.5 18,13.29 18,12C18,10.71 17.6,9.5 16.91,8.55L22,6M18,21H6L8.55,16.91C9.5,17.6 10.71,18 12,18C13.29,18 14.5,17.6 15.45,16.91L18,21M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10Z" /></g><g id="spotlight-beam"><path d="M9,16.5L9.91,15.59L15.13,20.8L14.21,21.71L9,16.5M15.5,10L16.41,9.09L21.63,14.3L20.71,15.21L15.5,10M6.72,2.72L10.15,6.15L6.15,10.15L2.72,6.72C1.94,5.94 1.94,4.67 2.72,3.89L3.89,2.72C4.67,1.94 5.94,1.94 6.72,2.72M14.57,7.5L15.28,8.21L8.21,15.28L7.5,14.57L6.64,11.07L11.07,6.64L14.57,7.5Z" /></g><g id="spray"><path d="M10,4H12V6H10V4M7,3H9V5H7V3M7,6H9V8H7V6M6,8V10H4V8H6M6,5V7H4V5H6M6,2V4H4V2H6M13,22A2,2 0 0,1 11,20V10A2,2 0 0,1 13,8V7H14V4H17V7H18V8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H13M13,10V20H18V10H13Z" /></g><g id="square-inc"><path d="M6,3H18A3,3 0 0,1 21,6V18A3,3 0 0,1 18,21H6A3,3 0 0,1 3,18V6A3,3 0 0,1 6,3M7,6A1,1 0 0,0 6,7V17A1,1 0 0,0 7,18H17A1,1 0 0,0 18,17V7A1,1 0 0,0 17,6H7M9.5,9H14.5A0.5,0.5 0 0,1 15,9.5V14.5A0.5,0.5 0 0,1 14.5,15H9.5A0.5,0.5 0 0,1 9,14.5V9.5A0.5,0.5 0 0,1 9.5,9Z" /></g><g id="square-inc-cash"><path d="M5.5,0H18.5A5.5,5.5 0 0,1 24,5.5V18.5A5.5,5.5 0 0,1 18.5,24H5.5A5.5,5.5 0 0,1 0,18.5V5.5A5.5,5.5 0 0,1 5.5,0M15.39,15.18C15.39,16.76 14.5,17.81 12.85,17.95V12.61C14.55,13.13 15.39,13.66 15.39,15.18M11.65,6V10.88C10.34,10.5 9.03,9.93 9.03,8.43C9.03,6.94 10.18,6.12 11.65,6M15.5,7.6L16.5,6.8C15.62,5.66 14.4,4.92 12.85,4.77V3.8H11.65V3.8L11.65,4.75C9.5,4.89 7.68,6.17 7.68,8.5C7.68,11 9.74,11.78 11.65,12.29V17.96C10.54,17.84 9.29,17.31 8.43,16.03L7.3,16.78C8.2,18.12 9.76,19 11.65,19.14V20.2H12.07L12.85,20.2V19.16C15.35,19 16.7,17.34 16.7,15.14C16.7,12.58 14.81,11.76 12.85,11.19V6.05C14,6.22 14.85,6.76 15.5,7.6Z" /></g><g id="stackexchange"><path d="M4,14.04V11H20V14.04H4M4,10V7H20V10H4M17.46,2C18.86,2 20,3.18 20,4.63V6H4V4.63C4,3.18 5.14,2 6.54,2H17.46M4,15H20V16.35C20,17.81 18.86,19 17.46,19H16.5L13,22V19H6.54C5.14,19 4,17.81 4,16.35V15Z" /></g><g id="stackoverflow"><path d="M17.36,20.2V14.82H19.15V22H3V14.82H4.8V20.2H17.36M6.77,14.32L7.14,12.56L15.93,14.41L15.56,16.17L6.77,14.32M7.93,10.11L8.69,8.5L16.83,12.28L16.07,13.9L7.93,10.11M10.19,6.12L11.34,4.74L18.24,10.5L17.09,11.87L10.19,6.12M14.64,1.87L20,9.08L18.56,10.15L13.2,2.94L14.64,1.87M6.59,18.41V16.61H15.57V18.41H6.59Z" /></g><g id="stairs"><path d="M15,5V9H11V13H7V17H3V20H10V16H14V12H18V8H22V5H15Z" /></g><g id="star"><path d="M12,17.27L18.18,21L16.54,13.97L22,9.24L14.81,8.62L12,2L9.19,8.62L2,9.24L7.45,13.97L5.82,21L12,17.27Z" /></g><g id="star-circle"><path d="M16.23,18L12,15.45L7.77,18L8.89,13.19L5.16,9.96L10.08,9.54L12,5L13.92,9.53L18.84,9.95L15.11,13.18L16.23,18M12,2C6.47,2 2,6.5 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" /></g><g id="star-half"><path d="M12,15.89V6.59L13.71,10.63L18.09,11L14.77,13.88L15.76,18.16M22,9.74L14.81,9.13L12,2.5L9.19,9.13L2,9.74L7.45,14.47L5.82,21.5L12,17.77L18.18,21.5L16.54,14.47L22,9.74Z" /></g><g id="star-off"><path d="M2,5.27L3.28,4L20,20.72L18.73,22L17.05,20.31L12,17.27L5.82,21L7.45,13.97L2,9.24L5.66,8.93L2,5.27M12,2L14.81,8.62L22,9.24L16.54,13.97L16.77,14.95L9.56,7.74L12,2Z" /></g><g id="star-outline"><path d="M12,15.39L8.24,17.66L9.23,13.38L5.91,10.5L10.29,10.13L12,6.09L13.71,10.13L18.09,10.5L14.77,13.38L15.76,17.66M22,9.24L14.81,8.63L12,2L9.19,8.63L2,9.24L7.45,13.97L5.82,21L12,17.27L18.18,21L16.54,13.97L22,9.24Z" /></g><g id="steam"><path d="M20.14,7.79C21.33,7.79 22.29,8.75 22.29,9.93C22.29,11.11 21.33,12.07 20.14,12.07A2.14,2.14 0 0,1 18,9.93C18,8.75 18.96,7.79 20.14,7.79M3,6.93A3,3 0 0,1 6,9.93V10.24L12.33,13.54C12.84,13.15 13.46,12.93 14.14,12.93L16.29,9.93C16.29,7.8 18,6.07 20.14,6.07A3.86,3.86 0 0,1 24,9.93A3.86,3.86 0 0,1 20.14,13.79L17.14,15.93A3,3 0 0,1 14.14,18.93C12.5,18.93 11.14,17.59 11.14,15.93C11.14,15.89 11.14,15.85 11.14,15.82L4.64,12.44C4.17,12.75 3.6,12.93 3,12.93A3,3 0 0,1 0,9.93A3,3 0 0,1 3,6.93M15.03,14.94C15.67,15.26 15.92,16.03 15.59,16.67C15.27,17.3 14.5,17.55 13.87,17.23L12.03,16.27C12.19,17.29 13.08,18.07 14.14,18.07C15.33,18.07 16.29,17.11 16.29,15.93C16.29,14.75 15.33,13.79 14.14,13.79C13.81,13.79 13.5,13.86 13.22,14L15.03,14.94M3,7.79C1.82,7.79 0.86,8.75 0.86,9.93C0.86,11.11 1.82,12.07 3,12.07C3.24,12.07 3.5,12.03 3.7,11.95L2.28,11.22C1.64,10.89 1.39,10.12 1.71,9.5C2.04,8.86 2.81,8.6 3.44,8.93L5.14,9.81C5.08,8.68 4.14,7.79 3,7.79M20.14,6.93C18.5,6.93 17.14,8.27 17.14,9.93A3,3 0 0,0 20.14,12.93A3,3 0 0,0 23.14,9.93A3,3 0 0,0 20.14,6.93Z" /></g><g id="steering"><path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4C16.1,4 19.5,7.1 20,11H17C16.5,9.9 14.4,9 12,9C9.6,9 7.5,9.9 7,11H4C4.5,7.1 7.9,4 12,4M4,13H7C7.2,14.3 8.2,16.6 11,17V20C7.4,19.6 4.4,16.6 4,13M13,20V17C15.8,16.6 16.7,14.3 17,13H20C19.6,16.6 16.6,19.6 13,20Z" /></g><g id="step-backward"><path d="M19,5V19H16V5M14,5V19L3,12" /></g><g id="step-backward-2"><path d="M17,5H14V19H17V5M12,5L1,12L12,19V5M22,5H19V19H22V5Z" /></g><g id="step-forward"><path d="M5,5V19H8V5M10,5V19L21,12" /></g><g id="step-forward-2"><path d="M7,5H10V19H7V5M12,5L23,12L12,19V5M2,5H5V19H2V5Z" /></g><g id="stethoscope"><path d="M19,8C19.56,8 20,8.43 20,9A1,1 0 0,1 19,10C18.43,10 18,9.55 18,9C18,8.43 18.43,8 19,8M2,2V11C2,13.96 4.19,16.5 7.14,16.91C7.76,19.92 10.42,22 13.5,22A6.5,6.5 0 0,0 20,15.5V11.81C21.16,11.39 22,10.29 22,9A3,3 0 0,0 19,6A3,3 0 0,0 16,9C16,10.29 16.84,11.4 18,11.81V15.41C18,17.91 16,19.91 13.5,19.91C11.5,19.91 9.82,18.7 9.22,16.9C12,16.3 14,13.8 14,11V2H10V5H12V11A4,4 0 0,1 8,15A4,4 0 0,1 4,11V5H6V2H2Z" /></g><g id="sticker"><path d="M12.12,18.46L18.3,12.28C16.94,12.59 15.31,13.2 14.07,14.46C13.04,15.5 12.39,16.83 12.12,18.46M20.75,10H21.05C21.44,10 21.79,10.27 21.93,10.64C22.07,11 22,11.43 21.7,11.71L11.7,21.71C11.5,21.9 11.26,22 11,22L10.64,21.93C10.27,21.79 10,21.44 10,21.05C9.84,17.66 10.73,14.96 12.66,13.03C15.5,10.2 19.62,10 20.75,10M12,2C16.5,2 20.34,5 21.58,9.11L20,9H19.42C18.24,6.07 15.36,4 12,4A8,8 0 0,0 4,12C4,15.36 6.07,18.24 9,19.42C8.97,20.13 9,20.85 9.11,21.57C5,20.33 2,16.5 2,12C2,6.47 6.5,2 12,2Z" /></g><g id="stocking"><path d="M17,2A2,2 0 0,1 19,4V7A2,2 0 0,1 17,9V17C17,17.85 16.5,18.57 15.74,18.86L9.5,21.77C8.5,22.24 7.29,21.81 6.83,20.81L6,19C5.5,18 5.95,16.8 6.95,16.34L10,14.91V9A2,2 0 0,1 8,7V4A2,2 0 0,1 10,2H17M10,4V7H17V4H10Z" /></g><g id="stop"><path d="M18,18H6V6H18V18Z" /></g><g id="stop-circle"><path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M9,9H15V15H9" /></g><g id="stop-circle-outline"><path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4M9,9V15H15V9" /></g><g id="store"><path d="M12,18H6V14H12M21,14V12L20,7H4L3,12V14H4V20H14V14H18V20H20V14M20,4H4V6H20V4Z" /></g><g id="store-24-hour"><path d="M16,12H15V10H13V7H14V9H15V7H16M11,10H9V11H11V12H8V9H10V8H8V7H11M19,7V4H5V7H2V20H10V16H14V20H22V7H19Z" /></g><g id="stove"><path d="M6,14H8L11,17H9L6,14M4,4H5V3A1,1 0 0,1 6,2H10A1,1 0 0,1 11,3V4H13V3A1,1 0 0,1 14,2H18A1,1 0 0,1 19,3V4H20A2,2 0 0,1 22,6V19A2,2 0 0,1 20,21V22H17V21H7V22H4V21A2,2 0 0,1 2,19V6A2,2 0 0,1 4,4M18,7A1,1 0 0,1 19,8A1,1 0 0,1 18,9A1,1 0 0,1 17,8A1,1 0 0,1 18,7M14,7A1,1 0 0,1 15,8A1,1 0 0,1 14,9A1,1 0 0,1 13,8A1,1 0 0,1 14,7M20,6H4V10H20V6M4,19H20V12H4V19M6,7A1,1 0 0,1 7,8A1,1 0 0,1 6,9A1,1 0 0,1 5,8A1,1 0 0,1 6,7M13,14H15L18,17H16L13,14Z" /></g><g id="subdirectory-arrow-left"><path d="M11,9L12.42,10.42L8.83,14H18V4H20V16H8.83L12.42,19.58L11,21L5,15L11,9Z" /></g><g id="subdirectory-arrow-right"><path d="M19,15L13,21L11.58,19.58L15.17,16H4V4H6V14H15.17L11.58,10.42L13,9L19,15Z" /></g><g id="subway"><path d="M8.5,15A1,1 0 0,1 9.5,16A1,1 0 0,1 8.5,17A1,1 0 0,1 7.5,16A1,1 0 0,1 8.5,15M7,9H17V14H7V9M15.5,15A1,1 0 0,1 16.5,16A1,1 0 0,1 15.5,17A1,1 0 0,1 14.5,16A1,1 0 0,1 15.5,15M18,15.88V9C18,6.38 15.32,6 12,6C9,6 6,6.37 6,9V15.88A2.62,2.62 0 0,0 8.62,18.5L7.5,19.62V20H9.17L10.67,18.5H13.5L15,20H16.5V19.62L15.37,18.5C16.82,18.5 18,17.33 18,15.88M17.8,2.8C20.47,3.84 22,6.05 22,8.86V22H2V8.86C2,6.05 3.53,3.84 6.2,2.8C8,2.09 10.14,2 12,2C13.86,2 16,2.09 17.8,2.8Z" /></g><g id="subway-variant"><path d="M18,11H13V6H18M16.5,17A1.5,1.5 0 0,1 15,15.5A1.5,1.5 0 0,1 16.5,14A1.5,1.5 0 0,1 18,15.5A1.5,1.5 0 0,1 16.5,17M11,11H6V6H11M7.5,17A1.5,1.5 0 0,1 6,15.5A1.5,1.5 0 0,1 7.5,14A1.5,1.5 0 0,1 9,15.5A1.5,1.5 0 0,1 7.5,17M12,2C7.58,2 4,2.5 4,6V15.5A3.5,3.5 0 0,0 7.5,19L6,20.5V21H18V20.5L16.5,19A3.5,3.5 0 0,0 20,15.5V6C20,2.5 16.42,2 12,2Z" /></g><g id="sunglasses"><path d="M7,17H4C2.38,17 0.96,15.74 0.76,14.14L0.26,11.15C0.15,10.3 0.39,9.5 0.91,8.92C1.43,8.34 2.19,8 3,8H9C9.83,8 10.58,8.35 11.06,8.96C11.17,9.11 11.27,9.27 11.35,9.45C11.78,9.36 12.22,9.36 12.64,9.45C12.72,9.27 12.82,9.11 12.94,8.96C13.41,8.35 14.16,8 15,8H21C21.81,8 22.57,8.34 23.09,8.92C23.6,9.5 23.84,10.3 23.74,11.11L23.23,14.18C23.04,15.74 21.61,17 20,17H17C15.44,17 13.92,15.81 13.54,14.3L12.64,11.59C12.26,11.31 11.73,11.31 11.35,11.59L10.43,14.37C10.07,15.82 8.56,17 7,17Z" /></g><g id="surround-sound"><path d="M20,4H4A2,2 0 0,0 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V6A2,2 0 0,0 20,4M7.76,16.24L6.35,17.65C4.78,16.1 4,14.05 4,12C4,9.95 4.78,7.9 6.34,6.34L7.75,7.75C6.59,8.93 6,10.46 6,12C6,13.54 6.59,15.07 7.76,16.24M12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16M17.66,17.66L16.25,16.25C17.41,15.07 18,13.54 18,12C18,10.46 17.41,8.93 16.24,7.76L17.65,6.35C19.22,7.9 20,9.95 20,12C20,14.05 19.22,16.1 17.66,17.66M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10Z" /></g><g id="swap-horizontal"><path d="M21,9L17,5V8H10V10H17V13M7,11L3,15L7,19V16H14V14H7V11Z" /></g><g id="swap-vertical"><path d="M9,3L5,7H8V14H10V7H13M16,17V10H14V17H11L15,21L19,17H16Z" /></g><g id="swim"><path d="M2,18C4.22,17 6.44,16 8.67,16C10.89,16 13.11,18 15.33,18C17.56,18 19.78,16 22,16V19C19.78,19 17.56,21 15.33,21C13.11,21 10.89,19 8.67,19C6.44,19 4.22,20 2,21V18M8.67,13C7.89,13 7.12,13.12 6.35,13.32L11.27,9.88L10.23,8.64C10.09,8.47 10,8.24 10,8C10,7.66 10.17,7.35 10.44,7.17L16.16,3.17L17.31,4.8L12.47,8.19L17.7,14.42C16.91,14.75 16.12,15 15.33,15C13.11,15 10.89,13 8.67,13M18,7A2,2 0 0,1 20,9A2,2 0 0,1 18,11A2,2 0 0,1 16,9A2,2 0 0,1 18,7Z" /></g><g id="switch"><path d="M13,18H14A1,1 0 0,1 15,19H22V21H15A1,1 0 0,1 14,22H10A1,1 0 0,1 9,21H2V19H9A1,1 0 0,1 10,18H11V16H8A1,1 0 0,1 7,15V3A1,1 0 0,1 8,2H16A1,1 0 0,1 17,3V15A1,1 0 0,1 16,16H13V18M13,6H14V4H13V6M9,4V6H11V4H9M9,8V10H11V8H9M9,12V14H11V12H9Z" /></g><g id="sword"><path d="M6.92,5H5L14,14L15,13.06M19.96,19.12L19.12,19.96C18.73,20.35 18.1,20.35 17.71,19.96L14.59,16.84L11.91,19.5L10.5,18.09L11.92,16.67L3,7.75V3H7.75L16.67,11.92L18.09,10.5L19.5,11.91L16.83,14.58L19.95,17.7C20.35,18.1 20.35,18.73 19.96,19.12Z" /></g><g id="sync"><path d="M12,18A6,6 0 0,1 6,12C6,11 6.25,10.03 6.7,9.2L5.24,7.74C4.46,8.97 4,10.43 4,12A8,8 0 0,0 12,20V23L16,19L12,15M12,4V1L8,5L12,9V6A6,6 0 0,1 18,12C18,13 17.75,13.97 17.3,14.8L18.76,16.26C19.54,15.03 20,13.57 20,12A8,8 0 0,0 12,4Z" /></g><g id="sync-alert"><path d="M11,13H13V7H11M21,4H15V10L17.24,7.76C18.32,8.85 19,10.34 19,12C19,14.61 17.33,16.83 15,17.65V19.74C18.45,18.85 21,15.73 21,12C21,9.79 20.09,7.8 18.64,6.36M11,17H13V15H11M3,12C3,14.21 3.91,16.2 5.36,17.64L3,20H9V14L6.76,16.24C5.68,15.15 5,13.66 5,12C5,9.39 6.67,7.17 9,6.35V4.26C5.55,5.15 3,8.27 3,12Z" /></g><g id="sync-off"><path d="M20,4H14V10L16.24,7.76C17.32,8.85 18,10.34 18,12C18,13 17.75,13.94 17.32,14.77L18.78,16.23C19.55,15 20,13.56 20,12C20,9.79 19.09,7.8 17.64,6.36L20,4M2.86,5.41L5.22,7.77C4.45,9 4,10.44 4,12C4,14.21 4.91,16.2 6.36,17.64L4,20H10V14L7.76,16.24C6.68,15.15 6,13.66 6,12C6,11 6.25,10.06 6.68,9.23L14.76,17.31C14.5,17.44 14.26,17.56 14,17.65V19.74C14.79,19.53 15.54,19.2 16.22,18.78L18.58,21.14L19.85,19.87L4.14,4.14L2.86,5.41M10,6.35V4.26C9.2,4.47 8.45,4.8 7.77,5.22L9.23,6.68C9.5,6.56 9.73,6.44 10,6.35Z" /></g><g id="tab"><path d="M19,19H5V5H12V9H19M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z" /></g><g id="tab-unselected"><path d="M15,21H17V19H15M11,21H13V19H11M19,13H21V11H19M19,21A2,2 0 0,0 21,19H19M7,5H9V3H7M19,17H21V15H19M19,3H11V9H21V5C21,3.89 20.1,3 19,3M5,21V19H3A2,2 0 0,0 5,21M3,17H5V15H3M7,21H9V19H7M3,5H5V3C3.89,3 3,3.89 3,5M3,13H5V11H3M3,9H5V7H3V9Z" /></g><g id="table"><path d="M5,4H19A2,2 0 0,1 21,6V18A2,2 0 0,1 19,20H5A2,2 0 0,1 3,18V6A2,2 0 0,1 5,4M5,8V12H11V8H5M13,8V12H19V8H13M5,14V18H11V14H5M13,14V18H19V14H13Z" /></g><g id="table-column-plus-after"><path d="M11,2A2,2 0 0,1 13,4V20A2,2 0 0,1 11,22H2V2H11M4,10V14H11V10H4M4,16V20H11V16H4M4,4V8H11V4H4M15,11H18V8H20V11H23V13H20V16H18V13H15V11Z" /></g><g id="table-column-plus-before"><path d="M13,2A2,2 0 0,0 11,4V20A2,2 0 0,0 13,22H22V2H13M20,10V14H13V10H20M20,16V20H13V16H20M20,4V8H13V4H20M9,11H6V8H4V11H1V13H4V16H6V13H9V11Z" /></g><g id="table-column-remove"><path d="M4,2H11A2,2 0 0,1 13,4V20A2,2 0 0,1 11,22H4A2,2 0 0,1 2,20V4A2,2 0 0,1 4,2M4,10V14H11V10H4M4,16V20H11V16H4M4,4V8H11V4H4M17.59,12L15,9.41L16.41,8L19,10.59L21.59,8L23,9.41L20.41,12L23,14.59L21.59,16L19,13.41L16.41,16L15,14.59L17.59,12Z" /></g><g id="table-column-width"><path d="M5,8H19A2,2 0 0,1 21,10V20A2,2 0 0,1 19,22H5A2,2 0 0,1 3,20V10A2,2 0 0,1 5,8M5,12V15H11V12H5M13,12V15H19V12H13M5,17V20H11V17H5M13,17V20H19V17H13M11,2H21V6H19V4H13V6H11V2Z" /></g><g id="table-edit"><path d="M21.7,13.35L20.7,14.35L18.65,12.3L19.65,11.3C19.86,11.08 20.21,11.08 20.42,11.3L21.7,12.58C21.92,12.79 21.92,13.14 21.7,13.35M12,18.94L18.07,12.88L20.12,14.93L14.06,21H12V18.94M4,2H18A2,2 0 0,1 20,4V8.17L16.17,12H12V16.17L10.17,18H4A2,2 0 0,1 2,16V4A2,2 0 0,1 4,2M4,6V10H10V6H4M12,6V10H18V6H12M4,12V16H10V12H4Z" /></g><g id="table-large"><path d="M4,3H20A2,2 0 0,1 22,5V20A2,2 0 0,1 20,22H4A2,2 0 0,1 2,20V5A2,2 0 0,1 4,3M4,7V10H8V7H4M10,7V10H14V7H10M20,10V7H16V10H20M4,12V15H8V12H4M4,20H8V17H4V20M10,12V15H14V12H10M10,20H14V17H10V20M20,20V17H16V20H20M20,12H16V15H20V12Z" /></g><g id="table-row-height"><path d="M3,5H15A2,2 0 0,1 17,7V17A2,2 0 0,1 15,19H3A2,2 0 0,1 1,17V7A2,2 0 0,1 3,5M3,9V12H8V9H3M10,9V12H15V9H10M3,14V17H8V14H3M10,14V17H15V14H10M23,14V7H19V9H21V12H19V14H23Z" /></g><g id="table-row-plus-after"><path d="M22,10A2,2 0 0,1 20,12H4A2,2 0 0,1 2,10V3H4V5H8V3H10V5H14V3H16V5H20V3H22V10M4,10H8V7H4V10M10,10H14V7H10V10M20,10V7H16V10H20M11,14H13V17H16V19H13V22H11V19H8V17H11V14Z" /></g><g id="table-row-plus-before"><path d="M22,14A2,2 0 0,0 20,12H4A2,2 0 0,0 2,14V21H4V19H8V21H10V19H14V21H16V19H20V21H22V14M4,14H8V17H4V14M10,14H14V17H10V14M20,14V17H16V14H20M11,10H13V7H16V5H13V2H11V5H8V7H11V10Z" /></g><g id="table-row-remove"><path d="M9.41,13L12,15.59L14.59,13L16,14.41L13.41,17L16,19.59L14.59,21L12,18.41L9.41,21L8,19.59L10.59,17L8,14.41L9.41,13M22,9A2,2 0 0,1 20,11H4A2,2 0 0,1 2,9V6A2,2 0 0,1 4,4H20A2,2 0 0,1 22,6V9M4,9H8V6H4V9M10,9H14V6H10V9M16,9H20V6H16V9Z" /></g><g id="tablet"><path d="M19,18H5V6H19M21,4H3C1.89,4 1,4.89 1,6V18A2,2 0 0,0 3,20H21A2,2 0 0,0 23,18V6C23,4.89 22.1,4 21,4Z" /></g><g id="tablet-android"><path d="M19.25,19H4.75V3H19.25M14,22H10V21H14M18,0H6A3,3 0 0,0 3,3V21A3,3 0 0,0 6,24H18A3,3 0 0,0 21,21V3A3,3 0 0,0 18,0Z" /></g><g id="tablet-ipad"><path d="M19,19H4V3H19M11.5,23A1.5,1.5 0 0,1 10,21.5A1.5,1.5 0 0,1 11.5,20A1.5,1.5 0 0,1 13,21.5A1.5,1.5 0 0,1 11.5,23M18.5,0H4.5A2.5,2.5 0 0,0 2,2.5V21.5A2.5,2.5 0 0,0 4.5,24H18.5A2.5,2.5 0 0,0 21,21.5V2.5A2.5,2.5 0 0,0 18.5,0Z" /></g><g id="tag"><path d="M5.5,7A1.5,1.5 0 0,1 4,5.5A1.5,1.5 0 0,1 5.5,4A1.5,1.5 0 0,1 7,5.5A1.5,1.5 0 0,1 5.5,7M21.41,11.58L12.41,2.58C12.05,2.22 11.55,2 11,2H4C2.89,2 2,2.89 2,4V11C2,11.55 2.22,12.05 2.59,12.41L11.58,21.41C11.95,21.77 12.45,22 13,22C13.55,22 14.05,21.77 14.41,21.41L21.41,14.41C21.78,14.05 22,13.55 22,13C22,12.44 21.77,11.94 21.41,11.58Z" /></g><g id="tag-faces"><path d="M15,18C11.68,18 9,15.31 9,12C9,8.68 11.68,6 15,6A6,6 0 0,1 21,12A6,6 0 0,1 15,18M4,13A1,1 0 0,1 3,12A1,1 0 0,1 4,11A1,1 0 0,1 5,12A1,1 0 0,1 4,13M22,3H7.63C6.97,3 6.38,3.32 6,3.81L0,12L6,20.18C6.38,20.68 6.97,21 7.63,21H22A2,2 0 0,0 24,19V5C24,3.89 23.1,3 22,3M13,11A1,1 0 0,0 14,10A1,1 0 0,0 13,9A1,1 0 0,0 12,10A1,1 0 0,0 13,11M15,16C16.86,16 18.35,14.72 18.8,13H11.2C11.65,14.72 13.14,16 15,16M17,11A1,1 0 0,0 18,10A1,1 0 0,0 17,9A1,1 0 0,0 16,10A1,1 0 0,0 17,11Z" /></g><g id="tag-heart"><path d="M21.41,11.58L12.41,2.58C12.05,2.22 11.55,2 11,2H4A2,2 0 0,0 2,4V11C2,11.55 2.22,12.05 2.59,12.42L11.59,21.42C11.95,21.78 12.45,22 13,22C13.55,22 14.05,21.78 14.41,21.41L21.41,14.41C21.78,14.05 22,13.55 22,13C22,12.45 21.77,11.94 21.41,11.58M5.5,7A1.5,1.5 0 0,1 4,5.5A1.5,1.5 0 0,1 5.5,4A1.5,1.5 0 0,1 7,5.5A1.5,1.5 0 0,1 5.5,7M17.27,15.27L13,19.54L8.73,15.27C8.28,14.81 8,14.19 8,13.5A2.5,2.5 0 0,1 10.5,11C11.19,11 11.82,11.28 12.27,11.74L13,12.46L13.73,11.73C14.18,11.28 14.81,11 15.5,11A2.5,2.5 0 0,1 18,13.5C18,14.19 17.72,14.82 17.27,15.27Z" /></g><g id="tag-multiple"><path d="M5.5,9A1.5,1.5 0 0,0 7,7.5A1.5,1.5 0 0,0 5.5,6A1.5,1.5 0 0,0 4,7.5A1.5,1.5 0 0,0 5.5,9M17.41,11.58C17.77,11.94 18,12.44 18,13C18,13.55 17.78,14.05 17.41,14.41L12.41,19.41C12.05,19.77 11.55,20 11,20C10.45,20 9.95,19.78 9.58,19.41L2.59,12.42C2.22,12.05 2,11.55 2,11V6C2,4.89 2.89,4 4,4H9C9.55,4 10.05,4.22 10.41,4.58L17.41,11.58M13.54,5.71L14.54,4.71L21.41,11.58C21.78,11.94 22,12.45 22,13C22,13.55 21.78,14.05 21.42,14.41L16.04,19.79L15.04,18.79L20.75,13L13.54,5.71Z" /></g><g id="tag-outline"><path d="M5.5,7A1.5,1.5 0 0,0 7,5.5A1.5,1.5 0 0,0 5.5,4A1.5,1.5 0 0,0 4,5.5A1.5,1.5 0 0,0 5.5,7M21.41,11.58C21.77,11.94 22,12.44 22,13C22,13.55 21.78,14.05 21.41,14.41L14.41,21.41C14.05,21.77 13.55,22 13,22C12.45,22 11.95,21.77 11.58,21.41L2.59,12.41C2.22,12.05 2,11.55 2,11V4C2,2.89 2.89,2 4,2H11C11.55,2 12.05,2.22 12.41,2.58L21.41,11.58M13,20L20,13L11.5,4.5L4.5,11.5L13,20Z" /></g><g id="tag-text-outline"><path d="M5.5,7A1.5,1.5 0 0,0 7,5.5A1.5,1.5 0 0,0 5.5,4A1.5,1.5 0 0,0 4,5.5A1.5,1.5 0 0,0 5.5,7M21.41,11.58C21.77,11.94 22,12.44 22,13C22,13.55 21.78,14.05 21.41,14.41L14.41,21.41C14.05,21.77 13.55,22 13,22C12.45,22 11.95,21.77 11.58,21.41L2.59,12.41C2.22,12.05 2,11.55 2,11V4C2,2.89 2.89,2 4,2H11C11.55,2 12.05,2.22 12.41,2.58L21.41,11.58M13,20L20,13L11.5,4.5L4.5,11.5L13,20M10.09,8.91L11.5,7.5L17,13L15.59,14.41L10.09,8.91M7.59,11.41L9,10L13,14L11.59,15.41L7.59,11.41Z" /></g><g id="target"><path d="M11,2V4.07C7.38,4.53 4.53,7.38 4.07,11H2V13H4.07C4.53,16.62 7.38,19.47 11,19.93V22H13V19.93C16.62,19.47 19.47,16.62 19.93,13H22V11H19.93C19.47,7.38 16.62,4.53 13,4.07V2M11,6.08V8H13V6.09C15.5,6.5 17.5,8.5 17.92,11H16V13H17.91C17.5,15.5 15.5,17.5 13,17.92V16H11V17.91C8.5,17.5 6.5,15.5 6.08,13H8V11H6.09C6.5,8.5 8.5,6.5 11,6.08M12,11A1,1 0 0,0 11,12A1,1 0 0,0 12,13A1,1 0 0,0 13,12A1,1 0 0,0 12,11Z" /></g><g id="taxi"><path d="M5,11L6.5,6.5H17.5L19,11M17.5,16A1.5,1.5 0 0,1 16,14.5A1.5,1.5 0 0,1 17.5,13A1.5,1.5 0 0,1 19,14.5A1.5,1.5 0 0,1 17.5,16M6.5,16A1.5,1.5 0 0,1 5,14.5A1.5,1.5 0 0,1 6.5,13A1.5,1.5 0 0,1 8,14.5A1.5,1.5 0 0,1 6.5,16M18.92,6C18.72,5.42 18.16,5 17.5,5H15V3H9V5H6.5C5.84,5 5.28,5.42 5.08,6L3,12V20A1,1 0 0,0 4,21H5A1,1 0 0,0 6,20V19H18V20A1,1 0 0,0 19,21H20A1,1 0 0,0 21,20V12L18.92,6Z" /></g><g id="teamviewer"><path d="M19,3A2,2 0 0,1 21,5V19C21,20.11 20.1,21 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3H19M12,5A7,7 0 0,0 5,12A7,7 0 0,0 12,19A7,7 0 0,0 19,12A7,7 0 0,0 12,5M7,12L10,9V11H14V9L17,12L14,15V13H10V15L7,12Z" /></g><g id="telegram"><path d="M9.78,18.65L10.06,14.42L17.74,7.5C18.08,7.19 17.67,7.04 17.22,7.31L7.74,13.3L3.64,12C2.76,11.75 2.75,11.14 3.84,10.7L19.81,4.54C20.54,4.21 21.24,4.72 20.96,5.84L18.24,18.65C18.05,19.56 17.5,19.78 16.74,19.36L12.6,16.3L10.61,18.23C10.38,18.46 10.19,18.65 9.78,18.65Z" /></g><g id="television"><path d="M20,17H4V5H20M20,3H4C2.89,3 2,3.89 2,5V17A2,2 0 0,0 4,19H8V21H16V19H20A2,2 0 0,0 22,17V5C22,3.89 21.1,3 20,3Z" /></g><g id="television-guide"><path d="M21,17V5H3V17H21M21,3A2,2 0 0,1 23,5V17A2,2 0 0,1 21,19H16V21H8V19H3A2,2 0 0,1 1,17V5A2,2 0 0,1 3,3H21M5,7H11V11H5V7M5,13H11V15H5V13M13,7H19V9H13V7M13,11H19V15H13V11Z" /></g><g id="temperature-celsius"><path d="M16.5,5C18.05,5 19.5,5.47 20.69,6.28L19.53,9.17C18.73,8.44 17.67,8 16.5,8C14,8 12,10 12,12.5C12,15 14,17 16.5,17C17.53,17 18.47,16.66 19.23,16.08L20.37,18.93C19.24,19.61 17.92,20 16.5,20A7.5,7.5 0 0,1 9,12.5A7.5,7.5 0 0,1 16.5,5M6,3A3,3 0 0,1 9,6A3,3 0 0,1 6,9A3,3 0 0,1 3,6A3,3 0 0,1 6,3M6,5A1,1 0 0,0 5,6A1,1 0 0,0 6,7A1,1 0 0,0 7,6A1,1 0 0,0 6,5Z" /></g><g id="temperature-fahrenheit"><path d="M11,20V5H20V8H14V11H19V14H14V20H11M6,3A3,3 0 0,1 9,6A3,3 0 0,1 6,9A3,3 0 0,1 3,6A3,3 0 0,1 6,3M6,5A1,1 0 0,0 5,6A1,1 0 0,0 6,7A1,1 0 0,0 7,6A1,1 0 0,0 6,5Z" /></g><g id="temperature-kelvin"><path d="M7,5H10V11L15,5H19L13.88,10.78L19,20H15.38L11.76,13.17L10,15.15V20H7V5Z" /></g><g id="tennis"><path d="M12,2C14.5,2 16.75,2.9 18.5,4.4C16.36,6.23 15,8.96 15,12C15,15.04 16.36,17.77 18.5,19.6C16.75,21.1 14.5,22 12,22C9.5,22 7.25,21.1 5.5,19.6C7.64,17.77 9,15.04 9,12C9,8.96 7.64,6.23 5.5,4.4C7.25,2.9 9.5,2 12,2M22,12C22,14.32 21.21,16.45 19.88,18.15C18.12,16.68 17,14.47 17,12C17,9.53 18.12,7.32 19.88,5.85C21.21,7.55 22,9.68 22,12M2,12C2,9.68 2.79,7.55 4.12,5.85C5.88,7.32 7,9.53 7,12C7,14.47 5.88,16.68 4.12,18.15C2.79,16.45 2,14.32 2,12Z" /></g><g id="tent"><path d="M4,6C4,7.19 4.39,8.27 5,9A3,3 0 0,1 2,6A3,3 0 0,1 5,3C4.39,3.73 4,4.81 4,6M2,21V19H4.76L12,4.78L19.24,19H22V21H2M12,9.19L7,19H17L12,9.19Z" /></g><g id="terrain"><path d="M14,6L10.25,11L13.1,14.8L11.5,16C9.81,13.75 7,10 7,10L1,18H23L14,6Z" /></g><g id="test-tube"><path d="M7,2V4H8V18A4,4 0 0,0 12,22A4,4 0 0,0 16,18V4H17V2H7M11,16C10.4,16 10,15.6 10,15C10,14.4 10.4,14 11,14C11.6,14 12,14.4 12,15C12,15.6 11.6,16 11,16M13,12C12.4,12 12,11.6 12,11C12,10.4 12.4,10 13,10C13.6,10 14,10.4 14,11C14,11.6 13.6,12 13,12M14,7H10V4H14V7Z" /></g><g id="text-shadow"><path d="M3,3H16V6H11V18H8V6H3V3M12,7H14V9H12V7M15,7H17V9H15V7M18,7H20V9H18V7M12,10H14V12H12V10M12,13H14V15H12V13M12,16H14V18H12V16M12,19H14V21H12V19Z" /></g><g id="text-to-speech"><path d="M8,7A2,2 0 0,1 10,9V14A2,2 0 0,1 8,16A2,2 0 0,1 6,14V9A2,2 0 0,1 8,7M14,14C14,16.97 11.84,19.44 9,19.92V22H7V19.92C4.16,19.44 2,16.97 2,14H4A4,4 0 0,0 8,18A4,4 0 0,0 12,14H14M21.41,9.41L17.17,13.66L18.18,10H14A2,2 0 0,1 12,8V4A2,2 0 0,1 14,2H20A2,2 0 0,1 22,4V8C22,8.55 21.78,9.05 21.41,9.41Z" /></g><g id="text-to-speech-off"><path d="M2,5.27L3.28,4L20,20.72L18.73,22L13.38,16.65C12.55,18.35 10.93,19.59 9,19.92V22H7V19.92C4.16,19.44 2,16.97 2,14H4A4,4 0 0,0 8,18C9.82,18 11.36,16.78 11.84,15.11L10,13.27V14A2,2 0 0,1 8,16A2,2 0 0,1 6,14V9.27L2,5.27M21.41,9.41L17.17,13.66L18.18,10H14A2,2 0 0,1 12,8V4A2,2 0 0,1 14,2H20A2,2 0 0,1 22,4V8C22,8.55 21.78,9.05 21.41,9.41Z" /></g><g id="textbox"><path d="M17,7H22V17H17V19A1,1 0 0,0 18,20H20V22H17.5C16.95,22 16,21.55 16,21C16,21.55 15.05,22 14.5,22H12V20H14A1,1 0 0,0 15,19V5A1,1 0 0,0 14,4H12V2H14.5C15.05,2 16,2.45 16,3C16,2.45 16.95,2 17.5,2H20V4H18A1,1 0 0,0 17,5V7M2,7H13V9H4V15H13V17H2V7M20,15V9H17V15H20Z" /></g><g id="texture"><path d="M9.29,21H12.12L21,12.12V9.29M19,21C19.55,21 20.05,20.78 20.41,20.41C20.78,20.05 21,19.55 21,19V17L17,21M5,3A2,2 0 0,0 3,5V7L7,3M11.88,3L3,11.88V14.71L14.71,3M19.5,3.08L3.08,19.5C3.17,19.85 3.35,20.16 3.59,20.41C3.84,20.65 4.15,20.83 4.5,20.92L20.93,4.5C20.74,3.8 20.2,3.26 19.5,3.08Z" /></g><g id="theater"><path d="M4,15H6A2,2 0 0,1 8,17V19H9V17A2,2 0 0,1 11,15H13A2,2 0 0,1 15,17V19H16V17A2,2 0 0,1 18,15H20A2,2 0 0,1 22,17V19H23V22H1V19H2V17A2,2 0 0,1 4,15M11,7L15,10L11,13V7M4,2H20A2,2 0 0,1 22,4V13.54C21.41,13.19 20.73,13 20,13V4H4V13C3.27,13 2.59,13.19 2,13.54V4A2,2 0 0,1 4,2Z" /></g><g id="theme-light-dark"><path d="M7.5,2C5.71,3.15 4.5,5.18 4.5,7.5C4.5,9.82 5.71,11.85 7.53,13C4.46,13 2,10.54 2,7.5A5.5,5.5 0 0,1 7.5,2M19.07,3.5L20.5,4.93L4.93,20.5L3.5,19.07L19.07,3.5M12.89,5.93L11.41,5L9.97,6L10.39,4.3L9,3.24L10.75,3.12L11.33,1.47L12,3.1L13.73,3.13L12.38,4.26L12.89,5.93M9.59,9.54L8.43,8.81L7.31,9.59L7.65,8.27L6.56,7.44L7.92,7.35L8.37,6.06L8.88,7.33L10.24,7.36L9.19,8.23L9.59,9.54M19,13.5A5.5,5.5 0 0,1 13.5,19C12.28,19 11.15,18.6 10.24,17.93L17.93,10.24C18.6,11.15 19,12.28 19,13.5M14.6,20.08L17.37,18.93L17.13,22.28L14.6,20.08M18.93,17.38L20.08,14.61L22.28,17.15L18.93,17.38M20.08,12.42L18.94,9.64L22.28,9.88L20.08,12.42M9.63,18.93L12.4,20.08L9.87,22.27L9.63,18.93Z" /></g><g id="thermometer"><path d="M17,17A5,5 0 0,1 12,22A5,5 0 0,1 7,17C7,15.36 7.79,13.91 9,13V5A3,3 0 0,1 12,2A3,3 0 0,1 15,5V13C16.21,13.91 17,15.36 17,17M11,8V14.17C9.83,14.58 9,15.69 9,17A3,3 0 0,0 12,20A3,3 0 0,0 15,17C15,15.69 14.17,14.58 13,14.17V8H11Z" /></g><g id="thermometer-lines"><path d="M17,3H21V5H17V3M17,7H21V9H17V7M17,11H21V13H17.75L17,12.1V11M21,15V17H19C19,16.31 18.9,15.63 18.71,15H21M17,17A5,5 0 0,1 12,22A5,5 0 0,1 7,17C7,15.36 7.79,13.91 9,13V5A3,3 0 0,1 12,2A3,3 0 0,1 15,5V13C16.21,13.91 17,15.36 17,17M11,8V14.17C9.83,14.58 9,15.69 9,17A3,3 0 0,0 12,20A3,3 0 0,0 15,17C15,15.69 14.17,14.58 13,14.17V8H11M7,3V5H3V3H7M7,7V9H3V7H7M7,11V12.1L6.25,13H3V11H7M3,15H5.29C5.1,15.63 5,16.31 5,17H3V15Z" /></g><g id="thumb-down"><path d="M19,15H23V3H19M15,3H6C5.17,3 4.46,3.5 4.16,4.22L1.14,11.27C1.05,11.5 1,11.74 1,12V13.91L1,14A2,2 0 0,0 3,16H9.31L8.36,20.57C8.34,20.67 8.33,20.77 8.33,20.88C8.33,21.3 8.5,21.67 8.77,21.94L9.83,23L16.41,16.41C16.78,16.05 17,15.55 17,15V5C17,3.89 16.1,3 15,3Z" /></g><g id="thumb-down-outline"><path d="M19,15V3H23V15H19M15,3A2,2 0 0,1 17,5V15C17,15.55 16.78,16.05 16.41,16.41L9.83,23L8.77,21.94C8.5,21.67 8.33,21.3 8.33,20.88L8.36,20.57L9.31,16H3C1.89,16 1,15.1 1,14V13.91L1,12C1,11.74 1.05,11.5 1.14,11.27L4.16,4.22C4.46,3.5 5.17,3 6,3H15M15,5H5.97L3,12V14H11.78L10.65,19.32L15,14.97V5Z" /></g><g id="thumb-up"><path d="M23,10C23,8.89 22.1,8 21,8H14.68L15.64,3.43C15.66,3.33 15.67,3.22 15.67,3.11C15.67,2.7 15.5,2.32 15.23,2.05L14.17,1L7.59,7.58C7.22,7.95 7,8.45 7,9V19A2,2 0 0,0 9,21H18C18.83,21 19.54,20.5 19.84,19.78L22.86,12.73C22.95,12.5 23,12.26 23,12V10.08L23,10M1,21H5V9H1V21Z" /></g><g id="thumb-up-outline"><path d="M5,9V21H1V9H5M9,21A2,2 0 0,1 7,19V9C7,8.45 7.22,7.95 7.59,7.59L14.17,1L15.23,2.06C15.5,2.33 15.67,2.7 15.67,3.11L15.64,3.43L14.69,8H21C22.11,8 23,8.9 23,10V10.09L23,12C23,12.26 22.95,12.5 22.86,12.73L19.84,19.78C19.54,20.5 18.83,21 18,21H9M9,19H18.03L21,12V10H12.21L13.34,4.68L9,9.03V19Z" /></g><g id="thumbs-up-down"><path d="M22.5,10.5H15.75C15.13,10.5 14.6,10.88 14.37,11.41L12.11,16.7C12.04,16.87 12,17.06 12,17.25V18.5A1,1 0 0,0 13,19.5H18.18L17.5,22.68V22.92C17.5,23.23 17.63,23.5 17.83,23.72L18.62,24.5L23.56,19.56C23.83,19.29 24,18.91 24,18.5V12A1.5,1.5 0 0,0 22.5,10.5M12,6.5A1,1 0 0,0 11,5.5H5.82L6.5,2.32V2.09C6.5,1.78 6.37,1.5 6.17,1.29L5.38,0.5L0.44,5.44C0.17,5.71 0,6.09 0,6.5V13A1.5,1.5 0 0,0 1.5,14.5H8.25C8.87,14.5 9.4,14.12 9.63,13.59L11.89,8.3C11.96,8.13 12,7.94 12,7.75V6.5Z" /></g><g id="ticket"><path d="M15.58,16.8L12,14.5L8.42,16.8L9.5,12.68L6.21,10L10.46,9.74L12,5.8L13.54,9.74L17.79,10L14.5,12.68M20,12C20,10.89 20.9,10 22,10V6C22,4.89 21.1,4 20,4H4A2,2 0 0,0 2,6V10C3.11,10 4,10.9 4,12A2,2 0 0,1 2,14V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V14A2,2 0 0,1 20,12Z" /></g><g id="ticket-account"><path d="M20,12A2,2 0 0,0 22,14V18A2,2 0 0,1 20,20H4A2,2 0 0,1 2,18V14C3.11,14 4,13.1 4,12A2,2 0 0,0 2,10V6C2,4.89 2.9,4 4,4H20A2,2 0 0,1 22,6V10A2,2 0 0,0 20,12M16.5,16.25C16.5,14.75 13.5,14 12,14C10.5,14 7.5,14.75 7.5,16.25V17H16.5V16.25M12,12.25A2.25,2.25 0 0,0 14.25,10A2.25,2.25 0 0,0 12,7.75A2.25,2.25 0 0,0 9.75,10A2.25,2.25 0 0,0 12,12.25Z" /></g><g id="ticket-confirmation"><path d="M13,8.5H11V6.5H13V8.5M13,13H11V11H13V13M13,17.5H11V15.5H13V17.5M22,10V6C22,4.89 21.1,4 20,4H4A2,2 0 0,0 2,6V10C3.11,10 4,10.9 4,12A2,2 0 0,1 2,14V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V14A2,2 0 0,1 20,12A2,2 0 0,1 22,10Z" /></g><g id="ticket-percent"><path d="M4,4A2,2 0 0,0 2,6V10C3.11,10 4,10.9 4,12A2,2 0 0,1 2,14V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V14A2,2 0 0,1 20,12C20,10.89 20.9,10 22,10V6C22,4.89 21.1,4 20,4H4M15.5,7L17,8.5L8.5,17L7,15.5L15.5,7M8.81,7.04C9.79,7.04 10.58,7.83 10.58,8.81A1.77,1.77 0 0,1 8.81,10.58C7.83,10.58 7.04,9.79 7.04,8.81A1.77,1.77 0 0,1 8.81,7.04M15.19,13.42C16.17,13.42 16.96,14.21 16.96,15.19A1.77,1.77 0 0,1 15.19,16.96C14.21,16.96 13.42,16.17 13.42,15.19A1.77,1.77 0 0,1 15.19,13.42Z" /></g><g id="tie"><path d="M6,2L10,6L7,17L12,22L17,17L14,6L18,2Z" /></g><g id="timelapse"><path d="M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M16.24,7.76C15.07,6.58 13.53,6 12,6V12L7.76,16.24C10.1,18.58 13.9,18.58 16.24,16.24C18.59,13.9 18.59,10.1 16.24,7.76Z" /></g><g id="timer"><path d="M12,20A7,7 0 0,1 5,13A7,7 0 0,1 12,6A7,7 0 0,1 19,13A7,7 0 0,1 12,20M19.03,7.39L20.45,5.97C20,5.46 19.55,5 19.04,4.56L17.62,6C16.07,4.74 14.12,4 12,4A9,9 0 0,0 3,13A9,9 0 0,0 12,22C17,22 21,17.97 21,13C21,10.88 20.26,8.93 19.03,7.39M11,14H13V8H11M15,1H9V3H15V1Z" /></g><g id="timer-10"><path d="M12.9,13.22C12.9,13.82 12.86,14.33 12.78,14.75C12.7,15.17 12.58,15.5 12.42,15.77C12.26,16.03 12.06,16.22 11.83,16.34C11.6,16.46 11.32,16.5 11,16.5C10.71,16.5 10.43,16.46 10.19,16.34C9.95,16.22 9.75,16.03 9.59,15.77C9.43,15.5 9.3,15.17 9.21,14.75C9.12,14.33 9.08,13.82 9.08,13.22V10.72C9.08,10.12 9.12,9.61 9.21,9.2C9.3,8.79 9.42,8.46 9.59,8.2C9.75,7.95 9.95,7.77 10.19,7.65C10.43,7.54 10.7,7.5 11,7.5C11.31,7.5 11.58,7.54 11.81,7.65C12.05,7.76 12.25,7.94 12.41,8.2C12.57,8.45 12.7,8.78 12.78,9.19C12.86,9.6 12.91,10.11 12.91,10.71V13.22M13.82,7.05C13.5,6.65 13.07,6.35 12.59,6.17C12.12,6 11.58,5.9 11,5.9C10.42,5.9 9.89,6 9.41,6.17C8.93,6.35 8.5,6.64 8.18,7.05C7.84,7.46 7.58,8 7.39,8.64C7.21,9.29 7.11,10.09 7.11,11.03V12.95C7.11,13.89 7.2,14.69 7.39,15.34C7.58,16 7.84,16.53 8.19,16.94C8.53,17.35 8.94,17.65 9.42,17.83C9.9,18 10.43,18.11 11,18.11C11.6,18.11 12.13,18 12.6,17.83C13.08,17.65 13.5,17.35 13.82,16.94C14.16,16.53 14.42,16 14.6,15.34C14.78,14.69 14.88,13.89 14.88,12.95V11.03C14.88,10.09 14.79,9.29 14.6,8.64C14.42,8 14.16,7.45 13.82,7.05M23.78,14.37C23.64,14.09 23.43,13.84 23.15,13.63C22.87,13.42 22.54,13.24 22.14,13.1C21.74,12.96 21.29,12.83 20.79,12.72C20.44,12.65 20.15,12.57 19.92,12.5C19.69,12.41 19.5,12.33 19.37,12.24C19.23,12.15 19.14,12.05 19.09,11.94C19.04,11.83 19,11.7 19,11.55C19,11.41 19.04,11.27 19.1,11.14C19.16,11 19.25,10.89 19.37,10.8C19.5,10.7 19.64,10.62 19.82,10.56C20,10.5 20.22,10.47 20.46,10.47C20.71,10.47 20.93,10.5 21.12,10.58C21.31,10.65 21.47,10.75 21.6,10.87C21.73,11 21.82,11.13 21.89,11.29C21.95,11.45 22,11.61 22,11.78H23.94C23.94,11.39 23.86,11.03 23.7,10.69C23.54,10.35 23.31,10.06 23,9.81C22.71,9.56 22.35,9.37 21.92,9.22C21.5,9.07 21,9 20.46,9C19.95,9 19.5,9.07 19.07,9.21C18.66,9.35 18.3,9.54 18,9.78C17.72,10 17.5,10.3 17.34,10.62C17.18,10.94 17.11,11.27 17.11,11.63C17.11,12 17.19,12.32 17.34,12.59C17.5,12.87 17.7,13.11 18,13.32C18.25,13.53 18.58,13.7 18.96,13.85C19.34,14 19.77,14.11 20.23,14.21C20.62,14.29 20.94,14.38 21.18,14.47C21.42,14.56 21.61,14.66 21.75,14.76C21.88,14.86 21.97,15 22,15.1C22.07,15.22 22.09,15.35 22.09,15.5C22.09,15.81 21.96,16.06 21.69,16.26C21.42,16.46 21.03,16.55 20.5,16.55C20.3,16.55 20.09,16.53 19.88,16.47C19.67,16.42 19.5,16.34 19.32,16.23C19.15,16.12 19,15.97 18.91,15.79C18.8,15.61 18.74,15.38 18.73,15.12H16.84C16.84,15.5 16.92,15.83 17.08,16.17C17.24,16.5 17.47,16.82 17.78,17.1C18.09,17.37 18.47,17.59 18.93,17.76C19.39,17.93 19.91,18 20.5,18C21.04,18 21.5,17.95 21.95,17.82C22.38,17.69 22.75,17.5 23.06,17.28C23.37,17.05 23.6,16.77 23.77,16.45C23.94,16.13 24,15.78 24,15.39C24,15 23.93,14.65 23.78,14.37M0,7.72V9.4L3,8.4V18H5V6H4.75L0,7.72Z" /></g><g id="timer-3"><path d="M20.87,14.37C20.73,14.09 20.5,13.84 20.24,13.63C19.96,13.42 19.63,13.24 19.23,13.1C18.83,12.96 18.38,12.83 17.88,12.72C17.53,12.65 17.24,12.57 17,12.5C16.78,12.41 16.6,12.33 16.46,12.24C16.32,12.15 16.23,12.05 16.18,11.94C16.13,11.83 16.1,11.7 16.1,11.55C16.1,11.4 16.13,11.27 16.19,11.14C16.25,11 16.34,10.89 16.46,10.8C16.58,10.7 16.73,10.62 16.91,10.56C17.09,10.5 17.31,10.47 17.55,10.47C17.8,10.47 18,10.5 18.21,10.58C18.4,10.65 18.56,10.75 18.69,10.87C18.82,11 18.91,11.13 19,11.29C19.04,11.45 19.08,11.61 19.08,11.78H21.03C21.03,11.39 20.95,11.03 20.79,10.69C20.63,10.35 20.4,10.06 20.1,9.81C19.8,9.56 19.44,9.37 19,9.22C18.58,9.07 18.09,9 17.55,9C17.04,9 16.57,9.07 16.16,9.21C15.75,9.35 15.39,9.54 15.1,9.78C14.81,10 14.59,10.3 14.43,10.62C14.27,10.94 14.2,11.27 14.2,11.63C14.2,12 14.28,12.31 14.43,12.59C14.58,12.87 14.8,13.11 15.07,13.32C15.34,13.53 15.67,13.7 16.05,13.85C16.43,14 16.86,14.11 17.32,14.21C17.71,14.29 18.03,14.38 18.27,14.47C18.5,14.56 18.7,14.66 18.84,14.76C18.97,14.86 19.06,15 19.11,15.1C19.16,15.22 19.18,15.35 19.18,15.5C19.18,15.81 19.05,16.06 18.78,16.26C18.5,16.46 18.12,16.55 17.61,16.55C17.39,16.55 17.18,16.53 16.97,16.47C16.76,16.42 16.57,16.34 16.41,16.23C16.24,16.12 16.11,15.97 16,15.79C15.89,15.61 15.83,15.38 15.82,15.12H13.93C13.93,15.5 14,15.83 14.17,16.17C14.33,16.5 14.56,16.82 14.87,17.1C15.18,17.37 15.56,17.59 16,17.76C16.5,17.93 17,18 17.6,18C18.13,18 18.61,17.95 19.04,17.82C19.47,17.69 19.84,17.5 20.15,17.28C20.46,17.05 20.69,16.77 20.86,16.45C21.03,16.13 21.11,15.78 21.11,15.39C21.09,15 21,14.65 20.87,14.37M11.61,12.97C11.45,12.73 11.25,12.5 11,12.32C10.74,12.13 10.43,11.97 10.06,11.84C10.36,11.7 10.63,11.54 10.86,11.34C11.09,11.14 11.28,10.93 11.43,10.7C11.58,10.47 11.7,10.24 11.77,10C11.85,9.75 11.88,9.5 11.88,9.26C11.88,8.71 11.79,8.22 11.6,7.8C11.42,7.38 11.16,7.03 10.82,6.74C10.5,6.46 10.09,6.24 9.62,6.1C9.17,5.97 8.65,5.9 8.09,5.9C7.54,5.9 7.03,6 6.57,6.14C6.1,6.31 5.7,6.54 5.37,6.83C5.04,7.12 4.77,7.46 4.59,7.86C4.39,8.25 4.3,8.69 4.3,9.15H6.28C6.28,8.89 6.33,8.66 6.42,8.46C6.5,8.26 6.64,8.08 6.8,7.94C6.97,7.8 7.16,7.69 7.38,7.61C7.6,7.53 7.84,7.5 8.11,7.5C8.72,7.5 9.17,7.65 9.47,7.96C9.77,8.27 9.91,8.71 9.91,9.28C9.91,9.55 9.87,9.8 9.79,10C9.71,10.24 9.58,10.43 9.41,10.59C9.24,10.75 9.03,10.87 8.78,10.96C8.53,11.05 8.23,11.09 7.89,11.09H6.72V12.66H7.9C8.24,12.66 8.54,12.7 8.81,12.77C9.08,12.85 9.31,12.96 9.5,13.12C9.69,13.28 9.84,13.5 9.94,13.73C10.04,13.97 10.1,14.27 10.1,14.6C10.1,15.22 9.92,15.69 9.57,16C9.22,16.35 8.73,16.5 8.12,16.5C7.83,16.5 7.56,16.47 7.32,16.38C7.08,16.3 6.88,16.18 6.71,16C6.54,15.86 6.41,15.68 6.32,15.46C6.23,15.24 6.18,15 6.18,14.74H4.19C4.19,15.29 4.3,15.77 4.5,16.19C4.72,16.61 5,16.96 5.37,17.24C5.73,17.5 6.14,17.73 6.61,17.87C7.08,18 7.57,18.08 8.09,18.08C8.66,18.08 9.18,18 9.67,17.85C10.16,17.7 10.58,17.47 10.93,17.17C11.29,16.87 11.57,16.5 11.77,16.07C11.97,15.64 12.07,15.14 12.07,14.59C12.07,14.3 12.03,14 11.96,13.73C11.88,13.5 11.77,13.22 11.61,12.97Z" /></g><g id="timer-off"><path d="M12,20A7,7 0 0,1 5,13C5,11.72 5.35,10.5 5.95,9.5L15.5,19.04C14.5,19.65 13.28,20 12,20M3,4L1.75,5.27L4.5,8.03C3.55,9.45 3,11.16 3,13A9,9 0 0,0 12,22C13.84,22 15.55,21.45 17,20.5L19.5,23L20.75,21.73L13.04,14L3,4M11,9.44L13,11.44V8H11M15,1H9V3H15M19.04,4.55L17.62,5.97C16.07,4.74 14.12,4 12,4C10.17,4 8.47,4.55 7.05,5.5L8.5,6.94C9.53,6.35 10.73,6 12,6A7,7 0 0,1 19,13C19,14.27 18.65,15.47 18.06,16.5L19.5,17.94C20.45,16.53 21,14.83 21,13C21,10.88 20.26,8.93 19.03,7.39L20.45,5.97L19.04,4.55Z" /></g><g id="timer-sand"><path d="M20,2V4H18V8.41L14.41,12L18,15.59V20H20V22H4V20H6V15.59L9.59,12L6,8.41V4H4V2H20M16,16.41L13,13.41V10.59L16,7.59V4H8V7.59L11,10.59V13.41L8,16.41V17H10L12,15L14,17H16V16.41M12,9L10,7H14L12,9Z" /></g><g id="timer-sand-empty"><path d="M20,2V4H18V8.41L14.41,12L18,15.59V20H20V22H4V20H6V15.59L9.59,12L6,8.41V4H4V2H20M16,16.41L13,13.41V10.59L16,7.59V4H8V7.59L11,10.59V13.41L8,16.41V20H16V16.41Z" /></g><g id="timetable"><path d="M14,12H15.5V14.82L17.94,16.23L17.19,17.53L14,15.69V12M4,2H18A2,2 0 0,1 20,4V10.1C21.24,11.36 22,13.09 22,15A7,7 0 0,1 15,22C13.09,22 11.36,21.24 10.1,20H4A2,2 0 0,1 2,18V4A2,2 0 0,1 4,2M4,15V18H8.67C8.24,17.09 8,16.07 8,15H4M4,8H10V5H4V8M18,8V5H12V8H18M4,13H8.29C8.63,11.85 9.26,10.82 10.1,10H4V13M15,10.15A4.85,4.85 0 0,0 10.15,15C10.15,17.68 12.32,19.85 15,19.85A4.85,4.85 0 0,0 19.85,15C19.85,12.32 17.68,10.15 15,10.15Z" /></g><g id="toggle-switch"><path d="M17,7A5,5 0 0,1 22,12A5,5 0 0,1 17,17A5,5 0 0,1 12,12A5,5 0 0,1 17,7M4,14A2,2 0 0,1 2,12A2,2 0 0,1 4,10H10V14H4Z" /></g><g id="toggle-switch-off"><path d="M7,7A5,5 0 0,1 12,12A5,5 0 0,1 7,17A5,5 0 0,1 2,12A5,5 0 0,1 7,7M20,14H14V10H20A2,2 0 0,1 22,12A2,2 0 0,1 20,14M7,9A3,3 0 0,0 4,12A3,3 0 0,0 7,15A3,3 0 0,0 10,12A3,3 0 0,0 7,9Z" /></g><g id="tooltip"><path d="M4,2H20A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H16L12,22L8,18H4A2,2 0 0,1 2,16V4A2,2 0 0,1 4,2Z" /></g><g id="tooltip-edit"><path d="M4,2H20A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H16L12,22L8,18H4A2,2 0 0,1 2,16V4A2,2 0 0,1 4,2M18,14V12H12.5L10.5,14H18M6,14H8.5L15.35,7.12C15.55,6.93 15.55,6.61 15.35,6.41L13.59,4.65C13.39,4.45 13.07,4.45 12.88,4.65L6,11.53V14Z" /></g><g id="tooltip-image"><path d="M4,2H20A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H16L12,22L8,18H4A2,2 0 0,1 2,16V4A2,2 0 0,1 4,2M19,15V7L15,11L13,9L7,15H19M7,5A2,2 0 0,0 5,7A2,2 0 0,0 7,9A2,2 0 0,0 9,7A2,2 0 0,0 7,5Z" /></g><g id="tooltip-outline"><path d="M4,2H20A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H16L12,22L8,18H4A2,2 0 0,1 2,16V4A2,2 0 0,1 4,2M4,4V16H8.83L12,19.17L15.17,16H20V4H4Z" /></g><g id="tooltip-outline-plus"><path d="M4,2H20A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H16L12,22L8,18H4A2,2 0 0,1 2,16V4A2,2 0 0,1 4,2M4,4V16H8.83L12,19.17L15.17,16H20V4H4M11,6H13V9H16V11H13V14H11V11H8V9H11V6Z" /></g><g id="tooltip-text"><path d="M4,2H20A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H16L12,22L8,18H4A2,2 0 0,1 2,16V4A2,2 0 0,1 4,2M5,5V7H19V5H5M5,9V11H15V9H5M5,13V15H17V13H5Z" /></g><g id="tooth"><path d="M7,2C4,2 2,5 2,8C2,10.11 3,13 4,14C5,15 6,22 8,22C12.54,22 10,15 12,15C14,15 11.46,22 16,22C18,22 19,15 20,14C21,13 22,10.11 22,8C22,5 20,2 17,2C14,2 14,3 12,3C10,3 10,2 7,2M7,4C9,4 10,5 12,5C14,5 15,4 17,4C18.67,4 20,6 20,8C20,9.75 19.14,12.11 18.19,13.06C17.33,13.92 16.06,19.94 15.5,19.94C15.29,19.94 15,18.88 15,17.59C15,15.55 14.43,13 12,13C9.57,13 9,15.55 9,17.59C9,18.88 8.71,19.94 8.5,19.94C7.94,19.94 6.67,13.92 5.81,13.06C4.86,12.11 4,9.75 4,8C4,6 5.33,4 7,4Z" /></g><g id="tor"><path d="M12,14C11,14 9,15 9,16C9,18 12,18 12,18V17A1,1 0 0,1 11,16A1,1 0 0,1 12,15V14M12,19C12,19 8,18.5 8,16.5C8,13.5 11,12.75 12,12.75V11.5C11,11.5 7,13 7,16C7,20 12,20 12,20V19M10.07,7.03L11.26,7.56C11.69,5.12 12.84,3.5 12.84,3.5C12.41,4.53 12.13,5.38 11.95,6.05C13.16,3.55 15.61,2 15.61,2C14.43,3.18 13.56,4.46 12.97,5.53C14.55,3.85 16.74,2.75 16.74,2.75C14.05,4.47 12.84,7.2 12.54,7.96L13.09,8.04C13.09,8.56 13.09,9.04 13.34,9.42C14.1,11.31 18,11.47 18,16C18,20.53 13.97,22 11.83,22C9.69,22 5,21.03 5,16C5,10.97 9.95,10.93 10.83,8.92C10.95,8.54 10.07,7.03 10.07,7.03Z" /></g><g id="tower-beach"><path d="M17,4V8H18V10H17.64L21,23H18.93L18.37,20.83L12,17.15L5.63,20.83L5.07,23H3L6.36,10H6V8H7V4H6V3L18,1V4H17M7.28,14.43L6.33,18.12L10,16L7.28,14.43M15.57,10H8.43L7.8,12.42L12,14.85L16.2,12.42L15.57,10M17.67,18.12L16.72,14.43L14,16L17.67,18.12Z" /></g><g id="tower-fire"><path d="M17,4V8H18V10H17.64L21,23H18.93L18.37,20.83L12,17.15L5.63,20.83L5.07,23H3L6.36,10H6V8H7V4H6V3L12,1L18,3V4H17M7.28,14.43L6.33,18.12L10,16L7.28,14.43M15.57,10H8.43L7.8,12.42L12,14.85L16.2,12.42L15.57,10M17.67,18.12L16.72,14.43L14,16L17.67,18.12Z" /></g><g id="traffic-light"><path d="M12,9A2,2 0 0,1 10,7C10,5.89 10.9,5 12,5C13.11,5 14,5.89 14,7A2,2 0 0,1 12,9M12,14A2,2 0 0,1 10,12C10,10.89 10.9,10 12,10C13.11,10 14,10.89 14,12A2,2 0 0,1 12,14M12,19A2,2 0 0,1 10,17C10,15.89 10.9,15 12,15C13.11,15 14,15.89 14,17A2,2 0 0,1 12,19M20,10H17V8.86C18.72,8.41 20,6.86 20,5H17V4A1,1 0 0,0 16,3H8A1,1 0 0,0 7,4V5H4C4,6.86 5.28,8.41 7,8.86V10H4C4,11.86 5.28,13.41 7,13.86V15H4C4,16.86 5.28,18.41 7,18.86V20A1,1 0 0,0 8,21H16A1,1 0 0,0 17,20V18.86C18.72,18.41 20,16.86 20,15H17V13.86C18.72,13.41 20,11.86 20,10Z" /></g><g id="train"><path d="M18,10H6V5H18M12,17C10.89,17 10,16.1 10,15C10,13.89 10.89,13 12,13A2,2 0 0,1 14,15A2,2 0 0,1 12,17M4,15.5A3.5,3.5 0 0,0 7.5,19L6,20.5V21H18V20.5L16.5,19A3.5,3.5 0 0,0 20,15.5V5C20,1.5 16.42,1 12,1C7.58,1 4,1.5 4,5V15.5Z" /></g><g id="tram"><path d="M17,18C16.4,18 16,17.6 16,17C16,16.4 16.4,16 17,16C17.6,16 18,16.4 18,17C18,17.6 17.6,18 17,18M6.7,10.7L7,7.3C7,6.6 7.6,6 8.3,6H15.6C16.4,6 17,6.6 17,7.3L17.3,10.6C17.3,11.3 16.7,11.9 16,11.9H8C7.3,12 6.7,11.4 6.7,10.7M7,18C6.4,18 6,17.6 6,17C6,16.4 6.4,16 7,16C7.6,16 8,16.4 8,17C8,17.6 7.6,18 7,18M19,6A2,2 0 0,0 17,4H15A2,2 0 0,0 13,2H11A2,2 0 0,0 9,4H7A2,2 0 0,0 5,6L4,18A2,2 0 0,0 6,20H8L7,22H17.1L16.1,20H18A2,2 0 0,0 20,18L19,6Z" /></g><g id="transcribe"><path d="M20,5A2,2 0 0,1 22,7V17A2,2 0 0,1 20,19H4C2.89,19 2,18.1 2,17V7C2,5.89 2.89,5 4,5H20M18,17V15H12.5L10.5,17H18M6,17H8.5L15.35,10.12C15.55,9.93 15.55,9.61 15.35,9.41L13.59,7.65C13.39,7.45 13.07,7.45 12.88,7.65L6,14.53V17Z" /></g><g id="transcribe-close"><path d="M12,23L8,19H16L12,23M20,3A2,2 0 0,1 22,5V15A2,2 0 0,1 20,17H4A2,2 0 0,1 2,15V5A2,2 0 0,1 4,3H20M18,15V13H12.5L10.5,15H18M6,15H8.5L15.35,8.12C15.55,7.93 15.55,7.61 15.35,7.42L13.59,5.65C13.39,5.45 13.07,5.45 12.88,5.65L6,12.53V15Z" /></g><g id="transfer"><path d="M3,8H5V16H3V8M7,8H9V16H7V8M11,8H13V16H11V8M15,19.25V4.75L22.25,12L15,19.25Z" /></g><g id="transit-transfer"><path d="M16.5,15.5H22V17H16.5V18.75L14,16.25L16.5,13.75V15.5M19.5,19.75V18L22,20.5L19.5,23V21.25H14V19.75H19.5M9.5,5.5A2,2 0 0,1 7.5,3.5A2,2 0 0,1 9.5,1.5A2,2 0 0,1 11.5,3.5A2,2 0 0,1 9.5,5.5M5.75,8.9L4,9.65V13H2V8.3L7.25,6.15C7.5,6.05 7.75,6 8,6C8.7,6 9.35,6.35 9.7,6.95L10.65,8.55C11.55,10 13.15,11 15,11V13C12.8,13 10.85,12 9.55,10.4L8.95,13.4L11,15.45V23H9V17L6.85,15L5.1,23H3L5.75,8.9Z" /></g><g id="translate"><path d="M12.87,15.07L10.33,12.56L10.36,12.53C12.1,10.59 13.34,8.36 14.07,6H17V4H10V2H8V4H1V6H12.17C11.5,7.92 10.44,9.75 9,11.35C8.07,10.32 7.3,9.19 6.69,8H4.69C5.42,9.63 6.42,11.17 7.67,12.56L2.58,17.58L4,19L9,14L12.11,17.11L12.87,15.07M18.5,10H16.5L12,22H14L15.12,19H19.87L21,22H23L18.5,10M15.88,17L17.5,12.67L19.12,17H15.88Z" /></g><g id="tree"><path d="M11,21V16.74C10.53,16.91 10.03,17 9.5,17C7,17 5,15 5,12.5C5,11.23 5.5,10.09 6.36,9.27C6.13,8.73 6,8.13 6,7.5C6,5 8,3 10.5,3C12.06,3 13.44,3.8 14.25,5C14.33,5 14.41,5 14.5,5A5.5,5.5 0 0,1 20,10.5A5.5,5.5 0 0,1 14.5,16C14,16 13.5,15.93 13,15.79V21H11Z" /></g><g id="trello"><path d="M4,3H20A1,1 0 0,1 21,4V20A1,1 0 0,1 20,21H4A1,1 0 0,1 3,20V4A1,1 0 0,1 4,3M5.5,5A0.5,0.5 0 0,0 5,5.5V17.5A0.5,0.5 0 0,0 5.5,18H10.5A0.5,0.5 0 0,0 11,17.5V5.5A0.5,0.5 0 0,0 10.5,5H5.5M13.5,5A0.5,0.5 0 0,0 13,5.5V11.5A0.5,0.5 0 0,0 13.5,12H18.5A0.5,0.5 0 0,0 19,11.5V5.5A0.5,0.5 0 0,0 18.5,5H13.5Z" /></g><g id="trending-down"><path d="M16,18L18.29,15.71L13.41,10.83L9.41,14.83L2,7.41L3.41,6L9.41,12L13.41,8L19.71,14.29L22,12V18H16Z" /></g><g id="trending-neutral"><path d="M22,12L18,8V11H3V13H18V16L22,12Z" /></g><g id="trending-up"><path d="M16,6L18.29,8.29L13.41,13.17L9.41,9.17L2,16.59L3.41,18L9.41,12L13.41,16L19.71,9.71L22,12V6H16Z" /></g><g id="triangle"><path d="M1,21H23L12,2" /></g><g id="triangle-outline"><path d="M12,2L1,21H23M12,6L19.53,19H4.47" /></g><g id="trophy"><path d="M20.2,2H19.5H18C17.1,2 16,3 16,4H8C8,3 6.9,2 6,2H4.5H3.8H2V11C2,12 3,13 4,13H6.2C6.6,15 7.9,16.7 11,17V19.1C8.8,19.3 8,20.4 8,21.7V22H16V21.7C16,20.4 15.2,19.3 13,19.1V17C16.1,16.7 17.4,15 17.8,13H20C21,13 22,12 22,11V2H20.2M4,11V4H6V6V11C5.1,11 4.3,11 4,11M20,11C19.7,11 18.9,11 18,11V6V4H20V11Z" /></g><g id="trophy-award"><path d="M15.2,10.7L16.6,16L12,12.2L7.4,16L8.8,10.8L4.6,7.3L10,7L12,2L14,7L19.4,7.3L15.2,10.7M14,19.1H13V16L12,15L11,16V19.1H10A2,2 0 0,0 8,21.1V22.1H16V21.1A2,2 0 0,0 14,19.1Z" /></g><g id="trophy-outline"><path d="M2,2V11C2,12 3,13 4,13H6.2C6.6,15 7.9,16.7 11,17V19.1C8.8,19.3 8,20.4 8,21.7V22H16V21.7C16,20.4 15.2,19.3 13,19.1V17C16.1,16.7 17.4,15 17.8,13H20C21,13 22,12 22,11V2H18C17.1,2 16,3 16,4H8C8,3 6.9,2 6,2H2M4,4H6V6L6,11H4V4M18,4H20V11H18V6L18,4M8,6H16V11.5C16,13.43 15.42,15 12,15C8.59,15 8,13.43 8,11.5V6Z" /></g><g id="trophy-variant"><path d="M20.2,4H20H17V2H7V4H4.5H3.8H2V11C2,12 3,13 4,13H7.2C7.6,14.9 8.6,16.6 11,16.9V19C8,19.2 8,20.3 8,21.6V22H16V21.7C16,20.4 16,19.3 13,19.1V17C15.5,16.7 16.5,15 16.8,13.1H20C21,13.1 22,12.1 22,11.1V4H20.2M4,11V6H7V8V11C5.6,11 4.4,11 4,11M20,11C19.6,11 18.4,11 17,11V6H18H20V11Z" /></g><g id="trophy-variant-outline"><path d="M7,2V4H2V11C2,12 3,13 4,13H7.2C7.6,14.9 8.6,16.6 11,16.9V19C8,19.2 8,20.3 8,21.6V22H16V21.7C16,20.4 16,19.3 13,19.1V17C15.5,16.7 16.5,15 16.8,13.1H20C21,13.1 22,12.1 22,11.1V4H17V2H7M9,4H15V12A3,3 0 0,1 12,15C10,15 9,13.66 9,12V4M4,6H7V8L7,11H4V6M17,6H20V11H17V6Z" /></g><g id="truck"><path d="M18,18.5A1.5,1.5 0 0,1 16.5,17A1.5,1.5 0 0,1 18,15.5A1.5,1.5 0 0,1 19.5,17A1.5,1.5 0 0,1 18,18.5M19.5,9.5L21.46,12H17V9.5M6,18.5A1.5,1.5 0 0,1 4.5,17A1.5,1.5 0 0,1 6,15.5A1.5,1.5 0 0,1 7.5,17A1.5,1.5 0 0,1 6,18.5M20,8H17V4H3C1.89,4 1,4.89 1,6V17H3A3,3 0 0,0 6,20A3,3 0 0,0 9,17H15A3,3 0 0,0 18,20A3,3 0 0,0 21,17H23V12L20,8Z" /></g><g id="truck-delivery"><path d="M3,4A2,2 0 0,0 1,6V17H3A3,3 0 0,0 6,20A3,3 0 0,0 9,17H15A3,3 0 0,0 18,20A3,3 0 0,0 21,17H23V12L20,8H17V4M10,6L14,10L10,14V11H4V9H10M17,9.5H19.5L21.47,12H17M6,15.5A1.5,1.5 0 0,1 7.5,17A1.5,1.5 0 0,1 6,18.5A1.5,1.5 0 0,1 4.5,17A1.5,1.5 0 0,1 6,15.5M18,15.5A1.5,1.5 0 0,1 19.5,17A1.5,1.5 0 0,1 18,18.5A1.5,1.5 0 0,1 16.5,17A1.5,1.5 0 0,1 18,15.5Z" /></g><g id="tshirt-crew"><path d="M16,21H8A1,1 0 0,1 7,20V12.07L5.7,13.12C5.31,13.5 4.68,13.5 4.29,13.12L1.46,10.29C1.07,9.9 1.07,9.27 1.46,8.88L7.34,3H9C9,4.1 10.34,5 12,5C13.66,5 15,4.1 15,3H16.66L22.54,8.88C22.93,9.27 22.93,9.9 22.54,10.29L19.71,13.12C19.32,13.5 18.69,13.5 18.3,13.12L17,12.07V20A1,1 0 0,1 16,21M20.42,9.58L16.11,5.28C15.8,5.63 15.43,5.94 15,6.2C14.16,6.7 13.13,7 12,7C10.3,7 8.79,6.32 7.89,5.28L3.58,9.58L5,11L8,9H9V19H15V9H16L19,11L20.42,9.58Z" /></g><g id="tshirt-v"><path d="M16,21H8A1,1 0 0,1 7,20V12.07L5.7,13.12C5.31,13.5 4.68,13.5 4.29,13.12L1.46,10.29C1.07,9.9 1.07,9.27 1.46,8.88L7.34,3H9C9,4.1 10,6 12,7.25C14,6 15,4.1 15,3H16.66L22.54,8.88C22.93,9.27 22.93,9.9 22.54,10.29L19.71,13.12C19.32,13.5 18.69,13.5 18.3,13.12L17,12.07V20A1,1 0 0,1 16,21M20.42,9.58L16.11,5.28C15,7 14,8.25 12,9.25C10,8.25 9,7 7.89,5.28L3.58,9.58L5,11L8,9H9V19H15V9H16L19,11L20.42,9.58Z" /></g><g id="tumblr"><path d="M16,11H13V14.9C13,15.63 13.14,16 14.1,16H16V19C16,19 14.97,19.1 13.9,19.1C11.25,19.1 10,17.5 10,15.7V11H8V8.2C10.41,8 10.62,6.16 10.8,5H13V8H16M20,2H4C2.89,2 2,2.89 2,4V20A2,2 0 0,0 4,22H20A2,2 0 0,0 22,20V4C22,2.89 21.1,2 20,2Z" /></g><g id="tumblr-reblog"><path d="M3.75,17L8,12.75V16H18V11.5L20,9.5V16A2,2 0 0,1 18,18H8V21.25L3.75,17M20.25,7L16,11.25V8H6V12.5L4,14.5V8A2,2 0 0,1 6,6H16V2.75L20.25,7Z" /></g><g id="tune"><path d="M3,17V19H9V17H3M3,5V7H13V5H3M13,21V19H21V17H13V15H11V21H13M7,9V11H3V13H7V15H9V9H7M21,13V11H11V13H21M15,9H17V7H21V5H17V3H15V9Z" /></g><g id="tune-vertical"><path d="M5,3V12H3V14H5V21H7V14H9V12H7V3M11,3V8H9V10H11V21H13V10H15V8H13V3M17,3V14H15V16H17V21H19V16H21V14H19V3" /></g><g id="twitch"><path d="M4,2H22V14L17,19H13L10,22H7V19H2V6L4,2M20,13V4H6V16H9V19L12,16H17L20,13M15,7H17V12H15V7M12,7V12H10V7H12Z" /></g><g id="twitter"><path d="M22.46,6C21.69,6.35 20.86,6.58 20,6.69C20.88,6.16 21.56,5.32 21.88,4.31C21.05,4.81 20.13,5.16 19.16,5.36C18.37,4.5 17.26,4 16,4C13.65,4 11.73,5.92 11.73,8.29C11.73,8.63 11.77,8.96 11.84,9.27C8.28,9.09 5.11,7.38 3,4.79C2.63,5.42 2.42,6.16 2.42,6.94C2.42,8.43 3.17,9.75 4.33,10.5C3.62,10.5 2.96,10.3 2.38,10C2.38,10 2.38,10 2.38,10.03C2.38,12.11 3.86,13.85 5.82,14.24C5.46,14.34 5.08,14.39 4.69,14.39C4.42,14.39 4.15,14.36 3.89,14.31C4.43,16 6,17.26 7.89,17.29C6.43,18.45 4.58,19.13 2.56,19.13C2.22,19.13 1.88,19.11 1.54,19.07C3.44,20.29 5.7,21 8.12,21C16,21 20.33,14.46 20.33,8.79C20.33,8.6 20.33,8.42 20.32,8.23C21.16,7.63 21.88,6.87 22.46,6Z" /></g><g id="twitter-box"><path d="M17.71,9.33C17.64,13.95 14.69,17.11 10.28,17.31C8.46,17.39 7.15,16.81 6,16.08C7.34,16.29 9,15.76 9.9,15C8.58,14.86 7.81,14.19 7.44,13.12C7.82,13.18 8.22,13.16 8.58,13.09C7.39,12.69 6.54,11.95 6.5,10.41C6.83,10.57 7.18,10.71 7.64,10.74C6.75,10.23 6.1,8.38 6.85,7.16C8.17,8.61 9.76,9.79 12.37,9.95C11.71,7.15 15.42,5.63 16.97,7.5C17.63,7.38 18.16,7.14 18.68,6.86C18.47,7.5 18.06,7.97 17.56,8.33C18.1,8.26 18.59,8.13 19,7.92C18.75,8.45 18.19,8.93 17.71,9.33M20,2H4A2,2 0 0,0 2,4V20A2,2 0 0,0 4,22H20A2,2 0 0,0 22,20V4C22,2.89 21.1,2 20,2Z" /></g><g id="twitter-circle"><path d="M17.71,9.33C18.19,8.93 18.75,8.45 19,7.92C18.59,8.13 18.1,8.26 17.56,8.33C18.06,7.97 18.47,7.5 18.68,6.86C18.16,7.14 17.63,7.38 16.97,7.5C15.42,5.63 11.71,7.15 12.37,9.95C9.76,9.79 8.17,8.61 6.85,7.16C6.1,8.38 6.75,10.23 7.64,10.74C7.18,10.71 6.83,10.57 6.5,10.41C6.54,11.95 7.39,12.69 8.58,13.09C8.22,13.16 7.82,13.18 7.44,13.12C7.81,14.19 8.58,14.86 9.9,15C9,15.76 7.34,16.29 6,16.08C7.15,16.81 8.46,17.39 10.28,17.31C14.69,17.11 17.64,13.95 17.71,9.33M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2Z" /></g><g id="twitter-retweet"><path d="M6,5.75L10.25,10H7V16H13.5L15.5,18H7A2,2 0 0,1 5,16V10H1.75L6,5.75M18,18.25L13.75,14H17V8H10.5L8.5,6H17A2,2 0 0,1 19,8V14H22.25L18,18.25Z" /></g><g id="ubuntu"><path d="M22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12M14.34,7.74C14.92,8.07 15.65,7.87 16,7.3C16.31,6.73 16.12,6 15.54,5.66C14.97,5.33 14.23,5.5 13.9,6.1C13.57,6.67 13.77,7.41 14.34,7.74M11.88,15.5C11.35,15.5 10.85,15.39 10.41,15.18L9.57,16.68C10.27,17 11.05,17.22 11.88,17.22C12.37,17.22 12.83,17.15 13.28,17.03C13.36,16.54 13.64,16.1 14.1,15.84C14.56,15.57 15.08,15.55 15.54,15.72C16.43,14.85 17,13.66 17.09,12.33L15.38,12.31C15.22,14.1 13.72,15.5 11.88,15.5M11.88,8.5C13.72,8.5 15.22,9.89 15.38,11.69L17.09,11.66C17,10.34 16.43,9.15 15.54,8.28C15.08,8.45 14.55,8.42 14.1,8.16C13.64,7.9 13.36,7.45 13.28,6.97C12.83,6.85 12.37,6.78 11.88,6.78C11.05,6.78 10.27,6.97 9.57,7.32L10.41,8.82C10.85,8.61 11.35,8.5 11.88,8.5M8.37,12C8.37,10.81 8.96,9.76 9.86,9.13L9,7.65C7.94,8.36 7.15,9.43 6.83,10.69C7.21,11 7.45,11.47 7.45,12C7.45,12.53 7.21,13 6.83,13.31C7.15,14.56 7.94,15.64 9,16.34L9.86,14.87C8.96,14.24 8.37,13.19 8.37,12M14.34,16.26C13.77,16.59 13.57,17.32 13.9,17.9C14.23,18.47 14.97,18.67 15.54,18.34C16.12,18 16.31,17.27 16,16.7C15.65,16.12 14.92,15.93 14.34,16.26M5.76,10.8C5.1,10.8 4.56,11.34 4.56,12C4.56,12.66 5.1,13.2 5.76,13.2C6.43,13.2 6.96,12.66 6.96,12C6.96,11.34 6.43,10.8 5.76,10.8Z" /></g><g id="umbraco"><path d="M8.6,8.6L7.17,8.38C6.5,11.67 6.46,14.24 7.61,15.5C8.6,16.61 11.89,16.61 11.89,16.61C11.89,16.61 15.29,16.61 16.28,15.5C17.43,14.24 17.38,11.67 16.72,8.38L15.29,8.6C15.29,8.6 16.54,13.88 14.69,14.69C13.81,15.07 11.89,15.07 11.89,15.07C11.89,15.07 10.08,15.07 9.2,14.69C7.35,13.88 8.6,8.6 8.6,8.6M12,3A9,9 0 0,1 21,12A9,9 0 0,1 12,21A9,9 0 0,1 3,12A9,9 0 0,1 12,3Z" /></g><g id="umbrella"><path d="M12,2A9,9 0 0,0 3,11H11V19A1,1 0 0,1 10,20A1,1 0 0,1 9,19H7A3,3 0 0,0 10,22A3,3 0 0,0 13,19V11H21A9,9 0 0,0 12,2Z" /></g><g id="umbrella-outline"><path d="M12,4C15.09,4 17.82,6.04 18.7,9H5.3C6.18,6.03 8.9,4 12,4M12,2A9,9 0 0,0 3,11H11V19A1,1 0 0,1 10,20A1,1 0 0,1 9,19H7A3,3 0 0,0 10,22A3,3 0 0,0 13,19V11H21A9,9 0 0,0 12,2Z" /></g><g id="undo"><path d="M12.5,8C9.85,8 7.45,9 5.6,10.6L2,7V16H11L7.38,12.38C8.77,11.22 10.54,10.5 12.5,10.5C16.04,10.5 19.05,12.81 20.1,16L22.47,15.22C21.08,11.03 17.15,8 12.5,8Z" /></g><g id="undo-variant"><path d="M13.5,7A6.5,6.5 0 0,1 20,13.5A6.5,6.5 0 0,1 13.5,20H10V18H13.5C16,18 18,16 18,13.5C18,11 16,9 13.5,9H7.83L10.91,12.09L9.5,13.5L4,8L9.5,2.5L10.92,3.91L7.83,7H13.5M6,18H8V20H6V18Z" /></g><g id="unfold-less"><path d="M16.59,5.41L15.17,4L12,7.17L8.83,4L7.41,5.41L12,10M7.41,18.59L8.83,20L12,16.83L15.17,20L16.58,18.59L12,14L7.41,18.59Z" /></g><g id="unfold-more"><path d="M12,18.17L8.83,15L7.42,16.41L12,21L16.59,16.41L15.17,15M12,5.83L15.17,9L16.58,7.59L12,3L7.41,7.59L8.83,9L12,5.83Z" /></g><g id="ungroup"><path d="M2,2H6V3H13V2H17V6H16V9H18V8H22V12H21V18H22V22H18V21H12V22H8V18H9V16H6V17H2V13H3V6H2V2M18,12V11H16V13H17V17H13V16H11V18H12V19H18V18H19V12H18M13,6V5H6V6H5V13H6V14H9V12H8V8H12V9H14V6H13M12,12H11V14H13V13H14V11H12V12Z" /></g><g id="unity"><path d="M9.11,17H6.5L1.59,12L6.5,7H9.11L10.42,4.74L17.21,3L19.08,9.74L17.77,12L19.08,14.26L17.21,21L10.42,19.26L9.11,17M9.25,16.75L14.38,18.13L11.42,13H5.5L9.25,16.75M16.12,17.13L17.5,12L16.12,6.87L13.15,12L16.12,17.13M9.25,7.25L5.5,11H11.42L14.38,5.87L9.25,7.25Z" /></g><g id="untappd"><path d="M14.41,4C14.41,4 14.94,4.39 14.97,4.71C14.97,4.81 14.73,4.85 14.68,4.93C14.62,5 14.7,5.15 14.65,5.21C14.59,5.26 14.5,5.26 14.41,5.41C14.33,5.56 12.07,10.09 11.73,10.63C11.59,11.03 11.47,12.46 11.37,12.66C11.26,12.85 6.34,19.84 6.16,20.05C5.67,20.63 4.31,20.3 3.28,19.56C2.3,18.86 1.74,17.7 2.11,17.16C2.27,16.93 7.15,9.92 7.29,9.75C7.44,9.58 8.75,9 9.07,8.71C9.47,8.22 12.96,4.54 13.07,4.42C13.18,4.3 13.15,4.2 13.18,4.13C13.22,4.06 13.38,4.08 13.43,4C13.5,3.93 13.39,3.71 13.5,3.68C13.59,3.64 13.96,3.67 14.41,4M10.85,4.44L11.74,5.37L10.26,6.94L9.46,5.37C9.38,5.22 9.28,5.22 9.22,5.17C9.17,5.11 9.24,4.97 9.19,4.89C9.13,4.81 8.9,4.83 8.9,4.73C8.9,4.62 9.05,4.28 9.5,3.96C9.5,3.96 10.06,3.6 10.37,3.68C10.47,3.71 10.43,3.95 10.5,4C10.54,4.1 10.7,4.08 10.73,4.15C10.77,4.21 10.73,4.32 10.85,4.44M21.92,17.15C22.29,17.81 21.53,19 20.5,19.7C19.5,20.39 18.21,20.54 17.83,20C17.66,19.78 12.67,12.82 12.56,12.62C12.45,12.43 12.32,11 12.18,10.59L12.15,10.55C12.45,10 13.07,8.77 13.73,7.47C14.3,8.06 14.75,8.56 14.88,8.72C15.21,9 16.53,9.58 16.68,9.75C16.82,9.92 21.78,16.91 21.92,17.15Z" /></g><g id="update"><path d="M21,10.12H14.22L16.96,7.3C14.23,4.6 9.81,4.5 7.08,7.2C4.35,9.91 4.35,14.28 7.08,17C9.81,19.7 14.23,19.7 16.96,17C18.32,15.65 19,14.08 19,12.1H21C21,14.08 20.12,16.65 18.36,18.39C14.85,21.87 9.15,21.87 5.64,18.39C2.14,14.92 2.11,9.28 5.62,5.81C9.13,2.34 14.76,2.34 18.27,5.81L21,3V10.12M12.5,8V12.25L16,14.33L15.28,15.54L11,13V8H12.5Z" /></g><g id="upload"><path d="M9,16V10H5L12,3L19,10H15V16H9M5,20V18H19V20H5Z" /></g><g id="usb"><path d="M15,7V11H16V13H13V5H15L12,1L9,5H11V13H8V10.93C8.7,10.56 9.2,9.85 9.2,9C9.2,7.78 8.21,6.8 7,6.8C5.78,6.8 4.8,7.78 4.8,9C4.8,9.85 5.3,10.56 6,10.93V13A2,2 0 0,0 8,15H11V18.05C10.29,18.41 9.8,19.15 9.8,20A2.2,2.2 0 0,0 12,22.2A2.2,2.2 0 0,0 14.2,20C14.2,19.15 13.71,18.41 13,18.05V15H16A2,2 0 0,0 18,13V11H19V7H15Z" /></g><g id="vector-arrange-above"><path d="M3,1C1.89,1 1,1.89 1,3V14C1,15.11 1.89,16 3,16C6.67,16 10.33,16 14,16C15.11,16 16,15.11 16,14C16,10.33 16,6.67 16,3C16,1.89 15.11,1 14,1H3M3,3H14V14H3V3M18,7V9H20V20H9V18H7V20C7,21.11 7.89,22 9,22H20C21.11,22 22,21.11 22,20V9C22,7.89 21.11,7 20,7H18Z" /></g><g id="vector-arrange-below"><path d="M20,22C21.11,22 22,21.11 22,20V9C22,7.89 21.11,7 20,7C16.33,7 12.67,7 9,7C7.89,7 7,7.89 7,9C7,12.67 7,16.33 7,20C7,21.11 7.89,22 9,22H20M20,20H9V9H20V20M5,16V14H3V3H14V5H16V3C16,1.89 15.11,1 14,1H3C1.89,1 1,1.89 1,3V14C1,15.11 1.89,16 3,16H5Z" /></g><g id="vector-circle"><path d="M9,2V4.06C6.72,4.92 4.92,6.72 4.05,9H2V15H4.06C4.92,17.28 6.72,19.09 9,19.95V22H15V19.94C17.28,19.08 19.09,17.28 19.95,15H22V9H19.94C19.08,6.72 17.28,4.92 15,4.05V2M11,4H13V6H11M9,6.25V8H15V6.25C16.18,6.86 17.14,7.82 17.75,9H16V15H17.75C17.14,16.18 16.18,17.14 15,17.75V16H9V17.75C7.82,17.14 6.86,16.18 6.25,15H8V9H6.25C6.86,7.82 7.82,6.86 9,6.25M4,11H6V13H4M18,11H20V13H18M11,18H13V20H11" /></g><g id="vector-circle-variant"><path d="M22,9H19.97C18.7,5.41 15.31,3 11.5,3A9,9 0 0,0 2.5,12C2.5,17 6.53,21 11.5,21C15.31,21 18.7,18.6 20,15H22M20,11V13H18V11M17.82,15C16.66,17.44 14.2,19 11.5,19C7.64,19 4.5,15.87 4.5,12C4.5,8.14 7.64,5 11.5,5C14.2,5 16.66,6.57 17.81,9H16V15" /></g><g id="vector-combine"><path d="M3,1C1.89,1 1,1.89 1,3V14C1,15.11 1.89,16 3,16C4.33,16 7,16 7,16C7,16 7,18.67 7,20C7,21.11 7.89,22 9,22H20C21.11,22 22,21.11 22,20V9C22,7.89 21.11,7 20,7C18.67,7 16,7 16,7C16,7 16,4.33 16,3C16,1.89 15.11,1 14,1H3M3,3H14C14,4.33 14,7 14,7H9C7.89,7 7,7.89 7,9V14C7,14 4.33,14 3,14V3M9,9H14V14H9V9M16,9C16,9 18.67,9 20,9V20H9C9,18.67 9,16 9,16H14C15.11,16 16,15.11 16,14V9Z" /></g><g id="vector-curve"><path d="M18.5,2A1.5,1.5 0 0,1 20,3.5A1.5,1.5 0 0,1 18.5,5C18.27,5 18.05,4.95 17.85,4.85L14.16,8.55L14.5,9C16.69,7.74 19.26,7 22,7L23,7.03V9.04L22,9C19.42,9 17,9.75 15,11.04A3.96,3.96 0 0,1 11.04,15C9.75,17 9,19.42 9,22L9.04,23H7.03L7,22C7,19.26 7.74,16.69 9,14.5L8.55,14.16L4.85,17.85C4.95,18.05 5,18.27 5,18.5A1.5,1.5 0 0,1 3.5,20A1.5,1.5 0 0,1 2,18.5A1.5,1.5 0 0,1 3.5,17C3.73,17 3.95,17.05 4.15,17.15L7.84,13.45C7.31,12.78 7,11.92 7,11A4,4 0 0,1 11,7C11.92,7 12.78,7.31 13.45,7.84L17.15,4.15C17.05,3.95 17,3.73 17,3.5A1.5,1.5 0 0,1 18.5,2M11,9A2,2 0 0,0 9,11A2,2 0 0,0 11,13A2,2 0 0,0 13,11A2,2 0 0,0 11,9Z" /></g><g id="vector-difference"><path d="M3,1C1.89,1 1,1.89 1,3V14C1,15.11 1.89,16 3,16H5V14H3V3H14V5H16V3C16,1.89 15.11,1 14,1H3M9,7C7.89,7 7,7.89 7,9V11H9V9H11V7H9M13,7V9H14V10H16V7H13M18,7V9H20V20H9V18H7V20C7,21.11 7.89,22 9,22H20C21.11,22 22,21.11 22,20V9C22,7.89 21.11,7 20,7H18M14,12V14H12V16H14C15.11,16 16,15.11 16,14V12H14M7,13V16H10V14H9V13H7Z" /></g><g id="vector-difference-ab"><path d="M3,1C1.89,1 1,1.89 1,3V5H3V3H5V1H3M7,1V3H10V1H7M12,1V3H14V5H16V3C16,1.89 15.11,1 14,1H12M1,7V10H3V7H1M14,7C14,7 14,11.67 14,14C11.67,14 7,14 7,14C7,14 7,18 7,20C7,21.11 7.89,22 9,22H20C21.11,22 22,21.11 22,20V9C22,7.89 21.11,7 20,7C18,7 14,7 14,7M16,9H20V20H9V16H14C15.11,16 16,15.11 16,14V9M1,12V14C1,15.11 1.89,16 3,16H5V14H3V12H1Z" /></g><g id="vector-difference-ba"><path d="M20,22C21.11,22 22,21.11 22,20V18H20V20H18V22H20M16,22V20H13V22H16M11,22V20H9V18H7V20C7,21.11 7.89,22 9,22H11M22,16V13H20V16H22M9,16C9,16 9,11.33 9,9C11.33,9 16,9 16,9C16,9 16,5 16,3C16,1.89 15.11,1 14,1H3C1.89,1 1,1.89 1,3V14C1,15.11 1.89,16 3,16C5,16 9,16 9,16M7,14H3V3H14V7H9C7.89,7 7,7.89 7,9V14M22,11V9C22,7.89 21.11,7 20,7H18V9H20V11H22Z" /></g><g id="vector-intersection"><path d="M3.14,1A2.14,2.14 0 0,0 1,3.14V5H3V3H5V1H3.14M7,1V3H10V1H7M12,1V3H14V5H16V3.14C16,1.96 15.04,1 13.86,1H12M1,7V10H3V7H1M9,7C7.89,7 7,7.89 7,9C7,11.33 7,16 7,16C7,16 11.57,16 13.86,16A2.14,2.14 0 0,0 16,13.86C16,11.57 16,7 16,7C16,7 11.33,7 9,7M18,7V9H20V11H22V9C22,7.89 21.11,7 20,7H18M9,9H14V14H9V9M1,12V13.86C1,15.04 1.96,16 3.14,16H5V14H3V12H1M20,13V16H22V13H20M7,18V20C7,21.11 7.89,22 9,22H11V20H9V18H7M20,18V20H18V22H20C21.11,22 22,21.11 22,20V18H20M13,20V22H16V20H13Z" /></g><g id="vector-line"><path d="M15,3V7.59L7.59,15H3V21H9V16.42L16.42,9H21V3M17,5H19V7H17M5,17H7V19H5" /></g><g id="vector-point"><path d="M12,20L7,22L12,11L17,22L12,20M8,2H16V5H22V7H16V10H8V7H2V5H8V2M10,4V8H14V4H10Z" /></g><g id="vector-polygon"><path d="M2,2V8H4.28L5.57,16H4V22H10V20.06L15,20.05V22H21V16H19.17L20,9H22V3H16V6.53L14.8,8H9.59L8,5.82V2M4,4H6V6H4M18,5H20V7H18M6.31,8H7.11L9,10.59V14H15V10.91L16.57,9H18L17.16,16H15V18.06H10V16H7.6M11,10H13V12H11M6,18H8V20H6M17,18H19V20H17" /></g><g id="vector-polyline"><path d="M16,2V8H17.08L14.95,13H14.26L12,9.97V5H6V11H6.91L4.88,16H2V22H8V16H7.04L9.07,11H10.27L12,13.32V19H18V13H17.12L19.25,8H22V2M18,4H20V6H18M8,7H10V9H8M14,15H16V17H14M4,18H6V20H4" /></g><g id="vector-rectangle"><path d="M2,4H8V6H16V4H22V10H20V14H22V20H16V18H8V20H2V14H4V10H2V4M16,10V8H8V10H6V14H8V16H16V14H18V10H16M4,6V8H6V6H4M18,6V8H20V6H18M4,16V18H6V16H4M18,16V18H20V16H18Z" /></g><g id="vector-selection"><path d="M3,1H5V3H3V5H1V3A2,2 0 0,1 3,1M14,1A2,2 0 0,1 16,3V5H14V3H12V1H14M20,7A2,2 0 0,1 22,9V11H20V9H18V7H20M22,20A2,2 0 0,1 20,22H18V20H20V18H22V20M20,13H22V16H20V13M13,9V7H16V10H14V9H13M13,22V20H16V22H13M9,22A2,2 0 0,1 7,20V18H9V20H11V22H9M7,16V13H9V14H10V16H7M7,3V1H10V3H7M3,16A2,2 0 0,1 1,14V12H3V14H5V16H3M1,7H3V10H1V7M9,7H11V9H9V11H7V9A2,2 0 0,1 9,7M16,14A2,2 0 0,1 14,16H12V14H14V12H16V14Z" /></g><g id="vector-square"><path d="M2,2H8V4H16V2H22V8H20V16H22V22H16V20H8V22H2V16H4V8H2V2M16,8V6H8V8H6V16H8V18H16V16H18V8H16M4,4V6H6V4H4M18,4V6H20V4H18M4,18V20H6V18H4M18,18V20H20V18H18Z" /></g><g id="vector-triangle"><path d="M9,3V9H9.73L5.79,16H2V22H8V20H16V22H22V16H18.21L14.27,9H15V3M11,5H13V7H11M12,9.04L16,16.15V18H8V16.15M4,18H6V20H4M18,18H20V20H18" /></g><g id="vector-union"><path d="M3,1C1.89,1 1,1.89 1,3V14C1,15.11 1.89,16 3,16H7V20C7,21.11 7.89,22 9,22H20C21.11,22 22,21.11 22,20V9C22,7.89 21.11,7 20,7H16V3C16,1.89 15.11,1 14,1H3M3,3H14V9H20V20H9V14H3V3Z" /></g><g id="verified"><path d="M10,17L6,13L7.41,11.59L10,14.17L16.59,7.58L18,9M12,1L3,5V11C3,16.55 6.84,21.74 12,23C17.16,21.74 21,16.55 21,11V5L12,1Z" /></g><g id="vibrate"><path d="M16,19H8V5H16M16.5,3H7.5A1.5,1.5 0 0,0 6,4.5V19.5A1.5,1.5 0 0,0 7.5,21H16.5A1.5,1.5 0 0,0 18,19.5V4.5A1.5,1.5 0 0,0 16.5,3M19,17H21V7H19M22,9V15H24V9M3,17H5V7H3M0,15H2V9H0V15Z" /></g><g id="video"><path d="M17,10.5V7A1,1 0 0,0 16,6H4A1,1 0 0,0 3,7V17A1,1 0 0,0 4,18H16A1,1 0 0,0 17,17V13.5L21,17.5V6.5L17,10.5Z" /></g><g id="video-off"><path d="M3.27,2L2,3.27L4.73,6H4A1,1 0 0,0 3,7V17A1,1 0 0,0 4,18H16C16.2,18 16.39,17.92 16.54,17.82L19.73,21L21,19.73M21,6.5L17,10.5V7A1,1 0 0,0 16,6H9.82L21,17.18V6.5Z" /></g><g id="video-switch"><path d="M13,15.5V13H7V15.5L3.5,12L7,8.5V11H13V8.5L16.5,12M18,9.5V6A1,1 0 0,0 17,5H3A1,1 0 0,0 2,6V18A1,1 0 0,0 3,19H17A1,1 0 0,0 18,18V14.5L22,18.5V5.5L18,9.5Z" /></g><g id="view-agenda"><path d="M20,3H3A1,1 0 0,0 2,4V10A1,1 0 0,0 3,11H20A1,1 0 0,0 21,10V4A1,1 0 0,0 20,3M20,13H3A1,1 0 0,0 2,14V20A1,1 0 0,0 3,21H20A1,1 0 0,0 21,20V14A1,1 0 0,0 20,13Z" /></g><g id="view-array"><path d="M8,18H17V5H8M18,5V18H21V5M4,18H7V5H4V18Z" /></g><g id="view-carousel"><path d="M18,6V17H22V6M2,17H6V6H2M7,19H17V4H7V19Z" /></g><g id="view-column"><path d="M16,5V18H21V5M4,18H9V5H4M10,18H15V5H10V18Z" /></g><g id="view-dashboard"><path d="M13,3V9H21V3M13,21H21V11H13M3,21H11V15H3M3,13H11V3H3V13Z" /></g><g id="view-day"><path d="M2,3V6H21V3M20,8H3A1,1 0 0,0 2,9V15A1,1 0 0,0 3,16H20A1,1 0 0,0 21,15V9A1,1 0 0,0 20,8M2,21H21V18H2V21Z" /></g><g id="view-grid"><path d="M3,11H11V3H3M3,21H11V13H3M13,21H21V13H13M13,3V11H21V3" /></g><g id="view-headline"><path d="M4,5V7H21V5M4,11H21V9H4M4,19H21V17H4M4,15H21V13H4V15Z" /></g><g id="view-list"><path d="M9,5V9H21V5M9,19H21V15H9M9,14H21V10H9M4,9H8V5H4M4,19H8V15H4M4,14H8V10H4V14Z" /></g><g id="view-module"><path d="M16,5V11H21V5M10,11H15V5H10M16,18H21V12H16M10,18H15V12H10M4,18H9V12H4M4,11H9V5H4V11Z" /></g><g id="view-quilt"><path d="M10,5V11H21V5M16,18H21V12H16M4,18H9V5H4M10,18H15V12H10V18Z" /></g><g id="view-stream"><path d="M4,5V11H21V5M4,18H21V12H4V18Z" /></g><g id="view-week"><path d="M13,5H10A1,1 0 0,0 9,6V18A1,1 0 0,0 10,19H13A1,1 0 0,0 14,18V6A1,1 0 0,0 13,5M20,5H17A1,1 0 0,0 16,6V18A1,1 0 0,0 17,19H20A1,1 0 0,0 21,18V6A1,1 0 0,0 20,5M6,5H3A1,1 0 0,0 2,6V18A1,1 0 0,0 3,19H6A1,1 0 0,0 7,18V6A1,1 0 0,0 6,5Z" /></g><g id="vimeo"><path d="M22,7.42C21.91,9.37 20.55,12.04 17.92,15.44C15.2,19 12.9,20.75 11,20.75C9.85,20.75 8.86,19.67 8.05,17.5C7.5,15.54 7,13.56 6.44,11.58C5.84,9.42 5.2,8.34 4.5,8.34C4.36,8.34 3.84,8.66 2.94,9.29L2,8.07C3,7.2 3.96,6.33 4.92,5.46C6.24,4.32 7.23,3.72 7.88,3.66C9.44,3.5 10.4,4.58 10.76,6.86C11.15,9.33 11.42,10.86 11.57,11.46C12,13.5 12.5,14.5 13.05,14.5C13.47,14.5 14.1,13.86 14.94,12.53C15.78,11.21 16.23,10.2 16.29,9.5C16.41,8.36 15.96,7.79 14.94,7.79C14.46,7.79 13.97,7.9 13.46,8.12C14.44,4.89 16.32,3.32 19.09,3.41C21.15,3.47 22.12,4.81 22,7.42Z" /></g><g id="vine"><path d="M19.89,11.95C19.43,12.06 19,12.1 18.57,12.1C16.3,12.1 14.55,10.5 14.55,7.76C14.55,6.41 15.08,5.7 15.82,5.7C16.5,5.7 17,6.33 17,7.61C17,8.34 16.79,9.14 16.65,9.61C16.65,9.61 17.35,10.83 19.26,10.46C19.67,9.56 19.89,8.39 19.89,7.36C19.89,4.6 18.5,3 15.91,3C13.26,3 11.71,5.04 11.71,7.72C11.71,10.38 12.95,12.67 15,13.71C14.14,15.43 13.04,16.95 11.9,18.1C9.82,15.59 7.94,12.24 7.17,5.7H4.11C5.53,16.59 9.74,20.05 10.86,20.72C11.5,21.1 12.03,21.08 12.61,20.75C13.5,20.24 16.23,17.5 17.74,14.34C18.37,14.33 19.13,14.26 19.89,14.09V11.95Z" /></g><g id="violin"><path d="M11,2A1,1 0 0,0 10,3V5L10,9A0.5,0.5 0 0,0 10.5,9.5H12A0.5,0.5 0 0,1 12.5,10A0.5,0.5 0 0,1 12,10.5H10.5C9.73,10.5 9,9.77 9,9V5.16C7.27,5.6 6,7.13 6,9V10.5A2.5,2.5 0 0,1 8.5,13A2.5,2.5 0 0,1 6,15.5V17C6,19.77 8.23,22 11,22H13C15.77,22 18,19.77 18,17V15.5A2.5,2.5 0 0,1 15.5,13A2.5,2.5 0 0,1 18,10.5V9C18,6.78 16.22,5 14,5V3A1,1 0 0,0 13,2H11M10.75,16.5H13.25L12.75,20H11.25L10.75,16.5Z" /></g><g id="visualstudio"><path d="M17,8.5L12.25,12.32L17,16V8.5M4.7,18.4L2,16.7V7.7L5,6.7L9.3,10.03L18,2L22,4.5V20L17,22L9.34,14.66L4.7,18.4M5,14L6.86,12.28L5,10.5V14Z" /></g><g id="vk"><path d="M19.54,14.6C21.09,16.04 21.41,16.73 21.46,16.82C22.1,17.88 20.76,17.96 20.76,17.96L18.18,18C18.18,18 17.62,18.11 16.9,17.61C15.93,16.95 15,15.22 14.31,15.45C13.6,15.68 13.62,17.23 13.62,17.23C13.62,17.23 13.62,17.45 13.46,17.62C13.28,17.81 12.93,17.74 12.93,17.74H11.78C11.78,17.74 9.23,18 7,15.67C4.55,13.13 2.39,8.13 2.39,8.13C2.39,8.13 2.27,7.83 2.4,7.66C2.55,7.5 2.97,7.5 2.97,7.5H5.73C5.73,7.5 6,7.5 6.17,7.66C6.32,7.77 6.41,8 6.41,8C6.41,8 6.85,9.11 7.45,10.13C8.6,12.12 9.13,12.55 9.5,12.34C10.1,12.03 9.93,9.53 9.93,9.53C9.93,9.53 9.94,8.62 9.64,8.22C9.41,7.91 8.97,7.81 8.78,7.79C8.62,7.77 8.88,7.41 9.21,7.24C9.71,7 10.58,7 11.62,7C12.43,7 12.66,7.06 12.97,7.13C13.93,7.36 13.6,8.25 13.6,10.37C13.6,11.06 13.5,12 13.97,12.33C14.18,12.47 14.7,12.35 16,10.16C16.6,9.12 17.06,7.89 17.06,7.89C17.06,7.89 17.16,7.68 17.31,7.58C17.47,7.5 17.69,7.5 17.69,7.5H20.59C20.59,7.5 21.47,7.4 21.61,7.79C21.76,8.2 21.28,9.17 20.09,10.74C18.15,13.34 17.93,13.1 19.54,14.6Z" /></g><g id="vk-box"><path d="M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M17.24,14.03C16.06,12.94 16.22,13.11 17.64,11.22C18.5,10.07 18.85,9.37 18.74,9.07C18.63,8.79 18,8.86 18,8.86L15.89,8.88C15.89,8.88 15.73,8.85 15.62,8.92C15.5,9 15.43,9.15 15.43,9.15C15.43,9.15 15.09,10.04 14.65,10.8C13.71,12.39 13.33,12.47 13.18,12.38C12.83,12.15 12.91,11.45 12.91,10.95C12.91,9.41 13.15,8.76 12.46,8.6C12.23,8.54 12.06,8.5 11.47,8.5C10.72,8.5 10.08,8.5 9.72,8.68C9.5,8.8 9.29,9.06 9.41,9.07C9.55,9.09 9.86,9.16 10.03,9.39C10.25,9.68 10.24,10.34 10.24,10.34C10.24,10.34 10.36,12.16 9.95,12.39C9.66,12.54 9.27,12.22 8.44,10.78C8,10.04 7.68,9.22 7.68,9.22L7.5,9L7.19,8.85H5.18C5.18,8.85 4.88,8.85 4.77,9C4.67,9.1 4.76,9.32 4.76,9.32C4.76,9.32 6.33,12.96 8.11,14.8C9.74,16.5 11.59,16.31 11.59,16.31H12.43C12.43,16.31 12.68,16.36 12.81,16.23C12.93,16.1 12.93,15.94 12.93,15.94C12.93,15.94 12.91,14.81 13.43,14.65C13.95,14.5 14.61,15.73 15.31,16.22C15.84,16.58 16.24,16.5 16.24,16.5L18.12,16.47C18.12,16.47 19.1,16.41 18.63,15.64C18.6,15.58 18.36,15.07 17.24,14.03Z" /></g><g id="vk-circle"><path d="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M17.24,14.03C16.06,12.94 16.22,13.11 17.64,11.22C18.5,10.07 18.85,9.37 18.74,9.07C18.63,8.79 18,8.86 18,8.86L15.89,8.88C15.89,8.88 15.73,8.85 15.62,8.92C15.5,9 15.43,9.15 15.43,9.15C15.43,9.15 15.09,10.04 14.65,10.8C13.71,12.39 13.33,12.47 13.18,12.38C12.83,12.15 12.91,11.45 12.91,10.95C12.91,9.41 13.15,8.76 12.46,8.6C12.23,8.54 12.06,8.5 11.47,8.5C10.72,8.5 10.08,8.5 9.72,8.68C9.5,8.8 9.29,9.06 9.41,9.07C9.55,9.09 9.86,9.16 10.03,9.39C10.25,9.68 10.24,10.34 10.24,10.34C10.24,10.34 10.36,12.16 9.95,12.39C9.66,12.54 9.27,12.22 8.44,10.78C8,10.04 7.68,9.22 7.68,9.22L7.5,9L7.19,8.85H5.18C5.18,8.85 4.88,8.85 4.77,9C4.67,9.1 4.76,9.32 4.76,9.32C4.76,9.32 6.33,12.96 8.11,14.8C9.74,16.5 11.59,16.31 11.59,16.31H12.43C12.43,16.31 12.68,16.36 12.81,16.23C12.93,16.1 12.93,15.94 12.93,15.94C12.93,15.94 12.91,14.81 13.43,14.65C13.95,14.5 14.61,15.73 15.31,16.22C15.84,16.58 16.24,16.5 16.24,16.5L18.12,16.47C18.12,16.47 19.1,16.41 18.63,15.64C18.6,15.58 18.36,15.07 17.24,14.03Z" /></g><g id="vlc"><path d="M12,1C11.58,1 11.19,1.23 11,1.75L9.88,4.88C10.36,5.4 11.28,5.5 12,5.5C12.72,5.5 13.64,5.4 14.13,4.88L13,1.75C12.82,1.25 12.42,1 12,1M8.44,8.91L7,12.91C8.07,14.27 10.26,14.5 12,14.5C13.74,14.5 15.93,14.27 17,12.91L15.56,8.91C14.76,9.83 13.24,10 12,10C10.76,10 9.24,9.83 8.44,8.91M5.44,15C4.62,15 3.76,15.65 3.53,16.44L2.06,21.56C1.84,22.35 2.3,23 3.13,23H20.88C21.7,23 22.16,22.35 21.94,21.56L20.47,16.44C20.24,15.65 19.38,15 18.56,15H17.75L18.09,15.97C18.21,16.29 18.29,16.69 18.09,16.97C16.84,18.7 14.14,19 12,19C9.86,19 7.16,18.7 5.91,16.97C5.71,16.69 5.79,16.29 5.91,15.97L6.25,15H5.44Z" /></g><g id="voice"><path d="M9,5A4,4 0 0,1 13,9A4,4 0 0,1 9,13A4,4 0 0,1 5,9A4,4 0 0,1 9,5M9,15C11.67,15 17,16.34 17,19V21H1V19C1,16.34 6.33,15 9,15M16.76,5.36C18.78,7.56 18.78,10.61 16.76,12.63L15.08,10.94C15.92,9.76 15.92,8.23 15.08,7.05L16.76,5.36M20.07,2C24,6.05 23.97,12.11 20.07,16L18.44,14.37C21.21,11.19 21.21,6.65 18.44,3.63L20.07,2Z" /></g><g id="voicemail"><path d="M18.5,15A3.5,3.5 0 0,1 15,11.5A3.5,3.5 0 0,1 18.5,8A3.5,3.5 0 0,1 22,11.5A3.5,3.5 0 0,1 18.5,15M5.5,15A3.5,3.5 0 0,1 2,11.5A3.5,3.5 0 0,1 5.5,8A3.5,3.5 0 0,1 9,11.5A3.5,3.5 0 0,1 5.5,15M18.5,6A5.5,5.5 0 0,0 13,11.5C13,12.83 13.47,14.05 14.26,15H9.74C10.53,14.05 11,12.83 11,11.5A5.5,5.5 0 0,0 5.5,6A5.5,5.5 0 0,0 0,11.5A5.5,5.5 0 0,0 5.5,17H18.5A5.5,5.5 0 0,0 24,11.5A5.5,5.5 0 0,0 18.5,6Z" /></g><g id="volume-high"><path d="M14,3.23V5.29C16.89,6.15 19,8.83 19,12C19,15.17 16.89,17.84 14,18.7V20.77C18,19.86 21,16.28 21,12C21,7.72 18,4.14 14,3.23M16.5,12C16.5,10.23 15.5,8.71 14,7.97V16C15.5,15.29 16.5,13.76 16.5,12M3,9V15H7L12,20V4L7,9H3Z" /></g><g id="volume-low"><path d="M7,9V15H11L16,20V4L11,9H7Z" /></g><g id="volume-medium"><path d="M5,9V15H9L14,20V4L9,9M18.5,12C18.5,10.23 17.5,8.71 16,7.97V16C17.5,15.29 18.5,13.76 18.5,12Z" /></g><g id="volume-off"><path d="M12,4L9.91,6.09L12,8.18M4.27,3L3,4.27L7.73,9H3V15H7L12,20V13.27L16.25,17.53C15.58,18.04 14.83,18.46 14,18.7V20.77C15.38,20.45 16.63,19.82 17.68,18.96L19.73,21L21,19.73L12,10.73M19,12C19,12.94 18.8,13.82 18.46,14.64L19.97,16.15C20.62,14.91 21,13.5 21,12C21,7.72 18,4.14 14,3.23V5.29C16.89,6.15 19,8.83 19,12M16.5,12C16.5,10.23 15.5,8.71 14,7.97V10.18L16.45,12.63C16.5,12.43 16.5,12.21 16.5,12Z" /></g><g id="vpn"><path d="M9,5H15L12,8L9,5M10.5,14.66C10.2,15 10,15.5 10,16A2,2 0 0,0 12,18A2,2 0 0,0 14,16C14,15.45 13.78,14.95 13.41,14.59L14.83,13.17C15.55,13.9 16,14.9 16,16A4,4 0 0,1 12,20A4,4 0 0,1 8,16C8,14.93 8.42,13.96 9.1,13.25L9.09,13.24L16.17,6.17V6.17C16.89,5.45 17.89,5 19,5A4,4 0 0,1 23,9A4,4 0 0,1 19,13C17.9,13 16.9,12.55 16.17,11.83L17.59,10.41C17.95,10.78 18.45,11 19,11A2,2 0 0,0 21,9A2,2 0 0,0 19,7C18.45,7 17.95,7.22 17.59,7.59L10.5,14.66M6.41,7.59C6.05,7.22 5.55,7 5,7A2,2 0 0,0 3,9A2,2 0 0,0 5,11C5.55,11 6.05,10.78 6.41,10.41L7.83,11.83C7.1,12.55 6.1,13 5,13A4,4 0 0,1 1,9A4,4 0 0,1 5,5C6.11,5 7.11,5.45 7.83,6.17V6.17L10.59,8.93L9.17,10.35L6.41,7.59Z" /></g><g id="walk"><path d="M14.12,10H19V8.2H15.38L13.38,4.87C13.08,4.37 12.54,4.03 11.92,4.03C11.74,4.03 11.58,4.06 11.42,4.11L6,5.8V11H7.8V7.33L9.91,6.67L6,22H7.8L10.67,13.89L13,17V22H14.8V15.59L12.31,11.05L13.04,8.18M14,3.8C15,3.8 15.8,3 15.8,2C15.8,1 15,0.2 14,0.2C13,0.2 12.2,1 12.2,2C12.2,3 13,3.8 14,3.8Z" /></g><g id="wallet"><path d="M21,18V19A2,2 0 0,1 19,21H5C3.89,21 3,20.1 3,19V5A2,2 0 0,1 5,3H19A2,2 0 0,1 21,5V6H12C10.89,6 10,6.9 10,8V16A2,2 0 0,0 12,18M12,16H22V8H12M16,13.5A1.5,1.5 0 0,1 14.5,12A1.5,1.5 0 0,1 16,10.5A1.5,1.5 0 0,1 17.5,12A1.5,1.5 0 0,1 16,13.5Z" /></g><g id="wallet-giftcard"><path d="M20,14H4V8H9.08L7,10.83L8.62,12L11,8.76L12,7.4L13,8.76L15.38,12L17,10.83L14.92,8H20M20,19H4V17H20M9,4A1,1 0 0,1 10,5A1,1 0 0,1 9,6A1,1 0 0,1 8,5A1,1 0 0,1 9,4M15,4A1,1 0 0,1 16,5A1,1 0 0,1 15,6A1,1 0 0,1 14,5A1,1 0 0,1 15,4M20,6H17.82C17.93,5.69 18,5.35 18,5A3,3 0 0,0 15,2C13.95,2 13.04,2.54 12.5,3.35L12,4L11.5,3.34C10.96,2.54 10.05,2 9,2A3,3 0 0,0 6,5C6,5.35 6.07,5.69 6.18,6H4C2.89,6 2,6.89 2,8V19C2,20.11 2.89,21 4,21H20C21.11,21 22,20.11 22,19V8C22,6.89 21.11,6 20,6Z" /></g><g id="wallet-membership"><path d="M20,10H4V4H20M20,15H4V13H20M20,2H4C2.89,2 2,2.89 2,4V15C2,16.11 2.89,17 4,17H8V22L12,20L16,22V17H20C21.11,17 22,16.11 22,15V4C22,2.89 21.11,2 20,2Z" /></g><g id="wallet-travel"><path d="M20,14H4V8H7V10H9V8H15V10H17V8H20M20,19H4V17H20M9,4H15V6H9M20,6H17V4C17,2.89 16.11,2 15,2H9C7.89,2 7,2.89 7,4V6H4C2.89,6 2,6.89 2,8V19C2,20.11 2.89,21 4,21H20C21.11,21 22,20.11 22,19V8C22,6.89 21.11,6 20,6Z" /></g><g id="wan"><path d="M12,2A8,8 0 0,0 4,10C4,14.03 7,17.42 11,17.93V19H10A1,1 0 0,0 9,20H2V22H9A1,1 0 0,0 10,23H14A1,1 0 0,0 15,22H22V20H15A1,1 0 0,0 14,19H13V17.93C17,17.43 20,14.03 20,10A8,8 0 0,0 12,2M12,4C12,4 12.74,5.28 13.26,7H10.74C11.26,5.28 12,4 12,4M9.77,4.43C9.5,4.93 9.09,5.84 8.74,7H6.81C7.5,5.84 8.5,4.93 9.77,4.43M14.23,4.44C15.5,4.94 16.5,5.84 17.19,7H15.26C14.91,5.84 14.5,4.93 14.23,4.44M6.09,9H8.32C8.28,9.33 8.25,9.66 8.25,10C8.25,10.34 8.28,10.67 8.32,11H6.09C6.03,10.67 6,10.34 6,10C6,9.66 6.03,9.33 6.09,9M10.32,9H13.68C13.72,9.33 13.75,9.66 13.75,10C13.75,10.34 13.72,10.67 13.68,11H10.32C10.28,10.67 10.25,10.34 10.25,10C10.25,9.66 10.28,9.33 10.32,9M15.68,9H17.91C17.97,9.33 18,9.66 18,10C18,10.34 17.97,10.67 17.91,11H15.68C15.72,10.67 15.75,10.34 15.75,10C15.75,9.66 15.72,9.33 15.68,9M6.81,13H8.74C9.09,14.16 9.5,15.07 9.77,15.56C8.5,15.06 7.5,14.16 6.81,13M10.74,13H13.26C12.74,14.72 12,16 12,16C12,16 11.26,14.72 10.74,13M15.26,13H17.19C16.5,14.16 15.5,15.07 14.23,15.57C14.5,15.07 14.91,14.16 15.26,13Z" /></g><g id="watch"><path d="M6,12A6,6 0 0,1 12,6A6,6 0 0,1 18,12A6,6 0 0,1 12,18A6,6 0 0,1 6,12M20,12C20,9.45 18.81,7.19 16.95,5.73L16,0H8L7.05,5.73C5.19,7.19 4,9.45 4,12C4,14.54 5.19,16.81 7.05,18.27L8,24H16L16.95,18.27C18.81,16.81 20,14.54 20,12Z" /></g><g id="watch-export"><path d="M14,11H19L16.5,8.5L17.92,7.08L22.84,12L17.92,16.92L16.5,15.5L19,13H14V11M12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.4,6 14.69,6.5 15.71,7.29L17.13,5.87L16.95,5.73L16,0H8L7.05,5.73C5.19,7.19 4,9.46 4,12C4,14.55 5.19,16.81 7.05,18.27L8,24H16L16.95,18.27L17.13,18.13L15.71,16.71C14.69,17.5 13.4,18 12,18Z" /></g><g id="watch-import"><path d="M2,11H7L4.5,8.5L5.92,7.08L10.84,12L5.92,16.92L4.5,15.5L7,13H2V11M12,18A6,6 0 0,0 18,12A6,6 0 0,0 12,6C10.6,6 9.31,6.5 8.29,7.29L6.87,5.87L7.05,5.73L8,0H16L16.95,5.73C18.81,7.19 20,9.45 20,12C20,14.54 18.81,16.81 16.95,18.27L16,24H8L7.05,18.27L6.87,18.13L8.29,16.71C9.31,17.5 10.6,18 12,18Z" /></g><g id="watch-vibrate"><path d="M3,17V7H5V17H3M19,17V7H21V17H19M22,9H24V15H22V9M0,15V9H2V15H0M17.96,11.97C17.96,13.87 17.07,15.57 15.68,16.67L14.97,20.95H9L8.27,16.67C6.88,15.57 6,13.87 6,11.97C6,10.07 6.88,8.37 8.27,7.28L9,3H14.97L15.68,7.28C17.07,8.37 17.96,10.07 17.96,11.97M7.5,11.97C7.5,14.45 9.5,16.46 11.97,16.46A4.49,4.49 0 0,0 16.46,11.97C16.46,9.5 14.45,7.5 11.97,7.5A4.47,4.47 0 0,0 7.5,11.97Z" /></g><g id="water"><path d="M12,20A6,6 0 0,1 6,14C6,10 12,3.25 12,3.25C12,3.25 18,10 18,14A6,6 0 0,1 12,20Z" /></g><g id="water-off"><path d="M17.12,17.12L12.5,12.5L5.27,5.27L4,6.55L7.32,9.87C6.55,11.32 6,12.79 6,14A6,6 0 0,0 12,20C13.5,20 14.9,19.43 15.96,18.5L18.59,21.13L19.86,19.86L17.12,17.12M18,14C18,10 12,3.2 12,3.2C12,3.2 10.67,4.71 9.27,6.72L17.86,15.31C17.95,14.89 18,14.45 18,14Z" /></g><g id="water-percent"><path d="M12,3.25C12,3.25 6,10 6,14C6,17.32 8.69,20 12,20A6,6 0 0,0 18,14C18,10 12,3.25 12,3.25M14.47,9.97L15.53,11.03L9.53,17.03L8.47,15.97M9.75,10A1.25,1.25 0 0,1 11,11.25A1.25,1.25 0 0,1 9.75,12.5A1.25,1.25 0 0,1 8.5,11.25A1.25,1.25 0 0,1 9.75,10M14.25,14.5A1.25,1.25 0 0,1 15.5,15.75A1.25,1.25 0 0,1 14.25,17A1.25,1.25 0 0,1 13,15.75A1.25,1.25 0 0,1 14.25,14.5Z" /></g><g id="water-pump"><path d="M19,14.5C19,14.5 21,16.67 21,18A2,2 0 0,1 19,20A2,2 0 0,1 17,18C17,16.67 19,14.5 19,14.5M5,18V9A2,2 0 0,1 3,7A2,2 0 0,1 5,5V4A2,2 0 0,1 7,2H9A2,2 0 0,1 11,4V5H19A2,2 0 0,1 21,7V9L21,11A1,1 0 0,1 22,12A1,1 0 0,1 21,13H17A1,1 0 0,1 16,12A1,1 0 0,1 17,11V9H11V18H12A2,2 0 0,1 14,20V22H2V20A2,2 0 0,1 4,18H5Z" /></g><g id="watermark"><path d="M21,3H3A2,2 0 0,0 1,5V19A2,2 0 0,0 3,21H21A2,2 0 0,0 23,19V5A2,2 0 0,0 21,3M21,19H12V13H21V19Z" /></g><g id="weather-cloudy"><path d="M6,19A5,5 0 0,1 1,14A5,5 0 0,1 6,9C7,6.65 9.3,5 12,5C15.43,5 18.24,7.66 18.5,11.03L19,11A4,4 0 0,1 23,15A4,4 0 0,1 19,19H6M19,13H17V12A5,5 0 0,0 12,7C9.5,7 7.45,8.82 7.06,11.19C6.73,11.07 6.37,11 6,11A3,3 0 0,0 3,14A3,3 0 0,0 6,17H19A2,2 0 0,0 21,15A2,2 0 0,0 19,13Z" /></g><g id="weather-fog"><path d="M3,15H13A1,1 0 0,1 14,16A1,1 0 0,1 13,17H3A1,1 0 0,1 2,16A1,1 0 0,1 3,15M16,15H21A1,1 0 0,1 22,16A1,1 0 0,1 21,17H16A1,1 0 0,1 15,16A1,1 0 0,1 16,15M1,12A5,5 0 0,1 6,7C7,4.65 9.3,3 12,3C15.43,3 18.24,5.66 18.5,9.03L19,9C21.19,9 22.97,10.76 23,13H21A2,2 0 0,0 19,11H17V10A5,5 0 0,0 12,5C9.5,5 7.45,6.82 7.06,9.19C6.73,9.07 6.37,9 6,9A3,3 0 0,0 3,12C3,12.35 3.06,12.69 3.17,13H1.1L1,12M3,19H5A1,1 0 0,1 6,20A1,1 0 0,1 5,21H3A1,1 0 0,1 2,20A1,1 0 0,1 3,19M8,19H21A1,1 0 0,1 22,20A1,1 0 0,1 21,21H8A1,1 0 0,1 7,20A1,1 0 0,1 8,19Z" /></g><g id="weather-hail"><path d="M6,14A1,1 0 0,1 7,15A1,1 0 0,1 6,16A5,5 0 0,1 1,11A5,5 0 0,1 6,6C7,3.65 9.3,2 12,2C15.43,2 18.24,4.66 18.5,8.03L19,8A4,4 0 0,1 23,12A4,4 0 0,1 19,16H18A1,1 0 0,1 17,15A1,1 0 0,1 18,14H19A2,2 0 0,0 21,12A2,2 0 0,0 19,10H17V9A5,5 0 0,0 12,4C9.5,4 7.45,5.82 7.06,8.19C6.73,8.07 6.37,8 6,8A3,3 0 0,0 3,11A3,3 0 0,0 6,14M10,18A2,2 0 0,1 12,20A2,2 0 0,1 10,22A2,2 0 0,1 8,20A2,2 0 0,1 10,18M14.5,16A1.5,1.5 0 0,1 16,17.5A1.5,1.5 0 0,1 14.5,19A1.5,1.5 0 0,1 13,17.5A1.5,1.5 0 0,1 14.5,16M10.5,12A1.5,1.5 0 0,1 12,13.5A1.5,1.5 0 0,1 10.5,15A1.5,1.5 0 0,1 9,13.5A1.5,1.5 0 0,1 10.5,12Z" /></g><g id="weather-lightning"><path d="M6,16A5,5 0 0,1 1,11A5,5 0 0,1 6,6C7,3.65 9.3,2 12,2C15.43,2 18.24,4.66 18.5,8.03L19,8A4,4 0 0,1 23,12A4,4 0 0,1 19,16H18A1,1 0 0,1 17,15A1,1 0 0,1 18,14H19A2,2 0 0,0 21,12A2,2 0 0,0 19,10H17V9A5,5 0 0,0 12,4C9.5,4 7.45,5.82 7.06,8.19C6.73,8.07 6.37,8 6,8A3,3 0 0,0 3,11A3,3 0 0,0 6,14H7A1,1 0 0,1 8,15A1,1 0 0,1 7,16H6M12,11H15L13,15H15L11.25,22L12,17H9.5L12,11Z" /></g><g id="weather-lightning-rainy"><path d="M4.5,13.59C5,13.87 5.14,14.5 4.87,14.96C4.59,15.44 4,15.6 3.5,15.33V15.33C2,14.47 1,12.85 1,11A5,5 0 0,1 6,6C7,3.65 9.3,2 12,2C15.43,2 18.24,4.66 18.5,8.03L19,8A4,4 0 0,1 23,12A4,4 0 0,1 19,16A1,1 0 0,1 18,15A1,1 0 0,1 19,14A2,2 0 0,0 21,12A2,2 0 0,0 19,10H17V9A5,5 0 0,0 12,4C9.5,4 7.45,5.82 7.06,8.19C6.73,8.07 6.37,8 6,8A3,3 0 0,0 3,11C3,12.11 3.6,13.08 4.5,13.6V13.59M9.5,11H12.5L10.5,15H12.5L8.75,22L9.5,17H7L9.5,11M17.5,18.67C17.5,19.96 16.5,21 15.25,21C14,21 13,19.96 13,18.67C13,17.12 15.25,14.5 15.25,14.5C15.25,14.5 17.5,17.12 17.5,18.67Z" /></g><g id="weather-night"><path d="M17.75,4.09L15.22,6.03L16.13,9.09L13.5,7.28L10.87,9.09L11.78,6.03L9.25,4.09L12.44,4L13.5,1L14.56,4L17.75,4.09M21.25,11L19.61,12.25L20.2,14.23L18.5,13.06L16.8,14.23L17.39,12.25L15.75,11L17.81,10.95L18.5,9L19.19,10.95L21.25,11M18.97,15.95C19.8,15.87 20.69,17.05 20.16,17.8C19.84,18.25 19.5,18.67 19.08,19.07C15.17,23 8.84,23 4.94,19.07C1.03,15.17 1.03,8.83 4.94,4.93C5.34,4.53 5.76,4.17 6.21,3.85C6.96,3.32 8.14,4.21 8.06,5.04C7.79,7.9 8.75,10.87 10.95,13.06C13.14,15.26 16.1,16.22 18.97,15.95M17.33,17.97C14.5,17.81 11.7,16.64 9.53,14.5C7.36,12.31 6.2,9.5 6.04,6.68C3.23,9.82 3.34,14.64 6.35,17.66C9.37,20.67 14.19,20.78 17.33,17.97Z" /></g><g id="weather-partlycloudy"><path d="M12.74,5.47C15.1,6.5 16.35,9.03 15.92,11.46C17.19,12.56 18,14.19 18,16V16.17C18.31,16.06 18.65,16 19,16A3,3 0 0,1 22,19A3,3 0 0,1 19,22H6A4,4 0 0,1 2,18A4,4 0 0,1 6,14H6.27C5,12.45 4.6,10.24 5.5,8.26C6.72,5.5 9.97,4.24 12.74,5.47M11.93,7.3C10.16,6.5 8.09,7.31 7.31,9.07C6.85,10.09 6.93,11.22 7.41,12.13C8.5,10.83 10.16,10 12,10C12.7,10 13.38,10.12 14,10.34C13.94,9.06 13.18,7.86 11.93,7.3M13.55,3.64C13,3.4 12.45,3.23 11.88,3.12L14.37,1.82L15.27,4.71C14.76,4.29 14.19,3.93 13.55,3.64M6.09,4.44C5.6,4.79 5.17,5.19 4.8,5.63L4.91,2.82L7.87,3.5C7.25,3.71 6.65,4.03 6.09,4.44M18,9.71C17.91,9.12 17.78,8.55 17.59,8L19.97,9.5L17.92,11.73C18.03,11.08 18.05,10.4 18,9.71M3.04,11.3C3.11,11.9 3.24,12.47 3.43,13L1.06,11.5L3.1,9.28C3,9.93 2.97,10.61 3.04,11.3M19,18H16V16A4,4 0 0,0 12,12A4,4 0 0,0 8,16H6A2,2 0 0,0 4,18A2,2 0 0,0 6,20H19A1,1 0 0,0 20,19A1,1 0 0,0 19,18Z" /></g><g id="weather-pouring"><path d="M9,12C9.53,12.14 9.85,12.69 9.71,13.22L8.41,18.05C8.27,18.59 7.72,18.9 7.19,18.76C6.65,18.62 6.34,18.07 6.5,17.54L7.78,12.71C7.92,12.17 8.47,11.86 9,12M13,12C13.53,12.14 13.85,12.69 13.71,13.22L11.64,20.95C11.5,21.5 10.95,21.8 10.41,21.66C9.88,21.5 9.56,20.97 9.7,20.43L11.78,12.71C11.92,12.17 12.47,11.86 13,12M17,12C17.53,12.14 17.85,12.69 17.71,13.22L16.41,18.05C16.27,18.59 15.72,18.9 15.19,18.76C14.65,18.62 14.34,18.07 14.5,17.54L15.78,12.71C15.92,12.17 16.47,11.86 17,12M17,10V9A5,5 0 0,0 12,4C9.5,4 7.45,5.82 7.06,8.19C6.73,8.07 6.37,8 6,8A3,3 0 0,0 3,11C3,12.11 3.6,13.08 4.5,13.6V13.59C5,13.87 5.14,14.5 4.87,14.96C4.59,15.43 4,15.6 3.5,15.32V15.33C2,14.47 1,12.85 1,11A5,5 0 0,1 6,6C7,3.65 9.3,2 12,2C15.43,2 18.24,4.66 18.5,8.03L19,8A4,4 0 0,1 23,12C23,13.5 22.2,14.77 21,15.46V15.46C20.5,15.73 19.91,15.57 19.63,15.09C19.36,14.61 19.5,14 20,13.72V13.73C20.6,13.39 21,12.74 21,12A2,2 0 0,0 19,10H17Z" /></g><g id="weather-rainy"><path d="M6,14A1,1 0 0,1 7,15A1,1 0 0,1 6,16A5,5 0 0,1 1,11A5,5 0 0,1 6,6C7,3.65 9.3,2 12,2C15.43,2 18.24,4.66 18.5,8.03L19,8A4,4 0 0,1 23,12A4,4 0 0,1 19,16H18A1,1 0 0,1 17,15A1,1 0 0,1 18,14H19A2,2 0 0,0 21,12A2,2 0 0,0 19,10H17V9A5,5 0 0,0 12,4C9.5,4 7.45,5.82 7.06,8.19C6.73,8.07 6.37,8 6,8A3,3 0 0,0 3,11A3,3 0 0,0 6,14M14.83,15.67C16.39,17.23 16.39,19.5 14.83,21.08C14.05,21.86 13,22 12,22C11,22 9.95,21.86 9.17,21.08C7.61,19.5 7.61,17.23 9.17,15.67L12,11L14.83,15.67M13.41,16.69L12,14.25L10.59,16.69C9.8,17.5 9.8,18.7 10.59,19.5C11,19.93 11.5,20 12,20C12.5,20 13,19.93 13.41,19.5C14.2,18.7 14.2,17.5 13.41,16.69Z" /></g><g id="weather-snowy"><path d="M6,14A1,1 0 0,1 7,15A1,1 0 0,1 6,16A5,5 0 0,1 1,11A5,5 0 0,1 6,6C7,3.65 9.3,2 12,2C15.43,2 18.24,4.66 18.5,8.03L19,8A4,4 0 0,1 23,12A4,4 0 0,1 19,16H18A1,1 0 0,1 17,15A1,1 0 0,1 18,14H19A2,2 0 0,0 21,12A2,2 0 0,0 19,10H17V9A5,5 0 0,0 12,4C9.5,4 7.45,5.82 7.06,8.19C6.73,8.07 6.37,8 6,8A3,3 0 0,0 3,11A3,3 0 0,0 6,14M7.88,18.07L10.07,17.5L8.46,15.88C8.07,15.5 8.07,14.86 8.46,14.46C8.85,14.07 9.5,14.07 9.88,14.46L11.5,16.07L12.07,13.88C12.21,13.34 12.76,13.03 13.29,13.17C13.83,13.31 14.14,13.86 14,14.4L13.41,16.59L15.6,16C16.14,15.86 16.69,16.17 16.83,16.71C16.97,17.24 16.66,17.79 16.12,17.93L13.93,18.5L15.54,20.12C15.93,20.5 15.93,21.15 15.54,21.54C15.15,21.93 14.5,21.93 14.12,21.54L12.5,19.93L11.93,22.12C11.79,22.66 11.24,22.97 10.71,22.83C10.17,22.69 9.86,22.14 10,21.6L10.59,19.41L8.4,20C7.86,20.14 7.31,19.83 7.17,19.29C7.03,18.76 7.34,18.21 7.88,18.07Z" /></g><g id="weather-snowy-rainy"><path d="M18.5,18.67C18.5,19.96 17.5,21 16.25,21C15,21 14,19.96 14,18.67C14,17.12 16.25,14.5 16.25,14.5C16.25,14.5 18.5,17.12 18.5,18.67M4,17.36C3.86,16.82 4.18,16.25 4.73,16.11L7,15.5L5.33,13.86C4.93,13.46 4.93,12.81 5.33,12.4C5.73,12 6.4,12 6.79,12.4L8.45,14.05L9.04,11.8C9.18,11.24 9.75,10.92 10.29,11.07C10.85,11.21 11.17,11.78 11,12.33L10.42,14.58L12.67,14C13.22,13.83 13.79,14.15 13.93,14.71C14.08,15.25 13.76,15.82 13.2,15.96L10.95,16.55L12.6,18.21C13,18.6 13,19.27 12.6,19.67C12.2,20.07 11.54,20.07 11.15,19.67L9.5,18L8.89,20.27C8.75,20.83 8.18,21.14 7.64,21C7.08,20.86 6.77,20.29 6.91,19.74L7.5,17.5L5.26,18.09C4.71,18.23 4.14,17.92 4,17.36M1,11A5,5 0 0,1 6,6C7,3.65 9.3,2 12,2C15.43,2 18.24,4.66 18.5,8.03L19,8A4,4 0 0,1 23,12A4,4 0 0,1 19,16A1,1 0 0,1 18,15A1,1 0 0,1 19,14A2,2 0 0,0 21,12A2,2 0 0,0 19,10H17V9A5,5 0 0,0 12,4C9.5,4 7.45,5.82 7.06,8.19C6.73,8.07 6.37,8 6,8A3,3 0 0,0 3,11C3,11.85 3.35,12.61 3.91,13.16C4.27,13.55 4.26,14.16 3.88,14.54C3.5,14.93 2.85,14.93 2.47,14.54C1.56,13.63 1,12.38 1,11Z" /></g><g id="weather-sunny"><path d="M12,7A5,5 0 0,1 17,12A5,5 0 0,1 12,17A5,5 0 0,1 7,12A5,5 0 0,1 12,7M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9M12,2L14.39,5.42C13.65,5.15 12.84,5 12,5C11.16,5 10.35,5.15 9.61,5.42L12,2M3.34,7L7.5,6.65C6.9,7.16 6.36,7.78 5.94,8.5C5.5,9.24 5.25,10 5.11,10.79L3.34,7M3.36,17L5.12,13.23C5.26,14 5.53,14.78 5.95,15.5C6.37,16.24 6.91,16.86 7.5,17.37L3.36,17M20.65,7L18.88,10.79C18.74,10 18.47,9.23 18.05,8.5C17.63,7.78 17.1,7.15 16.5,6.64L20.65,7M20.64,17L16.5,17.36C17.09,16.85 17.62,16.22 18.04,15.5C18.46,14.77 18.73,14 18.87,13.21L20.64,17M12,22L9.59,18.56C10.33,18.83 11.14,19 12,19C12.82,19 13.63,18.83 14.37,18.56L12,22Z" /></g><g id="weather-sunset"><path d="M3,12H7A5,5 0 0,1 12,7A5,5 0 0,1 17,12H21A1,1 0 0,1 22,13A1,1 0 0,1 21,14H3A1,1 0 0,1 2,13A1,1 0 0,1 3,12M5,16H19A1,1 0 0,1 20,17A1,1 0 0,1 19,18H5A1,1 0 0,1 4,17A1,1 0 0,1 5,16M17,20A1,1 0 0,1 18,21A1,1 0 0,1 17,22H7A1,1 0 0,1 6,21A1,1 0 0,1 7,20H17M15,12A3,3 0 0,0 12,9A3,3 0 0,0 9,12H15M12,2L14.39,5.42C13.65,5.15 12.84,5 12,5C11.16,5 10.35,5.15 9.61,5.42L12,2M3.34,7L7.5,6.65C6.9,7.16 6.36,7.78 5.94,8.5C5.5,9.24 5.25,10 5.11,10.79L3.34,7M20.65,7L18.88,10.79C18.74,10 18.47,9.23 18.05,8.5C17.63,7.78 17.1,7.15 16.5,6.64L20.65,7Z" /></g><g id="weather-sunset-down"><path d="M3,12H7A5,5 0 0,1 12,7A5,5 0 0,1 17,12H21A1,1 0 0,1 22,13A1,1 0 0,1 21,14H3A1,1 0 0,1 2,13A1,1 0 0,1 3,12M15,12A3,3 0 0,0 12,9A3,3 0 0,0 9,12H15M12,2L14.39,5.42C13.65,5.15 12.84,5 12,5C11.16,5 10.35,5.15 9.61,5.42L12,2M3.34,7L7.5,6.65C6.9,7.16 6.36,7.78 5.94,8.5C5.5,9.24 5.25,10 5.11,10.79L3.34,7M20.65,7L18.88,10.79C18.74,10 18.47,9.23 18.05,8.5C17.63,7.78 17.1,7.15 16.5,6.64L20.65,7M12.71,20.71L15.82,17.6C16.21,17.21 16.21,16.57 15.82,16.18C15.43,15.79 14.8,15.79 14.41,16.18L12,18.59L9.59,16.18C9.2,15.79 8.57,15.79 8.18,16.18C7.79,16.57 7.79,17.21 8.18,17.6L11.29,20.71C11.5,20.9 11.74,21 12,21C12.26,21 12.5,20.9 12.71,20.71Z" /></g><g id="weather-sunset-up"><path d="M3,12H7A5,5 0 0,1 12,7A5,5 0 0,1 17,12H21A1,1 0 0,1 22,13A1,1 0 0,1 21,14H3A1,1 0 0,1 2,13A1,1 0 0,1 3,12M15,12A3,3 0 0,0 12,9A3,3 0 0,0 9,12H15M12,2L14.39,5.42C13.65,5.15 12.84,5 12,5C11.16,5 10.35,5.15 9.61,5.42L12,2M3.34,7L7.5,6.65C6.9,7.16 6.36,7.78 5.94,8.5C5.5,9.24 5.25,10 5.11,10.79L3.34,7M20.65,7L18.88,10.79C18.74,10 18.47,9.23 18.05,8.5C17.63,7.78 17.1,7.15 16.5,6.64L20.65,7M12.71,16.3L15.82,19.41C16.21,19.8 16.21,20.43 15.82,20.82C15.43,21.21 14.8,21.21 14.41,20.82L12,18.41L9.59,20.82C9.2,21.21 8.57,21.21 8.18,20.82C7.79,20.43 7.79,19.8 8.18,19.41L11.29,16.3C11.5,16.1 11.74,16 12,16C12.26,16 12.5,16.1 12.71,16.3Z" /></g><g id="weather-windy"><path d="M4,10A1,1 0 0,1 3,9A1,1 0 0,1 4,8H12A2,2 0 0,0 14,6A2,2 0 0,0 12,4C11.45,4 10.95,4.22 10.59,4.59C10.2,5 9.56,5 9.17,4.59C8.78,4.2 8.78,3.56 9.17,3.17C9.9,2.45 10.9,2 12,2A4,4 0 0,1 16,6A4,4 0 0,1 12,10H4M19,12A1,1 0 0,0 20,11A1,1 0 0,0 19,10C18.72,10 18.47,10.11 18.29,10.29C17.9,10.68 17.27,10.68 16.88,10.29C16.5,9.9 16.5,9.27 16.88,8.88C17.42,8.34 18.17,8 19,8A3,3 0 0,1 22,11A3,3 0 0,1 19,14H5A1,1 0 0,1 4,13A1,1 0 0,1 5,12H19M18,18H4A1,1 0 0,1 3,17A1,1 0 0,1 4,16H18A3,3 0 0,1 21,19A3,3 0 0,1 18,22C17.17,22 16.42,21.66 15.88,21.12C15.5,20.73 15.5,20.1 15.88,19.71C16.27,19.32 16.9,19.32 17.29,19.71C17.47,19.89 17.72,20 18,20A1,1 0 0,0 19,19A1,1 0 0,0 18,18Z" /></g><g id="weather-windy-variant"><path d="M6,6L6.69,6.06C7.32,3.72 9.46,2 12,2A5.5,5.5 0 0,1 17.5,7.5L17.42,8.45C17.88,8.16 18.42,8 19,8A3,3 0 0,1 22,11A3,3 0 0,1 19,14H6A4,4 0 0,1 2,10A4,4 0 0,1 6,6M6,8A2,2 0 0,0 4,10A2,2 0 0,0 6,12H19A1,1 0 0,0 20,11A1,1 0 0,0 19,10H15.5V7.5A3.5,3.5 0 0,0 12,4A3.5,3.5 0 0,0 8.5,7.5V8H6M18,18H4A1,1 0 0,1 3,17A1,1 0 0,1 4,16H18A3,3 0 0,1 21,19A3,3 0 0,1 18,22C17.17,22 16.42,21.66 15.88,21.12C15.5,20.73 15.5,20.1 15.88,19.71C16.27,19.32 16.9,19.32 17.29,19.71C17.47,19.89 17.72,20 18,20A1,1 0 0,0 19,19A1,1 0 0,0 18,18Z" /></g><g id="web"><path d="M16.36,14C16.44,13.34 16.5,12.68 16.5,12C16.5,11.32 16.44,10.66 16.36,10H19.74C19.9,10.64 20,11.31 20,12C20,12.69 19.9,13.36 19.74,14M14.59,19.56C15.19,18.45 15.65,17.25 15.97,16H18.92C17.96,17.65 16.43,18.93 14.59,19.56M14.34,14H9.66C9.56,13.34 9.5,12.68 9.5,12C9.5,11.32 9.56,10.65 9.66,10H14.34C14.43,10.65 14.5,11.32 14.5,12C14.5,12.68 14.43,13.34 14.34,14M12,19.96C11.17,18.76 10.5,17.43 10.09,16H13.91C13.5,17.43 12.83,18.76 12,19.96M8,8H5.08C6.03,6.34 7.57,5.06 9.4,4.44C8.8,5.55 8.35,6.75 8,8M5.08,16H8C8.35,17.25 8.8,18.45 9.4,19.56C7.57,18.93 6.03,17.65 5.08,16M4.26,14C4.1,13.36 4,12.69 4,12C4,11.31 4.1,10.64 4.26,10H7.64C7.56,10.66 7.5,11.32 7.5,12C7.5,12.68 7.56,13.34 7.64,14M12,4.03C12.83,5.23 13.5,6.57 13.91,8H10.09C10.5,6.57 11.17,5.23 12,4.03M18.92,8H15.97C15.65,6.75 15.19,5.55 14.59,4.44C16.43,5.07 17.96,6.34 18.92,8M12,2C6.47,2 2,6.5 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" /></g><g id="webcam"><path d="M12,2A7,7 0 0,1 19,9A7,7 0 0,1 12,16A7,7 0 0,1 5,9A7,7 0 0,1 12,2M12,4A5,5 0 0,0 7,9A5,5 0 0,0 12,14A5,5 0 0,0 17,9A5,5 0 0,0 12,4M12,6A3,3 0 0,1 15,9A3,3 0 0,1 12,12A3,3 0 0,1 9,9A3,3 0 0,1 12,6M6,22A2,2 0 0,1 4,20C4,19.62 4.1,19.27 4.29,18.97L6.11,15.81C7.69,17.17 9.75,18 12,18C14.25,18 16.31,17.17 17.89,15.81L19.71,18.97C19.9,19.27 20,19.62 20,20A2,2 0 0,1 18,22H6Z" /></g><g id="webhook"><path d="M10.46,19C9,21.07 6.15,21.59 4.09,20.15C2.04,18.71 1.56,15.84 3,13.75C3.87,12.5 5.21,11.83 6.58,11.77L6.63,13.2C5.72,13.27 4.84,13.74 4.27,14.56C3.27,16 3.58,17.94 4.95,18.91C6.33,19.87 8.26,19.5 9.26,18.07C9.57,17.62 9.75,17.13 9.82,16.63V15.62L15.4,15.58L15.47,15.47C16,14.55 17.15,14.23 18.05,14.75C18.95,15.27 19.26,16.43 18.73,17.35C18.2,18.26 17.04,18.58 16.14,18.06C15.73,17.83 15.44,17.46 15.31,17.04L11.24,17.06C11.13,17.73 10.87,18.38 10.46,19M17.74,11.86C20.27,12.17 22.07,14.44 21.76,16.93C21.45,19.43 19.15,21.2 16.62,20.89C15.13,20.71 13.9,19.86 13.19,18.68L14.43,17.96C14.92,18.73 15.75,19.28 16.75,19.41C18.5,19.62 20.05,18.43 20.26,16.76C20.47,15.09 19.23,13.56 17.5,13.35C16.96,13.29 16.44,13.36 15.97,13.53L15.12,13.97L12.54,9.2H12.32C11.26,9.16 10.44,8.29 10.47,7.25C10.5,6.21 11.4,5.4 12.45,5.44C13.5,5.5 14.33,6.35 14.3,7.39C14.28,7.83 14.11,8.23 13.84,8.54L15.74,12.05C16.36,11.85 17.04,11.78 17.74,11.86M8.25,9.14C7.25,6.79 8.31,4.1 10.62,3.12C12.94,2.14 15.62,3.25 16.62,5.6C17.21,6.97 17.09,8.47 16.42,9.67L15.18,8.95C15.6,8.14 15.67,7.15 15.27,6.22C14.59,4.62 12.78,3.85 11.23,4.5C9.67,5.16 8.97,7 9.65,8.6C9.93,9.26 10.4,9.77 10.97,10.11L11.36,10.32L8.29,15.31C8.32,15.36 8.36,15.42 8.39,15.5C8.88,16.41 8.54,17.56 7.62,18.05C6.71,18.54 5.56,18.18 5.06,17.24C4.57,16.31 4.91,15.16 5.83,14.67C6.22,14.46 6.65,14.41 7.06,14.5L9.37,10.73C8.9,10.3 8.5,9.76 8.25,9.14Z" /></g><g id="wechat"><path d="M9.5,4C5.36,4 2,6.69 2,10C2,11.89 3.08,13.56 4.78,14.66L4,17L6.5,15.5C7.39,15.81 8.37,16 9.41,16C9.15,15.37 9,14.7 9,14C9,10.69 12.13,8 16,8C16.19,8 16.38,8 16.56,8.03C15.54,5.69 12.78,4 9.5,4M6.5,6.5A1,1 0 0,1 7.5,7.5A1,1 0 0,1 6.5,8.5A1,1 0 0,1 5.5,7.5A1,1 0 0,1 6.5,6.5M11.5,6.5A1,1 0 0,1 12.5,7.5A1,1 0 0,1 11.5,8.5A1,1 0 0,1 10.5,7.5A1,1 0 0,1 11.5,6.5M16,9C12.69,9 10,11.24 10,14C10,16.76 12.69,19 16,19C16.67,19 17.31,18.92 17.91,18.75L20,20L19.38,18.13C20.95,17.22 22,15.71 22,14C22,11.24 19.31,9 16,9M14,11.5A1,1 0 0,1 15,12.5A1,1 0 0,1 14,13.5A1,1 0 0,1 13,12.5A1,1 0 0,1 14,11.5M18,11.5A1,1 0 0,1 19,12.5A1,1 0 0,1 18,13.5A1,1 0 0,1 17,12.5A1,1 0 0,1 18,11.5Z" /></g><g id="weight"><path d="M12,3A4,4 0 0,1 16,7C16,7.73 15.81,8.41 15.46,9H18C18.95,9 19.75,9.67 19.95,10.56C21.96,18.57 22,18.78 22,19A2,2 0 0,1 20,21H4A2,2 0 0,1 2,19C2,18.78 2.04,18.57 4.05,10.56C4.25,9.67 5.05,9 6,9H8.54C8.19,8.41 8,7.73 8,7A4,4 0 0,1 12,3M12,5A2,2 0 0,0 10,7A2,2 0 0,0 12,9A2,2 0 0,0 14,7A2,2 0 0,0 12,5Z" /></g><g id="weight-kilogram"><path d="M12,3A4,4 0 0,1 16,7C16,7.73 15.81,8.41 15.46,9H18C18.95,9 19.75,9.67 19.95,10.56C21.96,18.57 22,18.78 22,19A2,2 0 0,1 20,21H4A2,2 0 0,1 2,19C2,18.78 2.04,18.57 4.05,10.56C4.25,9.67 5.05,9 6,9H8.54C8.19,8.41 8,7.73 8,7A4,4 0 0,1 12,3M12,5A2,2 0 0,0 10,7A2,2 0 0,0 12,9A2,2 0 0,0 14,7A2,2 0 0,0 12,5M9.04,15.44L10.4,18H12.11L10.07,14.66L11.95,11.94H10.2L8.87,14.33H8.39V11.94H6.97V18H8.39V15.44H9.04M17.31,17.16V14.93H14.95V16.04H15.9V16.79L15.55,16.93L14.94,17C14.59,17 14.31,16.85 14.11,16.6C13.92,16.34 13.82,16 13.82,15.59V14.34C13.82,13.93 13.92,13.6 14.12,13.35C14.32,13.09 14.58,12.97 14.91,12.97C15.24,12.97 15.5,13.05 15.64,13.21C15.8,13.37 15.9,13.61 15.95,13.93H17.27L17.28,13.9C17.23,13.27 17,12.77 16.62,12.4C16.23,12.04 15.64,11.86 14.86,11.86C14.14,11.86 13.56,12.09 13.1,12.55C12.64,13 12.41,13.61 12.41,14.34V15.6C12.41,16.34 12.65,16.94 13.12,17.4C13.58,17.86 14.19,18.09 14.94,18.09C15.53,18.09 16.03,18 16.42,17.81C16.81,17.62 17.11,17.41 17.31,17.16Z" /></g><g id="whatsapp"><path d="M16.75,13.96C17,14.09 17.16,14.16 17.21,14.26C17.27,14.37 17.25,14.87 17,15.44C16.8,16 15.76,16.54 15.3,16.56C14.84,16.58 14.83,16.92 12.34,15.83C9.85,14.74 8.35,12.08 8.23,11.91C8.11,11.74 7.27,10.53 7.31,9.3C7.36,8.08 8,7.5 8.26,7.26C8.5,7 8.77,6.97 8.94,7H9.41C9.56,7 9.77,6.94 9.96,7.45L10.65,9.32C10.71,9.45 10.75,9.6 10.66,9.76L10.39,10.17L10,10.59C9.88,10.71 9.74,10.84 9.88,11.09C10,11.35 10.5,12.18 11.2,12.87C12.11,13.75 12.91,14.04 13.15,14.17C13.39,14.31 13.54,14.29 13.69,14.13L14.5,13.19C14.69,12.94 14.85,13 15.08,13.08L16.75,13.96M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22C10.03,22 8.2,21.43 6.65,20.45L2,22L3.55,17.35C2.57,15.8 2,13.97 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12C4,13.72 4.54,15.31 5.46,16.61L4.5,19.5L7.39,18.54C8.69,19.46 10.28,20 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z" /></g><g id="wheelchair-accessibility"><path d="M18.4,11.2L14.3,11.4L16.6,8.8C16.8,8.5 16.9,8 16.8,7.5C16.7,7.2 16.6,6.9 16.3,6.7L10.9,3.5C10.5,3.2 9.9,3.3 9.5,3.6L6.8,6.1C6.3,6.6 6.2,7.3 6.7,7.8C7.1,8.3 7.9,8.3 8.4,7.9L10.4,6.1L12.3,7.2L8.1,11.5C8,11.6 8,11.7 7.9,11.7C7.4,11.9 6.9,12.1 6.5,12.4L8,13.9C8.5,13.7 9,13.5 9.5,13.5C11.4,13.5 13,15.1 13,17C13,17.6 12.9,18.1 12.6,18.5L14.1,20C14.7,19.1 15,18.1 15,17C15,15.8 14.6,14.6 13.9,13.7L17.2,13.4L17,18.2C16.9,18.9 17.4,19.4 18.1,19.5H18.2C18.8,19.5 19.3,19 19.4,18.4L19.6,12.5C19.6,12.2 19.5,11.8 19.3,11.6C19,11.3 18.7,11.2 18.4,11.2M18,5.5A2,2 0 0,0 20,3.5A2,2 0 0,0 18,1.5A2,2 0 0,0 16,3.5A2,2 0 0,0 18,5.5M12.5,21.6C11.6,22.2 10.6,22.5 9.5,22.5C6.5,22.5 4,20 4,17C4,15.9 4.3,14.9 4.9,14L6.4,15.5C6.2,16 6,16.5 6,17C6,18.9 7.6,20.5 9.5,20.5C10.1,20.5 10.6,20.4 11,20.1L12.5,21.6Z" /></g><g id="white-balance-auto"><path d="M10.3,16L9.6,14H6.4L5.7,16H3.8L7,7H9L12.2,16M22,7L20.8,13.29L19.3,7H17.7L16.21,13.29L15,7H14.24C12.77,5.17 10.5,4 8,4A8,8 0 0,0 0,12A8,8 0 0,0 8,20C11.13,20 13.84,18.19 15.15,15.57L15.25,16H17L18.5,9.9L20,16H21.75L23.8,7M6.85,12.65H9.15L8,9L6.85,12.65Z" /></g><g id="white-balance-incandescent"><path d="M17.24,18.15L19.04,19.95L20.45,18.53L18.66,16.74M20,12.5H23V10.5H20M15,6.31V1.5H9V6.31C7.21,7.35 6,9.28 6,11.5A6,6 0 0,0 12,17.5A6,6 0 0,0 18,11.5C18,9.28 16.79,7.35 15,6.31M4,10.5H1V12.5H4M11,22.45C11.32,22.45 13,22.45 13,22.45V19.5H11M3.55,18.53L4.96,19.95L6.76,18.15L5.34,16.74L3.55,18.53Z" /></g><g id="white-balance-iridescent"><path d="M4.96,19.95L6.76,18.15L5.34,16.74L3.55,18.53M3.55,4.46L5.34,6.26L6.76,4.84L4.96,3.05M20.45,18.53L18.66,16.74L17.24,18.15L19.04,19.95M13,22.45V19.5H11V22.45C11.32,22.45 13,22.45 13,22.45M19.04,3.05L17.24,4.84L18.66,6.26L20.45,4.46M11,3.5H13V0.55H11M5,14.5H19V8.5H5V14.5Z" /></g><g id="white-balance-sunny"><path d="M3.55,18.54L4.96,19.95L6.76,18.16L5.34,16.74M11,22.45C11.32,22.45 13,22.45 13,22.45V19.5H11M12,5.5A6,6 0 0,0 6,11.5A6,6 0 0,0 12,17.5A6,6 0 0,0 18,11.5C18,8.18 15.31,5.5 12,5.5M20,12.5H23V10.5H20M17.24,18.16L19.04,19.95L20.45,18.54L18.66,16.74M20.45,4.46L19.04,3.05L17.24,4.84L18.66,6.26M13,0.55H11V3.5H13M4,10.5H1V12.5H4M6.76,4.84L4.96,3.05L3.55,4.46L5.34,6.26L6.76,4.84Z" /></g><g id="widgets"><path d="M3,3H11V7.34L16.66,1.69L22.31,7.34L16.66,13H21V21H13V13H16.66L11,7.34V11H3V3M3,13H11V21H3V13Z" /></g><g id="wifi"><path d="M12,21L15.6,16.2C14.6,15.45 13.35,15 12,15C10.65,15 9.4,15.45 8.4,16.2L12,21M12,3C7.95,3 4.21,4.34 1.2,6.6L3,9C5.5,7.12 8.62,6 12,6C15.38,6 18.5,7.12 21,9L22.8,6.6C19.79,4.34 16.05,3 12,3M12,9C9.3,9 6.81,9.89 4.8,11.4L6.6,13.8C8.1,12.67 9.97,12 12,12C14.03,12 15.9,12.67 17.4,13.8L19.2,11.4C17.19,9.89 14.7,9 12,9Z" /></g><g id="wifi-off"><path d="M2.28,3L1,4.27L2.47,5.74C2.04,6 1.61,6.29 1.2,6.6L3,9C3.53,8.6 4.08,8.25 4.66,7.93L6.89,10.16C6.15,10.5 5.44,10.91 4.8,11.4L6.6,13.8C7.38,13.22 8.26,12.77 9.2,12.47L11.75,15C10.5,15.07 9.34,15.5 8.4,16.2L12,21L14.46,17.73L17.74,21L19,19.72M12,3C9.85,3 7.8,3.38 5.9,4.07L8.29,6.47C9.5,6.16 10.72,6 12,6C15.38,6 18.5,7.11 21,9L22.8,6.6C19.79,4.34 16.06,3 12,3M12,9C11.62,9 11.25,9 10.88,9.05L14.07,12.25C15.29,12.53 16.43,13.07 17.4,13.8L19.2,11.4C17.2,9.89 14.7,9 12,9Z" /></g><g id="wii"><path d="M17.84,16.94H15.97V10.79H17.84V16.94M18,8.58C18,9.19 17.5,9.69 16.9,9.69A1.11,1.11 0 0,1 15.79,8.58C15.79,7.96 16.29,7.46 16.9,7.46C17.5,7.46 18,7.96 18,8.58M21.82,16.94H19.94V10.79H21.82V16.94M22,8.58C22,9.19 21.5,9.69 20.88,9.69A1.11,1.11 0 0,1 19.77,8.58C19.77,7.96 20.27,7.46 20.88,7.46C21.5,7.46 22,7.96 22,8.58M12.9,8.05H14.9L12.78,15.5C12.78,15.5 12.5,17.04 11.28,17.04C10.07,17.04 9.79,15.5 9.79,15.5L8.45,10.64L7.11,15.5C7.11,15.5 6.82,17.04 5.61,17.04C4.4,17.04 4.12,15.5 4.12,15.5L2,8.05H4L5.72,14.67L7.11,9.3C7.43,7.95 8.45,7.97 8.45,7.97C8.45,7.97 9.47,7.95 9.79,9.3L11.17,14.67L12.9,8.05Z" /></g><g id="wiiu"><path d="M2,15.96C2,18.19 3.54,19.5 5.79,19.5H18.57C20.47,19.5 22,18.2 22,16.32V6.97C22,5.83 21.15,4.6 20.11,4.6H17.15V12.3C17.15,18.14 6.97,18.09 6.97,12.41V4.5H4.72C3.26,4.5 2,5.41 2,6.85V15.96M9.34,11.23C9.34,15.74 14.66,15.09 14.66,11.94V4.5H9.34V11.23Z" /></g><g id="wikipedia"><path d="M14.97,18.95L12.41,12.92C11.39,14.91 10.27,17 9.31,18.95C9.3,18.96 8.84,18.95 8.84,18.95C7.37,15.5 5.85,12.1 4.37,8.68C4.03,7.84 2.83,6.5 2,6.5C2,6.4 2,6.18 2,6.05H7.06V6.5C6.46,6.5 5.44,6.9 5.7,7.55C6.42,9.09 8.94,15.06 9.63,16.58C10.1,15.64 11.43,13.16 12,12.11C11.55,11.23 10.13,7.93 9.71,7.11C9.39,6.57 8.58,6.5 7.96,6.5C7.96,6.35 7.97,6.25 7.96,6.06L12.42,6.07V6.47C11.81,6.5 11.24,6.71 11.5,7.29C12.1,8.53 12.45,9.42 13,10.57C13.17,10.23 14.07,8.38 14.5,7.41C14.76,6.76 14.37,6.5 13.29,6.5C13.3,6.38 13.3,6.17 13.3,6.07C14.69,6.06 16.78,6.06 17.15,6.05V6.47C16.44,6.5 15.71,6.88 15.33,7.46L13.5,11.3C13.68,11.81 15.46,15.76 15.65,16.2L19.5,7.37C19.2,6.65 18.34,6.5 18,6.5C18,6.37 18,6.2 18,6.05L22,6.08V6.1L22,6.5C21.12,6.5 20.57,7 20.25,7.75C19.45,9.54 17,15.24 15.4,18.95C15.4,18.95 14.97,18.95 14.97,18.95Z" /></g><g id="window-close"><path d="M13.46,12L19,17.54V19H17.54L12,13.46L6.46,19H5V17.54L10.54,12L5,6.46V5H6.46L12,10.54L17.54,5H19V6.46L13.46,12Z" /></g><g id="window-closed"><path d="M6,11H10V9H14V11H18V4H6V11M18,13H6V20H18V13M6,2H18A2,2 0 0,1 20,4V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V4A2,2 0 0,1 6,2Z" /></g><g id="window-maximize"><path d="M4,4H20V20H4V4M6,8V18H18V8H6Z" /></g><g id="window-minimize"><path d="M20,14H4V10H20" /></g><g id="window-open"><path d="M6,8H10V6H14V8H18V4H6V8M18,10H6V15H18V10M6,20H18V17H6V20M6,2H18A2,2 0 0,1 20,4V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V4A2,2 0 0,1 6,2Z" /></g><g id="window-restore"><path d="M4,8H8V4H20V16H16V20H4V8M16,8V14H18V6H10V8H16M6,12V18H14V12H6Z" /></g><g id="windows"><path d="M3,12V6.75L9,5.43V11.91L3,12M20,3V11.75L10,11.9V5.21L20,3M3,13L9,13.09V19.9L3,18.75V13M20,13.25V22L10,20.09V13.1L20,13.25Z" /></g><g id="wordpress"><path d="M12.2,15.5L9.65,21.72C10.4,21.9 11.19,22 12,22C12.84,22 13.66,21.9 14.44,21.7M20.61,7.06C20.8,7.96 20.76,9.05 20.39,10.25C19.42,13.37 17,19 16.1,21.13C19.58,19.58 22,16.12 22,12.1C22,10.26 21.5,8.53 20.61,7.06M4.31,8.64C4.31,8.64 3.82,8 3.31,8H2.78C2.28,9.13 2,10.62 2,12C2,16.09 4.5,19.61 8.12,21.11M3.13,7.14C4.8,4.03 8.14,2 12,2C14.5,2 16.78,3.06 18.53,4.56C18.03,4.46 17.5,4.57 16.93,4.89C15.64,5.63 15.22,7.71 16.89,8.76C17.94,9.41 18.31,11.04 18.27,12.04C18.24,13.03 15.85,17.61 15.85,17.61L13.5,9.63C13.5,9.63 13.44,9.07 13.44,8.91C13.44,8.71 13.5,8.46 13.63,8.31C13.72,8.22 13.85,8 14,8H15.11V7.14H9.11V8H9.3C9.5,8 9.69,8.29 9.87,8.47C10.09,8.7 10.37,9.55 10.7,10.43L11.57,13.3L9.69,17.63L7.63,8.97C7.63,8.97 7.69,8.37 7.82,8.27C7.9,8.2 8,8 8.17,8H8.22V7.14H3.13Z" /></g><g id="worker"><path d="M12,15C7.58,15 4,16.79 4,19V21H20V19C20,16.79 16.42,15 12,15M8,9A4,4 0 0,0 12,13A4,4 0 0,0 16,9M11.5,2C11.2,2 11,2.21 11,2.5V5.5H10V3C10,3 7.75,3.86 7.75,6.75C7.75,6.75 7,6.89 7,8H17C16.95,6.89 16.25,6.75 16.25,6.75C16.25,3.86 14,3 14,3V5.5H13V2.5C13,2.21 12.81,2 12.5,2H11.5Z" /></g><g id="wrap"><path d="M21,5H3V7H21V5M3,19H10V17H3V19M3,13H18C19,13 20,13.43 20,15C20,16.57 19,17 18,17H16V15L12,18L16,21V19H18C20.95,19 22,17.73 22,15C22,12.28 21,11 18,11H3V13Z" /></g><g id="wrench"><path d="M22.7,19L13.6,9.9C14.5,7.6 14,4.9 12.1,3C10.1,1 7.1,0.6 4.7,1.7L9,6L6,9L1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1C4.8,14 7.5,14.5 9.8,13.6L18.9,22.7C19.3,23.1 19.9,23.1 20.3,22.7L22.6,20.4C23.1,20 23.1,19.3 22.7,19Z" /></g><g id="wunderlist"><path d="M17,17.5L12,15L7,17.5V5H5V19H19V5H17V17.5M12,12.42L14.25,13.77L13.65,11.22L15.64,9.5L13,9.27L12,6.86L11,9.27L8.36,9.5L10.35,11.22L9.75,13.77L12,12.42M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3Z" /></g><g id="xaml"><path d="M18.93,12L15.46,18H8.54L5.07,12L8.54,6H15.46L18.93,12M23.77,12L19.73,19L18,18L21.46,12L18,6L19.73,5L23.77,12M0.23,12L4.27,5L6,6L2.54,12L6,18L4.27,19L0.23,12Z" /></g><g id="xbox"><path d="M6.43,3.72C6.5,3.66 6.57,3.6 6.62,3.56C8.18,2.55 10,2 12,2C13.88,2 15.64,2.5 17.14,3.42C17.25,3.5 17.54,3.69 17.7,3.88C16.25,2.28 12,5.7 12,5.7C10.5,4.57 9.17,3.8 8.16,3.5C7.31,3.29 6.73,3.5 6.46,3.7M19.34,5.21C19.29,5.16 19.24,5.11 19.2,5.06C18.84,4.66 18.38,4.56 18,4.59C17.61,4.71 15.9,5.32 13.8,7.31C13.8,7.31 16.17,9.61 17.62,11.96C19.07,14.31 19.93,16.16 19.4,18.73C21,16.95 22,14.59 22,12C22,9.38 21,7 19.34,5.21M15.73,12.96C15.08,12.24 14.13,11.21 12.86,9.95C12.59,9.68 12.3,9.4 12,9.1C12,9.1 11.53,9.56 10.93,10.17C10.16,10.94 9.17,11.95 8.61,12.54C7.63,13.59 4.81,16.89 4.65,18.74C4.65,18.74 4,17.28 5.4,13.89C6.3,11.68 9,8.36 10.15,7.28C10.15,7.28 9.12,6.14 7.82,5.35L7.77,5.32C7.14,4.95 6.46,4.66 5.8,4.62C5.13,4.67 4.71,5.16 4.71,5.16C3.03,6.95 2,9.35 2,12A10,10 0 0,0 12,22C14.93,22 17.57,20.74 19.4,18.73C19.4,18.73 19.19,17.4 17.84,15.5C17.53,15.07 16.37,13.69 15.73,12.96Z" /></g><g id="xbox-controller"><path d="M8.75,15.75C6.75,15.75 6,18 4,19C2,19 0.5,16 4.5,7.5H4.75L5.19,6.67C5.19,6.67 8,5 9.33,6.23H14.67C16,5 18.81,6.67 18.81,6.67L19.25,7.5H19.5C23.5,16 22,19 20,19C18,18 17.25,15.75 15.25,15.75H8.75M12,7A1,1 0 0,0 11,8A1,1 0 0,0 12,9A1,1 0 0,0 13,8A1,1 0 0,0 12,7Z" /></g><g id="xbox-controller-off"><path d="M2,5.27L3.28,4L20,20.72L18.73,22L12.5,15.75H8.75C6.75,15.75 6,18 4,19C2,19 0.5,16.04 4.42,7.69L2,5.27M9.33,6.23H14.67C16,5 18.81,6.67 18.81,6.67L19.25,7.5H19.5C23,15 22.28,18.2 20.69,18.87L7.62,5.8C8.25,5.73 8.87,5.81 9.33,6.23M12,7A1,1 0 0,0 11,8A1,1 0 0,0 12,9A1,1 0 0,0 13,8A1,1 0 0,0 12,7Z" /></g><g id="xda"><path d="M-0.05,16.79L3.19,12.97L-0.05,9.15L1.5,7.86L4.5,11.41L7.5,7.86L9.05,9.15L5.81,12.97L9.05,16.79L7.5,18.07L4.5,14.5L1.5,18.07L-0.05,16.79M24,17A1,1 0 0,1 23,18H20A2,2 0 0,1 18,16V14A2,2 0 0,1 20,12H22V10H18V8H23A1,1 0 0,1 24,9M22,14H20V16H22V14M16,17A1,1 0 0,1 15,18H12A2,2 0 0,1 10,16V10A2,2 0 0,1 12,8H14V5H16V17M14,16V10H12V16H14Z" /></g><g id="xing"><path d="M17.67,2C17.24,2 17.05,2.27 16.9,2.55C16.9,2.55 10.68,13.57 10.5,13.93L14.58,21.45C14.72,21.71 14.94,22 15.38,22H18.26C18.44,22 18.57,21.93 18.64,21.82C18.72,21.69 18.72,21.53 18.64,21.37L14.57,13.92L20.96,2.63C21.04,2.47 21.04,2.31 20.97,2.18C20.89,2.07 20.76,2 20.58,2M5.55,5.95C5.38,5.95 5.23,6 5.16,6.13C5.08,6.26 5.09,6.41 5.18,6.57L7.12,9.97L4.06,15.37C4,15.53 4,15.69 4.06,15.82C4.13,15.94 4.26,16 4.43,16H7.32C7.75,16 7.96,15.72 8.11,15.45C8.11,15.45 11.1,10.16 11.22,9.95L9.24,6.5C9.1,6.24 8.88,5.95 8.43,5.95" /></g><g id="xing-box"><path d="M4.8,3C3.8,3 3,3.8 3,4.8V19.2C3,20.2 3.8,21 4.8,21H19.2C20.2,21 21,20.2 21,19.2V4.8C21,3.8 20.2,3 19.2,3M16.07,5H18.11C18.23,5 18.33,5.04 18.37,5.13C18.43,5.22 18.43,5.33 18.37,5.44L13.9,13.36L16.75,18.56C16.81,18.67 16.81,18.78 16.75,18.87C16.7,18.95 16.61,19 16.5,19H14.47C14.16,19 14,18.79 13.91,18.61L11.04,13.35C11.18,13.1 15.53,5.39 15.53,5.39C15.64,5.19 15.77,5 16.07,5M7.09,7.76H9.1C9.41,7.76 9.57,7.96 9.67,8.15L11.06,10.57C10.97,10.71 8.88,14.42 8.88,14.42C8.77,14.61 8.63,14.81 8.32,14.81H6.3C6.18,14.81 6.09,14.76 6.04,14.67C6,14.59 6,14.47 6.04,14.36L8.18,10.57L6.82,8.2C6.77,8.09 6.75,8 6.81,7.89C6.86,7.81 6.96,7.76 7.09,7.76Z" /></g><g id="xing-circle"><path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M15.85,6H17.74C17.86,6 17.94,6.04 18,6.12C18.04,6.2 18.04,6.3 18,6.41L13.84,13.76L16.5,18.59C16.53,18.69 16.53,18.8 16.5,18.88C16.43,18.96 16.35,19 16.24,19H14.36C14.07,19 13.93,18.81 13.84,18.64L11.17,13.76C11.31,13.5 15.35,6.36 15.35,6.36C15.45,6.18 15.57,6 15.85,6M7.5,8.57H9.39C9.67,8.57 9.81,8.75 9.9,8.92L11.19,11.17C11.12,11.3 9.17,14.75 9.17,14.75C9.07,14.92 8.94,15.11 8.66,15.11H6.78C6.67,15.11 6.59,15.06 6.54,15C6.5,14.9 6.5,14.8 6.54,14.69L8.53,11.17L7.27,9C7.21,8.87 7.2,8.77 7.25,8.69C7.3,8.61 7.39,8.57 7.5,8.57Z" /></g><g id="xml"><path d="M12.89,3L14.85,3.4L11.11,21L9.15,20.6L12.89,3M19.59,12L16,8.41V5.58L22.42,12L16,18.41V15.58L19.59,12M1.58,12L8,5.58V8.41L4.41,12L8,15.58V18.41L1.58,12Z" /></g><g id="yeast"><path d="M18,14A4,4 0 0,1 22,18A4,4 0 0,1 18,22A4,4 0 0,1 14,18L14.09,17.15C14.05,16.45 13.92,15.84 13.55,15.5C13.35,15.3 13.07,15.19 12.75,15.13C11.79,15.68 10.68,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3A6.5,6.5 0 0,1 16,9.5C16,10.68 15.68,11.79 15.13,12.75C15.19,13.07 15.3,13.35 15.5,13.55C15.84,13.92 16.45,14.05 17.15,14.09L18,14M7.5,10A1.5,1.5 0 0,1 9,11.5A1.5,1.5 0 0,1 7.5,13A1.5,1.5 0 0,1 6,11.5A1.5,1.5 0 0,1 7.5,10M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z" /></g><g id="yelp"><path d="M10.59,2C11.23,2 11.5,2.27 11.58,2.97L11.79,6.14L12.03,10.29C12.05,10.64 12,11 11.86,11.32C11.64,11.77 11.14,11.89 10.73,11.58C10.5,11.39 10.31,11.14 10.15,10.87L6.42,4.55C6.06,3.94 6.17,3.54 6.77,3.16C7.5,2.68 9.73,2 10.59,2M14.83,14.85L15.09,14.91L18.95,16.31C19.61,16.55 19.79,16.92 19.5,17.57C19.06,18.7 18.34,19.66 17.42,20.45C16.96,20.85 16.5,20.78 16.21,20.28L13.94,16.32C13.55,15.61 14.03,14.8 14.83,14.85M4.5,14C4.5,13.26 4.5,12.55 4.75,11.87C4.97,11.2 5.33,11 6,11.27L9.63,12.81C10.09,13 10.35,13.32 10.33,13.84C10.3,14.36 9.97,14.58 9.53,14.73L5.85,15.94C5.15,16.17 4.79,15.96 4.64,15.25C4.55,14.83 4.47,14.4 4.5,14M11.97,21C11.95,21.81 11.6,22.12 10.81,22C9.77,21.8 8.81,21.4 7.96,20.76C7.54,20.44 7.45,19.95 7.76,19.53L10.47,15.97C10.7,15.67 11.03,15.6 11.39,15.74C11.77,15.88 11.97,16.18 11.97,16.59V21M14.45,13.32C13.73,13.33 13.23,12.5 13.64,11.91C14.47,10.67 15.35,9.46 16.23,8.26C16.5,7.85 16.94,7.82 17.31,8.16C18.24,9 18.91,10 19.29,11.22C19.43,11.67 19.25,12.08 18.83,12.2L15.09,13.17L14.45,13.32Z" /></g><g id="yin-yang"><path d="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A4,4 0 0,1 8,16A4,4 0 0,1 12,12A4,4 0 0,0 16,8A4,4 0 0,0 12,4M12,6.5A1.5,1.5 0 0,1 13.5,8A1.5,1.5 0 0,1 12,9.5A1.5,1.5 0 0,1 10.5,8A1.5,1.5 0 0,1 12,6.5M12,14.5A1.5,1.5 0 0,0 10.5,16A1.5,1.5 0 0,0 12,17.5A1.5,1.5 0 0,0 13.5,16A1.5,1.5 0 0,0 12,14.5Z" /></g><g id="youtube-play"><path d="M10,16.5V7.5L16,12M20,4.4C19.4,4.2 15.7,4 12,4C8.3,4 4.6,4.19 4,4.38C2.44,4.9 2,8.4 2,12C2,15.59 2.44,19.1 4,19.61C4.6,19.81 8.3,20 12,20C15.7,20 19.4,19.81 20,19.61C21.56,19.1 22,15.59 22,12C22,8.4 21.56,4.91 20,4.4Z" /></g><g id="zip-box"><path d="M14,17H12V15H10V13H12V15H14M14,9H12V11H14V13H12V11H10V9H12V7H10V5H12V7H14M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z" /></g></defs></svg>
\ No newline at end of file
diff --git a/ui/src/thingsboard.ico b/ui/src/thingsboard.ico
new file mode 100644
index 0000000..8564792
Binary files /dev/null and b/ui/src/thingsboard.ico differ
diff --git a/ui/src/vendor/css.js/css.js b/ui/src/vendor/css.js/css.js
new file mode 100644
index 0000000..8c4160a
--- /dev/null
+++ b/ui/src/vendor/css.js/css.js
@@ -0,0 +1,672 @@
+/* eslint-disable */
+
+/* jshint unused:false */
+/* global base64_decode, CSSWizardView, window, console, jQuery */
+var fi = function () {
+
+    this.cssImportStatements = [];
+    this.cssKeyframeStatements = [];
+
+    this.cssRegex = new RegExp('([\\s\\S]*?){([\\s\\S]*?)}', 'gi');
+    this.cssMediaQueryRegex = '((@media [\\s\\S]*?){([\\s\\S]*?}\\s*?)})';
+    this.cssKeyframeRegex = '((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})';
+    this.combinedCSSRegex = '((\\s*?@media[\\s\\S]*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})'; //to match css & media queries together
+    this.cssCommentsRegex = '(\\/\\*[\\s\\S]*?\\*\\/)';
+    this.cssImportStatementRegex = new RegExp('@import .*?;', 'gi');
+};
+
+/*
+ Strip outs css comments and returns cleaned css string
+
+ @param css, the original css string to be stipped out of comments
+
+ @return cleanedCSS contains no css comments
+ */
+fi.prototype.stripComments = function (cssString) {
+    var regex = new RegExp(this.cssCommentsRegex, 'gi');
+
+    return cssString.replace(regex, '');
+};
+
+/*
+ Parses given css string, and returns css object
+ keys as selectors and values are css rules
+ eliminates all css comments before parsing
+
+ @param source css string to be parsed
+
+ @return object css
+ */
+fi.prototype.parseCSS = function (source) {
+
+    if (source === undefined) {
+        return [];
+    }
+
+    var css = [];
+    //strip out comments
+    //source = this.stripComments(source);
+
+    //get import statements
+
+    while (true) {
+        var imports = this.cssImportStatementRegex.exec(source);
+        if (imports !== null) {
+            this.cssImportStatements.push(imports[0]);
+            css.push({
+                selector: '@imports',
+                type: 'imports',
+                styles: imports[0]
+            });
+        } else {
+            break;
+        }
+    }
+    source = source.replace(this.cssImportStatementRegex, '');
+    //get keyframe statements
+    var keyframesRegex = new RegExp(this.cssKeyframeRegex, 'gi');
+    var arr;
+    while (true) {
+        arr = keyframesRegex.exec(source);
+        if (arr === null) {
+            break;
+        }
+        css.push({
+            selector: '@keyframes',
+            type: 'keyframes',
+            styles: arr[0]
+        });
+    }
+    source = source.replace(keyframesRegex, '');
+
+    //unified regex
+    var unified = new RegExp(this.combinedCSSRegex, 'gi');
+
+    while (true) {
+        arr = unified.exec(source);
+        if (arr === null) {
+            break;
+        }
+        var selector = '';
+        if (arr[2] === undefined) {
+            selector = arr[5].split('\r\n').join('\n').trim();
+        } else {
+            selector = arr[2].split('\r\n').join('\n').trim();
+        }
+
+        /*
+         fetch comments and associate it with current selector
+         */
+        var commentsRegex = new RegExp(this.cssCommentsRegex, 'gi');
+        var comments = commentsRegex.exec(selector);
+        if (comments !== null) {
+            selector = selector.replace(commentsRegex, '').trim();
+        }
+
+        //determine the type
+        if (selector.indexOf('@media') !== -1) {
+            //we have a media query
+            var cssObject = {
+                selector: selector,
+                type: 'media',
+                subStyles: this.parseCSS(arr[3] + '\n}') //recursively parse media query inner css
+            };
+            if (comments !== null) {
+                cssObject.comments = comments[0];
+            }
+            css.push(cssObject);
+        } else {
+            //we have standart css
+            var rules = this.parseRules(arr[6]);
+            var style = {
+                selector: selector,
+                rules: rules
+            };
+            if (selector === '@font-face') {
+                style.type = 'font-face';
+            }
+            if (comments !== null) {
+                style.comments = comments[0];
+            }
+            css.push(style);
+        }
+    }
+
+    return css;
+};
+
+/*
+ parses given string containing css directives
+ and returns an array of objects containing ruleName:ruleValue pairs
+
+ @param rules, css directive string example
+ \n\ncolor:white;\n    font-size:18px;\n
+ */
+fi.prototype.parseRules = function (rules) {
+    //convert all windows style line endings to unix style line endings
+    rules = rules.split('\r\n').join('\n');
+    var ret = [];
+
+    rules = rules.split(';');
+
+    //proccess rules line by line
+    for (var i = 0; i < rules.length; i++) {
+        var line = rules[i];
+
+        //determine if line is a valid css directive, ie color:white;
+        line = line.trim();
+        if (line.indexOf(':') !== -1) {
+            //line contains :
+            line = line.split(':');
+            var cssDirective = line[0].trim();
+            var cssValue = line.slice(1).join(':').trim();
+
+            //more checks
+            if (cssDirective.length < 1 || cssValue.length < 1) {
+                continue; //there is no css directive or value that is of length 1 or 0
+                // PLAIN WRONG WHAT ABOUT margin:0; ?
+            }
+
+            //push rule
+            ret.push({
+                directive: cssDirective,
+                value: cssValue
+            });
+        } else {
+            //if there is no ':', but what if it was mis splitted value which starts with base64
+            if (line.trim().substr(0, 7) == 'base64,') { //hack :)
+                ret[ret.length - 1].value += line.trim();
+            } else {
+                //add rule, even if it is defective
+                if (line.length > 0) {
+                    ret.push({
+                        directive: '',
+                        value: line,
+                        defective: true
+                    });
+                }
+            }
+        }
+    }
+
+    return ret; //we are done!
+};
+/*
+ just returns the rule having given directive
+ if not found returns false;
+ */
+fi.prototype.findCorrespondingRule = function (rules, directive, value) {
+    if (value === undefined) {
+        value = false;
+    }
+    var ret = false;
+    for (var i = 0; i < rules.length; i++) {
+        if (rules[i].directive == directive) {
+            ret = rules[i];
+            if (value === rules[i].value) {
+                break;
+            }
+        }
+    }
+    return ret;
+};
+
+/*
+ Finds styles that have given selector, compress them,
+ and returns them
+ */
+fi.prototype.findBySelector = function (cssObjectArray, selector, contains) {
+    if (contains === undefined) {
+        contains = false;
+    }
+
+    var found = [];
+    for (var i = 0; i < cssObjectArray.length; i++) {
+        if (contains === false) {
+            if (cssObjectArray[i].selector === selector) {
+                found.push(cssObjectArray[i]);
+            }
+        } else {
+            if (cssObjectArray[i].selector.indexOf(selector) !== -1) {
+                found.push(cssObjectArray[i]);
+            }
+        }
+
+    }
+    if (found.length < 2) {
+        return found;
+    } else {
+        var base = found[0];
+        for (i = 1; i < found.length; i++) {
+            this.intelligentCSSPush([base], found[i]);
+        }
+        return [base]; //we are done!! all properties merged into base!
+    }
+};
+
+/*
+ deletes cssObjects having given selector, and returns new array
+ */
+fi.prototype.deleteBySelector = function (cssObjectArray, selector) {
+    var ret = [];
+    for (var i = 0; i < cssObjectArray.length; i++) {
+        if (cssObjectArray[i].selector !== selector) {
+            ret.push(cssObjectArray[i]);
+        }
+    }
+    return ret;
+};
+
+/*
+ Compresses given cssObjectArray and tries to minimize
+ selector redundence.
+ */
+fi.prototype.compressCSS = function (cssObjectArray) {
+    var compressed = [];
+    var done = {};
+    for (var i = 0; i < cssObjectArray.length; i++) {
+        var obj = cssObjectArray[i];
+        if (done[obj.selector] === true) {
+            continue;
+        }
+
+        var found = this.findBySelector(cssObjectArray, obj.selector); //found compressed
+        if (found.length !== 0) {
+            compressed.push(found[0]);
+            done[obj.selector] = true;
+        }
+    }
+    return compressed;
+};
+
+/*
+ Received 2 css objects with following structure
+ {
+ rules : [{directive:"", value:""}, {directive:"", value:""}, ...]
+ selector : "SOMESELECTOR"
+ }
+
+ returns the changed(new,removed,updated) values on css1 parameter, on same structure
+
+ if two css objects are the same, then returns false
+
+ if a css directive exists in css1 and     css2, and its value is different, it is included in diff
+ if a css directive exists in css1 and not css2, it is then included in diff
+ if a css directive exists in css2 but not css1, then it is deleted in css1, it would be included in diff but will be marked as type='DELETED'
+
+ @object css1 css object
+ @object css2 css object
+
+ @return diff css object contains changed values in css1 in regards to css2 see test input output in /test/data/css.js
+ */
+fi.prototype.cssDiff = function (css1, css2) {
+    if (css1.selector !== css2.selector) {
+        return false;
+    }
+
+    //if one of them is media query return false, because diff function can not operate on media queries
+    if ((css1.type === 'media' || css2.type === 'media')) {
+        return false;
+    }
+
+    var diff = {
+        selector: css1.selector,
+        rules: []
+    };
+    var rule1, rule2;
+    for (var i = 0; i < css1.rules.length; i++) {
+        rule1 = css1.rules[i];
+        //find rule2 which has the same directive as rule1
+        rule2 = this.findCorrespondingRule(css2.rules, rule1.directive, rule1.value);
+        if (rule2 === false) {
+            //rule1 is a new rule in css1
+            diff.rules.push(rule1);
+        } else {
+            //rule2 was found only push if its value is different too
+            if (rule1.value !== rule2.value) {
+                diff.rules.push(rule1);
+            }
+        }
+    }
+
+    //now for rules exists in css2 but not in css1, which means deleted rules
+    for (var ii = 0; ii < css2.rules.length; ii++) {
+        rule2 = css2.rules[ii];
+        //find rule2 which has the same directive as rule1
+        rule1 = this.findCorrespondingRule(css1.rules, rule2.directive);
+        if (rule1 === false) {
+            //rule1 is a new rule
+            rule2.type = 'DELETED'; //mark it as a deleted rule, so that other merge operations could be true
+            diff.rules.push(rule2);
+        }
+    }
+
+
+    if (diff.rules.length === 0) {
+        return false;
+    }
+    return diff;
+};
+
+/*
+ Merges 2 different css objects together
+ using intelligentCSSPush,
+
+ @param cssObjectArray, target css object array
+ @param newArray, source array that will be pushed into cssObjectArray parameter
+ @param reverse, [optional], if given true, first parameter will be traversed on reversed order
+ effectively giving priority to the styles in newArray
+ */
+fi.prototype.intelligentMerge = function (cssObjectArray, newArray, reverse) {
+    if (reverse === undefined) {
+        reverse = false;
+    }
+
+
+    for (var i = 0; i < newArray.length; i++) {
+        this.intelligentCSSPush(cssObjectArray, newArray[i], reverse);
+    }
+    for (i = 0; i < cssObjectArray.length; i++) {
+        var cobj = cssObjectArray[i];
+        if (cobj.type === 'media' || (cobj.type === 'keyframes')) {
+            continue;
+        }
+        cobj.rules = this.compactRules(cobj.rules);
+    }
+};
+
+/*
+ inserts new css objects into a bigger css object
+ with same selectors groupped together
+
+ @param cssObjectArray, array of bigger css object to be pushed into
+ @param minimalObject, single css object
+ @param reverse [optional] default is false, if given, cssObjectArray will be reversly traversed
+ resulting more priority in minimalObject's styles
+ */
+fi.prototype.intelligentCSSPush = function (cssObjectArray, minimalObject, reverse) {
+    var pushSelector = minimalObject.selector;
+    //find correct selector if not found just push minimalObject into cssObject
+    var cssObject = false;
+
+    if (reverse === undefined) {
+        reverse = false;
+    }
+
+    if (reverse === false) {
+        for (var i = 0; i < cssObjectArray.length; i++) {
+            if (cssObjectArray[i].selector === minimalObject.selector) {
+                cssObject = cssObjectArray[i];
+                break;
+            }
+        }
+    } else {
+        for (var j = cssObjectArray.length - 1; j > -1; j--) {
+            if (cssObjectArray[j].selector === minimalObject.selector) {
+                cssObject = cssObjectArray[j];
+                break;
+            }
+        }
+    }
+
+    if (cssObject === false) {
+        cssObjectArray.push(minimalObject); //just push, because cssSelector is new
+    } else {
+        if (minimalObject.type !== 'media') {
+            for (var ii = 0; ii < minimalObject.rules.length; ii++) {
+                var rule = minimalObject.rules[ii];
+                //find rule inside cssObject
+                var oldRule = this.findCorrespondingRule(cssObject.rules, rule.directive);
+                if (oldRule === false) {
+                    cssObject.rules.push(rule);
+                } else if (rule.type == 'DELETED') {
+                    oldRule.type = 'DELETED';
+                } else {
+                    //rule found just update value
+
+                    oldRule.value = rule.value;
+                }
+            }
+        } else {
+            cssObject.subStyles = minimalObject.subStyles; //TODO, make this intelligent too
+        }
+
+    }
+};
+
+/*
+ filter outs rule objects whose type param equal to DELETED
+
+ @param rules, array of rules
+
+ @returns rules array, compacted by deleting all unneccessary rules
+ */
+fi.prototype.compactRules = function (rules) {
+    var newRules = [];
+    for (var i = 0; i < rules.length; i++) {
+        if (rules[i].type !== 'DELETED') {
+            newRules.push(rules[i]);
+        }
+    }
+    return newRules;
+};
+/*
+ computes string for ace editor using this.css or given cssBase optional parameter
+
+ @param [optional] cssBase, if given computes cssString from cssObject array
+ */
+fi.prototype.getCSSForEditor = function (cssBase, depth) {
+    if (depth === undefined) {
+        depth = 0;
+    }
+    var ret = '';
+    if (cssBase === undefined) {
+        cssBase = this.css;
+    }
+    //append imports
+    for (var i = 0; i < cssBase.length; i++) {
+        if (cssBase[i].type == 'imports') {
+            ret += cssBase[i].styles + '\n\n';
+        }
+    }
+    for (i = 0; i < cssBase.length; i++) {
+        var tmp = cssBase[i];
+        if (tmp.selector === undefined) { //temporarily omit media queries
+            continue;
+        }
+        var comments = "";
+        if (tmp.comments !== undefined) {
+            comments = tmp.comments + '\n';
+        }
+
+        if (tmp.type == 'media') { //also put media queries to output
+            ret += comments + tmp.selector + '{\n';
+            ret += this.getCSSForEditor(tmp.subStyles, depth + 1);
+            ret += '}\n\n';
+        } else if (tmp.type !== 'keyframes' && tmp.type !== 'imports') {
+            ret += this.getSpaces(depth) + comments + tmp.selector + ' {\n';
+            ret += this.getCSSOfRules(tmp.rules, depth + 1);
+            ret += this.getSpaces(depth) + '}\n\n';
+        }
+    }
+
+    //append keyFrames
+    for (i = 0; i < cssBase.length; i++) {
+        if (cssBase[i].type == 'keyframes') {
+            ret += cssBase[i].styles + '\n\n';
+        }
+    }
+
+    return ret;
+};
+
+fi.prototype.getImports = function (cssObjectArray) {
+    var imps = [];
+    for (var i = 0; i < cssObjectArray.length; i++) {
+        if (cssObjectArray[i].type == 'imports') {
+            imps.push(cssObjectArray[i].styles);
+        }
+    }
+    return imps;
+};
+/*
+ given rules array, returns visually formatted css string
+ to be used inside editor
+ */
+fi.prototype.getCSSOfRules = function (rules, depth) {
+    var ret = '';
+    for (var i = 0; i < rules.length; i++) {
+        if (rules[i] === undefined) {
+            continue;
+        }
+        if (rules[i].defective === undefined) {
+            ret += this.getSpaces(depth) + rules[i].directive + ' : ' + rules[i].value + ';\n';
+        } else {
+            ret += this.getSpaces(depth) + rules[i].value + ';\n';
+        }
+
+    }
+    return ret || '\n';
+};
+
+/*
+ A very simple helper function returns number of spaces appended in a single string,
+ the number depends input parameter, namely input*2
+ */
+fi.prototype.getSpaces = function (num) {
+    var ret = '';
+    for (var i = 0; i < num * 4; i++) {
+        ret += ' ';
+    }
+    return ret;
+};
+
+/*
+ Given css string or objectArray, parses it and then for every selector,
+ prepends this.cssPreviewNamespace to prevent css collision issues
+
+ @returns css string in which this.cssPreviewNamespace prepended
+ */
+fi.prototype.applyNamespacing = function (css, forcedNamespace) {
+    var cssObjectArray = css;
+    var namespaceClass = '.' + this.cssPreviewNamespace;
+    if (forcedNamespace !== undefined) {
+        namespaceClass = forcedNamespace;
+    }
+
+    if (typeof css === 'string') {
+        cssObjectArray = this.parseCSS(css);
+    }
+
+    for (var i = 0; i < cssObjectArray.length; i++) {
+        var obj = cssObjectArray[i];
+
+        //bypass namespacing for @font-face @keyframes @import
+        if (obj.selector.indexOf('@font-face') > -1 || obj.selector.indexOf('keyframes') > -1 || obj.selector.indexOf('@import') > -1 || obj.selector.indexOf('.form-all') > -1 || obj.selector.indexOf('#stage') > -1) {
+            continue;
+        }
+
+        if (obj.type !== 'media') {
+            var selector = obj.selector.split(',');
+            var newSelector = [];
+            for (var j = 0; j < selector.length; j++) {
+                if (selector[j].indexOf('.supernova') === -1) { //do not apply namespacing to selectors including supernova
+                    newSelector.push(namespaceClass + ' ' + selector[j]);
+                } else {
+                    newSelector.push(selector[j]);
+                }
+            }
+            obj.selector = newSelector.join(',');
+        } else {
+            obj.subStyles = this.applyNamespacing(obj.subStyles, forcedNamespace); //handle media queries as well
+        }
+    }
+
+    return cssObjectArray;
+};
+
+/*
+ given css string or object array, clears possible namespacing from
+ all of the selectors inside the css
+ */
+fi.prototype.clearNamespacing = function (css, returnObj) {
+    if (returnObj === undefined) {
+        returnObj = false;
+    }
+    var cssObjectArray = css;
+    var namespaceClass = '.' + this.cssPreviewNamespace;
+    if (typeof css === 'string') {
+        cssObjectArray = this.parseCSS(css);
+    }
+
+    for (var i = 0; i < cssObjectArray.length; i++) {
+        var obj = cssObjectArray[i];
+
+        if (obj.type !== 'media') {
+            var selector = obj.selector.split(',');
+            var newSelector = [];
+            for (var j = 0; j < selector.length; j++) {
+                newSelector.push(selector[j].split(namespaceClass + ' ').join(''));
+            }
+            obj.selector = newSelector.join(',');
+        } else {
+            obj.subStyles = this.clearNamespacing(obj.subStyles, true); //handle media queries as well
+        }
+    }
+    if (returnObj === false) {
+        return this.getCSSForEditor(cssObjectArray);
+    } else {
+        return cssObjectArray;
+    }
+
+};
+
+/*
+ creates a new style tag (also destroys the previous one)
+ and injects given css string into that css tag
+ */
+fi.prototype.createStyleElement = function (id, css, format) {
+    if (format === undefined) {
+        format = false;
+    }
+
+    if (this.testMode === false && format !== 'nonamespace') {
+        //apply namespacing classes
+        css = this.applyNamespacing(css);
+    }
+
+    if (typeof css != 'string') {
+        css = this.getCSSForEditor(css);
+    }
+    //apply formatting for css
+    if (format === true) {
+        css = this.getCSSForEditor(this.parseCSS(css));
+    }
+
+    if (this.testMode !== false) {
+        return this.testMode('create style #' + id, css); //if test mode, just pass result to callback
+    }
+
+    var __el = document.getElementById(id);
+    if (__el) {
+        __el.parentNode.removeChild(__el);
+    }
+
+    var head = document.head || document.getElementsByTagName('head')[0],
+        style = document.createElement('style');
+
+    style.id = id;
+    style.type = 'text/css';
+
+    head.appendChild(style);
+
+    if (style.styleSheet && !style.sheet) {
+        style.styleSheet.cssText = css;
+    } else {
+        style.appendChild(document.createTextNode(css));
+    }
+};
+
+export default fi;
+
+/* eslint-enable */
\ No newline at end of file
diff --git a/ui/src/vendor/css.js/css.min.js b/ui/src/vendor/css.js/css.min.js
new file mode 100644
index 0000000..b0e02da
--- /dev/null
+++ b/ui/src/vendor/css.js/css.min.js
@@ -0,0 +1,2 @@
+/*! css.js 19-04-2016 */
+!function(a){"use strict";var b=function(){this.cssImportStatements=[],this.cssKeyframeStatements=[],this.cssRegex=new RegExp("([\\s\\S]*?){([\\s\\S]*?)}","gi"),this.cssMediaQueryRegex="((@media [\\s\\S]*?){([\\s\\S]*?}\\s*?)})",this.cssKeyframeRegex="((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})",this.combinedCSSRegex="((\\s*?(?:\\/\\*[\\s\\S]*?\\*\\/)?\\s*?@media[\\s\\S]*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})",this.cssCommentsRegex="(\\/\\*[\\s\\S]*?\\*\\/)",this.cssImportStatementRegex=new RegExp("@import .*?;","gi")};b.prototype.stripComments=function(a){var b=new RegExp(this.cssCommentsRegex,"gi");return a.replace(b,"")},b.prototype.parseCSS=function(a){if(void 0===a)return[];for(var b=[];;){var c=this.cssImportStatementRegex.exec(a);if(null===c)break;this.cssImportStatements.push(c[0]),b.push({selector:"@imports",type:"imports",styles:c[0]})}a=a.replace(this.cssImportStatementRegex,"");for(var d,e=new RegExp(this.cssKeyframeRegex,"gi");;){if(d=e.exec(a),null===d)break;b.push({selector:"@keyframes",type:"keyframes",styles:d[0]})}a=a.replace(e,"");for(var f=new RegExp(this.combinedCSSRegex,"gi");;){if(d=f.exec(a),null===d)break;var g="";g=void 0===d[2]?d[5].split("\r\n").join("\n").trim():d[2].split("\r\n").join("\n").trim();var h=new RegExp(this.cssCommentsRegex,"gi"),i=h.exec(g);if(null!==i&&(g=g.replace(h,"").trim()),g=g.replace(/\n+/,"\n"),-1!==g.indexOf("@media")){var j={selector:g,type:"media",subStyles:this.parseCSS(d[3]+"\n}")};null!==i&&(j.comments=i[0]),b.push(j)}else{var k=this.parseRules(d[6]),l={selector:g,rules:k};"@font-face"===g&&(l.type="font-face"),null!==i&&(l.comments=i[0]),b.push(l)}}return b},b.prototype.parseRules=function(a){a=a.split("\r\n").join("\n");var b=[];a=a.split(";");for(var c=0;c<a.length;c++){var d=a[c];if(d=d.trim(),-1!==d.indexOf(":")){d=d.split(":");var e=d[0].trim(),f=d.slice(1).join(":").trim();if(e.length<1||f.length<1)continue;b.push({directive:e,value:f})}else"base64,"===d.trim().substr(0,7)?b[b.length-1].value+=d.trim():d.length>0&&b.push({directive:"",value:d,defective:!0})}return b},b.prototype.findCorrespondingRule=function(a,b,c){void 0===c&&(c=!1);for(var d=!1,e=0;e<a.length&&(a[e].directive!==b||(d=a[e],c!==a[e].value));e++);return d},b.prototype.findBySelector=function(a,b,c){void 0===c&&(c=!1);for(var d=[],e=0;e<a.length;e++)c===!1?a[e].selector===b&&d.push(a[e]):-1!==a[e].selector.indexOf(b)&&d.push(a[e]);if(d.length<2)return d;var f=d[0];for(e=1;e<d.length;e++)this.intelligentCSSPush([f],d[e]);return[f]},b.prototype.deleteBySelector=function(a,b){for(var c=[],d=0;d<a.length;d++)a[d].selector!==b&&c.push(a[d]);return c},b.prototype.compressCSS=function(a){for(var b=[],c={},d=0;d<a.length;d++){var e=a[d];if(c[e.selector]!==!0){var f=this.findBySelector(a,e.selector);0!==f.length&&(b.push(f[0]),c[e.selector]=!0)}}return b},b.prototype.cssDiff=function(a,b){if(a.selector!==b.selector)return!1;if("media"===a.type||"media"===b.type)return!1;for(var c,d,e={selector:a.selector,rules:[]},f=0;f<a.rules.length;f++)c=a.rules[f],d=this.findCorrespondingRule(b.rules,c.directive,c.value),d===!1?e.rules.push(c):c.value!==d.value&&e.rules.push(c);for(var g=0;g<b.rules.length;g++)d=b.rules[g],c=this.findCorrespondingRule(a.rules,d.directive),c===!1&&(d.type="DELETED",e.rules.push(d));return 0===e.rules.length?!1:e},b.prototype.intelligentMerge=function(a,b,c){void 0===c&&(c=!1);for(var d=0;d<b.length;d++)this.intelligentCSSPush(a,b[d],c);for(d=0;d<a.length;d++){var e=a[d];"media"!==e.type&&"keyframes"!==e.type&&(e.rules=this.compactRules(e.rules))}},b.prototype.intelligentCSSPush=function(a,b,c){var d=(b.selector,!1);if(void 0===c&&(c=!1),c===!1){for(var e=0;e<a.length;e++)if(a[e].selector===b.selector){d=a[e];break}}else for(var f=a.length-1;f>-1;f--)if(a[f].selector===b.selector){d=a[f];break}if(d===!1)a.push(b);else if("media"!==b.type)for(var g=0;g<b.rules.length;g++){var h=b.rules[g],i=this.findCorrespondingRule(d.rules,h.directive);i===!1?d.rules.push(h):"DELETED"===h.type?i.type="DELETED":i.value=h.value}else d.subStyles=d.subStyles.concat(b.subStyles)},b.prototype.compactRules=function(a){for(var b=[],c=0;c<a.length;c++)"DELETED"!==a[c].type&&b.push(a[c]);return b},b.prototype.getCSSForEditor=function(a,b){void 0===b&&(b=0);var c="";void 0===a&&(a=this.css);for(var d=0;d<a.length;d++)"imports"===a[d].type&&(c+=a[d].styles+"\n\n");for(d=0;d<a.length;d++){var e=a[d];if(void 0!==e.selector){var f="";void 0!==e.comments&&(f=e.comments+"\n"),"media"===e.type?(c+=f+e.selector+"{\n",c+=this.getCSSForEditor(e.subStyles,b+1),c+="}\n\n"):"keyframes"!==e.type&&"imports"!==e.type&&(c+=this.getSpaces(b)+f+e.selector+" {\n",c+=this.getCSSOfRules(e.rules,b+1),c+=this.getSpaces(b)+"}\n\n")}}for(d=0;d<a.length;d++)"keyframes"===a[d].type&&(c+=a[d].styles+"\n\n");return c},b.prototype.getImports=function(a){for(var b=[],c=0;c<a.length;c++)"imports"===a[c].type&&b.push(a[c].styles);return b},b.prototype.getCSSOfRules=function(a,b){for(var c="",d=0;d<a.length;d++)void 0!==a[d]&&(c+=void 0===a[d].defective?this.getSpaces(b)+a[d].directive+": "+a[d].value+";\n":this.getSpaces(b)+a[d].value+";\n");return c||"\n"},b.prototype.getSpaces=function(a){for(var b="",c=0;4*a>c;c++)b+=" ";return b},b.prototype.applyNamespacing=function(a,b){var c=a,d="."+this.cssPreviewNamespace;void 0!==b&&(d=b),"string"==typeof a&&(c=this.parseCSS(a));for(var e=0;e<c.length;e++){var f=c[e];if(!(f.selector.indexOf("@font-face")>-1||f.selector.indexOf("keyframes")>-1||f.selector.indexOf("@import")>-1||f.selector.indexOf(".form-all")>-1||f.selector.indexOf("#stage")>-1))if("media"!==f.type){for(var g=f.selector.split(","),h=[],i=0;i<g.length;i++)-1===g[i].indexOf(".supernova")?h.push(d+" "+g[i]):h.push(g[i]);f.selector=h.join(",")}else f.subStyles=this.applyNamespacing(f.subStyles,b)}return c},b.prototype.clearNamespacing=function(a,b){void 0===b&&(b=!1);var c=a,d="."+this.cssPreviewNamespace;"string"==typeof a&&(c=this.parseCSS(a));for(var e=0;e<c.length;e++){var f=c[e];if("media"!==f.type){for(var g=f.selector.split(","),h=[],i=0;i<g.length;i++)h.push(g[i].split(d+" ").join(""));f.selector=h.join(",")}else f.subStyles=this.clearNamespacing(f.subStyles,!0)}return b===!1?this.getCSSForEditor(c):c},b.prototype.createStyleElement=function(a,b,c){if(void 0===c&&(c=!1),this.testMode===!1&&"nonamespace"!==c&&(b=this.applyNamespacing(b)),"string"!=typeof b&&(b=this.getCSSForEditor(b)),c===!0&&(b=this.getCSSForEditor(this.parseCSS(b))),this.testMode!==!1)return this.testMode("create style #"+a,b);var d=document.getElementById(a);d&&d.parentNode.removeChild(d);var e=document.head||document.getElementsByTagName("head")[0],f=document.createElement("style");f.id=a,f.type="text/css",e.appendChild(f),f.styleSheet&&!f.sheet?f.styleSheet.cssText=b:f.appendChild(document.createTextNode(b))},a.cssjs=b}(this);
\ No newline at end of file
diff --git a/ui/webpack.config.dev.js b/ui/webpack.config.dev.js
new file mode 100644
index 0000000..a114c29
--- /dev/null
+++ b/ui/webpack.config.dev.js
@@ -0,0 +1,129 @@
+/*
+ * Copyright © 2016 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 */
+
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const ExtractTextPlugin = require('extract-text-webpack-plugin');
+const CopyWebpackPlugin = require('copy-webpack-plugin');
+const webpack = require('webpack');
+const path = require('path');
+
+/* devtool: 'cheap-module-eval-source-map', */
+
+module.exports = {
+    devtool: 'source-map',
+    entry: [
+        './src/app/app.js',
+        'webpack-hot-middleware/client?reload=true',
+    ],
+    output: {
+        path: path.resolve(__dirname, 'target/generated-resources/public/static'),
+        publicPath: '/',
+        filename: 'bundle.js',
+    },
+    plugins: [
+        new webpack.ProvidePlugin({
+            $: "jquery",
+            jQuery: "jquery",
+            "window.jQuery": "jquery",
+            tinycolor: "tinycolor2",
+            tv4: "tv4",
+            moment: "moment"
+        }),
+        new CopyWebpackPlugin([
+            { from: './src/locale', to: 'locale' },
+            { from: './src/thingsboard.ico', to: 'thingsboard.ico' }
+        ]),
+        new webpack.HotModuleReplacementPlugin(),
+        new HtmlWebpackPlugin({
+            template: './src/index.html',
+            filename: 'index.html',
+            title: 'Thingsboard',
+            inject: 'body',
+        }),
+        new webpack.optimize.OccurrenceOrderPlugin(),
+        new webpack.NoErrorsPlugin(),
+        new ExtractTextPlugin('style.[contentHash].css', {
+            allChunks: true,
+        }),
+        new webpack.DefinePlugin({
+            '__DEVTOOLS__': false,
+            'process.env': {
+                NODE_ENV: JSON.stringify('development'),
+            },
+        }),
+    ],
+    node: {
+        tls: "empty",
+        fs: "empty"
+    },
+    module: {
+        loaders: [
+            {
+                test: /\.jsx$/,
+                loader: 'babel',
+                exclude: /node_modules/,
+                include: __dirname,
+            },
+            {
+                test: /\.js$/,
+                loaders: ['ng-annotate', 'babel'],
+                exclude: /node_modules/,
+                include: __dirname,
+            },
+            {
+                test: /\.js$/,
+                loader: "eslint-loader?{parser: 'babel-eslint'}",
+                exclude: /node_modules|vendor/,
+                include: __dirname,
+            },
+            {
+                test: /\.css$/,
+                loader: ExtractTextPlugin.extract('style-loader', 'css-loader'),
+            },
+            {
+                test: /\.scss$/,
+                loader: ExtractTextPlugin.extract('style-loader', 'css-loader!postcss-loader!sass-loader'),
+            },
+            {
+                test: /\.less$/,
+                loader: ExtractTextPlugin.extract('style-loader', 'css-loader!postcss-loader!less-loader'),
+            },
+            {
+                test: /\.tpl\.html$/,
+                loader: 'ngtemplate?relativeTo=' + (path.resolve(__dirname, './src/app')) + '/!html!html-minifier-loader'
+            },
+            {
+                test: /\.(svg)(\?v=[0-9]+\.[0-9]+\.[0-9]+)?$/,
+                loader: 'url?limit=8192'
+            },
+            {
+                test: /\.(png|jpe?g|gif|woff|woff2|ttf|otf|eot|ico)(\?v=[0-9]+\.[0-9]+\.[0-9]+)?$/,
+                loaders: [
+                    'url?limit=8192',
+                    'img?minimize'
+                ]
+            },
+        ],
+    },
+    'html-minifier-loader': {
+        caseSensitive: true,
+        removeComments: true,
+        collapseWhitespace: false,
+        preventAttributesEscaping: true,
+        removeEmptyAttributes: false
+    }
+};
diff --git a/ui/webpack.config.js b/ui/webpack.config.js
new file mode 100644
index 0000000..9b4a5cd
--- /dev/null
+++ b/ui/webpack.config.js
@@ -0,0 +1,22 @@
+/*
+ * Copyright © 2016 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 */
+
+if (process.env.NODE_ENV === 'production') {
+  module.exports = require('./webpack.config.prod');
+} else {
+  module.exports = require('./webpack.config.dev');
+}
diff --git a/ui/webpack.config.prod.js b/ui/webpack.config.prod.js
new file mode 100644
index 0000000..7d6fce1
--- /dev/null
+++ b/ui/webpack.config.prod.js
@@ -0,0 +1,125 @@
+/*
+ * Copyright © 2016 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 */
+
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const ExtractTextPlugin = require('extract-text-webpack-plugin');
+const CopyWebpackPlugin = require('copy-webpack-plugin');
+const webpack = require('webpack');
+const path = require('path');
+
+module.exports = {
+    devtool: 'source-map',
+    entry: ['./src/app/app.js'],
+    output: {
+        path: path.resolve(__dirname, 'target/generated-resources/public/static'),
+        publicPath: '/static/',
+        filename: 'bundle.[hash].js',
+    },
+    plugins: [
+        new webpack.ProvidePlugin({
+            $: "jquery",
+            jQuery: "jquery",
+            "window.jQuery": "jquery",
+            tinycolor: "tinycolor2",
+            tv4: "tv4",
+            moment: "moment"
+        }),
+        new CopyWebpackPlugin([
+            {from: './src/locale', to: 'locale'},
+            {from: './src/thingsboard.ico', to: 'thingsboard.ico'}
+        ]),
+        new HtmlWebpackPlugin({
+            template: './src/index.html',
+            filename: '../index.html',
+            title: 'Thingsboard',
+            inject: 'body',
+        }),
+        new webpack.optimize.OccurrenceOrderPlugin(),
+        new webpack.NoErrorsPlugin(),
+        new webpack.optimize.DedupePlugin(),
+        new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.[hash].js'),
+        new ExtractTextPlugin('style.[contentHash].css', {
+            allChunks: true,
+        }),
+        new webpack.DefinePlugin({
+            '__DEVTOOLS__': false,
+            'process.env': {
+                NODE_ENV: JSON.stringify('production'),
+            },
+        }),
+    ],
+    node: {
+        tls: "empty",
+        fs: "empty"
+    },
+    module: {
+        loaders: [
+            {
+                test: /\.jsx$/,
+                loader: 'babel',
+                exclude: /node_modules/,
+                include: __dirname,
+            },
+            {
+                test: /\.js$/,
+                loaders: ['ng-annotate', 'babel'],
+                exclude: /node_modules/,
+                include: __dirname,
+            },
+            {
+                test: /\.js$/,
+                loader: "eslint-loader?{parser: 'babel-eslint'}",
+                exclude: /node_modules|vendor/,
+                include: __dirname,
+            },
+            {
+                test: /\.css$/,
+                loader: ExtractTextPlugin.extract('style-loader', 'css-loader'),
+            },
+            {
+                test: /\.scss$/,
+                loader: ExtractTextPlugin.extract('style-loader', 'css-loader!postcss-loader!sass-loader'),
+            },
+            {
+                test: /\.less$/,
+                loader: ExtractTextPlugin.extract('style-loader', 'css-loader!postcss-loader!less-loader'),
+            },
+            {
+                test: /\.tpl\.html$/,
+                loader: 'ngtemplate?relativeTo=' + (path.resolve(__dirname, './src/app')) + '/!html!html-minifier-loader'
+            },
+            {
+                test: /\.(svg)(\?v=[0-9]+\.[0-9]+\.[0-9]+)?$/,
+                loader: 'url?limit=8192'
+            },
+            {
+                test: /\.(png|jpe?g|gif|woff|woff2|ttf|otf|eot|ico)(\?v=[0-9]+\.[0-9]+\.[0-9]+)?$/,
+                loaders: [
+                    'url?limit=8192',
+                    'img?minimize'
+                ]
+            },
+        ],
+    },
+    'html-minifier-loader': {
+        caseSensitive: true,
+        removeComments: true,
+        collapseWhitespace: false,
+        preventAttributesEscaping: true,
+        removeEmptyAttributes: false
+    }
+};