- +
@@ -55,10 +55,18 @@
- - - - +
+ + + + + +
search diff --git a/ui/src/app/layout/user-menu.directive.js b/ui/src/app/layout/user-menu.directive.js index 6d09e679ba..53e8c45b8b 100644 --- a/ui/src/app/layout/user-menu.directive.js +++ b/ui/src/app/layout/user-menu.directive.js @@ -48,16 +48,10 @@ function UserMenuController($scope, userService, $translate, $state) { var dashboardUser = userService.getCurrentUser(); vm.authorityName = authorityName; - vm.displaySearchMode = displaySearchMode; vm.logout = logout; vm.openProfile = openProfile; vm.userDisplayName = userDisplayName; - function displaySearchMode() { - return $scope.searchConfig.searchEnabled && - $scope.searchConfig.showSearch; - } - function authorityName() { var name = "user.anonymous"; if (dashboardUser) { diff --git a/ui/src/app/locale/locale.constant-es.js b/ui/src/app/locale/locale.constant-es.js index 153019d44c..e4d4b49e53 100644 --- a/ui/src/app/locale/locale.constant-es.js +++ b/ui/src/app/locale/locale.constant-es.js @@ -235,7 +235,7 @@ "socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard", "socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard", "select-dashboard": "Seleccionar panel", - "no-dashboards-matching": "Panel '{{dashboard}}' no encontrado.", + "no-dashboards-matching": "Panel '{{entity}}' no encontrado.", "dashboard-required": "Panel requerido.", "select-existing": "Seleccionar paneles existentes", "create-new": "Crear nuevo panel", @@ -330,7 +330,7 @@ "create-new-key": "Crear nueva clave!", "duplicate-alias-error": "Alias duplicado '{{alias}}'.
El alias de los dispositivos deben ser únicos dentro del panel.", "configure-alias": "Configurar alias '{{alias}}'", - "no-devices-matching": "No se encontró dispositivo '{{device}}'", + "no-devices-matching": "No se encontró dispositivo '{{entity}}'", "alias": "Alias", "alias-required": "Alias de dispositivo requerido.", "remove-alias": "Eliminar alias", @@ -529,7 +529,7 @@ "system": "Sistema", "select-plugin": "plugin", "plugin": "Plugin", - "no-plugins-matching": "No se encontraron plugins: '{{plugin}}'", + "no-plugins-matching": "No se encontraron plugins: '{{entity}}'", "plugin-required": "Plugin requerido.", "plugin-require-match": "Por favor, elija un plugin existente.", "events": "Eventos", diff --git a/ui/src/app/locale/locale.constant-ko.js b/ui/src/app/locale/locale.constant-ko.js index 8b2d4bacb7..32dbf49c4a 100644 --- a/ui/src/app/locale/locale.constant-ko.js +++ b/ui/src/app/locale/locale.constant-ko.js @@ -218,7 +218,7 @@ export default function addLocaleKorean(locales) { "unassign-dashboards-title": "{ count, select, 1 {대시보드 1개} other {대시보드 #개} }의 할당을 취소하시겠습니까?", "unassign-dashboards-text": "선택된 대시보드가 할당 해제되고 커스터머는 액세스 할 수 없게됩니다.", "select-dashboard": "대시보드 선택", - "no-dashboards-matching": "'{{dashboard}}'와 일치하는 대시보드가 없습니다.", + "no-dashboards-matching": "'{{entity}}'와 일치하는 대시보드가 없습니다.", "dashboard-required": "대시보드를 입력하세요.", "select-existing": "기존 대시보드 선택", "create-new": "대시보드 생성", @@ -305,7 +305,7 @@ export default function addLocaleKorean(locales) { "create-new-key": "새로 만들기!", "duplicate-alias-error": "중복된 '{{alias}}' 앨리어스가 있습니다.
디바이스 앨리어스는 대시보드 내에서 고유해야 합니다.", "configure-alias": "'{{alias}}' 앨리어스 구성", - "no-devices-matching": "'{{device}}'와 일치하는 디바이스를 찾을 수 없습니다.", + "no-devices-matching": "'{{entity}}'와 일치하는 디바이스를 찾을 수 없습니다.", "alias": "앨리어스", "alias-required": "디바이스 앨리어스를 입력하세요.", "remove-alias": "디바이스 앨리어스 삭제", @@ -496,7 +496,7 @@ export default function addLocaleKorean(locales) { "system": "시스템", "select-plugin": "플러그인 선택", "plugin": "플러그인", - "no-plugins-matching": "'{{plugin}}'과 일치하는 플러그인을 찾을 수 없습니다.", + "no-plugins-matching": "'{{entity}}'과 일치하는 플러그인을 찾을 수 없습니다.", "plugin-required": "플러그인을 입력하세요.", "plugin-require-match": "기존의 플러그인을 선택해주세요.", "events": "이벤트", diff --git a/ui/src/app/locale/locale.constant-ru.js b/ui/src/app/locale/locale.constant-ru.js index 47c9460bbd..d7734b46d7 100644 --- a/ui/src/app/locale/locale.constant-ru.js +++ b/ui/src/app/locale/locale.constant-ru.js @@ -235,7 +235,7 @@ export default function addLocaleRussian(locales) { "socialshare-text": "'{{dashboardTitle}}' сделано ThingsBoard", "socialshare-title": "'{{dashboardTitle}}' сделано ThingsBoard", "select-dashboard": "Выберите дашборд", - "no-dashboards-matching": "Дашборд '{{dashboard}}' не найден.", + "no-dashboards-matching": "Дашборд '{{entity}}' не найден.", "dashboard-required": "Дашборд обязателен.", "select-existing": "Выберите существующий дашборд", "create-new": "Создать новый дашборд", @@ -330,7 +330,7 @@ export default function addLocaleRussian(locales) { "create-new-key": "Создать новый!", "duplicate-alias-error": "Найден дублирующийся псевдоним '{{alias}}'.
В рамках дашборда псевдонимы устройств должны быть уникальными.", "configure-alias": "Конфигурировать '{{alias}}' псевдоним", - "no-devices-matching": "Устройство '{{device}}' не найдено.", + "no-devices-matching": "Устройство '{{entity}}' не найдено.", "alias": "Псевдоним", "alias-required": "Псевдоним устройства обязателен.", "remove-alias": "Удалить псевдоним устройства", @@ -529,7 +529,7 @@ export default function addLocaleRussian(locales) { "system": "Системный", "select-plugin": "Выберите плагин", "plugin": "Плагин", - "no-plugins-matching": "Плагин '{{plugin}}' не найден.", + "no-plugins-matching": "Плагин '{{entity}}' не найден.", "plugin-required": "Плагин обязателен.", "plugin-require-match": "Пожалуйста, выберите существующий плагин.", "events": "События", diff --git a/ui/src/app/locale/locale.constant-zh.js b/ui/src/app/locale/locale.constant-zh.js index 56e4d57905..32460702ec 100644 --- a/ui/src/app/locale/locale.constant-zh.js +++ b/ui/src/app/locale/locale.constant-zh.js @@ -116,12 +116,12 @@ export default function addLocaleChinese(locales) { "delete-attributes-text" : "注意,确认后所有选中的属性都会被删除。", "delete-attributes" : "删除属性", "enter-attribute-value" : "输入属性值", - "show-on-widget" : "在小部件上显示", - "widget-mode" : "小部件模式", - "next-widget" : "下一个小部件", - "prev-widget" : "上一个小部件", + "show-on-widget" : "在部件上显示", + "widget-mode" : "部件模式", + "next-widget" : "下一个部件", + "prev-widget" : "上一个部件", "add-to-dashboard" : "添加到仪表板", - "add-widget-to-dashboard" : "将小部件添加到仪表板", + "add-widget-to-dashboard" : "将部件添加到仪表板", "selected-attributes" : "{ count, select, 1 {1 attribute} other {# attributes} } 被选中", "selected-telemetry" : "{ count, select, 1 {1 telemetry unit} other {# telemetry units} } 被选中" }, @@ -169,7 +169,7 @@ export default function addLocaleChinese(locales) { "customer-details" : "客户详情", "delete-customer-title" : "您确定要删除客户'{{customerTitle}}'吗?", "delete-customer-text" : "小心!确认后,客户及其所有相关数据将不可恢复。", - "delete-customers-title" : "您确定要删除 { count, select, 1 {1 customer} other {# customers} }?吗", + "delete-customers-title" : "您确定要删除 { count, select, 1 {1 customer} other {# customers} }吗?", "delete-customers-action-title" : "删除 { count, select, 1 {1 customer} other {# customers} }", "delete-customers-text" : "小心!确认后,所有选定的客户将被删除,所有相关数据将不可恢复。", "manage-users" : "管理用户", @@ -199,11 +199,11 @@ export default function addLocaleChinese(locales) { "make-public" : "使仪表板公有", "make-private" : "使仪表板私有", "no-dashboards-text" : "没有找到仪表板", - "no-widgets" : "没有配置小部件", - "add-widget" : "添加新的小部件", + "no-widgets" : "没有配置部件", + "add-widget" : "添加新的部件", "title" : "标题", - "select-widget-title" : "选择小部件", - "select-widget-subtitle" : "可用的小部件类型列表", + "select-widget-title" : "选择部件", + "select-widget-subtitle" : "可用的部件类型列表", "delete" : "删除仪表板", "title-required" : "需要标题。", "description" : "描述", @@ -235,7 +235,7 @@ export default function addLocaleChinese(locales) { "socialshare-text" : "'{{dashboardTitle}}' 由ThingsBoard提供支持", "socialshare-title" : "'{{dashboardTitle}}' 由ThingsBoard提供支持", "select-dashboard" : "选择仪表板", - "no-dashboards-matching" : "找不到符合 '{{dashboard}}' 的仪表板。", + "no-dashboards-matching" : "找不到符合 '{{entity}}' 的仪表板。", "dashboard-required" : "仪表板是必需的。", "select-existing" : "选择现有仪表板", "create-new" : "创建新的仪表板", @@ -273,11 +273,11 @@ export default function addLocaleChinese(locales) { "dashboard-file" : "仪表板文件", "invalid-dashboard-file-error" : "无法导入仪表板: 仪表板数据结构无效。", "dashboard-import-missing-aliases-title" : "配置导入仪表板使用的别名", - "create-new-widget" : "创建新小部件", - "import-widget" : "导入小部件", - "widget-file" : "小部件文件", - "invalid-widget-file-error" : "无法导入窗口小部件: 窗口小部件数据结构无效。", - "widget-import-missing-aliases-title" : "配置导入的窗口小部件使用的别名", + "create-new-widget" : "创建新部件", + "import-widget" : "导入部件", + "widget-file" : "部件文件", + "invalid-widget-file-error" : "无法导入窗口部件: 窗口部件数据结构无效。", + "widget-import-missing-aliases-title" : "配置导入的窗口部件使用的别名", "open-toolbar" : "打开仪表板工具栏", "close-toolbar" : "关闭工具栏", "configuration-error" : "配置错误", @@ -330,7 +330,7 @@ export default function addLocaleChinese(locales) { "create-new-key": "创建一个新的!", "duplicate-alias-error" : "找到重复别名 '{{alias}}'。
设备别名必须是唯一的。", "configure-alias" : "配置 '{{alias}}' 别名", - "no-devices-matching" : "找不到与 '{{device}}' 匹配的设备。", + "no-devices-matching" : "找不到与 '{{entity}}' 匹配的设备。", "alias" : "别名", "alias-required" : "需要设备别名。", "remove-alias": "删除设备别名", @@ -529,7 +529,7 @@ export default function addLocaleChinese(locales) { "system" : "系统", "select-plugin" : "选择插件", "plugin" : "插件", - "no-plugins-matching" : "没有找到匹配'{{plugin}}'的插件。", + "no-plugins-matching" : "没有找到匹配'{{entity}}'的插件。", "plugin-required" : "插件是必需的。", "plugin-require-match" : "请选择一个现有的插件。", "events" : "事件", @@ -694,34 +694,34 @@ export default function addLocaleChinese(locales) { "true" : "真" }, "widget" : { - "widget-library" : "小部件库", - "widget-bundle" : "小部件包", - "select-widgets-bundle" : "选择小部件包", - "management" : "小部件管理", - "editor" : "小部件编辑器", - "widget-type-not-found" : "加载小部件配置时出现问题。
可能关联的\n 小部件类型已删除。", - "widget-type-load-error" : "由于以下错误,小工具未加载:", - "remove" : "删除小部件", - "edit" : "编辑小部件", - "remove-widget-title" : "您确定要删除小部件 '{{widgetTitle}}' 吗?", - "remove-widget-text" : "确认后,窗口小部件和所有相关数据将不可恢复。", + "widget-library" : "部件库", + "widget-bundle" : "部件包", + "select-widgets-bundle" : "选择部件包", + "management" : "部件管理", + "editor" : "部件编辑器", + "widget-type-not-found" : "加载部件配置时出现问题。
可能关联的\n 部件类型已删除。", + "widget-type-load-error" : "由于以下错误,部件未加载:", + "remove" : "删除部件", + "edit" : "编辑部件", + "remove-widget-title" : "您确定要删除部件 '{{widgetTitle}}' 吗?", + "remove-widget-text" : "确认后,窗口部件和所有相关数据将不可恢复。", "timeseries" : "时间序列", "latest-values" : "最新值", - "rpc" : "控件小部件", - "static" : "静态小部件", - "select-widget-type" : "选择窗口小部件类型", - "missing-widget-title-error" : "小部件标题必须指定!", - "widget-saved" : "小部件已保存", - "unable-to-save-widget-error" : "无法保存窗口小部件! 小部件有错误!", - "save" : "保存小部件", - "saveAs" : "将小部件另存为", - "save-widget-type-as" : "将小部件类型另存为", - "save-widget-type-as-text" : "请输入新的小部件标题和/或选择目标小部件包", + "rpc" : "控件部件", + "static" : "静态部件", + "select-widget-type" : "选择窗口部件类型", + "missing-widget-title-error" : "部件标题必须指定!", + "widget-saved" : "部件已保存", + "unable-to-save-widget-error" : "无法保存窗口部件! 部件有错误!", + "save" : "保存部件", + "saveAs" : "将部件另存为", + "save-widget-type-as" : "将部件类型另存为", + "save-widget-type-as-text" : "请输入新的部件标题和/或选择目标部件包", "toggle-fullscreen" : "切换全屏", - "run" : "运行小部件", - "title" : "小部件标题", - "title-required" : "需要小部件标题。", - "type" : "小部件类型", + "run" : "运行部件", + "title" : "部件标题", + "title-required" : "需要部件标题。", + "type" : "部件类型", "resources" : "资源", "resource-url" : "JavaScript/CSS URL", "remove-resource" : "删除资源", @@ -732,42 +732,42 @@ export default function addLocaleChinese(locales) { "settings-schema" : "设置模式", "datakey-settings-schema" : "数据键设置模式", "javascript" : "Javascript", - "remove-widget-type-title" : "您确定要删除小部件类型 '{{widgetName}}'吗?", - "remove-widget-type-text" : "确认后,窗口小部件类型和所有相关数据将不可恢复。", - "remove-widget-type" : "删除小部件类型", - "add-widget-type" : "添加新的小部件类型", - "widget-type-load-failed-error" : "无法加载小部件类型!", - "widget-template-load-failed-error" : "无法加载小部件模板!", - "add" : "添加小部件", - "undo" : "撤消小部件更改", - "export" : "导出小部件" + "remove-widget-type-title" : "您确定要删除部件类型 '{{widgetName}}'吗?", + "remove-widget-type-text" : "确认后,窗口部件类型和所有相关数据将不可恢复。", + "remove-widget-type" : "删除部件类型", + "add-widget-type" : "添加新的部件类型", + "widget-type-load-failed-error" : "无法加载部件类型!", + "widget-template-load-failed-error" : "无法加载部件模板!", + "add" : "添加部件", + "undo" : "撤消部件更改", + "export" : "导出部件" }, "widgets-bundle" : { "current" : "当前包", - "widgets-bundles" : "小部件包", - "add" : "添加小部件包", - "delete" : "删除小部件包", + "widgets-bundles" : "部件包", + "add" : "添加部件包", + "delete" : "删除部件包", "title" : "标题", "title-required" : "标题是必填项。", - "add-widgets-bundle-text" : "添加新的小部件包", - "no-widgets-bundles-text" : "找不到小部件包", - "empty" : "小部件包是空的", + "add-widgets-bundle-text" : "添加新的部件包", + "no-widgets-bundles-text" : "找不到部件包", + "empty" : "部件包是空的", "details" : "详情", - "widgets-bundle-details" : "小部件包详细信息", - "delete-widgets-bundle-title" : "您确定要删除小部件包 '{{widgetsBundleTitle}}'吗?", - "delete-widgets-bundle-text" : "小心!确认后,小部件包和所有相关数据将不可恢复。", + "widgets-bundle-details" : "部件包详细信息", + "delete-widgets-bundle-title" : "您确定要删除部件包 '{{widgetsBundleTitle}}'吗?", + "delete-widgets-bundle-text" : "小心!确认后,部件包和所有相关数据将不可恢复。", "delete-widgets-bundles-title" : "你确定你要删除 { count, select, 1 {1 widgets bundle} other {# widgets bundles} } 吗?", "delete-widgets-bundles-action-title" : "删除 { count, select, 1 {1 widgets bundle} other {# widgets bundles} }", - "delete-widgets-bundles-text" : "小心!确认后,所有选定的小部件包将被删除,所有相关数据将不可恢复。", - "no-widgets-bundles-matching" : "没有找到与 '{{widgetsBundle}}' 匹配的小部件包。", - "widgets-bundle-required" : "需要小部件包。", + "delete-widgets-bundles-text" : "小心!确认后,所有选定的部件包将被删除,所有相关数据将不可恢复。", + "no-widgets-bundles-matching" : "没有找到与 '{{widgetsBundle}}' 匹配的部件包。", + "widgets-bundle-required" : "需要部件包。", "system" : "系统", - "import" : "导入小部件包", - "export" : "导出小部件包", - "export-failed-error" : "无法导出小部件包: {{error}}", - "create-new-widgets-bundle" : "创建新的小部件包", - "widgets-bundle-file" : "小部件包文件", - "invalid-widgets-bundle-file-error" : "无法导入小部件包:无效的小部件包数据结构。" + "import" : "导入部件包", + "export" : "导出部件包", + "export-failed-error" : "无法导出部件包: {{error}}", + "create-new-widgets-bundle" : "创建新的部件包", + "widgets-bundle-file" : "部件包文件", + "invalid-widgets-bundle-file-error" : "无法导入部件包:无效的部件包数据结构。" }, "widget-config" : { "data" : "数据", @@ -798,12 +798,12 @@ export default function addLocaleChinese(locales) { "target-device" : "目标设备" }, "widget-type" : { - "import" : "导入小部件类型", - "export" : "导出小部件类型", - "export-failed-error" : "无法导出小部件类型: {{error}}", - "create-new-widget-type" : "创建新的小部件类型", - "widget-type-file" : "小部件类型文件", - "invalid-widget-type-file-error" : "无法导入小部件类型:无效的小部件类型数据结构。" + "import" : "导入部件类型", + "export" : "导出部件类型", + "export-failed-error" : "无法导出部件类型: {{error}}", + "create-new-widget-type" : "创建新的部件类型", + "widget-type-file" : "部件类型文件", + "invalid-widget-type-file-error" : "无法导入部件类型:无效的部件类型数据结构。" }, "language" : { "language" : "语言", @@ -811,10 +811,10 @@ export default function addLocaleChinese(locales) { "ko_KR" : "韩语", "zh_CN" : "汉语", "ru_RU" : "俄语", - "es_ES": "西班牙語" + "es_ES": "西班牙语" } }; angular.extend(locales, { 'zh_CN' : zh_CN }); -} \ No newline at end of file +} diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index 1fc45f7681..d25db5da32 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -14,7 +14,10 @@ * limitations under the License. */ +import ThingsboardMissingTranslateHandler from './translate-handler'; + export default angular.module('thingsboard.locale', []) + .factory('tbMissingTranslationHandler', ThingsboardMissingTranslateHandler) .constant('locales', { 'en_US': { @@ -62,6 +65,8 @@ export default angular.module('thingsboard.locale', []) "undo": "Undo", "copy": "Copy", "paste": "Paste", + "copy-reference": "Copy reference", + "paste-reference": "Paste reference", "import": "Import", "export": "Export", "share-via": "Share via {{provider}}" @@ -101,6 +106,12 @@ export default angular.module('thingsboard.locale', []) "enable-tls": "Enable TLS", "send-test-mail": "Send test mail" }, + "alarm": { + "alarm": "Alarm", + "select-alarm": "Select alarm", + "no-alarms-matching": "No alarms matching '{{entity}}' were found.", + "alarm-required": "Alarm is required" + }, "asset": { "asset": "Asset", "assets": "Assets", @@ -119,6 +130,9 @@ export default angular.module('thingsboard.locale', []) "unassign-from-customer": "Unassign from customer", "delete": "Delete asset", "asset-public": "Asset is public", + "asset-type": "Asset type", + "asset-type-required": "Asset type is required.", + "select-asset-type": "Select asset type", "name": "Name", "name-required": "Name is required.", "description": "Description", @@ -149,7 +163,10 @@ export default angular.module('thingsboard.locale', []) "unassign-assets-title": "Are you sure you want to unassign { count, select, 1 {1 asset} other {# assets} }?", "unassign-assets-text": "After the confirmation all selected assets will be unassigned and won't be accessible by the customer.", "copyId": "Copy asset Id", - "idCopiedMessage": "Asset Id has been copied to clipboard" + "idCopiedMessage": "Asset Id has been copied to clipboard", + "select-asset": "Select asset", + "no-assets-matching": "No assets matching '{{entity}}' were found.", + "asset-required": "Asset is required" }, "attribute": { "attributes": "Attributes", @@ -161,6 +178,7 @@ export default angular.module('thingsboard.locale', []) "scope-shared": "Shared attributes", "add": "Add attribute", "key": "Key", + "last-update-time": "Last update time", "key-required": "Attribute key is required.", "value": "Value", "value-required": "Attribute value is required.", @@ -202,6 +220,7 @@ export default angular.module('thingsboard.locale', []) "enter-search": "Enter search" }, "customer": { + "customer": "Customer", "customers": "Customers", "management": "Customer management", "dashboard": "Customer Dashboard", @@ -238,7 +257,10 @@ export default angular.module('thingsboard.locale', []) "details": "Details", "events": "Events", "copyId": "Copy customer Id", - "idCopiedMessage": "Customer Id has been copied to clipboard" + "idCopiedMessage": "Customer Id has been copied to clipboard", + "select-customer": "Select customer", + "no-customers-matching": "No customers matching '{{entity}}' were found.", + "customer-required": "Customer is required" }, "datetime": { "date-from": "Date from", @@ -296,7 +318,7 @@ export default angular.module('thingsboard.locale', []) "socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard", "socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard", "select-dashboard": "Select dashboard", - "no-dashboards-matching": "No dashboards matching '{{dashboard}}' were found.", + "no-dashboards-matching": "No dashboards matching '{{entity}}' were found.", "dashboard-required": "Dashboard is required.", "select-existing": "Select existing dashboard", "create-new": "Create new dashboard", @@ -323,7 +345,9 @@ export default angular.module('thingsboard.locale', []) "min-vertical-margin-message": "Only 0 is allowed as minimum vertical margin value.", "max-vertical-margin-message": "Only 50 is allowed as maximum vertical margin value.", "display-title": "Display dashboard title", + "toolbar-always-open": "Keep toolbar opened", "title-color": "Title color", + "display-dashboards-selection": "Display dashboards selection", "display-entities-selection": "Display entities selection", "display-dashboard-timewindow": "Display timewindow", "display-dashboard-export": "Display export", @@ -350,13 +374,37 @@ export default angular.module('thingsboard.locale', []) "public": "Public", "public-link": "Public link", "copy-public-link": "Copy public link", - "public-link-copied-message": "Dashboard public link has been copied to clipboard" + "public-link-copied-message": "Dashboard public link has been copied to clipboard", + "manage-states": "Manage dashboard states", + "states": "Dashboard states", + "search-states": "Search dashboard states", + "selected-states": "{ count, select, 1 {1 dashboard state} other {# dashboard states} } selected", + "edit-state": "Edit dashboard state", + "delete-state": "Delete dashboard state", + "add-state": "Add dashboard state", + "state": "Dashboard state", + "state-name": "Name", + "state-name-required": "Dashboard state name is required.", + "state-name-exists": "Dashboard state with the same name is already exists.", + "state-id": "State Id", + "state-id-required": "Dashboard state id is required.", + "state-id-exists": "Dashboard state with the same id is already exists.", + "invalid-state-id-format": "Only alphanumeric characters and underscore are allowed.", + "is-root-state": "Root state", + "delete-state-title": "Delete dashboard state", + "delete-state-text": "Are you sure you want delete dashboard state with name '{{stateName}}'?", + "show-details": "Show details", + "hide-details": "Hide details", + "select-state": "Select target state", + "state-controller": "State controller" }, "datakey": { "settings": "Settings", "advanced": "Advanced", "label": "Label", "color": "Color", + "units": "Special symbol to show next to value", + "decimals": "Number of digits after floating point", "data-generation-func": "Data generation function", "use-data-post-processing-func": "Use data post-processing function", "configuration": "Data key configuration", @@ -391,7 +439,7 @@ export default angular.module('thingsboard.locale', []) "create-new-key": "Create a new one!", "duplicate-alias-error": "Duplicate alias found '{{alias}}'.
Device aliases must be unique whithin the dashboard.", "configure-alias": "Configure '{{alias}}' alias", - "no-devices-matching": "No devices matching '{{device}}' were found.", + "no-devices-matching": "No devices matching '{{entity}}' were found.", "alias": "Alias", "alias-required": "Device alias is required.", "remove-alias": "Remove device alias", @@ -446,6 +494,9 @@ export default angular.module('thingsboard.locale', []) "rsa-key-required": "RSA public key is required.", "secret": "Secret", "secret-required": "Secret is required.", + "device-type": "Device type", + "device-type-required": "Device type is required.", + "select-device-type": "Select device type", "name": "Name", "name-required": "Name is required.", "description": "Description", @@ -460,7 +511,8 @@ export default angular.module('thingsboard.locale', []) "unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):
{{widgetsList}}", "is-gateway": "Is gateway", "public": "Public", - "device-public": "Device is public" + "device-public": "Device is public", + "select-device": "Select device" }, "dialog": { "close": "Close dialog" @@ -490,6 +542,7 @@ export default angular.module('thingsboard.locale', []) "entity-list-empty": "No entities selected.", "entity-name-filter-required": "Entity name filter is required.", "entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.", + "all-subtypes": "All", "type": "Type", "type-device": "Device", "type-asset": "Asset", @@ -497,6 +550,9 @@ export default angular.module('thingsboard.locale', []) "type-plugin": "Plugin", "type-tenant": "Tenant", "type-customer": "Customer", + "type-user": "User", + "type-dashboard": "Dashboard", + "type-alarm": "Alarm", "select-entities": "Select entities", "no-aliases-found": "No aliases found.", "no-alias-matching": "'{{alias}}' not found.", @@ -569,6 +625,15 @@ export default angular.module('thingsboard.locale', []) "no-return-error": "Function must return value!", "return-type-mismatch": "Function must return value of '{{type}}' type!" }, + "layout": { + "layout": "Layout", + "manage": "Manage layouts", + "settings": "Layout settings", + "color": "Color", + "main": "Main", + "right": "Right", + "select": "Select target layout" + }, "legend": { "position": "Legend position", "show-max": "Show max value", @@ -625,7 +690,7 @@ export default angular.module('thingsboard.locale', []) "system": "System", "select-plugin": "Select plugin", "plugin": "Plugin", - "no-plugins-matching": "No plugins matching '{{plugin}}' were found.", + "no-plugins-matching": "No plugins matching '{{entity}}' were found.", "plugin-required": "Plugin is required.", "plugin-require-match": "Please select an existing plugin.", "events": "Events", @@ -638,6 +703,7 @@ export default angular.module('thingsboard.locale', []) "invalid-plugin-file-error": "Unable to import plugin: Invalid plugin data structure.", "copyId": "Copy plugin Id", "idCopiedMessage": "Plugin Id has been copied to clipboard" + }, "position": { "top": "Top", @@ -650,7 +716,24 @@ export default angular.module('thingsboard.locale', []) "change-password": "Change Password", "current-password": "Current password" }, + "relation": { + "relations": "Relations", + "entity-relations": "Entity relations", + "selected-relations": "{ count, select, 1 {1 relation} other {# relations} } selected", + "type": "Type", + "to-entity-type": "Entity type", + "to-entity-name": "Entity name", + "edit": "Edit relation", + "delete": "Delete relation", + "relation-type": "Relation type", + "relation-types": { + "Contains": "Contains", + "Manages": "Manages" + }, + "add": "Add relation" + }, "rule": { + "rule": "Rule", "rules": "Rules", "delete": "Delete rule", "activate": "Activate rule", @@ -702,12 +785,16 @@ export default angular.module('thingsboard.locale', []) "rule-file": "Rule file", "invalid-rule-file-error": "Unable to import rule: Invalid rule data structure.", "copyId": "Copy rule Id", - "idCopiedMessage": "Rule Id has been copied to clipboard" + "idCopiedMessage": "Rule Id has been copied to clipboard", + "select-rule": "Select rule", + "no-rules-matching": "No rules matching '{{entity}}' were found.", + "rule-required": "Rule is required" }, "rule-plugin": { "management": "Rules and plugins management" }, "tenant": { + "tenant": "Tenant", "tenants": "Tenants", "management": "Tenant management", "add": "Add Tenant", @@ -728,7 +815,10 @@ export default angular.module('thingsboard.locale', []) "details": "Details", "events": "Events", "copyId": "Copy tenant Id", - "idCopiedMessage": "Tenant Id has been copied to clipboard" + "idCopiedMessage": "Tenant Id has been copied to clipboard", + "select-tenant": "Select tenant", + "no-tenants-matching": "No tenants matching '{{entity}}' were found.", + "tenant-required": "Tenant is required" }, "timeinterval": { "seconds-interval": "{ seconds, select, 1 {1 second} other {# seconds} }", @@ -756,6 +846,7 @@ export default angular.module('thingsboard.locale', []) "time-period": "Time period" }, "user": { + "user": "User", "users": "Users", "customer-users": "Customer Users", "tenant-admins": "Tenant Admins", @@ -781,7 +872,10 @@ export default angular.module('thingsboard.locale', []) "last-name": "Last Name", "description": "Description", "default-dashboard": "Default dashboard", - "always-fullscreen": "Always fullscreen" + "always-fullscreen": "Always fullscreen", + "select-user": "Select user", + "no-users-matching": "No users matching '{{entity}}' were found.", + "user-required": "User is required" }, "value": { "type": "Value type", diff --git a/ui/src/app/locale/translate-handler.js b/ui/src/app/locale/translate-handler.js new file mode 100644 index 0000000000..d227041532 --- /dev/null +++ b/ui/src/app/locale/translate-handler.js @@ -0,0 +1,26 @@ +/* + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*@ngInject*/ +export default function ThingsboardMissingTranslateHandler($log, types) { + + return function (translationId) { + if (translationId && !translationId.startsWith(types.translate.dashboardStatePrefix)) { + $log.warn('Translation for ' + translationId + ' doesn\'t exist'); + } + }; + +} \ No newline at end of file diff --git a/ui/src/app/profile/profile.controller.js b/ui/src/app/profile/profile.controller.js index cea51bee84..187a9d21e9 100644 --- a/ui/src/app/profile/profile.controller.js +++ b/ui/src/app/profile/profile.controller.js @@ -31,7 +31,7 @@ export default function ProfileController(userService, $scope, $document, $mdDia en_US: {value : "en_US", name: "language.en_US"}, ko_KR: {value : "ko_KR", name: "language.ko_KR"}, zh_CN: {value : "zh_CN", name: "language.zh_CN"}, - ru_RU: {value : "ru_RU", name: "language.ru_RU"}ñ + ru_RU: {value : "ru_RU", name: "language.ru_RU"}, es_ES: {value : "es_ES", name: "language.es_ES"}, }; diff --git a/ui/src/app/services/item-buffer.service.js b/ui/src/app/services/item-buffer.service.js index 4b54b4ded8..afb63bf049 100644 --- a/ui/src/app/services/item-buffer.service.js +++ b/ui/src/app/services/item-buffer.service.js @@ -24,15 +24,19 @@ export default angular.module('thingsboard.itembuffer', [angularStorage]) .name; /*@ngInject*/ -function ItemBuffer(bufferStore, types, dashboardUtils) { +function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) { const WIDGET_ITEM = "widget_item"; + const WIDGET_REFERENCE = "widget_reference"; var service = { prepareWidgetItem: prepareWidgetItem, copyWidget: copyWidget, + copyWidgetReference: copyWidgetReference, hasWidget: hasWidget, + canPasteWidgetReference: canPasteWidgetReference, pasteWidget: pasteWidget, + pasteWidgetReference: pasteWidgetReference, addWidgetToDashboard: addWidgetToDashboard } @@ -66,16 +70,42 @@ function ItemBuffer(bufferStore, types, dashboardUtils) { }; } - function prepareWidgetItem(dashboard, widget) { + function getOriginalColumns(dashboard, sourceState, sourceLayout) { + var originalColumns = 24; + var gridSettings = null; + var state = dashboard.configuration.states[sourceState]; + var layoutCount = Object.keys(state.layouts).length; + if (state) { + var layout = state.layouts[sourceLayout]; + if (layout) { + gridSettings = layout.gridSettings; + + } + } + if (gridSettings && + gridSettings.columns) { + originalColumns = gridSettings.columns; + } + originalColumns = originalColumns * layoutCount; + return originalColumns; + } + + function getOriginalSize(dashboard, sourceState, sourceLayout, widget) { + var layout = dashboard.configuration.states[sourceState].layouts[sourceLayout]; + var widgetLayout = layout.widgets[widget.id]; + return { + sizeX: widgetLayout.sizeX, + sizeY: widgetLayout.sizeY + } + } + + function prepareWidgetItem(dashboard, sourceState, sourceLayout, widget) { var aliasesInfo = { datasourceAliases: {}, targetDeviceAliases: {} }; - var originalColumns = 24; - if (dashboard.configuration.gridSettings && - dashboard.configuration.gridSettings.columns) { - originalColumns = dashboard.configuration.gridSettings.columns; - } + var originalColumns = getOriginalColumns(dashboard, sourceState, sourceLayout); + var originalSize = getOriginalSize(dashboard, sourceState, sourceLayout, widget); if (widget.config && dashboard.configuration && dashboard.configuration.entityAliases) { var entityAlias; @@ -105,37 +135,113 @@ function ItemBuffer(bufferStore, types, dashboardUtils) { return { widget: widget, aliasesInfo: aliasesInfo, + originalSize: originalSize, originalColumns: originalColumns - } + }; } - function copyWidget(dashboard, widget) { - var widgetItem = prepareWidgetItem(dashboard, widget); + function prepareWidgetReference(dashboard, sourceState, sourceLayout, widget) { + var originalColumns = getOriginalColumns(dashboard, sourceState, sourceLayout); + var originalSize = getOriginalSize(dashboard, sourceState, sourceLayout, widget); + + return { + dashboardId: dashboard.id.id, + sourceState: sourceState, + sourceLayout: sourceLayout, + widgetId: widget.id, + originalSize: originalSize, + originalColumns: originalColumns + }; + } + + function copyWidget(dashboard, sourceState, sourceLayout, widget) { + var widgetItem = prepareWidgetItem(dashboard, sourceState, sourceLayout, widget); bufferStore.set(WIDGET_ITEM, angular.toJson(widgetItem)); } + function copyWidgetReference(dashboard, sourceState, sourceLayout, widget) { + var widgetReference = prepareWidgetReference(dashboard, sourceState, sourceLayout, widget); + bufferStore.set(WIDGET_REFERENCE, angular.toJson(widgetReference)); + } + function hasWidget() { return bufferStore.get(WIDGET_ITEM); } - function pasteWidget(targetDashboard, position, onAliasesUpdate) { + function canPasteWidgetReference(dashboard, state, layout) { + var widgetReferenceJson = bufferStore.get(WIDGET_REFERENCE); + if (widgetReferenceJson) { + var widgetReference = angular.fromJson(widgetReferenceJson); + if (widgetReference.dashboardId === dashboard.id.id) { + if ((widgetReference.sourceState != state || widgetReference.sourceLayout != layout) + && dashboard.configuration.widgets[widgetReference.widgetId]) { + return true; + } + } + } + return false; + } + + function pasteWidgetReference(targetDashboard, targetState, targetLayout, position) { + var deferred = $q.defer(); + var widgetReferenceJson = bufferStore.get(WIDGET_REFERENCE); + if (widgetReferenceJson) { + var widgetReference = angular.fromJson(widgetReferenceJson); + var widget = targetDashboard.configuration.widgets[widgetReference.widgetId]; + if (widget) { + var originalColumns = widgetReference.originalColumns; + var originalSize = widgetReference.originalSize; + var targetRow = -1; + var targetColumn = -1; + if (position) { + targetRow = position.row; + targetColumn = position.column; + } + addWidgetToDashboard(targetDashboard, targetState, targetLayout, widget, null, + null, originalColumns, originalSize, targetRow, targetColumn).then( + function () { + deferred.resolve(widget); + } + ); + } else { + deferred.reject(); + } + } else { + deferred.reject(); + } + return deferred.promise; + } + + function pasteWidget(targetDashboard, targetState, targetLayout, position, onAliasesUpdateFunction) { + var deferred = $q.defer(); var widgetItemJson = bufferStore.get(WIDGET_ITEM); if (widgetItemJson) { var widgetItem = angular.fromJson(widgetItemJson); var widget = widgetItem.widget; var aliasesInfo = widgetItem.aliasesInfo; var originalColumns = widgetItem.originalColumns; + var originalSize = widgetItem.originalSize; var targetRow = -1; var targetColumn = -1; if (position) { targetRow = position.row; targetColumn = position.column; } - addWidgetToDashboard(targetDashboard, widget, aliasesInfo, onAliasesUpdate, originalColumns, targetRow, targetColumn); + widget.id = utils.guid(); + addWidgetToDashboard(targetDashboard, targetState, targetLayout, widget, aliasesInfo, + onAliasesUpdateFunction, originalColumns, originalSize, targetRow, targetColumn).then( + function () { + deferred.resolve(widget); + } + ); + } else { + deferred.reject(); } + return deferred.promise; } - function addWidgetToDashboard(dashboard, widget, aliasesInfo, onAliasesUpdate, originalColumns, row, column) { + function addWidgetToDashboard(dashboard, targetState, targetLayout, widget, aliasesInfo, onAliasesUpdateFunction, originalColumns, originalSize, row, column) { + var deferred = $q.defer(); var theDashboard; if (dashboard) { theDashboard = dashboard; @@ -145,42 +251,28 @@ function ItemBuffer(bufferStore, types, dashboardUtils) { theDashboard = dashboardUtils.validateAndUpdateDashboard(theDashboard); - var newEntityAliases = updateAliases(theDashboard, widget, aliasesInfo); - - var targetColumns = 24; - if (theDashboard.configuration.gridSettings && - theDashboard.configuration.gridSettings.columns) { - targetColumns = theDashboard.configuration.gridSettings.columns; - } - if (targetColumns != originalColumns) { - var ratio = targetColumns / originalColumns; - widget.sizeX *= ratio; - widget.sizeY *= ratio; + var callAliasUpdateFunction = false; + if (aliasesInfo) { + var newEntityAliases = updateAliases(theDashboard, widget, aliasesInfo); + var aliasesUpdated = !angular.equals(newEntityAliases, theDashboard.configuration.entityAliases); + if (aliasesUpdated) { + theDashboard.configuration.entityAliases = newEntityAliases; + if (onAliasesUpdateFunction) { + callAliasUpdateFunction = true; + } + } } - if (row > -1 && column > - 1) { - widget.row = row; - widget.col = column; + dashboardUtils.addWidgetToLayout(theDashboard, targetState, targetLayout, widget, originalColumns, originalSize, row, column); + if (callAliasUpdateFunction) { + onAliasesUpdateFunction().then( + function() { + deferred.resolve(theDashboard); + } + ); } else { - 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; - widget.col = 0; - } - var aliasesUpdated = !angular.equals(newEntityAliases, theDashboard.configuration.entityAliases); - if (aliasesUpdated) { - theDashboard.configuration.entityAliases = newEntityAliases; - if (onAliasesUpdate) { - onAliasesUpdate(); - } + deferred.resolve(theDashboard); } - theDashboard.configuration.widgets.push(widget); - return theDashboard; + return deferred.promise; } function updateAliases(dashboard, widget, aliasesInfo) { @@ -242,6 +334,4 @@ function ItemBuffer(bufferStore, types, dashboardUtils) { } return newAlias; } - - } \ No newline at end of file diff --git a/ui/src/app/user/user-fieldset.tpl.html b/ui/src/app/user/user-fieldset.tpl.html index 7c9b0d60c2..a2559ff98a 100644 --- a/ui/src/app/user/user-fieldset.tpl.html +++ b/ui/src/app/user/user-fieldset.tpl.html @@ -43,10 +43,19 @@ -
+
user.default-dashboard
- + + 0 ? settings.units : ctx.units; + } + } + + function getValueDec(settings) { + var dataKey; + if (ctx.data && ctx.data[0]) { + dataKey = ctx.data[0].dataKey; + } + if (dataKey && angular.isDefined(dataKey.decimals)) { + return dataKey.decimals; + } else { + return (angular.isDefined(settings.valueDec) && settings.valueDec !== null) + ? settings.valueDec : ctx.decimals; + } + } + + function getFontFamily(fontSettings) { var family = fontSettings && fontSettings.family ? fontSettings.family : 'Roboto'; if (family === 'RobotoDraft') { @@ -92,7 +117,7 @@ export default class TbAnalogueLinearGauge { maxValue: maxValue, majorTicks: majorTicks, minorTicks: settings.minorTicks || 2, - units: angular.isDefined(settings.units) && settings.units.length > 0 ? settings.units : ctx.units, + units: getUnits(settings), title: ((settings.showUnitTitle !== false) ? (settings.unitTitle && settings.unitTitle.length > 0 ? settings.unitTitle : dataKey.label) : ''), diff --git a/ui/src/app/widget/lib/analogue-radial-gauge.js b/ui/src/app/widget/lib/analogue-radial-gauge.js index 3a526bd3d1..76a2c383aa 100644 --- a/ui/src/app/widget/lib/analogue-radial-gauge.js +++ b/ui/src/app/widget/lib/analogue-radial-gauge.js @@ -42,8 +42,7 @@ export default class TbAnalogueRadialGauge { var valueInt = settings.valueInt || 3; - var valueDec = (angular.isDefined(settings.valueDec) && settings.valueDec !== null) - ? settings.valueDec : ctx.decimals; + var valueDec = getValueDec(settings); step = parseFloat(parseFloat(step).toFixed(valueDec)); @@ -71,6 +70,31 @@ export default class TbAnalogueRadialGauge { var colorNumbers = tinycolor(keyColor).darken(20).toRgbString(); + function getUnits(settings) { + var dataKey; + if (ctx.data && ctx.data[0]) { + dataKey = ctx.data[0].dataKey; + } + if (dataKey && dataKey.units && dataKey.units.length) { + return dataKey.units; + } else { + return angular.isDefined(settings.units) && settings.units.length > 0 ? settings.units : ctx.units; + } + } + + function getValueDec(settings) { + var dataKey; + if (ctx.data && ctx.data[0]) { + dataKey = ctx.data[0].dataKey; + } + if (dataKey && angular.isDefined(dataKey.decimals)) { + return dataKey.decimals; + } else { + return (angular.isDefined(settings.valueDec) && settings.valueDec !== null) + ? settings.valueDec : ctx.decimals; + } + } + function getFontFamily(fontSettings) { var family = fontSettings && fontSettings.family ? fontSettings.family : 'Roboto'; if (family === 'RobotoDraft') { @@ -89,7 +113,7 @@ export default class TbAnalogueRadialGauge { maxValue: maxValue, majorTicks: majorTicks, minorTicks: settings.minorTicks || 2, - units: angular.isDefined(settings.units) && settings.units.length > 0 ? settings.units : ctx.units, + units: getUnits(settings), title: ((settings.showUnitTitle !== false) ? (settings.unitTitle && settings.unitTitle.length > 0 ? settings.unitTitle : dataKey.label) : ''), diff --git a/ui/src/app/widget/lib/canvas-digital-gauge.js b/ui/src/app/widget/lib/canvas-digital-gauge.js index 33283ef693..1290f5f344 100644 --- a/ui/src/app/widget/lib/canvas-digital-gauge.js +++ b/ui/src/app/widget/lib/canvas-digital-gauge.js @@ -54,10 +54,13 @@ export default class TbCanvasDigitalGauge { this.localSettings.levelColors = settings.levelColors.slice(); } - this.localSettings.decimals = (angular.isDefined(settings.decimals) && settings.decimals !== null) - ? settings.decimals : ctx.decimals; + this.localSettings.decimals = angular.isDefined(dataKey.decimals) ? dataKey.decimals : + ((angular.isDefined(settings.decimals) && settings.decimals !== null) + ? settings.decimals : ctx.decimals); + + this.localSettings.units = dataKey.units && dataKey.units.length ? dataKey.units : + (angular.isDefined(settings.units) && settings.units.length > 0 ? settings.units : ctx.units); - this.localSettings.units = angular.isDefined(settings.units) && settings.units.length > 0 ? settings.units : ctx.units; this.localSettings.hideValue = settings.showValue !== true; this.localSettings.hideMinMax = settings.showMinMax !== true; diff --git a/ui/src/app/widget/lib/flot-widget.js b/ui/src/app/widget/lib/flot-widget.js index 340b7eb948..6687f1ad8c 100644 --- a/ui/src/app/widget/lib/flot-widget.js +++ b/ui/src/app/widget/lib/flot-widget.js @@ -110,8 +110,10 @@ export default class TbFlot { if (this.chartType === 'pie') { ctx.tooltipFormatter = function(item) { + var units = item.series.dataKey.units && item.series.dataKey.units.length ? item.series.dataKey.units : tbFlot.ctx.trackUnits; + var decimals = angular.isDefined(item.series.dataKey.decimals) ? item.series.dataKey.decimals : tbFlot.ctx.trackDecimals; var divElement = seriesInfoDiv(item.series.dataKey.label, item.series.dataKey.color, - item.datapoint[1][0][1], tbFlot.ctx.trackUnits, tbFlot.ctx.trackDecimals, true, item.series.percent); + item.datapoint[1][0][1], units, decimals, true, item.series.percent); return divElement.prop('outerHTML'); }; } else { @@ -133,18 +135,19 @@ export default class TbFlot { if (tbFlot.ctx.tooltipIndividual && seriesHoverInfo.index !== seriesIndex) { continue; } + var units = seriesHoverInfo.units && seriesHoverInfo.units.length ? seriesHoverInfo.units : tbFlot.ctx.trackUnits; + var decimals = angular.isDefined(seriesHoverInfo.decimals) ? seriesHoverInfo.decimals : tbFlot.ctx.trackDecimals; var divElement = seriesInfoDiv(seriesHoverInfo.label, seriesHoverInfo.color, - seriesHoverInfo.value, tbFlot.ctx.trackUnits, tbFlot.ctx.trackDecimals, seriesHoverInfo.index === seriesIndex); + seriesHoverInfo.value, units, decimals, seriesHoverInfo.index === seriesIndex); content += divElement.prop('outerHTML'); } return content; }; } - ctx.trackDecimals = angular.isDefined(settings.decimals) ? - settings.decimals : ctx.decimals; + ctx.trackDecimals = ctx.decimals; - ctx.trackUnits = angular.isDefined(settings.units) ? settings.units : ctx.units; + ctx.trackUnits = ctx.units; ctx.tooltipIndividual = this.chartType === 'pie' || (angular.isDefined(settings.tooltipIndividual) ? settings.tooltipIndividual : false); ctx.tooltipCumulative = angular.isDefined(settings.tooltipCumulative) ? settings.tooltipCumulative : false; @@ -178,7 +181,7 @@ export default class TbFlot { font: angular.copy(font), labelFont: angular.copy(font) }; - options.yaxis = { + this.yaxis = { font: angular.copy(font), labelFont: angular.copy(font) }; @@ -194,32 +197,33 @@ export default class TbFlot { options.xaxis.labelFont.size = options.xaxis.font.size+2; options.xaxis.labelFont.weight = "bold"; } - if (settings.yaxis) { + + ctx.yAxisTickFormatter = function(value/*, axis*/) { if (settings.yaxis.showLabels === false) { - options.yaxis.tickFormatter = function() { - return ''; - }; - } else if (ctx.trackUnits && ctx.trackUnits.length > 0) { - options.yaxis.tickFormatter = function(value, axis) { - var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1, - formatted = "" + Math.round(value * factor) / factor; - if (axis.tickDecimals != null) { - var decimal = formatted.indexOf("."), - precision = decimal === -1 ? 0 : formatted.length - decimal - 1; - - if (precision < axis.tickDecimals) { - formatted = (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision); - } - } - formatted += ' ' + tbFlot.ctx.trackUnits; - return formatted; - }; + return ''; + } + var factor = this.tickDecimals ? Math.pow(10, this.tickDecimals) : 1, + formatted = "" + Math.round(value * factor) / factor; + if (this.tickDecimals != null) { + var decimal = formatted.indexOf("."), + precision = decimal === -1 ? 0 : formatted.length - decimal - 1; + + if (precision < this.tickDecimals) { + formatted = (precision ? formatted : formatted + ".") + ("" + factor).substr(1, this.tickDecimals - precision); + } } - options.yaxis.font.color = settings.yaxis.color || options.yaxis.font.color; - options.yaxis.label = settings.yaxis.title || null; - options.yaxis.labelFont.color = options.yaxis.font.color; - options.yaxis.labelFont.size = options.yaxis.font.size+2; - options.yaxis.labelFont.weight = "bold"; + formatted += ' ' + this.tickUnits; + return formatted; + } + + this.yaxis.tickFormatter = ctx.yAxisTickFormatter; + + if (settings.yaxis) { + this.yaxis.font.color = settings.yaxis.color || this.yaxis.font.color; + this.yaxis.label = settings.yaxis.title || null; + this.yaxis.labelFont.color = this.yaxis.font.color; + this.yaxis.labelFont.size = this.yaxis.font.size+2; + this.yaxis.labelFont.weight = "bold"; } options.grid.borderWidth = 1; @@ -235,7 +239,7 @@ export default class TbFlot { options.xaxis.tickLength = 0; } if (settings.grid.horizontalLines === false) { - options.yaxis.tickLength = 0; + this.yaxis.tickLength = 0; } } @@ -317,6 +321,8 @@ export default class TbFlot { this.subscription = subscription; this.$element = $element; var colors = []; + this.yaxes = []; + var yaxesMap = {}; for (var i = 0; i < this.subscription.data.length; i++) { var series = this.subscription.data[i]; colors.push(series.dataKey.color); @@ -348,8 +354,29 @@ export default class TbFlot { series.highlightColor = lineColor.toRgbString(); + if (this.yaxis) { + var units = series.dataKey.units && series.dataKey.units.length ? series.dataKey.units : this.ctx.trackUnits; + var yaxis; + if (keySettings.showSeparateAxis) { + yaxis = this.createYAxis(keySettings, units); + this.yaxes.push(yaxis); + } else { + yaxis = yaxesMap[units]; + if (!yaxis) { + yaxis = this.createYAxis(keySettings, units); + yaxesMap[units] = yaxis; + this.yaxes.push(yaxis); + } + } + series.yaxisIndex = this.yaxes.indexOf(yaxis); + series.yaxis = series.yaxisIndex+1; + yaxis.keysInfo[i] = {hidden: false}; + yaxis.hidden = false; + } } + this.options.colors = colors; + this.options.yaxes = angular.copy(this.yaxes); if (this.chartType === 'line' || this.chartType === 'bar') { if (this.chartType === 'bar') { this.options.series.bars.barWidth = this.subscription.timeWindow.interval * 0.6; @@ -379,6 +406,23 @@ export default class TbFlot { } } + createYAxis(keySettings, units) { + var yaxis = angular.copy(this.yaxis); + + var label = keySettings.axisTitle && keySettings.axisTitle.length ? keySettings.axisTitle : yaxis.label; + var tickDecimals = angular.isDefined(keySettings.axisTickDecimals) ? keySettings.axisTickDecimals : 0; + var position = keySettings.axisPosition && keySettings.axisPosition.length ? keySettings.axisPosition : "left"; + + yaxis.label = label; + yaxis.tickUnits = units; + yaxis.tickDecimals = tickDecimals; + yaxis.alignTicksWithAxis = position == "right" ? 1 : null; + yaxis.position = position; + + yaxis.keysInfo = []; + return yaxis; + } + update() { if (this.updateTimeoutHandle) { this.ctx.$scope.$timeout.cancel(this.updateTimeoutHandle); @@ -387,17 +431,64 @@ export default class TbFlot { if (this.subscription) { if (!this.isMouseInteraction && this.ctx.plot) { if (this.chartType === 'line' || this.chartType === 'bar') { + + var axisVisibilityChanged = false; + if (this.yaxis) { + for (var i = 0; i < this.subscription.data.length; i++) { + var series = this.subscription.data[i]; + var yaxisIndex = series.yaxisIndex; + if (this.yaxes[yaxisIndex].keysInfo[i].hidden != series.dataKey.hidden) { + this.yaxes[yaxisIndex].keysInfo[i].hidden = series.dataKey.hidden; + axisVisibilityChanged = true; + } + } + if (axisVisibilityChanged) { + this.options.yaxes.length = 0; + for (var y = 0; y < this.yaxes.length; y++) { + var yaxis = this.yaxes[y]; + var hidden = true; + for (var k = 0; k < yaxis.keysInfo.length; k++) { + if (yaxis.keysInfo[k]) { + hidden = hidden && yaxis.keysInfo[k].hidden; + } + } + yaxis.hidden = hidden + var newIndex = -1; + if (!yaxis.hidden) { + this.options.yaxes.push(yaxis); + newIndex = this.options.yaxes.length; + } + for (k = 0; k < yaxis.keysInfo.length; k++) { + if (yaxis.keysInfo[k]) { + this.subscription.data[k].yaxis = newIndex; + } + } + + } + this.options.yaxis = { + show: this.options.yaxes.length ? true : false + }; + } + } + this.options.xaxis.min = this.subscription.timeWindow.minTime; this.options.xaxis.max = this.subscription.timeWindow.maxTime; - this.ctx.plot.getOptions().xaxes[0].min = this.subscription.timeWindow.minTime; - this.ctx.plot.getOptions().xaxes[0].max = this.subscription.timeWindow.maxTime; if (this.chartType === 'bar') { this.options.series.bars.barWidth = this.subscription.timeWindow.interval * 0.6; - this.ctx.plot.getOptions().series.bars.barWidth = this.subscription.timeWindow.interval * 0.6; } - this.ctx.plot.setData(this.subscription.data); - this.ctx.plot.setupGrid(); - this.ctx.plot.draw(); + + if (axisVisibilityChanged) { + this.redrawPlot(); + } else { + this.ctx.plot.getOptions().xaxes[0].min = this.subscription.timeWindow.minTime; + this.ctx.plot.getOptions().xaxes[0].max = this.subscription.timeWindow.maxTime; + if (this.chartType === 'bar') { + this.ctx.plot.getOptions().series.bars.barWidth = this.subscription.timeWindow.interval * 0.6; + } + this.ctx.plot.setData(this.subscription.data); + this.ctx.plot.setupGrid(); + this.ctx.plot.draw(); + } } else if (this.chartType === 'pie') { if (this.ctx.animatedPie) { this.nextPieDataAnimation(true); @@ -726,6 +817,26 @@ export default class TbFlot { "title": "Show points", "type": "boolean", "default": false + }, + "showSeparateAxis": { + "title": "Show separate axis", + "type": "boolean", + "default": false + }, + "axisTitle": { + "title": "Axis title", + "type": "string", + "default": "" + }, + "axisTickDecimals": { + "title": "Axis tick number of digits after floating point", + "type": "number", + "default": 0 + }, + "axisPosition": { + "title": "Axis position", + "type": "string", + "default": "left" } }, "required": ["showLines", "fillLines", "showPoints"] @@ -733,7 +844,26 @@ export default class TbFlot { "form": [ "showLines", "fillLines", - "showPoints" + "showPoints", + "showSeparateAxis", + "axisTitle", + "axisTickDecimals", + { + "key": "axisPosition", + "type": "rc-select", + "multiple": false, + "items": [ + { + "value": "left", + "label": "Left" + }, + { + "value": "right", + "label": "Right" + } + ] + } + ] } } @@ -760,6 +890,17 @@ export default class TbFlot { } } + redrawPlot() { + if (this.ctx.plot) { + this.ctx.plot.destroy(); + if (this.chartType === 'pie' && this.ctx.animatedPie) { + this.ctx.plot = $.plot(this.$element, this.pieData, this.options); + } else { + this.ctx.plot = $.plot(this.$element, this.subscription.data, this.options); + } + } + } + destroy() { if (this.ctx.plot) { this.ctx.plot.destroy(); @@ -974,6 +1115,8 @@ export default class TbFlot { hoverIndex: hoverIndex, color: series.dataKey.color, label: series.dataKey.label, + units: series.dataKey.units, + decimals: series.dataKey.decimals, time: pointTime, distance: hoverDistance, index: i diff --git a/ui/src/app/widget/widget-library.controller.js b/ui/src/app/widget/widget-library.controller.js index 69fb85bd07..bda8835fb8 100644 --- a/ui/src/app/widget/widget-library.controller.js +++ b/ui/src/app/widget/widget-library.controller.js @@ -87,7 +87,7 @@ export default function WidgetLibraryController($scope, $rootScope, $q, widgetSe var sizeX = 8; var sizeY = Math.floor(widgetTypeInfo.sizeY); var widget = { - id: widgetType.id, + typeId: widgetType.id, isSystemType: isSystem, bundleAlias: bundleAlias, typeAlias: widgetTypeInfo.alias, @@ -158,7 +158,7 @@ export default function WidgetLibraryController($scope, $rootScope, $q, widgetSe } if (widget) { $state.go('home.widgets-bundles.widget-types.widget-type', - {widgetTypeId: widget.id.id}); + {widgetTypeId: widget.typeId.id}); } else { $mdDialog.show({ controller: 'SelectWidgetTypeController', @@ -177,7 +177,7 @@ export default function WidgetLibraryController($scope, $rootScope, $q, widgetSe function exportWidgetType(event, widget) { event.stopPropagation(); - importExport.exportWidgetType(widget.id.id); + importExport.exportWidgetType(widget.typeId.id); } function importWidgetType($event) { diff --git a/ui/src/scss/constants.scss b/ui/src/scss/constants.scss index 73697fc591..c56d40bc16 100644 --- a/ui/src/scss/constants.scss +++ b/ui/src/scss/constants.scss @@ -20,10 +20,12 @@ $gray: #eee; $primary-palette-color: 'indigo'; +$default: '500'; $hue-1: '300'; $hue-2: '800'; $hue-3: 'a100'; +$primary-default: #305680; //material-color($primary-palette-color, $default); $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); diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss index ab9b0d7d73..c062d4ea83 100644 --- a/ui/src/scss/main.scss +++ b/ui/src/scss/main.scss @@ -261,6 +261,45 @@ pre.tb-highlight { font-size: 16px; } +.tb-data-table { + md-toolbar { + z-index: 0; + } + span.no-data-found { + position: relative; + height: calc(100% - 57px); + text-transform: uppercase; + display: flex; + } + table.md-table { + tbody { + tr { + td { + &.tb-action-cell { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 72px; + max-width: 72px; + width: 72px; + .md-button { + &.md-icon-button { + margin: 0; + padding: 6px; + width: 36px; + height: 36px; + } + } + .tb-spacer { + padding-left: 38px; + } + } + } + } + } + } +} + /*********************** * Flow @@ -280,8 +319,11 @@ $previewSize: 100px; overflow: hidden; label { width: 100%; - font-size: 24px; + font-size: 16px; text-align: center; + @media (min-width: $layout-breakpoint-sm) { + font-size: 24px; + } } } @@ -369,6 +411,19 @@ md-tabs.tb-headless { } } +.md-button.tb-layout-button { + width: 100%; + height: 100%; + max-width: 240px; + span { + padding: 40px; + font-size: 18px; + font-weight: 400; + white-space: normal; + line-height: 18px; + } +} + .md-button.tb-add-new-widget { border-style: dashed; border-width: 2px;