Commit 12667d89eaa523bfe48f1d7ae3749ee4c77cedd8

Authored by ShvaykaD
2 parents 409fb1f2 8fe13a2c

Merge branch 'master' of github.com:thingsboard/thingsboard

Showing 70 changed files with 2960 additions and 161 deletions
... ... @@ -18,7 +18,7 @@
18 18 "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n hasDataPageLink: true,\n warnOnPageDataOverflow: false,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
19 19 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}",
20 20 "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}",
21   - "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"entitiesTitle\":\"Device admin table\",\"enableSelectColumnDisplay\":true},\"title\":\"Device admin table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"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;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"name\":\"Add device\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"<form #addDeviceForm=\\\"ngForm\\\" [formGroup]=\\\"addDeviceFormGroup\\\"\\n (ngSubmit)=\\\"save()\\\" style=\\\"width: 480px;\\\">\\n <mat-toolbar fxLayout=\\\"row\\\" color=\\\"primary\\\">\\n <h2>Add device</h2>\\n <span fxFlex></span>\\n <button mat-button mat-icon-button\\n (click)=\\\"cancel()\\\"\\n type=\\\"button\\\">\\n <mat-icon class=\\\"material-icons\\\">close</mat-icon>\\n </button>\\n </mat-toolbar>\\n <mat-progress-bar color=\\\"warn\\\" mode=\\\"indeterminate\\\" *ngIf=\\\"isLoading$ | async\\\">\\n </mat-progress-bar>\\n <div style=\\\"height: 4px;\\\" *ngIf=\\\"!(isLoading$ | async)\\\"></div>\\n <div mat-dialog-content>\\n <div class=\\\"mat-padding\\\" fxLayout=\\\"column\\\">\\n <mat-form-field class=\\\"mat-block\\\">\\n <mat-label>Device name</mat-label>\\n <input matInput formControlName=\\\"deviceName\\\" required>\\n <mat-error *ngIf=\\\"addDeviceFormGroup.get('deviceName').hasError('required')\\\">\\n Device name is required.\\n </mat-error>\\n </mat-form-field>\\n <div fxFlex fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <tb-entity-subtype-autocomplete\\n fxFlex=\\\"50\\\"\\n formControlName=\\\"deviceType\\\"\\n [required]=\\\"true\\\"\\n [entityType]=\\\"'DEVICE'\\\"\\n ></tb-entity-subtype-autocomplete>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Label</mat-label>\\n <input matInput formControlName=\\\"deviceLabel\\\">\\n </mat-form-field>\\n </div>\\n <div formGroupName=\\\"attributes\\\" fxFlex fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Latitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"latitude\\\">\\n </mat-form-field>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Longitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"longitude\\\">\\n </mat-form-field>\\n </div>\\n </div> \\n </div>\\n <div mat-dialog-actions fxLayout=\\\"row\\\">\\n <span fxFlex></span>\\n <button mat-button mat-raised-button color=\\\"primary\\\"\\n type=\\\"submit\\\"\\n [disabled]=\\\"(isLoading$ | async) || addDeviceForm.invalid || !addDeviceForm.dirty\\\">\\n Create\\n </button>\\n <button mat-button color=\\\"primary\\\"\\n style=\\\"margin-right: 20px;\\\"\\n type=\\\"button\\\"\\n [disabled]=\\\"(isLoading$ | async)\\\"\\n (click)=\\\"cancel()\\\" cdkFocusInitial>\\n Cancel\\n </button>\\n </div>\\n</form>\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenAddDeviceDialog();\\n\\nfunction openAddDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, AddDeviceDialogController).subscribe();\\n}\\n\\nfunction AddDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.addDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.addDeviceFormGroup.markAsPristine();\\n let device = {\\n name: vm.addDeviceFormGroup.get('deviceName').value,\\n type: vm.addDeviceFormGroup.get('deviceType').value,\\n label: vm.addDeviceFormGroup.get('deviceLabel').value\\n };\\n deviceService.saveDevice(device).subscribe(\\n function (device) {\\n saveAttributes(device.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributes = vm.addDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\"}],\"actionCellButton\":[{\"name\":\"Edit device\",\"icon\":\"edit\",\"type\":\"customPretty\",\"customHtml\":\"<form #editDeviceForm=\\\"ngForm\\\" [formGroup]=\\\"editDeviceFormGroup\\\"\\n (ngSubmit)=\\\"save()\\\" style=\\\"width: 480px;\\\">\\n <mat-toolbar fxLayout=\\\"row\\\" color=\\\"primary\\\">\\n <h2>Edit device</h2>\\n <span fxFlex></span>\\n <button mat-button mat-icon-button\\n (click)=\\\"cancel()\\\"\\n type=\\\"button\\\">\\n <mat-icon class=\\\"material-icons\\\">close</mat-icon>\\n </button>\\n </mat-toolbar>\\n <mat-progress-bar color=\\\"warn\\\" mode=\\\"indeterminate\\\" *ngIf=\\\"isLoading$ | async\\\">\\n </mat-progress-bar>\\n <div style=\\\"height: 4px;\\\" *ngIf=\\\"!(isLoading$ | async)\\\"></div>\\n <div mat-dialog-content>\\n <div class=\\\"mat-padding\\\" fxLayout=\\\"column\\\">\\n <mat-form-field class=\\\"mat-block\\\">\\n <mat-label>Device name</mat-label>\\n <input matInput formControlName=\\\"deviceName\\\" required>\\n <mat-error *ngIf=\\\"editDeviceFormGroup.get('deviceName').hasError('required')\\\">\\n Device name is required.\\n </mat-error>\\n </mat-form-field>\\n <div fxFlex fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <tb-entity-subtype-autocomplete\\n fxFlex=\\\"50\\\"\\n formControlName=\\\"deviceType\\\"\\n [required]=\\\"true\\\"\\n [entityType]=\\\"'DEVICE'\\\"\\n ></tb-entity-subtype-autocomplete>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Label</mat-label>\\n <input matInput formControlName=\\\"deviceLabel\\\">\\n </mat-form-field>\\n </div>\\n <div formGroupName=\\\"attributes\\\" fxFlex fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Latitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"latitude\\\">\\n </mat-form-field>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Longitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"longitude\\\">\\n </mat-form-field>\\n </div>\\n </div> \\n </div>\\n <div mat-dialog-actions fxLayout=\\\"row\\\">\\n <span fxFlex></span>\\n <button mat-button mat-raised-button color=\\\"primary\\\"\\n type=\\\"submit\\\"\\n [disabled]=\\\"(isLoading$ | async) || editDeviceForm.invalid || !editDeviceForm.dirty\\\">\\n Update\\n </button>\\n <button mat-button color=\\\"primary\\\"\\n style=\\\"margin-right: 20px;\\\"\\n type=\\\"button\\\"\\n [disabled]=\\\"(isLoading$ | async)\\\"\\n (click)=\\\"cancel()\\\" cdkFocusInitial>\\n Cancel\\n </button>\\n </div>\\n</form>\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenEditDeviceDialog();\\n\\nfunction openEditDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, EditDeviceDialogController).subscribe();\\n}\\n\\nfunction EditDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.device = null;\\n vm.attributes = {};\\n \\n vm.editDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.editDeviceFormGroup.markAsPristine();\\n vm.device.name = vm.editDeviceFormGroup.get('deviceName').value,\\n vm.device.type = vm.editDeviceFormGroup.get('deviceType').value,\\n vm.device.label = vm.editDeviceFormGroup.get('deviceLabel').value\\n deviceService.saveDevice(vm.device).subscribe(\\n function () {\\n saveAttributes().subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n deviceService.getDevice(entityId.id).subscribe(\\n function (device) {\\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE',\\n ['latitude', 'longitude']).subscribe(\\n function (attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n vm.device = device;\\n vm.editDeviceFormGroup.patchValue(\\n {\\n deviceName: vm.device.name,\\n deviceType: vm.device.type,\\n deviceLabel: vm.device.label,\\n attributes: {\\n latitude: vm.attributes.latitude,\\n longitude: vm.attributes.longitude\\n }\\n }, {emitEvent: false}\\n );\\n } \\n );\\n }\\n ); \\n }\\n \\n function saveAttributes() {\\n let attributes = vm.editDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, 'SERVER_SCOPE', attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\"},{\"name\":\"Delete device\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\n\\nopenDeleteDeviceDialog();\\n\\nfunction openDeleteDeviceDialog() {\\n let title = \\\"Are you sure you want to delete the device \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the device and all related data will become unrecoverable!\\\";\\n dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(\\n function (result) {\\n if (result) {\\n deleteDevice();\\n }\\n }\\n );\\n}\\n\\nfunction deleteDevice() {\\n deviceService.deleteDevice(entityId.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n }\\n );\\n}\\n\",\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\"}]}}"
  21 + "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"entitiesTitle\":\"Device admin table\",\"enableSelectColumnDisplay\":true},\"title\":\"Device admin table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"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;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"name\":\"Add device\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"<form #addDeviceForm=\\\"ngForm\\\" [formGroup]=\\\"addDeviceFormGroup\\\"\\n (ngSubmit)=\\\"save()\\\" style=\\\"width: 480px;\\\">\\n <mat-toolbar fxLayout=\\\"row\\\" color=\\\"primary\\\">\\n <h2>Add device</h2>\\n <span fxFlex></span>\\n <button mat-button mat-icon-button\\n (click)=\\\"cancel()\\\"\\n type=\\\"button\\\">\\n <mat-icon class=\\\"material-icons\\\">close</mat-icon>\\n </button>\\n </mat-toolbar>\\n <mat-progress-bar color=\\\"warn\\\" mode=\\\"indeterminate\\\" *ngIf=\\\"isLoading$ | async\\\">\\n </mat-progress-bar>\\n <div style=\\\"height: 4px;\\\" *ngIf=\\\"!(isLoading$ | async)\\\"></div>\\n <div mat-dialog-content>\\n <div class=\\\"mat-padding\\\" fxLayout=\\\"column\\\">\\n <mat-form-field class=\\\"mat-block\\\">\\n <mat-label>Device name</mat-label>\\n <input matInput formControlName=\\\"deviceName\\\" required>\\n <mat-error *ngIf=\\\"addDeviceFormGroup.get('deviceName').hasError('required')\\\">\\n Device name is required.\\n </mat-error>\\n </mat-form-field>\\n <div fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <tb-entity-subtype-autocomplete\\n fxFlex=\\\"50\\\"\\n formControlName=\\\"deviceType\\\"\\n [required]=\\\"true\\\"\\n [entityType]=\\\"'DEVICE'\\\"\\n ></tb-entity-subtype-autocomplete>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Label</mat-label>\\n <input matInput formControlName=\\\"deviceLabel\\\">\\n </mat-form-field>\\n </div>\\n <div formGroupName=\\\"attributes\\\" fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Latitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"latitude\\\">\\n </mat-form-field>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Longitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"longitude\\\">\\n </mat-form-field>\\n </div>\\n </div> \\n </div>\\n <div mat-dialog-actions fxLayout=\\\"row\\\">\\n <span fxFlex></span>\\n <button mat-button color=\\\"primary\\\"\\n type=\\\"button\\\"\\n [disabled]=\\\"(isLoading$ | async)\\\"\\n (click)=\\\"cancel()\\\" cdkFocusInitial>\\n Cancel\\n </button>\\n <button mat-button mat-raised-button color=\\\"primary\\\"\\n style=\\\"margin-right: 20px;\\\"\\n type=\\\"submit\\\"\\n [disabled]=\\\"(isLoading$ | async) || addDeviceForm.invalid || !addDeviceForm.dirty\\\">\\n Create\\n </button>\\n </div>\\n</form>\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenAddDeviceDialog();\\n\\nfunction openAddDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, AddDeviceDialogController).subscribe();\\n}\\n\\nfunction AddDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.addDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.addDeviceFormGroup.markAsPristine();\\n let device = {\\n name: vm.addDeviceFormGroup.get('deviceName').value,\\n type: vm.addDeviceFormGroup.get('deviceType').value,\\n label: vm.addDeviceFormGroup.get('deviceLabel').value\\n };\\n deviceService.saveDevice(device).subscribe(\\n function (device) {\\n saveAttributes(device.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributes = vm.addDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\"}],\"actionCellButton\":[{\"name\":\"Edit device\",\"icon\":\"edit\",\"type\":\"customPretty\",\"customHtml\":\"<form #editDeviceForm=\\\"ngForm\\\" [formGroup]=\\\"editDeviceFormGroup\\\"\\n (ngSubmit)=\\\"save()\\\" style=\\\"width: 480px;\\\">\\n <mat-toolbar fxLayout=\\\"row\\\" color=\\\"primary\\\">\\n <h2>Edit device</h2>\\n <span fxFlex></span>\\n <button mat-button mat-icon-button\\n (click)=\\\"cancel()\\\"\\n type=\\\"button\\\">\\n <mat-icon class=\\\"material-icons\\\">close</mat-icon>\\n </button>\\n </mat-toolbar>\\n <mat-progress-bar color=\\\"warn\\\" mode=\\\"indeterminate\\\" *ngIf=\\\"isLoading$ | async\\\">\\n </mat-progress-bar>\\n <div style=\\\"height: 4px;\\\" *ngIf=\\\"!(isLoading$ | async)\\\"></div>\\n <div mat-dialog-content>\\n <div class=\\\"mat-padding\\\" fxLayout=\\\"column\\\">\\n <mat-form-field class=\\\"mat-block\\\">\\n <mat-label>Device name</mat-label>\\n <input matInput formControlName=\\\"deviceName\\\" required>\\n <mat-error *ngIf=\\\"editDeviceFormGroup.get('deviceName').hasError('required')\\\">\\n Device name is required.\\n </mat-error>\\n </mat-form-field>\\n <div fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <tb-entity-subtype-autocomplete\\n fxFlex=\\\"50\\\"\\n formControlName=\\\"deviceType\\\"\\n [required]=\\\"true\\\"\\n [entityType]=\\\"'DEVICE'\\\"\\n ></tb-entity-subtype-autocomplete>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Label</mat-label>\\n <input matInput formControlName=\\\"deviceLabel\\\">\\n </mat-form-field>\\n </div>\\n <div formGroupName=\\\"attributes\\\" fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Latitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"latitude\\\">\\n </mat-form-field>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Longitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"longitude\\\">\\n </mat-form-field>\\n </div>\\n </div> \\n </div>\\n <div mat-dialog-actions fxLayout=\\\"row\\\">\\n <span fxFlex></span>\\n <button mat-button color=\\\"primary\\\"\\n type=\\\"button\\\"\\n [disabled]=\\\"(isLoading$ | async)\\\"\\n (click)=\\\"cancel()\\\" cdkFocusInitial>\\n Cancel\\n </button>\\n <button mat-button mat-raised-button color=\\\"primary\\\"\\n style=\\\"margin-right: 20px;\\\"\\n type=\\\"submit\\\"\\n [disabled]=\\\"(isLoading$ | async) || editDeviceForm.invalid || !editDeviceForm.dirty\\\">\\n Update\\n </button>\\n </div>\\n</form>\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenEditDeviceDialog();\\n\\nfunction openEditDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, EditDeviceDialogController).subscribe();\\n}\\n\\nfunction EditDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.device = null;\\n vm.attributes = {};\\n \\n vm.editDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.editDeviceFormGroup.markAsPristine();\\n vm.device.name = vm.editDeviceFormGroup.get('deviceName').value,\\n vm.device.type = vm.editDeviceFormGroup.get('deviceType').value,\\n vm.device.label = vm.editDeviceFormGroup.get('deviceLabel').value\\n deviceService.saveDevice(vm.device).subscribe(\\n function () {\\n saveAttributes().subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n deviceService.getDevice(entityId.id).subscribe(\\n function (device) {\\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE',\\n ['latitude', 'longitude']).subscribe(\\n function (attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n vm.device = device;\\n vm.editDeviceFormGroup.patchValue(\\n {\\n deviceName: vm.device.name,\\n deviceType: vm.device.type,\\n deviceLabel: vm.device.label,\\n attributes: {\\n latitude: vm.attributes.latitude,\\n longitude: vm.attributes.longitude\\n }\\n }, {emitEvent: false}\\n );\\n } \\n );\\n }\\n ); \\n }\\n \\n function saveAttributes() {\\n let attributes = vm.editDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, 'SERVER_SCOPE', attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\"},{\"name\":\"Delete device\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\n\\nopenDeleteDeviceDialog();\\n\\nfunction openDeleteDeviceDialog() {\\n let title = \\\"Are you sure you want to delete the device \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the device and all related data will become unrecoverable!\\\";\\n dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(\\n function (result) {\\n if (result) {\\n deleteDevice();\\n }\\n }\\n );\\n}\\n\\nfunction deleteDevice() {\\n deviceService.deleteDevice(entityId.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n }\\n );\\n}\\n\",\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\"}]}}"
22 22 }
23 23 },
24 24 {
... ... @@ -34,8 +34,8 @@
34 34 "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n hasDataPageLink: true,\n warnOnPageDataOverflow: false,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
35 35 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}",
36 36 "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}",
37   - "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"entitiesTitle\":\"Asset admin table\",\"enableSelectColumnDisplay\":true},\"title\":\"Asset admin table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"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;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"name\":\"Add asset\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"<form #addAssetForm=\\\"ngForm\\\" [formGroup]=\\\"addAssetFormGroup\\\"\\n (ngSubmit)=\\\"save()\\\" style=\\\"width: 480px;\\\">\\n <mat-toolbar fxLayout=\\\"row\\\" color=\\\"primary\\\">\\n <h2>Add asset</h2>\\n <span fxFlex></span>\\n <button mat-button mat-icon-button\\n (click)=\\\"cancel()\\\"\\n type=\\\"button\\\">\\n <mat-icon class=\\\"material-icons\\\">close</mat-icon>\\n </button>\\n </mat-toolbar>\\n <mat-progress-bar color=\\\"warn\\\" mode=\\\"indeterminate\\\" *ngIf=\\\"isLoading$ | async\\\">\\n </mat-progress-bar>\\n <div style=\\\"height: 4px;\\\" *ngIf=\\\"!(isLoading$ | async)\\\"></div>\\n <div mat-dialog-content>\\n <div class=\\\"mat-padding\\\" fxLayout=\\\"column\\\">\\n <mat-form-field class=\\\"mat-block\\\">\\n <mat-label>Asset name</mat-label>\\n <input matInput formControlName=\\\"assetName\\\" required>\\n <mat-error *ngIf=\\\"addAssetFormGroup.get('assetName').hasError('required')\\\">\\n Asset name is required.\\n </mat-error>\\n </mat-form-field>\\n <div fxFlex fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <tb-entity-subtype-autocomplete\\n fxFlex=\\\"50\\\"\\n formControlName=\\\"assetType\\\"\\n [required]=\\\"true\\\"\\n [entityType]=\\\"'ASSET'\\\"\\n ></tb-entity-subtype-autocomplete>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Label</mat-label>\\n <input matInput formControlName=\\\"assetLabel\\\">\\n </mat-form-field>\\n </div>\\n <div formGroupName=\\\"attributes\\\" fxFlex fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Latitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"latitude\\\">\\n </mat-form-field>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Longitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"longitude\\\">\\n </mat-form-field>\\n </div>\\n </div> \\n </div>\\n <div mat-dialog-actions fxLayout=\\\"row\\\">\\n <span fxFlex></span>\\n <button mat-button mat-raised-button color=\\\"primary\\\"\\n type=\\\"submit\\\"\\n [disabled]=\\\"(isLoading$ | async) || addAssetForm.invalid || !addAssetForm.dirty\\\">\\n Create\\n </button>\\n <button mat-button color=\\\"primary\\\"\\n style=\\\"margin-right: 20px;\\\"\\n type=\\\"button\\\"\\n [disabled]=\\\"(isLoading$ | async)\\\"\\n (click)=\\\"cancel()\\\" cdkFocusInitial>\\n Cancel\\n </button>\\n </div>\\n</form>\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenAddAssetDialog();\\n\\nfunction openAddAssetDialog() {\\n customDialog.customDialog(htmlTemplate, AddAssetDialogController).subscribe();\\n}\\n\\nfunction AddAssetDialogController(instance) {\\n let vm = instance;\\n \\n vm.addAssetFormGroup = vm.fb.group({\\n assetName: ['', [vm.validators.required]],\\n assetType: ['', [vm.validators.required]],\\n assetLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.addAssetFormGroup.markAsPristine();\\n let asset = {\\n name: vm.addAssetFormGroup.get('assetName').value,\\n type: vm.addAssetFormGroup.get('assetType').value,\\n label: vm.addAssetFormGroup.get('assetLabel').value\\n };\\n assetService.saveAsset(asset).subscribe(\\n function (asset) {\\n saveAttributes(asset.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributes = vm.addAssetFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\"}],\"actionCellButton\":[{\"name\":\"Edit asset\",\"icon\":\"edit\",\"type\":\"customPretty\",\"customHtml\":\"<form #editAssetForm=\\\"ngForm\\\" [formGroup]=\\\"editAssetFormGroup\\\"\\n (ngSubmit)=\\\"save()\\\" style=\\\"width: 480px;\\\">\\n <mat-toolbar fxLayout=\\\"row\\\" color=\\\"primary\\\">\\n <h2>Edit asset</h2>\\n <span fxFlex></span>\\n <button mat-button mat-icon-button\\n (click)=\\\"cancel()\\\"\\n type=\\\"button\\\">\\n <mat-icon class=\\\"material-icons\\\">close</mat-icon>\\n </button>\\n </mat-toolbar>\\n <mat-progress-bar color=\\\"warn\\\" mode=\\\"indeterminate\\\" *ngIf=\\\"isLoading$ | async\\\">\\n </mat-progress-bar>\\n <div style=\\\"height: 4px;\\\" *ngIf=\\\"!(isLoading$ | async)\\\"></div>\\n <div mat-dialog-content>\\n <div class=\\\"mat-padding\\\" fxLayout=\\\"column\\\">\\n <mat-form-field class=\\\"mat-block\\\">\\n <mat-label>Asset name</mat-label>\\n <input matInput formControlName=\\\"assetName\\\" required>\\n <mat-error *ngIf=\\\"editAssetFormGroup.get('assetName').hasError('required')\\\">\\n Asset name is required.\\n </mat-error>\\n </mat-form-field>\\n <div fxFlex fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <tb-entity-subtype-autocomplete\\n fxFlex=\\\"50\\\"\\n formControlName=\\\"assetType\\\"\\n [required]=\\\"true\\\"\\n [entityType]=\\\"'ASSET'\\\"\\n ></tb-entity-subtype-autocomplete>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Label</mat-label>\\n <input matInput formControlName=\\\"assetLabel\\\">\\n </mat-form-field>\\n </div>\\n <div formGroupName=\\\"attributes\\\" fxFlex fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Latitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"latitude\\\">\\n </mat-form-field>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Longitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"longitude\\\">\\n </mat-form-field>\\n </div>\\n </div> \\n </div>\\n <div mat-dialog-actions fxLayout=\\\"row\\\">\\n <span fxFlex></span>\\n <button mat-button mat-raised-button color=\\\"primary\\\"\\n type=\\\"submit\\\"\\n [disabled]=\\\"(isLoading$ | async) || editAssetForm.invalid || !editAssetForm.dirty\\\">\\n Update\\n </button>\\n <button mat-button color=\\\"primary\\\"\\n style=\\\"margin-right: 20px;\\\"\\n type=\\\"button\\\"\\n [disabled]=\\\"(isLoading$ | async)\\\"\\n (click)=\\\"cancel()\\\" cdkFocusInitial>\\n Cancel\\n </button>\\n </div>\\n</form>\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenEditAssetDialog();\\n\\nfunction openEditAssetDialog() {\\n customDialog.customDialog(htmlTemplate, EditAssetDialogController).subscribe();\\n}\\n\\nfunction EditAssetDialogController(instance) {\\n let vm = instance;\\n \\n vm.asset = null;\\n vm.attributes = {};\\n \\n vm.editAssetFormGroup = vm.fb.group({\\n assetName: ['', [vm.validators.required]],\\n assetType: ['', [vm.validators.required]],\\n assetLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.editAssetFormGroup.markAsPristine();\\n vm.asset.name = vm.editAssetFormGroup.get('assetName').value,\\n vm.asset.type = vm.editAssetFormGroup.get('assetType').value,\\n vm.asset.label = vm.editAssetFormGroup.get('assetLabel').value\\n assetService.saveAsset(vm.asset).subscribe(\\n function () {\\n saveAttributes().subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n assetService.getAsset(entityId.id).subscribe(\\n function (asset) {\\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE',\\n ['latitude', 'longitude']).subscribe(\\n function (attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n vm.asset = asset;\\n vm.editAssetFormGroup.patchValue(\\n {\\n assetName: vm.asset.name,\\n assetType: vm.asset.type,\\n assetLabel: vm.asset.label,\\n attributes: {\\n latitude: vm.attributes.latitude,\\n longitude: vm.attributes.longitude\\n }\\n }, {emitEvent: false}\\n );\\n } \\n );\\n }\\n ); \\n }\\n \\n function saveAttributes() {\\n let attributes = vm.editAssetFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, 'SERVER_SCOPE', attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\"},{\"name\":\"Delete asset\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\n\\nopenDeleteAssetDialog();\\n\\nfunction openDeleteAssetDialog() {\\n let title = \\\"Are you sure you want to delete the asset \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the asset and all related data will become unrecoverable!\\\";\\n dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(\\n function (result) {\\n if (result) {\\n deleteAsset();\\n }\\n }\\n );\\n}\\n\\nfunction deleteAsset() {\\n assetService.deleteAsset(entityId.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n }\\n );\\n}\\n\",\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\"}]}}"
  37 + "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"entitiesTitle\":\"Asset admin table\",\"enableSelectColumnDisplay\":true},\"title\":\"Asset admin table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"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;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"name\":\"Add asset\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"<form #addAssetForm=\\\"ngForm\\\" [formGroup]=\\\"addAssetFormGroup\\\"\\n (ngSubmit)=\\\"save()\\\" style=\\\"width: 480px;\\\">\\n <mat-toolbar fxLayout=\\\"row\\\" color=\\\"primary\\\">\\n <h2>Add asset</h2>\\n <span fxFlex></span>\\n <button mat-button mat-icon-button\\n (click)=\\\"cancel()\\\"\\n type=\\\"button\\\">\\n <mat-icon class=\\\"material-icons\\\">close</mat-icon>\\n </button>\\n </mat-toolbar>\\n <mat-progress-bar color=\\\"warn\\\" mode=\\\"indeterminate\\\" *ngIf=\\\"isLoading$ | async\\\">\\n </mat-progress-bar>\\n <div style=\\\"height: 4px;\\\" *ngIf=\\\"!(isLoading$ | async)\\\"></div>\\n <div mat-dialog-content>\\n <div class=\\\"mat-padding\\\" fxLayout=\\\"column\\\">\\n <mat-form-field class=\\\"mat-block\\\">\\n <mat-label>Asset name</mat-label>\\n <input matInput formControlName=\\\"assetName\\\" required>\\n <mat-error *ngIf=\\\"addAssetFormGroup.get('assetName').hasError('required')\\\">\\n Asset name is required.\\n </mat-error>\\n </mat-form-field>\\n <div fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <tb-entity-subtype-autocomplete\\n fxFlex=\\\"50\\\"\\n formControlName=\\\"assetType\\\"\\n [required]=\\\"true\\\"\\n [entityType]=\\\"'ASSET'\\\"\\n ></tb-entity-subtype-autocomplete>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Label</mat-label>\\n <input matInput formControlName=\\\"assetLabel\\\">\\n </mat-form-field>\\n </div>\\n <div formGroupName=\\\"attributes\\\" fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Latitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"latitude\\\">\\n </mat-form-field>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Longitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"longitude\\\">\\n </mat-form-field>\\n </div>\\n </div> \\n </div>\\n <div mat-dialog-actions fxLayout=\\\"row\\\">\\n <span fxFlex></span>\\n <button mat-button color=\\\"primary\\\"\\n type=\\\"button\\\"\\n [disabled]=\\\"(isLoading$ | async)\\\"\\n (click)=\\\"cancel()\\\" cdkFocusInitial>\\n Cancel\\n </button>\\n <button mat-button mat-raised-button color=\\\"primary\\\"\\n style=\\\"margin-right: 20px;\\\"\\n type=\\\"submit\\\"\\n [disabled]=\\\"(isLoading$ | async) || addAssetForm.invalid || !addAssetForm.dirty\\\">\\n Create\\n </button>\\n </div>\\n</form>\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenAddAssetDialog();\\n\\nfunction openAddAssetDialog() {\\n customDialog.customDialog(htmlTemplate, AddAssetDialogController).subscribe();\\n}\\n\\nfunction AddAssetDialogController(instance) {\\n let vm = instance;\\n \\n vm.addAssetFormGroup = vm.fb.group({\\n assetName: ['', [vm.validators.required]],\\n assetType: ['', [vm.validators.required]],\\n assetLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.addAssetFormGroup.markAsPristine();\\n let asset = {\\n name: vm.addAssetFormGroup.get('assetName').value,\\n type: vm.addAssetFormGroup.get('assetType').value,\\n label: vm.addAssetFormGroup.get('assetLabel').value\\n };\\n assetService.saveAsset(asset).subscribe(\\n function (asset) {\\n saveAttributes(asset.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributes = vm.addAssetFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\"}],\"actionCellButton\":[{\"name\":\"Edit asset\",\"icon\":\"edit\",\"type\":\"customPretty\",\"customHtml\":\"<form #editAssetForm=\\\"ngForm\\\" [formGroup]=\\\"editAssetFormGroup\\\"\\n (ngSubmit)=\\\"save()\\\" style=\\\"width: 480px;\\\">\\n <mat-toolbar fxLayout=\\\"row\\\" color=\\\"primary\\\">\\n <h2>Edit asset</h2>\\n <span fxFlex></span>\\n <button mat-button mat-icon-button\\n (click)=\\\"cancel()\\\"\\n type=\\\"button\\\">\\n <mat-icon class=\\\"material-icons\\\">close</mat-icon>\\n </button>\\n </mat-toolbar>\\n <mat-progress-bar color=\\\"warn\\\" mode=\\\"indeterminate\\\" *ngIf=\\\"isLoading$ | async\\\">\\n </mat-progress-bar>\\n <div style=\\\"height: 4px;\\\" *ngIf=\\\"!(isLoading$ | async)\\\"></div>\\n <div mat-dialog-content>\\n <div class=\\\"mat-padding\\\" fxLayout=\\\"column\\\">\\n <mat-form-field class=\\\"mat-block\\\">\\n <mat-label>Asset name</mat-label>\\n <input matInput formControlName=\\\"assetName\\\" required>\\n <mat-error *ngIf=\\\"editAssetFormGroup.get('assetName').hasError('required')\\\">\\n Asset name is required.\\n </mat-error>\\n </mat-form-field>\\n <div fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <tb-entity-subtype-autocomplete\\n fxFlex=\\\"50\\\"\\n formControlName=\\\"assetType\\\"\\n [required]=\\\"true\\\"\\n [entityType]=\\\"'ASSET'\\\"\\n ></tb-entity-subtype-autocomplete>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Label</mat-label>\\n <input matInput formControlName=\\\"assetLabel\\\">\\n </mat-form-field>\\n </div>\\n <div formGroupName=\\\"attributes\\\" fxLayout=\\\"row\\\" fxLayoutGap=\\\"8px\\\">\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Latitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"latitude\\\">\\n </mat-form-field>\\n <mat-form-field fxFlex=\\\"50\\\" class=\\\"mat-block\\\">\\n <mat-label>Longitude</mat-label>\\n <input type=\\\"number\\\" step=\\\"any\\\" matInput formControlName=\\\"longitude\\\">\\n </mat-form-field>\\n </div>\\n </div> \\n </div>\\n <div mat-dialog-actions fxLayout=\\\"row\\\">\\n <span fxFlex></span>\\n <button mat-button color=\\\"primary\\\"\\n type=\\\"button\\\"\\n [disabled]=\\\"(isLoading$ | async)\\\"\\n (click)=\\\"cancel()\\\" cdkFocusInitial>\\n Cancel\\n </button>\\n <button mat-button mat-raised-button color=\\\"primary\\\"\\n type=\\\"submit\\\"\\n style=\\\"margin-right: 20px;\\\"\\n [disabled]=\\\"(isLoading$ | async) || editAssetForm.invalid || !editAssetForm.dirty\\\">\\n Update\\n </button>\\n </div>\\n</form>\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenEditAssetDialog();\\n\\nfunction openEditAssetDialog() {\\n customDialog.customDialog(htmlTemplate, EditAssetDialogController).subscribe();\\n}\\n\\nfunction EditAssetDialogController(instance) {\\n let vm = instance;\\n \\n vm.asset = null;\\n vm.attributes = {};\\n \\n vm.editAssetFormGroup = vm.fb.group({\\n assetName: ['', [vm.validators.required]],\\n assetType: ['', [vm.validators.required]],\\n assetLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.editAssetFormGroup.markAsPristine();\\n vm.asset.name = vm.editAssetFormGroup.get('assetName').value,\\n vm.asset.type = vm.editAssetFormGroup.get('assetType').value,\\n vm.asset.label = vm.editAssetFormGroup.get('assetLabel').value\\n assetService.saveAsset(vm.asset).subscribe(\\n function () {\\n saveAttributes().subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n assetService.getAsset(entityId.id).subscribe(\\n function (asset) {\\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE',\\n ['latitude', 'longitude']).subscribe(\\n function (attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n vm.asset = asset;\\n vm.editAssetFormGroup.patchValue(\\n {\\n assetName: vm.asset.name,\\n assetType: vm.asset.type,\\n assetLabel: vm.asset.label,\\n attributes: {\\n latitude: vm.attributes.latitude,\\n longitude: vm.attributes.longitude\\n }\\n }, {emitEvent: false}\\n );\\n } \\n );\\n }\\n ); \\n }\\n \\n function saveAttributes() {\\n let attributes = vm.editAssetFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, 'SERVER_SCOPE', attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\"},{\"name\":\"Delete asset\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));\\nlet assetService = $injector.get(widgetContext.servicesMap.get('assetService'));\\n\\nopenDeleteAssetDialog();\\n\\nfunction openDeleteAssetDialog() {\\n let title = \\\"Are you sure you want to delete the asset \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the asset and all related data will become unrecoverable!\\\";\\n dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(\\n function (result) {\\n if (result) {\\n deleteAsset();\\n }\\n }\\n );\\n}\\n\\nfunction deleteAsset() {\\n assetService.deleteAsset(entityId.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n }\\n );\\n}\\n\",\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\"}]}}"
38 38 }
39 39 }
40 40 ]
41   -}
\ No newline at end of file
  41 +}
... ...
... ... @@ -244,8 +244,18 @@ class DefaultTbContext implements TbContext {
244 244 if (nodeCtx.getSelf().isDebugMode()) {
245 245 mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, TbRelationTypes.FAILURE, th);
246 246 }
  247 + String failureMessage;
  248 + if (th != null) {
  249 + if (!StringUtils.isEmpty(th.getMessage())) {
  250 + failureMessage = th.getMessage();
  251 + } else {
  252 + failureMessage = th.getClass().getSimpleName();
  253 + }
  254 + } else {
  255 + failureMessage = null;
  256 + }
247 257 nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), Collections.singleton(TbRelationTypes.FAILURE),
248   - msg, th != null ? th.getMessage() : null));
  258 + msg, failureMessage));
249 259 }
250 260
251 261 public void updateSelf(RuleNode self) {
... ...
... ... @@ -56,6 +56,7 @@ import java.util.HashMap;
56 56 import java.util.List;
57 57 import java.util.Map;
58 58 import java.util.Set;
  59 +import java.util.UUID;
59 60 import java.util.stream.Collectors;
60 61
61 62 /**
... ... @@ -288,10 +289,10 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
288 289 private void putToQueue(TopicPartitionInfo tpi, TbMsg msg, TbQueueCallback callbackWrapper, EntityId target) {
289 290 switch (target.getEntityType()) {
290 291 case RULE_NODE:
291   - putToQueue(tpi, msg.copyWithRuleNodeId(entityId, new RuleNodeId(target.getId())), callbackWrapper);
  292 + putToQueue(tpi, msg.copyWithRuleNodeId(entityId, new RuleNodeId(target.getId()), UUID.randomUUID()), callbackWrapper);
292 293 break;
293 294 case RULE_CHAIN:
294   - putToQueue(tpi, msg.copyWithRuleChainId(new RuleChainId(target.getId())), callbackWrapper);
  295 + putToQueue(tpi, msg.copyWithRuleChainId(new RuleChainId(target.getId()), UUID.randomUUID()), callbackWrapper);
295 296 break;
296 297 }
297 298 }
... ...
... ... @@ -16,6 +16,8 @@
16 16 package org.thingsboard.server.controller;
17 17
18 18 import lombok.extern.slf4j.Slf4j;
  19 +import org.apache.commons.lang3.StringUtils;
  20 +import org.springframework.beans.factory.annotation.Autowired;
19 21 import org.springframework.http.HttpStatus;
20 22 import org.springframework.security.access.prepost.PreAuthorize;
21 23 import org.springframework.web.bind.annotation.PathVariable;
... ... @@ -35,21 +37,30 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
35 37 import org.thingsboard.server.common.data.page.PageData;
36 38 import org.thingsboard.server.common.data.page.PageLink;
37 39 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
  40 +import org.thingsboard.server.dao.timeseries.TimeseriesService;
38 41 import org.thingsboard.server.queue.util.TbCoreComponent;
39 42 import org.thingsboard.server.service.security.permission.Operation;
40 43 import org.thingsboard.server.service.security.permission.Resource;
41 44
  45 +import java.util.List;
  46 +import java.util.UUID;
  47 +
42 48 @RestController
43 49 @TbCoreComponent
44 50 @RequestMapping("/api")
45 51 @Slf4j
46 52 public class DeviceProfileController extends BaseController {
47 53
  54 + private static final String DEVICE_PROFILE_ID = "deviceProfileId";
  55 +
  56 + @Autowired
  57 + private TimeseriesService timeseriesService;
  58 +
48 59 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
49 60 @RequestMapping(value = "/deviceProfile/{deviceProfileId}", method = RequestMethod.GET)
50 61 @ResponseBody
51   - public DeviceProfile getDeviceProfileById(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException {
52   - checkParameter("deviceProfileId", strDeviceProfileId);
  62 + public DeviceProfile getDeviceProfileById(@PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException {
  63 + checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId);
53 64 try {
54 65 DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
55 66 return checkDeviceProfileId(deviceProfileId, Operation.READ);
... ... @@ -61,8 +72,8 @@ public class DeviceProfileController extends BaseController {
61 72 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
62 73 @RequestMapping(value = "/deviceProfileInfo/{deviceProfileId}", method = RequestMethod.GET)
63 74 @ResponseBody
64   - public DeviceProfileInfo getDeviceProfileInfoById(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException {
65   - checkParameter("deviceProfileId", strDeviceProfileId);
  75 + public DeviceProfileInfo getDeviceProfileInfoById(@PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException {
  76 + checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId);
66 77 try {
67 78 DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
68 79 return checkNotNull(deviceProfileService.findDeviceProfileInfoById(getTenantId(), deviceProfileId));
... ... @@ -82,6 +93,46 @@ public class DeviceProfileController extends BaseController {
82 93 }
83 94 }
84 95
  96 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  97 + @RequestMapping(value = "/deviceProfile/devices/keys/timeseries", method = RequestMethod.GET)
  98 + @ResponseBody
  99 + public List<String> getTimeseriesKeys(
  100 + @RequestParam(name = DEVICE_PROFILE_ID, required = false) String deviceProfileIdStr) throws ThingsboardException {
  101 + DeviceProfileId deviceProfileId;
  102 + if (StringUtils.isNotEmpty(deviceProfileIdStr)) {
  103 + deviceProfileId = new DeviceProfileId(UUID.fromString(deviceProfileIdStr));
  104 + checkDeviceProfileId(deviceProfileId, Operation.READ);
  105 + } else {
  106 + deviceProfileId = null;
  107 + }
  108 +
  109 + try {
  110 + return timeseriesService.findAllKeysByDeviceProfileId(getTenantId(), deviceProfileId);
  111 + } catch (Exception e) {
  112 + throw handleException(e);
  113 + }
  114 + }
  115 +
  116 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  117 + @RequestMapping(value = "/deviceProfile/devices/keys/attributes", method = RequestMethod.GET)
  118 + @ResponseBody
  119 + public List<String> getAttributesKeys(
  120 + @RequestParam(name = DEVICE_PROFILE_ID, required = false) String deviceProfileIdStr) throws ThingsboardException {
  121 + DeviceProfileId deviceProfileId;
  122 + if (StringUtils.isNotEmpty(deviceProfileIdStr)) {
  123 + deviceProfileId = new DeviceProfileId(UUID.fromString(deviceProfileIdStr));
  124 + checkDeviceProfileId(deviceProfileId, Operation.READ);
  125 + } else {
  126 + deviceProfileId = null;
  127 + }
  128 +
  129 + try {
  130 + return attributesService.findAllKeysByDeviceProfileId(getTenantId(), deviceProfileId);
  131 + } catch (Exception e) {
  132 + throw handleException(e);
  133 + }
  134 + }
  135 +
85 136 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
86 137 @RequestMapping(value = "/deviceProfile", method = RequestMethod.POST)
87 138 @ResponseBody
... ... @@ -113,8 +164,8 @@ public class DeviceProfileController extends BaseController {
113 164 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
114 165 @RequestMapping(value = "/deviceProfile/{deviceProfileId}", method = RequestMethod.DELETE)
115 166 @ResponseStatus(value = HttpStatus.OK)
116   - public void deleteDeviceProfile(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException {
117   - checkParameter("deviceProfileId", strDeviceProfileId);
  167 + public void deleteDeviceProfile(@PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException {
  168 + checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId);
118 169 try {
119 170 DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
120 171 DeviceProfile deviceProfile = checkDeviceProfileId(deviceProfileId, Operation.DELETE);
... ... @@ -139,8 +190,8 @@ public class DeviceProfileController extends BaseController {
139 190 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
140 191 @RequestMapping(value = "/deviceProfile/{deviceProfileId}/default", method = RequestMethod.POST)
141 192 @ResponseBody
142   - public DeviceProfile setDefaultDeviceProfile(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException {
143   - checkParameter("deviceProfileId", strDeviceProfileId);
  193 + public DeviceProfile setDefaultDeviceProfile(@PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException {
  194 + checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId);
144 195 try {
145 196 DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
146 197 DeviceProfile deviceProfile = checkDeviceProfileId(deviceProfileId, Operation.WRITE);
... ...
... ... @@ -186,6 +186,10 @@ public class ThingsboardInstallService {
186 186 systemDataLoaderService.updateSystemWidgets();
187 187 systemDataLoaderService.createOAuth2Templates();
188 188 break;
  189 + case "3.2.0":
  190 + log.info("Upgrading ThingsBoard from version 3.2.0 to 3.2.1 ...");
  191 + databaseEntitiesUpgradeService.upgradeDatabase("3.2.0");
  192 + break;
189 193 default:
190 194 throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion);
191 195
... ...
... ... @@ -181,7 +181,7 @@ public class TenantApiUsageState {
181 181 long threshold = getProfileThreshold(recordKey);
182 182 long warnThreshold = getProfileWarnThreshold(recordKey);
183 183 ApiUsageStateValue tmpValue;
184   - if (threshold == 0 || value < warnThreshold) {
  184 + if (threshold == 0 || value == 0 || value < warnThreshold) {
185 185 tmpValue = ApiUsageStateValue.ENABLED;
186 186 } else if (value < threshold) {
187 187 tmpValue = ApiUsageStateValue.WARNING;
... ...
... ... @@ -421,6 +421,19 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
421 421 log.error("Failed updating schema!!!", e);
422 422 }
423 423 break;
  424 + case "3.2.0":
  425 + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
  426 + log.info("Updating schema ...");
  427 + try {
  428 + conn.createStatement().execute("CREATE INDEX IF NOT EXISTS idx_device_device_profile_id ON device(tenant_id, device_profile_id);");
  429 + conn.createStatement().execute("ALTER TABLE dashboard ALTER COLUMN configuration TYPE varchar;");
  430 + conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3002001;");
  431 + } catch (Exception e) {
  432 + log.error("Failed updating schema!!!", e);
  433 + }
  434 + log.info("Schema updated.");
  435 + }
  436 + break;
424 437 default:
425 438 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
426 439 }
... ...
... ... @@ -20,6 +20,7 @@ import com.amazonaws.auth.AWSStaticCredentialsProvider;
20 20 import com.amazonaws.auth.BasicAWSCredentials;
21 21 import com.amazonaws.services.sns.AmazonSNS;
22 22 import com.amazonaws.services.sns.AmazonSNSClient;
  23 +import com.amazonaws.services.sns.model.MessageAttributeValue;
23 24 import com.amazonaws.services.sns.model.PublishRequest;
24 25 import lombok.extern.slf4j.Slf4j;
25 26 import org.apache.commons.lang3.StringUtils;
... ... @@ -28,9 +29,20 @@ import org.thingsboard.rule.engine.api.sms.exception.SmsException;
28 29 import org.thingsboard.rule.engine.api.sms.exception.SmsSendException;
29 30 import org.thingsboard.server.service.sms.AbstractSmsSender;
30 31
  32 +import java.util.HashMap;
  33 +import java.util.Map;
  34 +
31 35 @Slf4j
32 36 public class AwsSmsSender extends AbstractSmsSender {
33 37
  38 + private static final Map<String, MessageAttributeValue> SMS_ATTRIBUTES = new HashMap<>();
  39 +
  40 + static {
  41 + SMS_ATTRIBUTES.put("AWS.SNS.SMS.SMSType", new MessageAttributeValue()
  42 + .withStringValue("Transactional")
  43 + .withDataType("String"));
  44 + }
  45 +
34 46 private AmazonSNS snsClient;
35 47
36 48 public AwsSmsSender(AwsSnsSmsProviderConfiguration config) {
... ... @@ -51,6 +63,7 @@ public class AwsSmsSender extends AbstractSmsSender {
51 63 message = this.prepareMessage(message);
52 64 try {
53 65 PublishRequest publishRequest = new PublishRequest()
  66 + .withMessageAttributes(SMS_ATTRIBUTES)
54 67 .withPhoneNumber(numberTo)
55 68 .withMessage(message);
56 69 this.snsClient.publish(publishRequest);
... ...
... ... @@ -64,9 +64,9 @@ server:
64 64 # Minimum value of the server side RPC timeout. May override value provided in the REST API call.
65 65 # Since 2.5 migration to queues, the RPC delay depends on the size of the pending messages in the queue,
66 66 # so default UI parameter of 500ms may not be sufficient for loaded environments.
67   - min_timeout: "${MIN_SERVER_SIDE_RPC_TIMEOUT:5000}"
  67 + min_timeout: "${MIN_SERVER_SIDE_RPC_TIMEOUT:5000}"
68 68 # Default value of the server side RPC timeout.
69   - default_timeout: "${DEFAULT_SERVER_SIDE_RPC_TIMEOUT:10000}"
  69 + default_timeout: "${DEFAULT_SERVER_SIDE_RPC_TIMEOUT:10000}"
70 70
71 71 # Zookeeper connection parameters. Used for service discovery.
72 72 zk:
... ... @@ -522,7 +522,7 @@ transport:
522 522 # Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check)
523 523 max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}"
524 524 client_side_rpc:
525   - timeout: "${CLIENT_SIDE_RPC_TIMEOUT:60000}"
  525 + timeout: "${CLIENT_SIDE_RPC_TIMEOUT:60000}"
526 526 # Enable/disable http/mqtt/coap transport protocols (has higher priority than certain protocol's 'enabled' property)
527 527 api_enabled: "${TB_TRANSPORT_API_ENABLED:true}"
528 528 # Local HTTP transport parameters
... ... @@ -595,7 +595,7 @@ queue:
595 595 linger.ms: "${TB_KAFKA_LINGER_MS:1}"
596 596 buffer.memory: "${TB_BUFFER_MEMORY:33554432}"
597 597 replication_factor: "${TB_QUEUE_KAFKA_REPLICATION_FACTOR:1}"
598   - max_poll_interval_ms: "${TB_QUEUE_KAFKA_MAX_POLL_INTERVAL_MS:0}"
  598 + max_poll_interval_ms: "${TB_QUEUE_KAFKA_MAX_POLL_INTERVAL_MS:300000}"
599 599 max_poll_records: "${TB_QUEUE_KAFKA_MAX_POLL_RECORDS:8192}"
600 600 max_partition_fetch_bytes: "${TB_QUEUE_KAFKA_MAX_PARTITION_FETCH_BYTES:16777216}"
601 601 fetch_max_bytes: "${TB_QUEUE_KAFKA_FETCH_MAX_BYTES:134217728}"
... ... @@ -695,8 +695,6 @@ queue:
695 695 max_requests_timeout: "${REMOTE_JS_MAX_REQUEST_TIMEOUT:10000}"
696 696 # JS response poll interval
697 697 response_poll_interval: "${REMOTE_JS_RESPONSE_POLL_INTERVAL_MS:25}"
698   - # JS response auto commit interval
699   - response_auto_commit_interval: "${REMOTE_JS_RESPONSE_AUTO_COMMIT_INTERVAL_MS:100}"
700 698 rule-engine:
701 699 topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}"
702 700 poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}"
... ...
... ... @@ -16,6 +16,7 @@
16 16 package org.thingsboard.server.dao.attributes;
17 17
18 18 import com.google.common.util.concurrent.ListenableFuture;
  19 +import org.thingsboard.server.common.data.id.DeviceProfileId;
19 20 import org.thingsboard.server.common.data.id.EntityId;
20 21 import org.thingsboard.server.common.data.id.TenantId;
21 22 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
... ... @@ -38,4 +39,7 @@ public interface AttributesService {
38 39 ListenableFuture<List<Void>> save(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes);
39 40
40 41 ListenableFuture<List<Void>> removeAll(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys);
  42 +
  43 + List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId);
  44 +
41 45 }
... ...
... ... @@ -16,6 +16,7 @@
16 16 package org.thingsboard.server.dao.timeseries;
17 17
18 18 import com.google.common.util.concurrent.ListenableFuture;
  19 +import org.thingsboard.server.common.data.id.DeviceProfileId;
19 20 import org.thingsboard.server.common.data.id.EntityId;
20 21 import org.thingsboard.server.common.data.id.TenantId;
21 22 import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
... ... @@ -47,4 +48,6 @@ public interface TimeseriesService {
47 48 ListenableFuture<List<Void>> removeLatest(TenantId tenantId, EntityId entityId, Collection<String> keys);
48 49
49 50 ListenableFuture<Collection<String>> removeAllLatest(TenantId tenantId, EntityId entityId);
  51 +
  52 + List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId);
50 53 }
... ...
... ... @@ -195,11 +195,15 @@ public final class TbMsg implements Serializable {
195 195 }
196 196
197 197 public TbMsg copyWithRuleChainId(RuleChainId ruleChainId) {
198   - return new TbMsg(this.queueName, this.id, this.ts, this.type, this.originator, this.metaData, this.dataType, this.data, ruleChainId, null, this.ruleNodeExecCounter.get(), callback);
  198 + return copyWithRuleChainId(ruleChainId, this.id);
199 199 }
200 200
201   - public TbMsg copyWithRuleNodeId(RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
202   - return new TbMsg(this.queueName, this.id, this.ts, this.type, this.originator, this.metaData, this.dataType, this.data, ruleChainId, ruleNodeId, this.ruleNodeExecCounter.get(), callback);
  201 + public TbMsg copyWithRuleChainId(RuleChainId ruleChainId, UUID msgId) {
  202 + return new TbMsg(this.queueName, msgId, this.ts, this.type, this.originator, this.metaData, this.dataType, this.data, ruleChainId, null, this.ruleNodeExecCounter.get(), callback);
  203 + }
  204 +
  205 + public TbMsg copyWithRuleNodeId(RuleChainId ruleChainId, RuleNodeId ruleNodeId, UUID msgId) {
  206 + return new TbMsg(this.queueName, msgId, this.ts, this.type, this.originator, this.metaData, this.dataType, this.data, ruleChainId, ruleNodeId, this.ruleNodeExecCounter.get(), callback);
203 207 }
204 208
205 209 public TbMsgCallback getCallback() {
... ...
... ... @@ -42,7 +42,7 @@ public class TbKafkaAdmin implements TbQueueAdmin {
42 42 private final short replicationFactor;
43 43
44 44 public TbKafkaAdmin(TbKafkaSettings settings, Map<String, String> topicConfigs) {
45   - client = AdminClient.create(settings.toProps());
  45 + client = AdminClient.create(settings.toAdminProps());
46 46 this.topicConfigs = topicConfigs;
47 47
48 48 try {
... ...
... ... @@ -45,24 +45,14 @@ public class TbKafkaConsumerTemplate<T extends TbQueueMsg> extends AbstractTbQue
45 45 @Builder
46 46 private TbKafkaConsumerTemplate(TbKafkaSettings settings, TbKafkaDecoder<T> decoder,
47 47 String clientId, String groupId, String topic,
48   - boolean autoCommit, int autoCommitIntervalMs,
49 48 TbQueueAdmin admin) {
50 49 super(topic);
51   - Properties props = settings.toProps();
  50 + Properties props = settings.toConsumerProps();
52 51 props.put(ConsumerConfig.CLIENT_ID_CONFIG, clientId);
53 52 if (groupId != null) {
54 53 props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
55 54 }
56   - if (settings.getMaxPollIntervalMs() > 0) {
57   - props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, settings.getMaxPollIntervalMs());
58   - }
59   - props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, settings.getMaxPollRecords());
60   - props.put(ConsumerConfig.MAX_PARTITION_FETCH_BYTES_CONFIG, settings.getMaxPartitionFetchBytes());
61   - props.put(ConsumerConfig.FETCH_MAX_BYTES_CONFIG, settings.getFetchMaxBytes());
62   - props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, autoCommit);
63   - props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, autoCommitIntervalMs);
64   - props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
65   - props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.ByteArrayDeserializer");
  55 +
66 56 this.admin = admin;
67 57 this.consumer = new KafkaConsumer<>(props);
68 58 this.decoder = decoder;
... ...
... ... @@ -55,9 +55,8 @@ public class TbKafkaProducerTemplate<T extends TbQueueMsg> implements TbQueuePro
55 55
56 56 @Builder
57 57 private TbKafkaProducerTemplate(TbKafkaSettings settings, String defaultTopic, String clientId, TbQueueAdmin admin) {
58   - Properties props = settings.toProps();
59   - props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
60   - props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.ByteArraySerializer");
  58 + Properties props = settings.toProducerProps();
  59 +
61 60 if (!StringUtils.isEmpty(clientId)) {
62 61 props.put(ProducerConfig.CLIENT_ID_CONFIG, clientId);
63 62 }
... ...
... ... @@ -19,9 +19,11 @@ import lombok.Getter;
19 19 import lombok.Setter;
20 20 import lombok.extern.slf4j.Slf4j;
21 21 import org.apache.kafka.clients.CommonClientConfigs;
  22 +import org.apache.kafka.clients.admin.AdminClientConfig;
  23 +import org.apache.kafka.clients.consumer.ConsumerConfig;
22 24 import org.apache.kafka.clients.producer.ProducerConfig;
23 25 import org.springframework.beans.factory.annotation.Value;
24   -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  26 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
25 27 import org.springframework.boot.context.properties.ConfigurationProperties;
26 28 import org.springframework.stereotype.Component;
27 29
... ... @@ -32,7 +34,7 @@ import java.util.Properties;
32 34 * Created by ashvayka on 25.09.18.
33 35 */
34 36 @Slf4j
35   -@ConditionalOnExpression("'${queue.type:null}'=='kafka'")
  37 +@ConditionalOnProperty(prefix = "queue", value = "type", havingValue = "kafka")
36 38 @ConfigurationProperties(prefix = "queue.kafka")
37 39 @Component
38 40 public class TbKafkaSettings {
... ... @@ -60,19 +62,15 @@ public class TbKafkaSettings {
60 62 private short replicationFactor;
61 63
62 64 @Value("${queue.kafka.max_poll_records:8192}")
63   - @Getter
64 65 private int maxPollRecords;
65 66
66   - @Value("${queue.kafka.max_poll_interval_ms:0}")
67   - @Getter
  67 + @Value("${queue.kafka.max_poll_interval_ms:300000}")
68 68 private int maxPollIntervalMs;
69 69
70 70 @Value("${queue.kafka.max_partition_fetch_bytes:16777216}")
71   - @Getter
72 71 private int maxPartitionFetchBytes;
73 72
74 73 @Value("${queue.kafka.fetch_max_bytes:134217728}")
75   - @Getter
76 74 private int fetchMaxBytes;
77 75
78 76 @Value("${queue.kafka.use_confluent_cloud:false}")
... ... @@ -93,21 +91,48 @@ public class TbKafkaSettings {
93 91 @Setter
94 92 private List<TbKafkaProperty> other;
95 93
96   - public Properties toProps() {
97   - Properties props = new Properties();
  94 + public Properties toAdminProps() {
  95 + Properties props = toProps();
  96 + props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
  97 + props.put(AdminClientConfig.RETRIES_CONFIG, retries);
  98 +
  99 + return props;
  100 + }
  101 +
  102 + public Properties toConsumerProps() {
  103 + Properties props = toProps();
  104 + props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
  105 + props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, maxPollRecords);
  106 + props.put(ConsumerConfig.MAX_PARTITION_FETCH_BYTES_CONFIG, maxPartitionFetchBytes);
  107 + props.put(ConsumerConfig.FETCH_MAX_BYTES_CONFIG, fetchMaxBytes);
  108 + props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, maxPollIntervalMs);
  109 +
  110 + props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
  111 + props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.ByteArrayDeserializer");
  112 + return props;
  113 + }
  114 +
  115 + public Properties toProducerProps() {
  116 + Properties props = toProps();
98 117 props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
99 118 props.put(ProducerConfig.RETRIES_CONFIG, retries);
  119 + props.put(ProducerConfig.ACKS_CONFIG, acks);
  120 + props.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize);
  121 + props.put(ProducerConfig.LINGER_MS_CONFIG, lingerMs);
  122 + props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, bufferMemory);
  123 + props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
  124 + props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.ByteArraySerializer");
  125 + return props;
  126 + }
  127 +
  128 + private Properties toProps() {
  129 + Properties props = new Properties();
100 130
101 131 if (useConfluent) {
102 132 props.put("ssl.endpoint.identification.algorithm", sslAlgorithm);
103 133 props.put("sasl.mechanism", saslMechanism);
104 134 props.put("sasl.jaas.config", saslConfig);
105 135 props.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, securityProtocol);
106   - } else {
107   - props.put(ProducerConfig.ACKS_CONFIG, acks);
108   - props.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize);
109   - props.put(ProducerConfig.LINGER_MS_CONFIG, lingerMs);
110   - props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, bufferMemory);
111 136 }
112 137
113 138 if (other != null) {
... ...
... ... @@ -17,7 +17,7 @@ package org.thingsboard.server.queue.kafka;
17 17
18 18 import lombok.Getter;
19 19 import org.springframework.beans.factory.annotation.Value;
20   -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  20 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
21 21 import org.springframework.stereotype.Component;
22 22
23 23 import javax.annotation.PostConstruct;
... ... @@ -25,7 +25,7 @@ import java.util.HashMap;
25 25 import java.util.Map;
26 26
27 27 @Component
28   -@ConditionalOnExpression("'${queue.type:null}'=='kafka'")
  28 +@ConditionalOnProperty(prefix = "queue", value = "type", havingValue = "kafka")
29 29 public class TbKafkaTopicConfigs {
30 30 @Value("${queue.kafka.topic-properties.core}")
31 31 private String coreProperties;
... ...
... ... @@ -34,9 +34,6 @@ public class TbQueueRemoteJsInvokeSettings {
34 34 @Value("${queue.js.response_poll_interval}")
35 35 private int responsePollInterval;
36 36
37   - @Value("${queue.js.response_auto_commit_interval}")
38   - private int autoCommitInterval;
39   -
40 37 @Value("${queue.js.max_requests_timeout}")
41 38 private long maxRequestsTimeout;
42 39 }
... ...
... ... @@ -16,6 +16,7 @@
16 16 package org.thingsboard.server.dao.attributes;
17 17
18 18 import com.google.common.util.concurrent.ListenableFuture;
  19 +import org.thingsboard.server.common.data.id.DeviceProfileId;
19 20 import org.thingsboard.server.common.data.id.EntityId;
20 21 import org.thingsboard.server.common.data.id.TenantId;
21 22 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
... ... @@ -38,4 +39,6 @@ public interface AttributesDao {
38 39 ListenableFuture<Void> save(TenantId tenantId, EntityId entityId, String attributeType, AttributeKvEntry attribute);
39 40
40 41 ListenableFuture<List<Void>> removeAll(TenantId tenantId, EntityId entityId, String attributeType, List<String> keys);
  42 +
  43 + List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId);
41 44 }
... ...
... ... @@ -20,6 +20,7 @@ import com.google.common.util.concurrent.Futures;
20 20 import com.google.common.util.concurrent.ListenableFuture;
21 21 import org.springframework.beans.factory.annotation.Autowired;
22 22 import org.springframework.stereotype.Service;
  23 +import org.thingsboard.server.common.data.id.DeviceProfileId;
23 24 import org.thingsboard.server.common.data.id.EntityId;
24 25 import org.thingsboard.server.common.data.id.TenantId;
25 26 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
... ... @@ -60,6 +61,11 @@ public class BaseAttributesService implements AttributesService {
60 61 }
61 62
62 63 @Override
  64 + public List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId) {
  65 + return attributesDao.findAllKeysByDeviceProfileId(tenantId, deviceProfileId);
  66 + }
  67 +
  68 + @Override
63 69 public ListenableFuture<List<Void>> save(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
64 70 validate(entityId, scope);
65 71 attributes.forEach(attribute -> validate(attribute));
... ...
... ... @@ -46,5 +46,15 @@ public interface AttributeKvRepository extends CrudRepository<AttributeKvEntity,
46 46 @Param("entityId") UUID entityId,
47 47 @Param("attributeType") String attributeType,
48 48 @Param("attributeKey") String attributeKey);
  49 +
  50 + @Query(value = "SELECT DISTINCT attribute_key FROM attribute_kv WHERE entity_type = 'DEVICE' " +
  51 + "AND entity_id in (SELECT id FROM device WHERE tenant_id = :tenantId and device_profile_id = :deviceProfileId limit 100) ORDER BY attribute_key", nativeQuery = true)
  52 + List<String> findAllKeysByDeviceProfileId(@Param("tenantId") UUID tenantId,
  53 + @Param("deviceProfileId") UUID deviceProfileId);
  54 +
  55 + @Query(value = "SELECT DISTINCT attribute_key FROM attribute_kv WHERE entity_type = 'DEVICE' " +
  56 + "AND entity_id in (SELECT id FROM device WHERE tenant_id = :tenantId limit 100) ORDER BY attribute_key", nativeQuery = true)
  57 + List<String> findAllKeysByTenantId(@Param("tenantId") UUID tenantId);
  58 +
49 59 }
50 60
... ...
... ... @@ -22,6 +22,7 @@ import lombok.extern.slf4j.Slf4j;
22 22 import org.springframework.beans.factory.annotation.Autowired;
23 23 import org.springframework.beans.factory.annotation.Value;
24 24 import org.springframework.stereotype.Component;
  25 +import org.thingsboard.server.common.data.id.DeviceProfileId;
25 26 import org.thingsboard.server.common.data.id.EntityId;
26 27 import org.thingsboard.server.common.data.id.TenantId;
27 28 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
... ... @@ -136,6 +137,15 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl
136 137 }
137 138
138 139 @Override
  140 + public List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId) {
  141 + if (deviceProfileId != null) {
  142 + return attributeKvRepository.findAllKeysByDeviceProfileId(tenantId.getId(), deviceProfileId.getId());
  143 + } else {
  144 + return attributeKvRepository.findAllKeysByTenantId(tenantId.getId());
  145 + }
  146 + }
  147 +
  148 + @Override
139 149 public ListenableFuture<Void> save(TenantId tenantId, EntityId entityId, String attributeType, AttributeKvEntry attribute) {
140 150 AttributeKvEntity entity = new AttributeKvEntity();
141 151 entity.setId(new AttributeKvCompositeKey(entityId.getEntityType(), entityId.getId(), attributeType, attribute.getKey()));
... ...
... ... @@ -24,6 +24,7 @@ import lombok.extern.slf4j.Slf4j;
24 24 import org.springframework.beans.factory.annotation.Autowired;
25 25 import org.springframework.beans.factory.annotation.Value;
26 26 import org.springframework.stereotype.Component;
  27 +import org.thingsboard.server.common.data.id.DeviceProfileId;
27 28 import org.thingsboard.server.common.data.id.EntityId;
28 29 import org.thingsboard.server.common.data.id.TenantId;
29 30 import org.thingsboard.server.common.data.kv.Aggregation;
... ... @@ -51,10 +52,15 @@ import org.thingsboard.server.dao.util.SqlTsLatestAnyDao;
51 52 import javax.annotation.Nullable;
52 53 import javax.annotation.PostConstruct;
53 54 import javax.annotation.PreDestroy;
54   -import java.util.*;
  55 +import java.util.ArrayList;
  56 +import java.util.Comparator;
  57 +import java.util.HashMap;
  58 +import java.util.List;
  59 +import java.util.Map;
  60 +import java.util.Optional;
  61 +import java.util.UUID;
55 62 import java.util.concurrent.ExecutionException;
56 63 import java.util.function.Function;
57   -import java.util.stream.Collectors;
58 64
59 65 @Slf4j
60 66 @Component
... ... @@ -154,6 +160,15 @@ public class SqlTimeseriesLatestDao extends BaseAbstractSqlTimeseriesDao impleme
154 160 return getFindAllLatestFuture(entityId);
155 161 }
156 162
  163 + @Override
  164 + public List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId) {
  165 + if (deviceProfileId != null) {
  166 + return tsKvLatestRepository.getKeysByDeviceProfileId(tenantId.getId(), deviceProfileId.getId());
  167 + } else {
  168 + return tsKvLatestRepository.getKeysByTenantId(tenantId.getId());
  169 + }
  170 + }
  171 +
157 172 private ListenableFuture<Void> getNewLatestEntryFuture(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) {
158 173 ListenableFuture<List<TsKvEntry>> future = findNewLatestEntryFuture(tenantId, entityId, query);
159 174 return Futures.transformAsync(future, entryList -> {
... ...
... ... @@ -15,10 +15,25 @@
15 15 */
16 16 package org.thingsboard.server.dao.sqlts.latest;
17 17
  18 +import org.springframework.data.jpa.repository.Query;
18 19 import org.springframework.data.repository.CrudRepository;
  20 +import org.springframework.data.repository.query.Param;
19 21 import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestCompositeKey;
20 22 import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity;
21 23
  24 +import java.util.List;
  25 +import java.util.UUID;
  26 +
22 27 public interface TsKvLatestRepository extends CrudRepository<TsKvLatestEntity, TsKvLatestCompositeKey> {
23 28
  29 + @Query(value = "SELECT DISTINCT ts_kv_dictionary.key AS strKey FROM ts_kv_latest " +
  30 + "INNER JOIN ts_kv_dictionary ON ts_kv_latest.key = ts_kv_dictionary.key_id " +
  31 + "WHERE ts_kv_latest.entity_id IN (SELECT id FROM device WHERE device_profile_id = :device_profile_id AND tenant_id = :tenant_id limit 100) ORDER BY ts_kv_dictionary.key", nativeQuery = true)
  32 + List<String> getKeysByDeviceProfileId(@Param("tenant_id") UUID tenantId, @Param("device_profile_id") UUID deviceProfileId);
  33 +
  34 + @Query(value = "SELECT DISTINCT ts_kv_dictionary.key AS strKey FROM ts_kv_latest " +
  35 + "INNER JOIN ts_kv_dictionary ON ts_kv_latest.key = ts_kv_dictionary.key_id " +
  36 + "WHERE ts_kv_latest.entity_id IN (SELECT id FROM device WHERE tenant_id = :tenant_id limit 100) ORDER BY ts_kv_dictionary.key", nativeQuery = true)
  37 + List<String> getKeysByTenantId(@Param("tenant_id") UUID tenantId);
  38 +
24 39 }
... ...
... ... @@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.Value;
27 27 import org.springframework.stereotype.Service;
28 28 import org.thingsboard.server.common.data.EntityType;
29 29 import org.thingsboard.server.common.data.EntityView;
  30 +import org.thingsboard.server.common.data.id.DeviceProfileId;
30 31 import org.thingsboard.server.common.data.id.EntityId;
31 32 import org.thingsboard.server.common.data.id.EntityViewId;
32 33 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -40,7 +41,6 @@ import org.thingsboard.server.dao.entityview.EntityViewService;
40 41 import org.thingsboard.server.dao.exception.IncorrectParameterException;
41 42 import org.thingsboard.server.dao.service.Validator;
42 43
43   -import java.util.ArrayList;
44 44 import java.util.Collection;
45 45 import java.util.Collections;
46 46 import java.util.List;
... ... @@ -117,6 +117,11 @@ public class BaseTimeseriesService implements TimeseriesService {
117 117 }
118 118
119 119 @Override
  120 + public List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId) {
  121 + return timeseriesLatestDao.findAllKeysByDeviceProfileId(tenantId, deviceProfileId);
  122 + }
  123 +
  124 + @Override
120 125 public ListenableFuture<Integer> save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) {
121 126 validate(entityId);
122 127 if (tsKvEntry == null) {
... ...
... ... @@ -18,7 +18,6 @@ package org.thingsboard.server.dao.timeseries;
18 18 import com.datastax.oss.driver.api.core.cql.BoundStatement;
19 19 import com.datastax.oss.driver.api.core.cql.BoundStatementBuilder;
20 20 import com.datastax.oss.driver.api.core.cql.PreparedStatement;
21   -import com.datastax.oss.driver.api.core.cql.Row;
22 21 import com.datastax.oss.driver.api.core.cql.Statement;
23 22 import com.datastax.oss.driver.api.querybuilder.QueryBuilder;
24 23 import com.google.common.util.concurrent.FutureCallback;
... ... @@ -28,14 +27,13 @@ import com.google.common.util.concurrent.MoreExecutors;
28 27 import lombok.extern.slf4j.Slf4j;
29 28 import org.springframework.beans.factory.annotation.Autowired;
30 29 import org.springframework.stereotype.Component;
  30 +import org.thingsboard.server.common.data.id.DeviceProfileId;
31 31 import org.thingsboard.server.common.data.id.EntityId;
32 32 import org.thingsboard.server.common.data.id.TenantId;
33 33 import org.thingsboard.server.common.data.kv.Aggregation;
34 34 import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
35   -import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
36 35 import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
37 36 import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
38   -import org.thingsboard.server.common.data.kv.StringDataEntry;
39 37 import org.thingsboard.server.common.data.kv.TsKvEntry;
40 38 import org.thingsboard.server.dao.model.ModelConstants;
41 39 import org.thingsboard.server.dao.nosql.TbResultSet;
... ... @@ -43,6 +41,7 @@ import org.thingsboard.server.dao.sqlts.AggregationTimeseriesDao;
43 41 import org.thingsboard.server.dao.util.NoSqlTsLatestDao;
44 42
45 43 import javax.annotation.Nullable;
  44 +import java.util.Collections;
46 45 import java.util.List;
47 46 import java.util.Optional;
48 47 import java.util.concurrent.ExecutionException;
... ... @@ -83,6 +82,11 @@ public class CassandraBaseTimeseriesLatestDao extends AbstractCassandraBaseTimes
83 82 }
84 83
85 84 @Override
  85 + public List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId) {
  86 + return Collections.emptyList();
  87 + }
  88 +
  89 + @Override
86 90 public ListenableFuture<Void> saveLatest(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) {
87 91 BoundStatementBuilder stmtBuilder = new BoundStatementBuilder(getLatestStmt().bind());
88 92 stmtBuilder.setString(0, entityId.getEntityType().name())
... ...
... ... @@ -16,6 +16,7 @@
16 16 package org.thingsboard.server.dao.timeseries;
17 17
18 18 import com.google.common.util.concurrent.ListenableFuture;
  19 +import org.thingsboard.server.common.data.id.DeviceProfileId;
19 20 import org.thingsboard.server.common.data.id.EntityId;
20 21 import org.thingsboard.server.common.data.id.TenantId;
21 22 import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
... ... @@ -33,4 +34,5 @@ public interface TimeseriesLatestDao {
33 34
34 35 ListenableFuture<Void> removeLatest(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query);
35 36
  37 + List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId);
36 38 }
... ...
... ... @@ -114,7 +114,7 @@ CREATE TABLE IF NOT EXISTS customer (
114 114 CREATE TABLE IF NOT EXISTS dashboard (
115 115 id uuid NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY,
116 116 created_time bigint NOT NULL,
117   - configuration varchar(10000000),
  117 + configuration varchar,
118 118 assigned_customers varchar(1000000),
119 119 search_text varchar(255),
120 120 tenant_id uuid,
... ...
... ... @@ -34,6 +34,8 @@ CREATE INDEX IF NOT EXISTS idx_device_customer_id_and_type ON device(tenant_id,
34 34
35 35 CREATE INDEX IF NOT EXISTS idx_device_type ON device(tenant_id, type);
36 36
  37 +CREATE INDEX IF NOT EXISTS idx_device_device_profile_id ON device(tenant_id, device_profile_id);
  38 +
37 39 CREATE INDEX IF NOT EXISTS idx_asset_customer_id ON asset(tenant_id, customer_id);
38 40
39 41 CREATE INDEX IF NOT EXISTS idx_asset_customer_id_and_type ON asset(tenant_id, customer_id, type);
... ...
... ... @@ -132,7 +132,7 @@ CREATE TABLE IF NOT EXISTS customer (
132 132 CREATE TABLE IF NOT EXISTS dashboard (
133 133 id uuid NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY,
134 134 created_time bigint NOT NULL,
135   - configuration varchar(10000000),
  135 + configuration varchar,
136 136 assigned_customers varchar(1000000),
137 137 search_text varchar(255),
138 138 tenant_id uuid,
... ...
... ... @@ -19,7 +19,7 @@ version: '2.2'
19 19 services:
20 20 postgres:
21 21 restart: always
22   - image: "postgres:11.6"
  22 + image: "postgres:12"
23 23 ports:
24 24 - "5432"
25 25 environment:
... ...
... ... @@ -19,7 +19,7 @@ version: '2.2'
19 19 services:
20 20 kafka:
21 21 restart: always
22   - image: "wurstmeister/kafka:2.12-2.3.0"
  22 + image: "wurstmeister/kafka:2.13-2.6.0"
23 23 ports:
24 24 - "9092:9092"
25 25 env_file:
... ...
... ... @@ -19,7 +19,7 @@ version: '2.2'
19 19 services:
20 20 postgres:
21 21 restart: always
22   - image: "postgres:11.6"
  22 + image: "postgres:12"
23 23 ports:
24 24 - "5432"
25 25 environment:
... ...
... ... @@ -19,7 +19,7 @@
19 19 "azure-sb": "^0.11.1",
20 20 "config": "^3.3.1",
21 21 "js-yaml": "^3.14.0",
22   - "kafkajs": "^1.14.0",
  22 + "kafkajs": "^1.15.0",
23 23 "long": "^4.0.0",
24 24 "uuid-parse": "^1.1.0",
25 25 "uuid-random": "^1.3.2",
... ...
... ... @@ -1665,10 +1665,10 @@ jws@^4.0.0:
1665 1665 jwa "^2.0.0"
1666 1666 safe-buffer "^5.0.1"
1667 1667
1668   -kafkajs@^1.14.0:
1669   - version "1.14.0"
1670   - resolved "https://registry.yarnpkg.com/kafkajs/-/kafkajs-1.14.0.tgz#3d998a77bfde54dc502e8e88690eedf0b21a1ed6"
1671   - integrity sha512-W+WCekiooY5rJP3Me5N3gWcQ8O6uG6lw0vv9t+sI+WqXKjKwj2+CWIXJy241x+ITE+1M1D19ABSiL2J8lKja5A==
  1668 +kafkajs@^1.15.0:
  1669 + version "1.15.0"
  1670 + resolved "https://registry.yarnpkg.com/kafkajs/-/kafkajs-1.15.0.tgz#a5ada0d933edca2149177393562be6fb0875ec3a"
  1671 + integrity sha512-yjPyEnQCkPxAuQLIJnY5dI+xnmmgXmhuOQ1GVxClG5KTOV/rJcW1qA3UfvyEJKTp/RTSqQnUR3HJsKFvHyTpNg==
1672 1672
1673 1673 keyv@^3.0.0:
1674 1674 version "3.1.0"
... ...
... ... @@ -92,7 +92,7 @@
92 92 </sonar.exclusions>
93 93 <elasticsearch.version>5.0.2</elasticsearch.version>
94 94 <delight-nashorn-sandbox.version>0.1.14</delight-nashorn-sandbox.version>
95   - <kafka.version>2.3.0</kafka.version>
  95 + <kafka.version>2.6.0</kafka.version>
96 96 <bucket4j.version>4.1.1</bucket4j.version>
97 97 <fst.version>2.57</fst.version>
98 98 <antlr.version>2.7.7</antlr.version>
... ... @@ -729,7 +729,8 @@
729 729 <exclude>docker/haproxy/**</exclude>
730 730 <exclude>docker/tb-node/**</exclude>
731 731 <exclude>ui/**</exclude>
732   - <exclude>src/browserslist</exclude>
  732 + <exclude>src/.browserslistrc</exclude>
  733 + <exclude>**/yarn.lock</exclude>
733 734 <exclude>**/*.raw</exclude>
734 735 <exclude>**/apache/cassandra/io/**</exclude>
735 736 <exclude>.run/**</exclude>
... ...
... ... @@ -160,8 +160,6 @@ queue:
160 160 max_requests_timeout: "${REMOTE_JS_MAX_REQUEST_TIMEOUT:10000}"
161 161 # JS response poll interval
162 162 response_poll_interval: "${REMOTE_JS_RESPONSE_POLL_INTERVAL_MS:25}"
163   - # JS response auto commit interval
164   - response_auto_commit_interval: "${REMOTE_JS_RESPONSE_AUTO_COMMIT_INTERVAL_MS:100}"
165 163 rule-engine:
166 164 topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}"
167 165 poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}"
... ...
... ... @@ -153,8 +153,6 @@ queue:
153 153 max_requests_timeout: "${REMOTE_JS_MAX_REQUEST_TIMEOUT:10000}"
154 154 # JS response poll interval
155 155 response_poll_interval: "${REMOTE_JS_RESPONSE_POLL_INTERVAL_MS:25}"
156   - # JS response auto commit interval
157   - response_auto_commit_interval: "${REMOTE_JS_RESPONSE_AUTO_COMMIT_INTERVAL_MS:100}"
158 156 rule-engine:
159 157 topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}"
160 158 poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}"
... ...
... ... @@ -182,8 +182,6 @@ queue:
182 182 max_requests_timeout: "${REMOTE_JS_MAX_REQUEST_TIMEOUT:10000}"
183 183 # JS response poll interval
184 184 response_poll_interval: "${REMOTE_JS_RESPONSE_POLL_INTERVAL_MS:25}"
185   - # JS response auto commit interval
186   - response_auto_commit_interval: "${REMOTE_JS_RESPONSE_AUTO_COMMIT_INTERVAL_MS:100}"
187 185 rule-engine:
188 186 topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}"
189 187 poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}"
... ...
... ... @@ -59,6 +59,11 @@
59 59 "glob": "marker-shadow.png",
60 60 "input": "node_modules/leaflet/dist/images/",
61 61 "output": "/"
  62 + },
  63 + {
  64 + "glob": "**/*",
  65 + "input": "node_modules/material-design-icons/iconfont/",
  66 + "output": "assets/fonts"
62 67 }
63 68 ],
64 69 "styles": [
... ...
... ... @@ -69,4 +69,20 @@ export class DeviceProfileService {
69 69 return this.http.get<PageData<DeviceProfileInfo>>(url, defaultHttpOptionsFromConfig(config));
70 70 }
71 71
  72 + public getDeviceProfileDevicesAttributesKeys(deviceProfileId?: string, config?: RequestConfig): Observable<Array<string>> {
  73 + let url = `/api/deviceProfile/devices/keys/attributes`;
  74 + if (isDefinedAndNotNull(deviceProfileId)) {
  75 + url += `?deviceProfileId=${deviceProfileId}`;
  76 + }
  77 + return this.http.get<Array<string>>(url, defaultHttpOptionsFromConfig(config));
  78 + }
  79 +
  80 + public getDeviceProfileDevicesTimeseriesKeys(deviceProfileId?: string, config?: RequestConfig): Observable<Array<string>> {
  81 + let url = `/api/deviceProfile/devices/keys/timeseries`;
  82 + if (isDefinedAndNotNull(deviceProfileId)) {
  83 + url += `?deviceProfileId=${deviceProfileId}`;
  84 + }
  85 + return this.http.get<Array<string>>(url, defaultHttpOptionsFromConfig(config));
  86 + }
  87 +
72 88 }
... ...
... ... @@ -65,6 +65,7 @@ export class TranslateDefaultCompiler extends TranslateMessageFormatCompiler {
65 65 } catch (e) {
66 66 console.warn(`Failed to parse source: ${src}`);
67 67 console.error(e);
  68 + return false;
68 69 }
69 70 const res = tokens.filter(
70 71 (value) => typeof value !== 'string' && value.type === 'plural'
... ...
... ... @@ -40,11 +40,19 @@
40 40 <mat-form-field fxFlex="60" class="mat-block">
41 41 <mat-label translate>filter.key-name</mat-label>
42 42 <input matInput required formControlName="key"
43   - [matAutocomplete]="auto"
44   - [matAutocompleteDisabled]="keyFilterFormGroup.get('key.type').value !== entityField">
45   - <mat-autocomplete autoActiveFirstOption #auto="matAutocomplete">
46   - <mat-option *ngFor="let option of filteredEntityFields | async" [value]="option">
47   - {{option}}
  43 + #keyNameInput
  44 + (focusin)="onFocus()"
  45 + [matAutocomplete]="keyName"
  46 + [matAutocompleteDisabled]="!showAutocomplete">
  47 + <button *ngIf="keyFilterFormGroup.get('key.key').value && showAutocomplete"
  48 + type="button"
  49 + matSuffix mat-button mat-icon-button aria-label="Clear"
  50 + (click)="clear()">
  51 + <mat-icon class="material-icons">close</mat-icon>
  52 + </button>
  53 + <mat-autocomplete autoActiveFirstOption #keyName="matAutocomplete">
  54 + <mat-option *ngFor="let keyName of filteredKeysName | async" [value]="keyName">
  55 + <span [innerHTML]="keyName | highlight:searchText"></span>
48 56 </mat-option>
49 57 </mat-autocomplete>
50 58 <mat-error *ngIf="keyFilterFormGroup.get('key.key').hasError('required')">
... ...
... ... @@ -14,7 +14,7 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import { Component, Inject, OnInit, SkipSelf } from '@angular/core';
  17 +import { Component, ElementRef, Inject, OnDestroy, OnInit, SkipSelf, ViewChild } from '@angular/core';
18 18 import { ErrorStateMatcher } from '@angular/material/core';
19 19 import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
20 20 import { Store } from '@ngrx/store';
... ... @@ -32,9 +32,12 @@ import {
32 32 } from '@shared/models/query/query.models';
33 33 import { DialogService } from '@core/services/dialog.service';
34 34 import { TranslateService } from '@ngx-translate/core';
35   -import { EntityField, entityFields } from '@shared/models/entity.models';
36   -import { Observable } from 'rxjs';
37   -import { filter, map, startWith } from 'rxjs/operators';
  35 +import { entityFields } from '@shared/models/entity.models';
  36 +import { Observable, of, Subject } from 'rxjs';
  37 +import { filter, map, mergeMap, publishReplay, refCount, startWith, takeUntil } from 'rxjs/operators';
  38 +import { isDefined } from '@core/utils';
  39 +import { EntityId } from '@shared/models/id/entity-id';
  40 +import { DeviceProfileService } from '@core/http/device-profile.service';
38 41
39 42 export interface KeyFilterDialogData {
40 43 keyFilter: KeyFilterInfo;
... ... @@ -43,6 +46,7 @@ export interface KeyFilterDialogData {
43 46 allowUserDynamicSource: boolean;
44 47 readonly: boolean;
45 48 telemetryKeysOnly: boolean;
  49 + entityId?: EntityId;
46 50 }
47 51
48 52 @Component({
... ... @@ -53,7 +57,13 @@ export interface KeyFilterDialogData {
53 57 })
54 58 export class KeyFilterDialogComponent extends
55 59 DialogComponent<KeyFilterDialogComponent, KeyFilterInfo>
56   - implements OnInit, ErrorStateMatcher {
  60 + implements OnInit, OnDestroy, ErrorStateMatcher {
  61 +
  62 + @ViewChild('keyNameInput', {static: true}) private keyNameInput: ElementRef;
  63 +
  64 + private dirty = false;
  65 + private entityKeysName: Observable<Array<string>>;
  66 + private destroy$ = new Subject();
57 67
58 68 keyFilterFormGroup: FormGroup;
59 69
... ... @@ -72,19 +82,18 @@ export class KeyFilterDialogComponent extends
72 82
73 83 submitted = false;
74 84
75   - entityFields: { [fieldName: string]: EntityField };
76   -
77   - entityFieldsList: string[];
  85 + showAutocomplete = false;
78 86
79   - readonly entityField = EntityKeyType.ENTITY_FIELD;
  87 + filteredKeysName: Observable<Array<string>>;
80 88
81   - filteredEntityFields: Observable<string[]>;
  89 + searchText = '';
82 90
83 91 constructor(protected store: Store<AppState>,
84 92 protected router: Router,
85 93 @Inject(MAT_DIALOG_DATA) public data: KeyFilterDialogData,
86 94 @SkipSelf() private errorStateMatcher: ErrorStateMatcher,
87 95 public dialogRef: MatDialogRef<KeyFilterDialogComponent, KeyFilterInfo>,
  96 + private deviceProfileService: DeviceProfileService,
88 97 private dialogs: DialogService,
89 98 private translate: TranslateService,
90 99 private fb: FormBuilder) {
... ... @@ -104,7 +113,9 @@ export class KeyFilterDialogComponent extends
104 113 );
105 114
106 115 if (!this.data.readonly) {
107   - this.keyFilterFormGroup.get('valueType').valueChanges.subscribe((valueType: EntityKeyValueType) => {
  116 + this.keyFilterFormGroup.get('valueType').valueChanges.pipe(
  117 + takeUntil(this.destroy$)
  118 + ).subscribe((valueType: EntityKeyValueType) => {
108 119 const prevValue: EntityKeyValueType = this.keyFilterFormGroup.value.valueType;
109 120 const predicates: KeyFilterPredicate[] = this.keyFilterFormGroup.get('predicates').value;
110 121 if (prevValue && prevValue !== valueType && predicates && predicates.length) {
... ... @@ -121,11 +132,26 @@ export class KeyFilterDialogComponent extends
121 132 }
122 133 });
123 134
  135 + this.keyFilterFormGroup.get('key.type').valueChanges.pipe(
  136 + startWith(this.data.keyFilter.key.type),
  137 + takeUntil(this.destroy$)
  138 + ).subscribe((type: EntityKeyType) => {
  139 + if (type === EntityKeyType.ENTITY_FIELD || isDefined(this.data.entityId)) {
  140 + this.entityKeysName = null;
  141 + this.dirty = false;
  142 + this.showAutocomplete = true;
  143 + } else {
  144 + this.showAutocomplete = false;
  145 + }
  146 + });
  147 +
124 148 this.keyFilterFormGroup.get('key.key').valueChanges.pipe(
125   - filter((keyName) => this.keyFilterFormGroup.get('key.type').value === this.entityField && this.entityFields.hasOwnProperty(keyName))
  149 + filter((keyName) =>
  150 + this.keyFilterFormGroup.get('key.type').value === EntityKeyType.ENTITY_FIELD && entityFields.hasOwnProperty(keyName)),
  151 + takeUntil(this.destroy$)
126 152 ).subscribe((keyName: string) => {
127 153 const prevValueType: EntityKeyValueType = this.keyFilterFormGroup.value.valueType;
128   - const newValueType = this.entityFields[keyName]?.time ? EntityKeyValueType.DATE_TIME : EntityKeyValueType.STRING;
  154 + const newValueType = entityFields[keyName]?.time ? EntityKeyValueType.DATE_TIME : EntityKeyValueType.STRING;
129 155 if (prevValueType !== newValueType) {
130 156 this.keyFilterFormGroup.get('valueType').patchValue(newValueType, {emitEvent: false});
131 157 }
... ... @@ -133,18 +159,20 @@ export class KeyFilterDialogComponent extends
133 159 } else {
134 160 this.keyFilterFormGroup.disable({emitEvent: false});
135 161 }
  162 + }
136 163
137   - this.entityFields = entityFields;
138   - this.entityFieldsList = Object.values(entityFields).map(entityField => entityField.keyName).sort();
  164 + ngOnInit() {
  165 + this.filteredKeysName = this.keyFilterFormGroup.get('key.key').valueChanges
  166 + .pipe(
  167 + map(value => value ? value : ''),
  168 + mergeMap(name => this.fetchEntityName(name))
  169 + );
139 170 }
140 171
141   - ngOnInit(): void {
142   - this.filteredEntityFields = this.keyFilterFormGroup.get('key.key').valueChanges.pipe(
143   - startWith(''),
144   - map(value => {
145   - return this.entityFieldsList.filter(option => option.startsWith(value));
146   - })
147   - );
  172 + ngOnDestroy() {
  173 + super.ngOnDestroy();
  174 + this.destroy$.next();
  175 + this.destroy$.complete();
148 176 }
149 177
150 178 isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
... ... @@ -157,6 +185,21 @@ export class KeyFilterDialogComponent extends
157 185 this.dialogRef.close(null);
158 186 }
159 187
  188 + clear() {
  189 + this.keyFilterFormGroup.get('key.key').patchValue('', {emitEvent: true});
  190 + setTimeout(() => {
  191 + this.keyNameInput.nativeElement.blur();
  192 + this.keyNameInput.nativeElement.focus();
  193 + }, 0);
  194 + }
  195 +
  196 + onFocus() {
  197 + if (!this.dirty && this.showAutocomplete) {
  198 + this.keyFilterFormGroup.get('key.key').updateValueAndValidity({onlySelf: true, emitEvent: true});
  199 + this.dirty = true;
  200 + }
  201 + }
  202 +
160 203 save(): void {
161 204 this.submitted = true;
162 205 if (this.keyFilterFormGroup.valid) {
... ... @@ -164,4 +207,41 @@ export class KeyFilterDialogComponent extends
164 207 this.dialogRef.close(keyFilter);
165 208 }
166 209 }
  210 +
  211 + private fetchEntityName(searchText?: string): Observable<Array<string>> {
  212 + this.searchText = searchText;
  213 + return this.getEntityKeys().pipe(
  214 + map(keys => searchText ? keys.filter(key => key.toUpperCase().startsWith(searchText.toUpperCase())) : keys)
  215 + );
  216 + }
  217 +
  218 + private getEntityKeys(): Observable<Array<string>> {
  219 + if (!this.entityKeysName) {
  220 + let keyNameObservable: Observable<Array<string>>;
  221 + switch (this.keyFilterFormGroup.get('key.type').value) {
  222 + case EntityKeyType.ENTITY_FIELD:
  223 + keyNameObservable = of(Object.values(entityFields).map(entityField => entityField.keyName).sort());
  224 + break;
  225 + case EntityKeyType.ATTRIBUTE:
  226 + keyNameObservable = this.deviceProfileService.getDeviceProfileDevicesAttributesKeys(
  227 + this.data.entityId?.id,
  228 + {ignoreLoading: true}
  229 + );
  230 + break;
  231 + case EntityKeyType.TIME_SERIES:
  232 + keyNameObservable = this.deviceProfileService.getDeviceProfileDevicesTimeseriesKeys(
  233 + this.data.entityId?.id,
  234 + {ignoreLoading: true}
  235 + );
  236 + break;
  237 + default:
  238 + keyNameObservable = of([]);
  239 + }
  240 + this.entityKeysName = keyNameObservable.pipe(
  241 + publishReplay(1),
  242 + refCount()
  243 + );
  244 + }
  245 + return this.entityKeysName;
  246 + }
167 247 }
... ...
... ... @@ -19,7 +19,8 @@ import {
19 19 AbstractControl,
20 20 ControlValueAccessor,
21 21 FormArray,
22   - FormBuilder, FormControl,
  22 + FormBuilder,
  23 + FormControl,
23 24 FormGroup,
24 25 NG_VALUE_ACCESSOR,
25 26 Validators
... ... @@ -28,12 +29,13 @@ import { Observable, Subscription } from 'rxjs';
28 29 import {
29 30 EntityKeyType,
30 31 entityKeyTypeTranslationMap,
31   - KeyFilter,
32   - KeyFilterInfo, keyFilterInfosToKeyFilters
  32 + KeyFilterInfo,
  33 + keyFilterInfosToKeyFilters
33 34 } from '@shared/models/query/query.models';
34 35 import { MatDialog } from '@angular/material/dialog';
35 36 import { deepClone } from '@core/utils';
36 37 import { KeyFilterDialogComponent, KeyFilterDialogData } from '@home/components/filter/key-filter-dialog.component';
  38 +import { EntityId } from '@shared/models/id/entity-id';
37 39
38 40 @Component({
39 41 selector: 'tb-key-filter-list',
... ... @@ -57,6 +59,8 @@ export class KeyFilterListComponent implements ControlValueAccessor, OnInit {
57 59
58 60 @Input() telemetryKeysOnly = false;
59 61
  62 + @Input() entityId: EntityId;
  63 +
60 64 keyFilterListFormGroup: FormGroup;
61 65
62 66 entityKeyTypeTranslations = entityKeyTypeTranslationMap;
... ... @@ -170,7 +174,8 @@ export class KeyFilterListComponent implements ControlValueAccessor, OnInit {
170 174 readonly: this.disabled,
171 175 displayUserParameters: this.displayUserParameters,
172 176 allowUserDynamicSource: this.allowUserDynamicSource,
173   - telemetryKeysOnly: this.telemetryKeysOnly
  177 + telemetryKeysOnly: this.telemetryKeysOnly,
  178 + entityId: this.entityId
174 179 }
175 180 }).afterClosed();
176 181 }
... ...
... ... @@ -96,7 +96,8 @@
96 96 {count: alarmRulesFormGroup.get('alarms').value ?
97 97 alarmRulesFormGroup.get('alarms').value.length : 0} }}</ng-template>
98 98 <tb-device-profile-alarms
99   - formControlName="alarms">
  99 + formControlName="alarms"
  100 + [deviceProfileId]="null">
100 101 </tb-device-profile-alarms>
101 102 </form>
102 103 </mat-step>
... ...
... ... @@ -34,6 +34,7 @@
34 34 [displayUserParameters]="false"
35 35 [allowUserDynamicSource]="false"
36 36 [telemetryKeysOnly]="true"
  37 + [entityId]="entityId"
37 38 formControlName="keyFilters">
38 39 </tb-key-filter-list>
39 40 <section formGroupName="spec" class="row">
... ...
... ... @@ -22,15 +22,16 @@ import { AppState } from '@core/core.state';
22 22 import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms';
23 23 import { Router } from '@angular/router';
24 24 import { DialogComponent } from '@app/shared/components/dialog.component';
25   -import { UtilsService } from '@core/services/utils.service';
26 25 import { TranslateService } from '@ngx-translate/core';
27   -import { KeyFilter, keyFilterInfosToKeyFilters, keyFiltersToKeyFilterInfos } from '@shared/models/query/query.models';
  26 +import { keyFilterInfosToKeyFilters, keyFiltersToKeyFilterInfos } from '@shared/models/query/query.models';
28 27 import { AlarmCondition, AlarmConditionType, AlarmConditionTypeTranslationMap } from '@shared/models/device.models';
29 28 import { TimeUnit, timeUnitTranslationMap } from '@shared/models/time/time.models';
  29 +import { EntityId } from '@shared/models/id/entity-id';
30 30
31 31 export interface AlarmRuleConditionDialogData {
32 32 readonly: boolean;
33 33 condition: AlarmCondition;
  34 + entityId?: EntityId;
34 35 }
35 36
36 37 @Component({
... ... @@ -50,6 +51,7 @@ export class AlarmRuleConditionDialogComponent extends DialogComponent<AlarmRule
50 51
51 52 readonly = this.data.readonly;
52 53 condition = this.data.condition;
  54 + entityId = this.data.entityId;
53 55
54 56 conditionFormGroup: FormGroup;
55 57
... ... @@ -61,7 +63,6 @@ export class AlarmRuleConditionDialogComponent extends DialogComponent<AlarmRule
61 63 @SkipSelf() private errorStateMatcher: ErrorStateMatcher,
62 64 public dialogRef: MatDialogRef<AlarmRuleConditionDialogComponent, AlarmCondition>,
63 65 private fb: FormBuilder,
64   - private utils: UtilsService,
65 66 public translate: TranslateService) {
66 67 super(store, router, dialogRef);
67 68
... ...
... ... @@ -33,6 +33,7 @@ import {
33 33 AlarmRuleConditionDialogData
34 34 } from '@home/components/profile/alarm/alarm-rule-condition-dialog.component';
35 35 import { TimeUnit } from '@shared/models/time/time.models';
  36 +import { EntityId } from '@shared/models/id/entity-id';
36 37
37 38 @Component({
38 39 selector: 'tb-alarm-rule-condition',
... ... @@ -56,6 +57,9 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit
56 57 @Input()
57 58 disabled: boolean;
58 59
  60 + @Input()
  61 + deviceProfileId: EntityId;
  62 +
59 63 alarmRuleConditionFormGroup: FormGroup;
60 64
61 65 specText = '';
... ... @@ -123,7 +127,8 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit
123 127 panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
124 128 data: {
125 129 readonly: this.disabled,
126   - condition: this.disabled ? this.modelValue : deepClone(this.modelValue)
  130 + condition: this.disabled ? this.modelValue : deepClone(this.modelValue),
  131 + entityId: this.deviceProfileId
127 132 }
128 133 }).afterClosed().subscribe((result) => {
129 134 if (result) {
... ...
... ... @@ -16,7 +16,7 @@
16 16
17 17 -->
18 18 <div fxLayout="column" [formGroup]="alarmRuleFormGroup">
19   - <tb-alarm-rule-condition formControlName="condition">
  19 + <tb-alarm-rule-condition formControlName="condition" [deviceProfileId]="deviceProfileId">
20 20 </tb-alarm-rule-condition>
21 21 <tb-alarm-schedule-info formControlName="schedule">
22 22 </tb-alarm-schedule-info>
... ...
... ... @@ -33,6 +33,7 @@ import {
33 33 EditAlarmDetailsDialogComponent,
34 34 EditAlarmDetailsDialogData
35 35 } from '@home/components/profile/alarm/edit-alarm-details-dialog.component';
  36 +import { EntityId } from '@shared/models/id/entity-id';
36 37
37 38 @Component({
38 39 selector: 'tb-alarm-rule',
... ... @@ -65,6 +66,9 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat
65 66 this.requiredValue = coerceBooleanProperty(value);
66 67 }
67 68
  69 + @Input()
  70 + deviceProfileId: EntityId;
  71 +
68 72 private modelValue: AlarmRule;
69 73
70 74 alarmRuleFormGroup: FormGroup;
... ...
... ... @@ -35,7 +35,7 @@
35 35 </mat-error>
36 36 </mat-form-field>
37 37 <mat-divider vertical></mat-divider>
38   - <tb-alarm-rule formControlName="alarmRule" required fxFlex>
  38 + <tb-alarm-rule formControlName="alarmRule" [deviceProfileId]="deviceProfileId" required fxFlex>
39 39 </tb-alarm-rule>
40 40 </div>
41 41 <button *ngIf="!disabled"
... ...
... ... @@ -30,7 +30,8 @@ import {
30 30 import { AlarmRule, alarmRuleValidator } from '@shared/models/device.models';
31 31 import { MatDialog } from '@angular/material/dialog';
32 32 import { Subscription } from 'rxjs';
33   -import { AlarmSeverity, alarmSeverityTranslations } from '../../../../../shared/models/alarm.models';
  33 +import { AlarmSeverity, alarmSeverityTranslations } from '@shared/models/alarm.models';
  34 +import { EntityId } from '@shared/models/id/entity-id';
34 35
35 36 @Component({
36 37 selector: 'tb-create-alarm-rules',
... ... @@ -59,6 +60,9 @@ export class CreateAlarmRulesComponent implements ControlValueAccessor, OnInit,
59 60 @Input()
60 61 disabled: boolean;
61 62
  63 + @Input()
  64 + deviceProfileId: EntityId;
  65 +
62 66 createAlarmRulesFormGroup: FormGroup;
63 67
64 68 private usedSeverities: AlarmSeverity[] = [];
... ...
... ... @@ -80,14 +80,16 @@
80 80 </mat-expansion-panel>
81 81 <div fxFlex fxLayout="column">
82 82 <div translate class="tb-small" style="padding-bottom: 8px;">device-profile.create-alarm-rules</div>
83   - <tb-create-alarm-rules formControlName="createRules" style="padding-bottom: 16px;">
  83 + <tb-create-alarm-rules formControlName="createRules"
  84 + style="padding-bottom: 16px;"
  85 + [deviceProfileId]="deviceProfileId">
84 86 </tb-create-alarm-rules>
85 87 <div translate class="tb-small" style="padding-bottom: 8px;">device-profile.clear-alarm-rule</div>
86 88 <div fxLayout="row" fxLayoutGap="8px;" fxLayoutAlign="start center"
87 89 [fxShow]="alarmFormGroup.get('clearRule').value"
88 90 style="padding-bottom: 8px;">
89 91 <div class="clear-alarm-rule" fxFlex fxLayout="row">
90   - <tb-alarm-rule formControlName="clearRule" fxFlex>
  92 + <tb-alarm-rule formControlName="clearRule" fxFlex [deviceProfileId]="deviceProfileId">
91 93 </tb-alarm-rule>
92 94 </div>
93 95 <button *ngIf="!disabled"
... ...
... ... @@ -29,6 +29,7 @@ import { AlarmRule, DeviceProfileAlarm, deviceProfileAlarmValidator } from '@sha
29 29 import { MatDialog } from '@angular/material/dialog';
30 30 import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
31 31 import { MatChipInputEvent } from '@angular/material/chips';
  32 +import { EntityId } from '@shared/models/id/entity-id';
32 33
33 34 @Component({
34 35 selector: 'tb-device-profile-alarm',
... ... @@ -60,6 +61,9 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit
60 61 @Input()
61 62 expanded = false;
62 63
  64 + @Input()
  65 + deviceProfileId: EntityId;
  66 +
63 67 private modelValue: DeviceProfileAlarm;
64 68
65 69 alarmFormGroup: FormGroup;
... ...
... ... @@ -22,6 +22,7 @@
22 22 fxLayout="column" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
23 23 <tb-device-profile-alarm [formControl]="alarmControl"
24 24 [expanded]="$index === 0"
  25 + [deviceProfileId]="deviceProfileId"
25 26 (removeAlarm)="removeAlarm($index)">
26 27 </tb-device-profile-alarm>
27 28 </div>
... ...
... ... @@ -34,6 +34,7 @@ import { DeviceProfileAlarm, deviceProfileAlarmValidator } from '@shared/models/
34 34 import { guid } from '@core/utils';
35 35 import { Subscription } from 'rxjs';
36 36 import { MatDialog } from '@angular/material/dialog';
  37 +import { EntityId } from '@shared/models/id/entity-id';
37 38
38 39 @Component({
39 40 selector: 'tb-device-profile-alarms',
... ... @@ -68,6 +69,9 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni
68 69 @Input()
69 70 disabled: boolean;
70 71
  72 + @Input()
  73 + deviceProfileId: EntityId;
  74 +
71 75 private valueChangeSubscription: Subscription = null;
72 76
73 77 private propagateChange = (v: any) => { };
... ...
... ... @@ -116,7 +116,8 @@
116 116 </mat-panel-title>
117 117 </mat-expansion-panel-header>
118 118 <tb-device-profile-alarms
119   - formControlName="alarms">
  119 + formControlName="alarms"
  120 + [deviceProfileId]="deviceProfileId">
120 121 </tb-device-profile-alarms>
121 122 </mat-expansion-panel>
122 123 <mat-expansion-panel [expanded]="true">
... ...
... ... @@ -39,6 +39,7 @@ import {
39 39 import { EntityType } from '@shared/models/entity-type.models';
40 40 import { RuleChainId } from '@shared/models/id/rule-chain-id';
41 41 import { ServiceType } from '@shared/models/queue.models';
  42 +import { EntityId } from '@shared/models/id/entity-id';
42 43
43 44 @Component({
44 45 selector: 'tb-device-profile',
... ... @@ -66,6 +67,8 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
66 67
67 68 serviceType = ServiceType.TB_RULE_ENGINE;
68 69
  70 + deviceProfileId: EntityId;
  71 +
69 72 constructor(protected store: Store<AppState>,
70 73 protected translate: TranslateService,
71 74 @Optional() @Inject('entity') protected entityValue: DeviceProfile,
... ... @@ -83,6 +86,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
83 86 }
84 87
85 88 buildForm(entity: DeviceProfile): FormGroup {
  89 + this.deviceProfileId = entity?.id ? entity.id : null;
86 90 this.displayProfileConfiguration = entity && entity.type &&
87 91 deviceProfileTypeConfigurationInfoMap.get(entity.type).hasProfileConfiguration;
88 92 this.displayTransportConfiguration = entity && entity.transportType &&
... ... @@ -157,6 +161,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
157 161 }
158 162
159 163 updateForm(entity: DeviceProfile) {
  164 + this.deviceProfileId = entity.id;
160 165 this.displayProfileConfiguration = entity.type &&
161 166 deviceProfileTypeConfigurationInfoMap.get(entity.type).hasProfileConfiguration;
162 167 this.displayTransportConfiguration = entity.transportType &&
... ...
... ... @@ -178,17 +178,17 @@
178 178 <!-- </div>-->
179 179 <!-- </div>-->
180 180 <!-- <div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center">-->
181   -<!-- <button mat-button mat-raised-button color="primary"-->
182   -<!-- type="submit"-->
183   -<!-- [disabled]="(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty">-->
184   -<!-- Save-->
185   -<!-- </button>-->
186 181 <!-- <button mat-button color="primary"-->
187 182 <!-- type="button"-->
188 183 <!-- [disabled]="(isLoading$ | async)"-->
189 184 <!-- (click)="cancel()" cdkFocusInitial>-->
190 185 <!-- Cancel-->
191 186 <!-- </button>-->
  187 +<!-- <button mat-button mat-raised-button color="primary"-->
  188 +<!-- type="submit"-->
  189 +<!-- [disabled]="(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty">-->
  190 +<!-- Save-->
  191 +<!-- </button>-->
192 192 <!-- </div>-->
193 193 <!--</form>-->
194 194 <!---->
... ... @@ -338,16 +338,16 @@
338 338 <!-- </div>-->
339 339 <!-- </div>-->
340 340 <!-- <div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center">-->
341   -<!-- <button mat-button mat-raised-button color="primary"-->
342   -<!-- type="submit"-->
343   -<!-- [disabled]="(isLoading$ | async) || addEntityForm.invalid || !addEntityForm.dirty">-->
344   -<!-- Create-->
345   -<!-- </button>-->
346 341 <!-- <button mat-button color="primary"-->
347 342 <!-- type="button"-->
348 343 <!-- [disabled]="(isLoading$ | async)"-->
349 344 <!-- (click)="cancel()" cdkFocusInitial>-->
350 345 <!-- Cancel-->
351 346 <!-- </button>-->
  347 +<!-- <button mat-button mat-raised-button color="primary"-->
  348 +<!-- type="submit"-->
  349 +<!-- [disabled]="(isLoading$ | async) || addEntityForm.invalid || !addEntityForm.dirty">-->
  350 +<!-- Create-->
  351 +<!-- </button>-->
352 352 <!-- </div>-->
353 353 <!--</form>-->
... ...
... ... @@ -283,6 +283,13 @@ export class PhotoCameraInputWidgetComponent extends PageComponent implements On
283 283 window.navigator.mediaDevices.getUserMedia(videoTrackConstraints).then((stream: MediaStream) => {
284 284 if (init) {
285 285 this.isShowCamera = true;
  286 + if (this.availableVideoInputs.find((device) => device.deviceId === '')) {
  287 + PhotoCameraInputWidgetComponent.getAvailableVideoInputs().then((devices) => {
  288 + this.singleDevice = devices.length < 2;
  289 + this.availableVideoInputs = devices;
  290 + this.ctx.detectChanges();
  291 + });
  292 + }
286 293 }
287 294 this.mediaStream = stream;
288 295 this.videoElement.srcObject = stream;
... ...
... ... @@ -76,7 +76,7 @@
76 76 [required]="!createProfile"
77 77 [transportType]="deviceWizardFormGroup.get('transportType').value"
78 78 formControlName="deviceProfileId"
79   - [ngClass]="{invisible: deviceWizardFormGroup.get('addProfileType').value !== 0}"
  79 + [ngClass]="{invisible: deviceWizardFormGroup.get('addProfileType').value !== 0}"
80 80 (deviceProfileChanged)="$event?.transportType ? deviceWizardFormGroup.get('transportType').patchValue($event?.transportType) : {}"
81 81 [addNewProfile]="false"
82 82 [selectDefaultProfile]="true"
... ... @@ -133,7 +133,8 @@
133 133 {count: alarmRulesFormGroup.get('alarms').value ?
134 134 alarmRulesFormGroup.get('alarms').value.length : 0} }}</ng-template>
135 135 <tb-device-profile-alarms
136   - formControlName="alarms">
  136 + formControlName="alarms"
  137 + [deviceProfileId]="null">
137 138 </tb-device-profile-alarms>
138 139 </form>
139 140 </mat-step>
... ...
... ... @@ -32,9 +32,11 @@ import { DashboardService } from '@core/http/dashboard.service';
32 32 import { UserService } from '@core/http/user.service';
33 33 import { AlarmService } from '@core/http/alarm.service';
34 34 import { Router } from '@angular/router';
  35 +import { BroadcastService } from "@core/services/broadcast.service";
35 36
36 37 export const ServicesMap = new Map<string, Type<any>>(
37 38 [
  39 + ['broadcastService', BroadcastService],
38 40 ['deviceService', DeviceService],
39 41 ['alarmService', AlarmService],
40 42 ['assetService', AssetService],
... ...
... ... @@ -46,7 +46,7 @@
46 46 }}" #alarmRules="matTab">
47 47 <div class="mat-padding" [formGroup]="detailsForm">
48 48 <div formGroupName="profileData">
49   - <tb-device-profile-alarms formControlName="alarms"></tb-device-profile-alarms>
  49 + <tb-device-profile-alarms formControlName="alarms" [deviceProfileId]="entity.id"></tb-device-profile-alarms>
50 50 </div>
51 51 </div>
52 52 </mat-tab>
... ...
... ... @@ -1937,25 +1937,6 @@
1937 1937 }
1938 1938 },
1939 1939 "language": {
1940   - "language": "Jazyk",
1941   - "locales": {
1942   - "de_DE": "Deutsch",
1943   - "fr_FR": "Français",
1944   - "zh_CN": "简体中文",
1945   - "zh_TW": "繁體中文",
1946   - "en_US": "English",
1947   - "it_IT": "Italiano",
1948   - "ko_KR": "한글",
1949   - "ru_RU": "Русский",
1950   - "es_ES": "Español",
1951   - "ja_JA": "日本語",
1952   - "tr_TR": "Türkçe",
1953   - "fa_IR": "فارسي",
1954   - "uk_UA": "Українська",
1955   - "cs_CZ": "Česky",
1956   - "el_GR": "Ελληνικά",
1957   - "ro_RO": "Română",
1958   - "lv_LV": "Latviešu"
1959   - }
  1940 + "language": "Jazyk"
1960 1941 }
1961 1942 }
... ...
... ... @@ -2494,7 +2494,8 @@
2494 2494 "ro_RO": "Română",
2495 2495 "lv_LV": "Latviešu",
2496 2496 "ka_GE": "ქართული",
2497   - "pt_BR": "Português do Brasil"
  2497 + "pt_BR": "Português do Brasil",
  2498 + "sl_SI": "Slovenščina"
2498 2499 }
2499 2500 }
2500 2501 }
... ...
... ... @@ -266,7 +266,7 @@
266 266 "add-to-dashboard": "대시보드에 추가",
267 267 "add-widget-to-dashboard": "대시보드에 위젯 추가",
268 268 "selected-attributes": "{ count, plural, 1 {속성 1개} other {속성 #개} } 선택됨",
269   - "selected-telemetry": "{ count, plural, 1 {최근 데이터 1개} other {최근 데이터 #개} } 선택됨"
  269 + "selected-telemetry": "{ count, plural, 1 {최근 데이터 1개} other {최근 데이터 #개} } 선택됨",
270 270 "no-attributes-text": "아무 속성도 찾을 수 없습니다",
271 271 "no-telemetry-text": "아무 텔레메트리도 찾을 수 없습니다."
272 272 },
... ...
  1 +{
  2 + "access": {
  3 + "unauthorized": "Nepooblaščeno",
  4 + "unauthorized-access": "Nepooblaščen dostop",
  5 + "unauthorized-access-text": "Za dostop do tega vira se morate prijaviti!",
  6 + "access-forbidden": "Dostop prepovedan",
  7 + "access-forbidden-text": "Nimate pravic dostopa do te lokacije! <br/> Poskusite se prijaviti z drugim uporabnikom, če še vedno želite dostopati do te lokacije.",
  8 + "refresh-token-expired": "Seja je potekla",
  9 + "refresh-token-failed": "Seje ni mogoče osvežiti",
  10 + "permission-denied": "Dovoljenje zavrnjeno",
  11 + "permission-denied-text": "Nimate dovoljenja za izvedbo tega dejanja!"
  12 + },
  13 + "action": {
  14 + "activate": "Aktiviraj",
  15 + "suspend": "Začasno ustavi",
  16 + "save": "Shrani",
  17 + "saveAs": "Shrani kot",
  18 + "cancel": "Prekliči",
  19 + "ok": "V redu",
  20 + "delete": "Izbriši",
  21 + "add": "Dodaj",
  22 + "yes": "Da",
  23 + "no": "Ne",
  24 + "update": "Nadgradnja",
  25 + "remove": "Odstrani",
  26 + "select": "Izberi",
  27 + "search": "Iskanje",
  28 + "clear-search": "Počisti iskanje",
  29 + "assign": "Dodeli",
  30 + "unassign": "Preklic dodelitve",
  31 + "share": "Deliti",
  32 + "make-private": "Naj bo zasebno",
  33 + "apply": "Uporabi",
  34 + "apply-changes": "Uporabi spremembe",
  35 + "edit-mode": "Način urejanja",
  36 + "enter-edit-mode": "Vstopi v način urejanja",
  37 + "decline-changes": "Zavrni spremembe",
  38 + "close": "Zapri",
  39 + "back": "Nazaj",
  40 + "run": "Zaženi",
  41 + "sign-in": "Prijavite se!",
  42 + "edit": "Uredi",
  43 + "view": "Pogled",
  44 + "create": "Ustvari",
  45 + "drag": "Povleci",
  46 + "refresh": "Osveži",
  47 + "undo": "Razveljavi",
  48 + "copy": "Kopiraj",
  49 + "paste": "Prilepi",
  50 + "copy-reference": "Kopiraj sklic",
  51 + "paste-reference": "Prilepi sklic",
  52 + "import": "Uvozi",
  53 + "export": "Izvozi",
  54 + "share-via": "Deli prek {{provider}}",
  55 + "continue": "Nadaljuj",
  56 + "discard-changes": "Zavrzi spremembe",
  57 + "download": "Prenesi",
  58 + "next-with-label": "Naslednji: {{label}}",
  59 + "read-more": "Preberi več",
  60 + "hide": "Skrij"
  61 + },
  62 + "aggregation": {
  63 + "aggregation": "Združevanje",
  64 + "function": "Funkcija združevanja podatkov",
  65 + "limit": "Največje vrednosti",
  66 + "group-interval": "Interval združevanja",
  67 + "min": "Min",
  68 + "max": "Max",
  69 + "avg": "Povprečje",
  70 + "sum": "Vsota",
  71 + "count": "Štetje",
  72 + "none": "Brez"
  73 + },
  74 + "admin": {
  75 + "general": "Splošno",
  76 + "general-settings": "Splošne nastavitve",
  77 + "outgoing-mail": "Poštni strežnik",
  78 + "outgoing-mail-settings": "Nastavitve strežnika odhodne pošte",
  79 + "system-settings": "Sistemske nastavitve",
  80 + "test-mail-sent": "Testna pošta je bila uspešno poslana!",
  81 + "base-url": "Osnovni URL",
  82 + "base-url-required": "Zahtevan je osnovni URL.",
  83 + "prohibit-different-url": "Prohibit to use hostname from the client request headers",
  84 + "prohibit-different-url-hint": "This setting should be enabled for production environments. May cause security issues when disabled",
  85 + "mail-from": "Pošta od",
  86 + "mail-from-required": "Zahtevana je pošta od.",
  87 + "smtp-protocol": "Protokol SMTP",
  88 + "smtp-host": "SMTP gostitelj",
  89 + "smtp-host-required": "Zahtevan je gostitelj SMTP.",
  90 + "smtp-port": "Vrata SMTP",
  91 + "smtp-port-required": "Navesti morate vrata SMTP.",
  92 + "smtp-port-invalid": "To ne izgleda kot veljavna vrata SMTP.",
  93 + "timeout-msec": "Časovna omejitev (msec)",
  94 + "timeout-required": "Časovna omejitev je potrebna.",
  95 + "timeout-invalid": "To ne izgleda kot veljavna časovna omejitev.",
  96 + "enable-tls": "Omogoči TLS",
  97 + "tls-version": "Različica TLS",
  98 + "enable-proxy": "Omogoči proxy",
  99 + "proxy-host": "Gostitelj proxy",
  100 + "proxy-host-required": "Zahtevan je strežnik proxy.",
  101 + "proxy-port": "Proxy vrata",
  102 + "proxy-port-required": "Vrata proxy so potrebna.",
  103 + "proxy-port-range": "Vrata proxy naj bodo v območju od 1 do 65535.",
  104 + "proxy-user": "Uporabnik proxy",
  105 + "proxy-password": "Geslo proxy",
  106 + "send-test-mail": "Pošlji testno pošto",
  107 + "sms-provider": "SMS provider",
  108 + "sms-provider-settings": "SMS provider settings",
  109 + "sms-provider-type": "SMS provider type",
  110 + "sms-provider-type-required": "SMS provider type is required.",
  111 + "sms-provider-type-aws-sns": "Amazon SNS",
  112 + "sms-provider-type-twilio": "Twilio",
  113 + "aws-access-key-id": "AWS Access Key ID",
  114 + "aws-access-key-id-required": "AWS Access Key ID is required",
  115 + "aws-secret-access-key": "AWS Secret Access Key",
  116 + "aws-secret-access-key-required": "AWS Secret Access Key is required",
  117 + "aws-region": "AWS Region",
  118 + "aws-region-required": "AWS Region is required",
  119 + "number-from": "Phone Number From",
  120 + "number-from-required": "Phone Number From is required.",
  121 + "number-to": "Phone Number To",
  122 + "number-to-required": "Phone Number To is required.",
  123 + "phone-number-hint": "Phone Number in E.164 format, ex. +19995550123",
  124 + "phone-number-pattern": "Invalid phone number. Should be in E.164 format, ex. +19995550123.",
  125 + "sms-message": "SMS message",
  126 + "sms-message-required": "SMS message is required.",
  127 + "sms-message-max-length": "SMS message can't be longer 1600 characters",
  128 + "twilio-account-sid": "Twilio Account SID",
  129 + "twilio-account-sid-required": "Twilio Account SID is required",
  130 + "twilio-account-token": "Twilio Account Token",
  131 + "twilio-account-token-required": "Twilio Account Token is required",
  132 + "send-test-sms": "Send test SMS",
  133 + "test-sms-sent": "Test SMS was successfully sent!",
  134 + "security-settings": "Varnostne nastavitve",
  135 + "password-policy": "Pravilnik o geslih",
  136 + "minimum-password-length": "Najkrajša dolžina gesla",
  137 + "minimum-password-length-required": "Zahtevana je najkrajša dolžina gesla",
  138 + "minimum-password-length-range": "Minimalna dolžina gesla naj bo v območju od 5 do 50",
  139 + "minimum-uppercase-letters": "Najmanjše število velikih črk",
  140 + "minimum-uppercase-letters-range": "Najmanjše število velikih črk ne sme biti negativno",
  141 + "minimum-lowercase-letters": "Najmanjše število malih črk",
  142 + "minimum-lowercase-letters-range": "Najmanjše število malih črk ne sme biti negativno",
  143 + "minimum-digits": "Najmanjše število številk",
  144 + "minimum-digits-range": "Najmanjše število številk ne sme biti negativno",
  145 + "minimum-special-characters": "Najmanjše število posebnih znakov",
  146 + "minimum-special-characters-range": "Najmanjše število posebnih znakov ne sme biti negativno",
  147 + "password-expiration-period-days": "Obdobje veljavnosti gesla v dneh",
  148 + "password-expiration-period-days-range": "Obdobje veljavnosti gesla v dneh ne sme biti negativno",
  149 + "password-reuse-frequency-days": "Pogostost ponovne uporabe gesla v dneh",
  150 + "password-reuse-frequency-days-range": "Pogostost ponovne uporabe gesla v dneh ne sme biti negativna",
  151 + "general-policy": "Splošna politika",
  152 + "max-failed-login-attempts": "Največje število neuspelih poskusov prijave, preden se račun zaklene",
  153 + "minimum-max-failed-login-attempts-range": "Največje število neuspelih poskusov prijave ne sme biti negativno",
  154 + "user-lockout-notification-email": "V primeru zaklepa uporabniškega računa, pošlji obvestilo na e-pošto",
  155 + "domain-name": "Domain name",
  156 + "domain-name-unique": "Domain name and protocol need to unique.",
  157 + "error-verification-url": "A domain name shouldn't contain symbols '/' and ':'. Example: thingsboard.io",
  158 + "oauth2": {
  159 + "access-token-uri": "Access token URI",
  160 + "access-token-uri-required": "Access token URI is required.",
  161 + "activate-user": "Activate user",
  162 + "add-domain": "Add domain",
  163 + "delete-domain": "Delete domain",
  164 + "add-provider": "Add provider",
  165 + "delete-provider": "Delete provider",
  166 + "allow-user-creation": "Allow user creation",
  167 + "always-fullscreen": "Always fullscreen",
  168 + "authorization-uri": "Authorization URI",
  169 + "authorization-uri-required": "Authorization URI is required.",
  170 + "client-authentication-method": "Client authentication method",
  171 + "client-id": "Client ID",
  172 + "client-id-required": "Client ID is required.",
  173 + "client-secret": "Client secret",
  174 + "client-secret-required": "Client secret is required.",
  175 + "custom-setting": "Custom settings",
  176 + "customer-name-pattern": "Customer name pattern",
  177 + "default-dashboard-name": "Default dashboard name",
  178 + "delete-domain-text": "Be careful, after the confirmation a domain and all provider data will be unavailable.",
  179 + "delete-domain-title": "Are you sure you want to delete settings the domain '{{domainName}}'?",
  180 + "delete-registration-text": "Be careful, after the confirmation a provider data will be unavailable.",
  181 + "delete-registration-title": "Are you sure you want to delete the provider '{{name}}'?",
  182 + "email-attribute-key": "Email attribute key",
  183 + "email-attribute-key-required": "Email attribute key is required.",
  184 + "first-name-attribute-key": "First name attribute key",
  185 + "general": "General",
  186 + "jwk-set-uri": "JSON Web Key URI",
  187 + "last-name-attribute-key": "Last name attribute key",
  188 + "login-button-icon": "Login button icon",
  189 + "login-button-label": "Provider label",
  190 + "login-button-label-placeholder": "Login with $(Provider label)",
  191 + "login-button-label-required": "Label is required.",
  192 + "login-provider": "Login provider",
  193 + "mapper": "Mapper",
  194 + "new-domain": "New domain",
  195 + "oauth2": "OAuth2",
  196 + "redirect-uri-template": "Redirect URI template",
  197 + "copy-redirect-uri": "Copy redirect URI",
  198 + "registration-id": "Registration ID",
  199 + "registration-id-required": "Registration ID is required.",
  200 + "registration-id-unique": "Registration ID need to unique for the system.",
  201 + "scope": "Scope",
  202 + "scope-required": "Scope is required.",
  203 + "tenant-name-pattern": "Tenant name pattern",
  204 + "tenant-name-pattern-required": "Tenant name pattern is required.",
  205 + "tenant-name-strategy": "Tenant name strategy",
  206 + "type": "Mapper type",
  207 + "uri-pattern-error": "Invalid URI format.",
  208 + "url": "URL",
  209 + "url-pattern": "Invalid URL format.",
  210 + "url-required": "URL is required.",
  211 + "user-info-uri": "User info URI",
  212 + "user-info-uri-required": "User info URI is required.",
  213 + "user-name-attribute-name": "User name attribute key",
  214 + "user-name-attribute-name-required": "User name attribute key is required",
  215 + "protocol": "Protocol",
  216 + "domain-schema-http": "HTTP",
  217 + "domain-schema-https": "HTTPS",
  218 + "domain-schema-mixed": "HTTP+HTTPS",
  219 + "enable": "Enable OAuth2 settings"
  220 + }
  221 + },
  222 + "alarm": {
  223 + "alarm": "Alarm",
  224 + "alarms": "Alarmi",
  225 + "select-alarm": "Izberi alarm",
  226 + "no-alarms-matching": "Najdeni niso bili nobeni alarmi, ki se ujemajo z '{{entity}}'.",
  227 + "alarm-required": "Potreben je alarm",
  228 + "alarm-status": "Stanje alarma",
  229 + "alarm-status-list": "Seznam stanja alarma",
  230 + "any-status": "Kateri koli status",
  231 + "search-status": {
  232 + "ANY": "Katerikoli",
  233 + "ACTIVE": "Aktivno",
  234 + "CLEARED": "Počiščeno",
  235 + "ACK": "Potrjeno",
  236 + "UNACK": "Nepriznano"
  237 + },
  238 + "display-status": {
  239 + "ACTIVE_UNACK": "Aktivno nepriznano",
  240 + "ACTIVE_ACK": "Aktivno potrjeno",
  241 + "CLEARED_UNACK": "Počiščeno nepriznano",
  242 + "CLEARED_ACK": "Počiščeno potrjeno"
  243 + },
  244 + "no-alarms-prompt": "Ni najdenih alarmov",
  245 + "created-time": "Ustvarjeni čas",
  246 + "type": "Vrsta",
  247 + "severity": "Resnost",
  248 + "originator": "Izvirnik",
  249 + "originator-type": "Vrsta izvirnika",
  250 + "details": "Podrobnosti",
  251 + "status": "Stanje",
  252 + "alarm-details": "Podrobnosti alarma",
  253 + "start-time": "Začetni čas",
  254 + "end-time": "Končni čas",
  255 + "ack-time": "Priznani čas",
  256 + "clear-time": "Očiščen čas",
  257 + "alarm-severity-list": "Seznam resnosti alarma",
  258 + "any-severity": "Katerakoli resnost",
  259 + "severity-critical": "Kritična",
  260 + "severity-major": "Visoka",
  261 + "severity-minor": "Nizka",
  262 + "severity-warning": "Opozorilo",
  263 + "severity-indeterminate": "Nedoločeno",
  264 + "acknowledge": "Potrdi",
  265 + "clear": "Počisti",
  266 + "search": "Iskanje alarmov",
  267 + "selected-alarms": "{ count, plural, 1 {1 alarm} other {# alarms} } izbran",
  268 + "no-data": "Ni podatkov za prikaz",
  269 + "polling-interval": "Interval iskanja alarmov (s)",
  270 + "polling-interval-required": "Zahtevan je interval glasovanja alarmov.",
  271 + "min-polling-interval-message": "Dovoljen je vsaj 1-sekundni interval glasovanja.",
  272 + "aknowledge-alarms-title": "Potrdite { count, plural, 1 {1 alarm} other {# alarms} }",
  273 + "aknowledge-alarms-text": "Ali ste prepričani, da želite potrditi { count, plural, 1 {1 alarm} other {# alarms} }?",
  274 + "aknowledge-alarm-title": "Potrdite alarm",
  275 + "aknowledge-alarm-text": "Ali ste prepričani, da želite potrditi alarm?",
  276 + "clear-alarms-title": "Počisti { count, plural, 1 {1 alarm} other {# alarmi} }",
  277 + "clear-alarms-text": "Ali ste prepričani, da želite počistiti { count, plural, 1 {1 alarm} other {# alarme} }?",
  278 + "clear-alarm-title": "Počisti alarm",
  279 + "clear-alarm-text": "Ali ste prepričani, da želite počistiti alarm?",
  280 + "alarm-status-filter": "Filter stanja alarma",
  281 + "alarm-filter": "Alarmni filter",
  282 + "max-count-load": "Največje število naloženih alarmov (0 - neomejeno)",
  283 + "max-count-load-required": "Zahtevano je največje število alarmov za nalaganje.",
  284 + "max-count-load-error-min": "Najmanjša vrednost je 0.",
  285 + "fetch-size": "Velikost prenosa",
  286 + "fetch-size-required": "Zahtevana je velikost prenosa.",
  287 + "fetch-size-error-min": "Najmanjša vrednost je 10.",
  288 + "alarm-type-list": "Seznam vrst alarmov",
  289 + "any-type": "Katerakoli vrsta",
  290 + "search-propagated-alarms": "Iskanje razširjenih alarmov"
  291 + },
  292 + "alias": {
  293 + "add": "Dodaj vzdevek",
  294 + "edit": "Uredi vzdevek",
  295 + "name": "Ime vzdevka",
  296 + "name-required": "Ime vzdevka je obvezno",
  297 + "duplicate-alias": "Vzdevek z istim imenom že obstaja.",
  298 + "filter-type-single-entity": "Enota",
  299 + "filter-type-entity-list": "Seznam entitet",
  300 + "filter-type-entity-name": "Ime entitete",
  301 + "filter-type-state-entity": "Subjekt iz stanja nadzorne plošče",
  302 + "filter-type-state-entity-description": "Entiteta, vzeta iz parametrov stanja nadzorne plošče",
  303 + "filter-type-asset-type": "Vrsta sredstva",
  304 + "filter-type-asset-type-description": "Sredstva vrste '{{assetType}}'",
  305 + "filter-type-asset-type-and-name-description": "Sredstva vrste '{{assetType}}' in z imenom, ki se začne z '{{prefix}}'",
  306 + "filter-type-device-type": "Vrsta naprave",
  307 + "filter-type-device-type-description": "Naprave tipa '{{deviceType}}'",
  308 + "filter-type-device-type-and-name-description": "Naprave vrste '{{deviceType}}' in z imenom, ki se začne z '{{prefix}}'",
  309 + "filter-type-entity-view-type": "Vrsta pogleda entitete",
  310 + "filter-type-entity-view-type-description": "Pogledi entitet vrste '{{entityView}}'",
  311 + "filter-type-entity-view-type-and-name-description": "Pogledi entitet vrste '{{entityView}}' in z imenom, ki se začne z '{{prefix}}'",
  312 + "filter-type-relations-query": "Poizvedba odnosov",
  313 + "filter-type-relations-query-description": "{{entities}}, ki imajo {{relationType}} relacijo {{direction}} {{rootEntity}}",
  314 + "filter-type-asset-search-query": "Iskalna poizvedba sredstva",
  315 + "filter-type-asset-search-query-description": "Sredstva s tipi {{assetTypes}}, ki imajo {{relationType}} relacijo {{direction}} {{rootEntity}}",
  316 + "filter-type-device-search-query": "Iskalna poizvedba naprave",
  317 + "filter-type-device-search-query-description": "Naprave s tipi {{deviceTypes}}, ki imajo {{relationType}} relacijo {{direction}} {{rootEntity}}",
  318 + "filter-type-entity-view-search-query": "Iskalna poizvedba pogleda entitete",
  319 + "filter-type-entity-view-search-query-description": "Pogledi entitet s tipi {{entityViewTypes}}, ki imajo {{relationType}} relacijo {{direction}} {{rootEntity}}",
  320 + "filter-type-apiUsageState": "Api Usage State",
  321 + "entity-filter": "Filter entitet",
  322 + "resolve-multiple": "Reši kot več entitet",
  323 + "filter-type": "Vrsta filtra",
  324 + "filter-type-required": "Zahtevana je vrsta filtra.",
  325 + "entity-filter-no-entity-matched": "Najdenih ni nobenih entitet, ki se ujemajo z navedenim filtrom.",
  326 + "no-entity-filter-specified": "Ni določen noben filter entitete",
  327 + "root-state-entity": "Uporabi entiteto stanja na nadzorni plošči kot izvorno",
  328 + "last-level-relation": "Pridobi samo razmerje na zadnji ravni",
  329 + "root-entity": "Izvorna entiteta",
  330 + "state-entity-parameter-name": "Ime parametra državne entitete",
  331 + "default-state-entity": "Privzeta državna entiteta",
  332 + "default-entity-parameter-name": "Privzeto",
  333 + "max-relation-level": "Najvišja stopnja relacije",
  334 + "unlimited-level": "Neomejena raven",
  335 + "state-entity": "Entiteta stanja nadzorne plošče",
  336 + "all-entities": "Vsi subjekti",
  337 + "any-relation": "katerikoli"
  338 + },
  339 + "asset": {
  340 + "asset": "Sredstvo",
  341 + "assets": "Sredstva",
  342 + "management": "Upravljanje sredstev",
  343 + "view-assets": "Ogled sredstev",
  344 + "add": "Dodaj sredstvo",
  345 + "assign-to-customer": "Dodeli stranki",
  346 + "assign-asset-to-customer": "Dodeli sredstva stranki",
  347 + "assign-asset-to-customer-text": "Izberite sredstva, ki jih želite dodeliti stranki",
  348 + "no-assets-text": "Sredstva ni bilo mogoče najti",
  349 + "assign-to-customer-text": "Izberite stranko, ki bo dodelila sredstvo (-a)",
  350 + "public": "Javno",
  351 + "assignedToCustomer": "Dodeljeno stranki",
  352 + "make-public": "Naj bo sredstvo javno",
  353 + "make-private": "Naj bo sredstvo zasebno",
  354 + "unassign-from-customer": "Odjavi od stranke",
  355 + "delete": "Izbriši sredstvo",
  356 + "asset-public": "Sredstvo je javno",
  357 + "asset-type": "Vrsta sredstva",
  358 + "asset-type-required": "Zahtevana je vrsta sredstva.",
  359 + "select-asset-type": "Izberite vrsto sredstva",
  360 + "enter-asset-type": "Vnesite vrsto sredstva",
  361 + "any-asset": "Katerokoli sredstvo",
  362 + "no-asset-types-matching": "Najdena ni bila nobena vrsta sredstva, ki se ujema z '{{entitySubtype}}'.",
  363 + "asset-type-list-empty": "Izbrana ni nobena vrsta sredstva.",
  364 + "asset-types": "Vrste sredstev",
  365 + "name": "Ime",
  366 + "name-required": "Ime je obvezno.",
  367 + "description": "Opis",
  368 + "type": "Vrsta",
  369 + "type-required": "Tip je obvezen.",
  370 + "details": "Podrobnosti",
  371 + "events": "Dogodki",
  372 + "add-asset-text": "Dodaj novo sredstvo",
  373 + "asset-details": "Podrobnosti o sredstvih",
  374 + "assign-assets": "Dodelitev sredstev",
  375 + "assign-assets-text": "Stranki dodeli { count, plural, 1 {1 sredstvo} other {# assets} }",
  376 + "delete-assets": "Izbriši sredstva",
  377 + "unassign-assets": "Preklic dodelitve sredstev",
  378 + "unassign-assets-action-title": "Stranki ne dodeli { count, plural, 1 {1 sredstvo} other {# assets} }",
  379 + "assign-new-asset": "Dodeli novo sredstvo",
  380 + "delete-asset-title": "Ali ste prepričani, da želite izbrisati sredstvo '{{assetName}}'?",
  381 + "delete-asset-text": "Bodite previdni, po potrditvi bo sredstvo in vsi povezani podatki nepopravljivi.",
  382 + "delete-assets-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 sredstvo} other {# assets} }?",
  383 + "delete-assets-action-title": "Izbriši { count, plural, 1 {1 sredstvo} other {# assets} }",
  384 + "delete-assets-text": "Bodite previdni, po potrditvi bodo vsa izbrana sredstva odstranjena in vsi povezani podatki bodo postali nepopravljivi.",
  385 + "make-public-asset-title": "Ali ste prepričani, da želite sredstvo '{{assetName}}' objaviti kot javno?",
  386 + "make-public-asset-text": "Po potrditvi bodo sredstvo in vsi njegovi podatki javno dostopni in dostopni drugim.",
  387 + "make-private-asset-title": "Ali ste prepričani, da želite sredstvo '{{assetName}}' narediti zasebno?",
  388 + "make-private-asset-text": "Po potrditvi bodo sredstvo in vsi njegovi podatki postali zasebni in ne bodo dostopni drugim.",
  389 + "unassign-asset-title": "Ali ste prepričani, da želite dodeliti sredstvo '{{assetName}}'?",
  390 + "unassign-asset-text": "Po potrditvi sredstvo ne bo dodeljeno in stranka ne bo dostopna.",
  391 + "unassign-asset": "Preklic dodelitve sredstva",
  392 + "unassign-assets-title": "Ali ste prepričani, da želite odpovedati { count, plural, 1 {1 asset} other {# assets} }?",
  393 + "unassign-assets-text": "Po potrditvi vsa izbrana sredstva ne bodo dodeljena in stranka ne bo dostopna do njih.",
  394 + "copyId": "Kopiraj ID sredstva",
  395 + "idCopiedMessage": "ID sredstva je kopiran v odložišče",
  396 + "select-asset": "Izberi sredstvo",
  397 + "no-assets-matching": "Nobeno sredstvo, ki se ujema z '{{entity}}', ni bilo mogoče najti.",
  398 + "asset-required": "Zahtevano je sredstvo",
  399 + "name-starts-with": "Ime sredstva se začne z",
  400 + "import": "Uvozi sredstva",
  401 + "asset-file": "Datoteka sredstva",
  402 + "search": "Išči sredstva",
  403 + "selected-assets": "{ count, plural, 1 {1 asset} other {# assets} } izbrano",
  404 + "label": "Oznaka"
  405 + },
  406 + "attribute": {
  407 + "attributes": "Lastnosti",
  408 + "latest-telemetry": "Najnovejša telemetrija",
  409 + "attributes-scope": "Obseg atributov entitete",
  410 + "scope-latest-telemetry": "Najnovejša telemetrija",
  411 + "scope-client": "Atributi odjemalca",
  412 + "scope-server": "Atributi strežnika",
  413 + "scope-shared": "Skupni atributi",
  414 + "add": "Dodaj atribut",
  415 + "key": "Ključ",
  416 + "last-update-time": "Čas zadnje posodobitve",
  417 + "key-required": "Potreben je ključ atributa.",
  418 + "value": "Vrednost",
  419 + "value-required": "Vrednost atributa je obvezna.",
  420 + "delete-attributes-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 attribute} other {# attributes} }?",
  421 + "delete-attributes-text": "Bodite previdni, po potrditvi bodo odstranjeni vsi izbrani atributi.",
  422 + "delete-attributes": "Izbriši atribute",
  423 + "enter-attribute-value": "Vnesite vrednost atributa",
  424 + "show-on-widget": "Pokaži na pripomočku",
  425 + "widget-mode": "Način pripomočka",
  426 + "next-widget": "Naslednji pripomoček",
  427 + "prev-widget": "Prejšnji pripomoček",
  428 + "add-to-dashboard": "Dodaj na nadzorno ploščo",
  429 + "add-widget-to-dashboard": "Dodaj pripomoček na nadzorno ploščo",
  430 + "selected-attributes": "{ count, plural, 1 {1 attribute} other {# attributes} } izbrano",
  431 + "selected-telemetry": "{ count, plural, 1 {1 telemetry unit} other {# telemetry units} } izbran",
  432 + "no-attributes-text": "Ni najdenih atributov",
  433 + "no-telemetry-text": "Telemetrija ni najdena"
  434 + },
  435 + "api-usage": {
  436 + "api-usage": "Api Usage",
  437 + "data-points": "Data points",
  438 + "data-points-storage-days": "Data points storage days",
  439 + "email": "Email",
  440 + "email-messages": "Email messages",
  441 + "email-messages-daily-activity": "Email messages daily activity",
  442 + "email-messages-hourly-activity": "Email messages hourly activity",
  443 + "email-messages-monthly-activity": "Email messages monthly activity",
  444 + "exceptions": "Exceptions",
  445 + "executions": "Executions",
  446 + "javascript": "JavaScript",
  447 + "javascript-executions": "JavaScript executions",
  448 + "javascript-functions": "JavaScript functions",
  449 + "javascript-functions-daily-activity": "JavaScript functions daily activity",
  450 + "javascript-functions-hourly-activity": "JavaScript functions hourly activity",
  451 + "javascript-functions-monthly-activity": "JavaScript functions monthly activity",
  452 + "latest-error": "Latest Error",
  453 + "messages": "Messages",
  454 + "permanent-failures": "${entityName} Permanent Failures",
  455 + "permanent-timeouts": "${entityName} Permanent Timeouts",
  456 + "processing-failures": "${entityName} Processing Failures",
  457 + "processing-failures-and-timeouts": "Processing Failures and Timeouts",
  458 + "processing-timeouts": "${entityName} Processing Timeouts",
  459 + "queue-stats": "Queue Stats",
  460 + "rule-chain": "Rule Chain",
  461 + "rule-engine": "Rule Engine",
  462 + "rule-engine-daily-activity": "Rule Engine daily activity",
  463 + "rule-engine-executions": "Rule Engine executions",
  464 + "rule-engine-hourly-activity": "Rule Engine hourly activity",
  465 + "rule-engine-monthly-activity": "Rule Engine monthly activity",
  466 + "rule-engine-statistics": "Rule Engine Statistics",
  467 + "rule-node": "Rule Node",
  468 + "sms": "SMS",
  469 + "sms-messages": "SMS messages",
  470 + "sms-messages-daily-activity": "SMS messages daily activity",
  471 + "sms-messages-hourly-activity": "SMS messages hourly activity",
  472 + "sms-messages-monthly-activity": "SMS messages monthly activity",
  473 + "successful": "${entityName} Successful",
  474 + "telemetry": "Telemetry",
  475 + "telemetry-persistence": "Telemetry persistence",
  476 + "telemetry-persistence-daily-activity": "Telemetry persistence daily activity",
  477 + "telemetry-persistence-hourly-activity": "Telemetry persistence hourly activity",
  478 + "telemetry-persistence-monthly-activity": "Telemetry persistence monthly activity",
  479 + "transport": "Transport",
  480 + "transport-daily-activity": "Transport daily activity",
  481 + "transport-data-points": "Transport data points",
  482 + "transport-hourly-activity": "Transport hourly activity",
  483 + "transport-messages": "Transport messages",
  484 + "transport-monthly-activity": "Transport monthly activity",
  485 + "view-details": "View details",
  486 + "view-statistics": "View statistics"
  487 + },
  488 + "audit-log": {
  489 + "audit": "Revizija",
  490 + "audit-logs": "Revizijski dnevniki",
  491 + "timestamp": "Časovni žig",
  492 + "entity-type": "Vrsta entitete",
  493 + "entity-name": "Ime entitete",
  494 + "user": "Uporabnik",
  495 + "type": "Vrsta",
  496 + "status": "Stanje",
  497 + "details": "Podrobnosti",
  498 + "type-added": "Dodano",
  499 + "type-deleted": "Izbrisano",
  500 + "type-updated": "Posodobljeno",
  501 + "type-attributes-updated": "Atributi posodobljeni",
  502 + "type-attributes-deleted": "Atributi izbrisani",
  503 + "type-rpc-call": "Klic RPC",
  504 + "type-credentials-updated": "Poverilnice posodobljene",
  505 + "type-assigned-to-customer": "Dodeljeno kupcu",
  506 + "type-unassigned-from-customer": "Odstop od stranke",
  507 + "type-activated": "Aktivirano",
  508 + "type-suspended": "Suspendirano",
  509 + "type-credentials-read": "Poverilnice prebrane",
  510 + "type-attributes-read": "Atributi prebrani",
  511 + "type-relation-add-or-update": "Razmerje posodobljeno",
  512 + "type-relation-delete": "Razmerje izbrisano",
  513 + "type-relations-delete": "Vse povezave izbrisane",
  514 + "type-alarm-ack": "Potrjeno",
  515 + "type-alarm-clear": "Počiščeno",
  516 + "type-login": "Vpiši se",
  517 + "type-logout": "Odjava",
  518 + "type-lockout": "Izključitev",
  519 + "status-success": "Uspeh",
  520 + "status-failure": "Neuspeh",
  521 + "audit-log-details": "Podrobnosti dnevnika revizije",
  522 + "no-audit-logs-prompt": "Ni najdenih dnevnikov",
  523 + "action-data": "Podatki o dejanju",
  524 + "failure-details": "Podrobnosti o napaki",
  525 + "search": "Išči dnevnike revizije",
  526 + "clear-search": "Počisti iskanje",
  527 + "type-assigned-from-tenant": "Dodeljeno od najemnika",
  528 + "type-assigned-to-tenant": "Dodeljeno najemniku",
  529 + "type-provision-success": "Device provisioned",
  530 + "type-provision-failure": "Device provisioning was failed"
  531 + },
  532 + "confirm-on-exit": {
  533 + "message": "Imate neshranjene spremembe. Ali ste prepričani, da želite zapustiti to stran?",
  534 + "html-message": "Imate neshranjene spremembe. <br/> Ali ste prepričani, da želite zapustiti to stran?",
  535 + "title": "Neshranjene spremembe"
  536 + },
  537 + "contact": {
  538 + "country": "Država",
  539 + "city": "Mesto",
  540 + "state": "Država / Regija",
  541 + "postal-code": "Poštna številka",
  542 + "postal-code-invalid": "Neveljavna oblika poštne številke.",
  543 + "address": "Naslov",
  544 + "address2": "Naslov 2",
  545 + "phone": "Telefon",
  546 + "email": "E-naslov",
  547 + "no-address": "Brez naslova"
  548 + },
  549 + "common": {
  550 + "username": "Uporabniško ime",
  551 + "password": "Geslo",
  552 + "enter-username": "Vnesite uporabniško ime",
  553 + "enter-password": "Vnesite geslo",
  554 + "enter-search": "Vnesite iskanje",
  555 + "created-time": "Ustvarjeni čas",
  556 + "loading": "Nalaganje..."
  557 + },
  558 + "content-type": {
  559 + "json": "Json",
  560 + "text": "Besedilo",
  561 + "binary": "Binarni (Base64)"
  562 + },
  563 + "customer": {
  564 + "customer": "Stranka",
  565 + "customers": "Stranke",
  566 + "management": "Upravljanje strank",
  567 + "dashboard": "Nadzorna plošča stranke",
  568 + "dashboards": "Nadzorne plošče strank",
  569 + "devices": "Naprave za stranke",
  570 + "entity-views": "Pogledi stranke",
  571 + "assets": "Sredstva strank",
  572 + "public-dashboards": "Javne nadzorne plošče",
  573 + "public-devices": "Javne naprave",
  574 + "public-assets": "Javno sredstvo",
  575 + "public-entity-views": "Pogledi javnih subjektov",
  576 + "add": "Dodaj stranko",
  577 + "delete": "Izbriši stranko",
  578 + "manage-customer-users": "Upravljanje uporabniških strank",
  579 + "manage-customer-devices": "Upravljanje naprav strank",
  580 + "manage-customer-dashboards": "Upravljanje nadzornih plošč za stranke",
  581 + "manage-public-devices": "Upravljanje javnih naprav",
  582 + "manage-public-dashboards": "Upravljanje javnih nadzornih plošč",
  583 + "manage-customer-assets": "Upravljanje sredstev strank",
  584 + "manage-public-assets": "Upravljanje javnih sredstev",
  585 + "add-customer-text": "Dodaj novo stranko",
  586 + "no-customers-text": "Nobena stranka ni najdena",
  587 + "customer-details": "Podrobnosti o stranki",
  588 + "delete-customer-title": "Ali ste prepričani, da želite izbrisati stranko '{{customerTitle}}'?",
  589 + "delete-customer-text": "Previdno, po potrditvi bo stranka in vsi povezani podatki postali nepopravljivi.",
  590 + "delete-customers-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 customer} other {# customers} }?",
  591 + "delete-customers-action-title": "Izbriši { count, plural, 1 {1 customer} other {# customers} }",
  592 + "delete-customers-text": "Bodite previdni, po potrditvi bodo vse izbrane stranke odstranjene in vsi povezani podatki bodo postali nepopravljivi.",
  593 + "manage-users": "Upravljanje uporabnikov",
  594 + "manage-assets": "Upravljanje sredstev",
  595 + "manage-devices": "Upravljanje naprav",
  596 + "manage-dashboards": "Upravljanje nadzornih plošč",
  597 + "title": "Naslov",
  598 + "title-required": "Naslov je obvezen.",
  599 + "description": "Opis",
  600 + "details": "Podrobnosti",
  601 + "events": "Dogodki",
  602 + "copyId": "Kopiraj ID stranke",
  603 + "idCopiedMessage": "Id stranke je kopiran v odložišče",
  604 + "select-customer": "Izberi stranko",
  605 + "no-customers-matching": "Najdena ni bila nobena stranka, ki se ujema z '{{entity}}'.",
  606 + "customer-required": "Stranka je obvezna",
  607 + "select-default-customer": "Izberi privzeto stranko",
  608 + "default-customer": "Privzeta stranka",
  609 + "default-customer-required": "Za razhroščevanje nadzorne plošče na ravni najemnika je potrebna privzeta stranka",
  610 + "search": "Iskanje strank",
  611 + "selected-customers": "{ count, plural, 1 {1 customer} other {# customers} } izbrano"
  612 + },
  613 + "datetime": {
  614 + "date-from": "Datum, od",
  615 + "time-from": "Čas od",
  616 + "date-to": "Datum do",
  617 + "time-to": "Čas do"
  618 + },
  619 + "dashboard": {
  620 + "dashboard": "Nadzorna plošča",
  621 + "dashboards": "Nadzorne plošče",
  622 + "management": "Upravljanje z nadzorno ploščo",
  623 + "view-dashboards": "Ogled nadzornih plošč",
  624 + "add": "Dodaj nadzorno ploščo",
  625 + "assign-dashboard-to-customer": "Dodeli nadzorne plošče strankam",
  626 + "assign-dashboard-to-customer-text": "Izberite nadzorne plošče, ki jih želite dodeliti stranki",
  627 + "assign-to-customer-text": "Izberite stranko, ki bo dodelila nadzorno ploščo (-e)",
  628 + "assign-to-customer": "Dodeli stranki",
  629 + "unassign-from-customer": "Odvzemi stranki",
  630 + "make-public": "Naredi nadzorno ploščo javno",
  631 + "make-private": "Naj nadzorna plošča postane zasebna",
  632 + "manage-assigned-customers": "Upravljanje dodeljenih strank",
  633 + "assigned-customers": "Dodeljene stranke",
  634 + "assign-to-customers": "Dodeli nadzorne plošče strankam",
  635 + "assign-to-customers-text": "Izberite stranke, ki bodo dodelile nadzorno ploščo (-e)",
  636 + "unassign-from-customers": "Odstrani nadzorne plošče strankam",
  637 + "unassign-from-customers-text": "Na nadzorni plošči izberite stranke, ki jih želite odjaviti",
  638 + "no-dashboards-text": "Ni najdenih nadzornih plošč",
  639 + "no-widgets": "Pripomočki niso nastavljeni",
  640 + "add-widget": "Dodaj nov pripomoček",
  641 + "title": "Naslov",
  642 + "select-widget-title": "Izberi pripomoček",
  643 + "select-widget-subtitle": "Seznam razpoložljivih vrst gradnikov",
  644 + "delete": "Izbriši nadzorno ploščo",
  645 + "title-required": "Naslov je obvezen.",
  646 + "description": "Opis",
  647 + "details": "Podrobnosti",
  648 + "dashboard-details": "Podrobnosti o nadzorni plošči",
  649 + "add-dashboard-text": "Dodaj novo nadzorno ploščo",
  650 + "assign-dashboards": "Dodeli nadzorne plošče",
  651 + "assign-new-dashboard": "Dodeli novo nadzorno ploščo",
  652 + "assign-dashboards-text": "Strankam dodeli { count, plural, 1 {1 dashboard} other {# dashboards} }",
  653 + "unassign-dashboards-action-text": "Strankam ne dodeli { count, plural, 1 {1 dashboard} other {# dashboards} }",
  654 + "delete-dashboards": "Izbriši nadzorne plošče",
  655 + "unassign-dashboards": "Preklic dodelitve nadzornih plošč",
  656 + "unassign-dashboards-action-title": "Stranki odstrani { count, plural, 1 {1 dashboard} other {# dashboards} }",
  657 + "delete-dashboard-title": "Ali ste prepričani, da želite izbrisati nadzorno ploščo '{{dashboardTitle}}'?",
  658 + "delete-dashboard-text": "Previdno, po potrditvi bodo armaturna plošča in vsi povezani podatki nepopravljivi.",
  659 + "delete-dashboards-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 dashboard} other {# dashboards} }?",
  660 + "delete-dashboards-action-title": "Izbriši { count, plural, 1 {1 dashboard} other {# dashboards} }",
  661 + "delete-dashboards-text": "Bodite previdni, po potrditvi bodo vse izbrane nadzorne plošče odstranjene in vsi povezani podatki bodo postali nepopravljivi.",
  662 + "unassign-dashboard-title": "Ali ste prepričani, da želite odstraniti nadzorno ploščo '{{dashboardTitle}}'?",
  663 + "unassign-dashboard-text": "Po potrditvi bo nadzorna plošča odstranjena in nedostopna stranki.",
  664 + "unassign-dashboard": "Preklic dodelitve nadzorne plošče",
  665 + "unassign-dashboards-title": "Ali ste prepričani, da želite odstraniti { count, plural, 1 {1 dashboard} other {# dashboards} }?",
  666 + "unassign-dashboards-text": "Po potrditvi bodo vse izbrane nadzorne plošče odstranjene in nedostopne stranki.",
  667 + "public-dashboard-title": "Nadzorna plošča je zdaj javna",
  668 + "public-dashboard-text": "Vaša nadzorna plošča <b>{{dashboardTitle}}</b> je zdaj javna in dostopna prek naslednje javne <a href='{{publicLink}}' target='_blank'>povezave</a>",
  669 + "public-dashboard-notice": "<b>Opomba:</b> Ne pozabite objaviti sorodnih naprav za dostop do njihovih podatkov.",
  670 + "make-private-dashboard-title": "Ali ste prepričani, da želite nadzorno ploščo '{{dashboardTitle}}' narediti zasebno?",
  671 + "make-private-dashboard-text": "Po potrditvi bo nadzorna plošča postala zasebna in ne bo dostopna drugim.",
  672 + "make-private-dashboard": "Naj nadzorna plošča postane zasebna",
  673 + "socialshare-text": "'{{dashboardTitle}}' poganja ThingsBoard",
  674 + "socialshare-title": "'{{dashboardTitle}}' poganja ThingsBoard",
  675 + "select-dashboard": "Izberi nadzorno ploščo",
  676 + "no-dashboards-matching": "Najdena ni bila nobena nadzorna plošča, ki se ujema z '{{entity}}'.",
  677 + "dashboard-required": "Potrebna je nadzorna plošča.",
  678 + "select-existing": "Izberi obstoječo nadzorno ploščo",
  679 + "create-new": "Ustvari novo nadzorno ploščo",
  680 + "new-dashboard-title": "Nov naslov nadzorne plošče",
  681 + "open-dashboard": "Odpri nadzorno ploščo",
  682 + "set-background": "Nastavi ozadje",
  683 + "background-color": "Barva ozadja",
  684 + "background-image": "Slika ozadja",
  685 + "background-size-mode": "Način velikosti ozadja",
  686 + "no-image": "Izbrana ni nobena slika",
  687 + "drop-image": "Spustite sliko ali kliknite, da izberete datoteko za nalaganje.",
  688 + "settings": "Nastavitve",
  689 + "columns-count": "Število stolpcev",
  690 + "columns-count-required": "Število stolpcev je obvezno.",
  691 + "min-columns-count-message": "Dovoljeno je samo 10 najmanjših števcev stolpcev.",
  692 + "max-columns-count-message": "Dovoljeno je samo 1000 največje število stolpcev.",
  693 + "widgets-margins": "Razmik med pripomočki",
  694 + "margin-required": "Zahtevana je vrednost razmika.",
  695 + "min-margin-message": "Najmanjša dovoljena vrednost razmika je 0.",
  696 + "max-margin-message": "Največja dovoljena vrednost razmika je 50.",
  697 + "horizontal-margin": "Vodoravni razmik",
  698 + "horizontal-margin-required": "Zahtevana je horizontalna vrednost razmika.",
  699 + "min-horizontal-margin-message": "Najmanjša dovoljena vrednost vodoravnega razmika je 0.",
  700 + "max-horizontal-margin-message": "Največja dovoljena vrednost vodoravnega razmika je 50.",
  701 + "vertical-margin": "Navpični razmik",
  702 + "vertical-margin-required": "Zahtevana je navpična vrednost razmika.",
  703 + "min-vertical-margin-message": "Najmanjša dovoljena vrednost navpičnega razmika je 0.",
  704 + "max-vertical-margin-message": "Največja dovoljena vrednost navpičnega razmika je 50.",
  705 + "autofill-height": "Samodejno polnjenje višine postavitve",
  706 + "mobile-layout": "Nastavitve mobilne postavitve",
  707 + "mobile-row-height": "Višina mobilne vrstice, px",
  708 + "mobile-row-height-required": "Vrednost višine mobilne vrstice je obvezna.",
  709 + "min-mobile-row-height-message": "Najmanjša dovoljena vrednost višine mobilne vrstice je 5 slikovnih pik.",
  710 + "max-mobile-row-height-message": "Največja dovoljena vrednost višine mobilne vrstice je 200 slikovnih pik.",
  711 + "display-title": "Prikaži naslov nadzorne plošče",
  712 + "toolbar-always-open": "Orodna vrstica naj bo odprta",
  713 + "title-color": "Barva naslova",
  714 + "display-dashboards-selection": "Prikaži izbiro nadzornih plošč",
  715 + "display-entities-selection": "Prikaži izbor entitet",
  716 + "display-filters": "Prikaži filtre",
  717 + "display-dashboard-timewindow": "Prikaži časovno okno",
  718 + "display-dashboard-export": "Prikaži izvoz",
  719 + "import": "Uvozi nadzorno ploščo",
  720 + "export": "Izvozi nadzorno ploščo",
  721 + "export-failed-error": "Nadzorne plošče ni mogoče izvoziti: {{error}}",
  722 + "create-new-dashboard": "Ustvari novo nadzorno ploščo",
  723 + "dashboard-file": "Datoteka nadzorne plošče",
  724 + "invalid-dashboard-file-error": "Nadzorne plošče ni mogoče uvoziti: neveljavna struktura podatkov na nadzorni plošči.",
  725 + "dashboard-import-missing-aliases-title": "Konfiguriranje vzdevkov, ki jih uporablja uvožena nadzorna plošča",
  726 + "create-new-widget": "Ustvari nov pripomoček",
  727 + "import-widget": "Uvozi pripomoček",
  728 + "widget-file": "Datoteka pripomočka",
  729 + "invalid-widget-file-error": "Pripomočka ni mogoče uvoziti: neveljavna struktura podatkov pripomočka.",
  730 + "widget-import-missing-aliases-title": "Konfiguriranje vzdevkov, ki jih uporablja uvoženi gradnik",
  731 + "open-toolbar": "Odpri orodno vrstico armaturne plošče",
  732 + "close-toolbar": "Zapri orodno vrstico",
  733 + "configuration-error": "Napaka v konfiguraciji",
  734 + "alias-resolution-error-title": "Napaka pri konfiguraciji vzdevkov na nadzorni plošči",
  735 + "invalid-aliases-config": "Ni mogoče najti nobene naprave, ki se ujema z nekaterimi filtri vzdevkov. <br/> Prosimo, obrnite se na skrbnika, da odpravite to težavo.",
  736 + "select-devices": "Izberi naprave",
  737 + "assignedToCustomer": "Dodeljeno stranki",
  738 + "assignedToCustomers": "Dodeljeno strankam",
  739 + "public": "Javno",
  740 + "public-link": "Javna povezava",
  741 + "copy-public-link": "Kopiraj javno povezavo",
  742 + "public-link-copied-message": "Javna povezava na nadzorni plošči je bila kopirana v odložišče",
  743 + "manage-states": "Upravljanje stanj nadzorne plošče",
  744 + "states": "Stanja na nadzorni plošči",
  745 + "search-states": "Išči stanja na nadzorni plošči",
  746 + "selected-states": "{ count, plural, 1 {1 dashboard state} other {# dashboard state} } izbrano",
  747 + "edit-state": "Uredi stanje nadzorne plošče",
  748 + "delete-state": "Izbriši stanje armaturne plošče",
  749 + "add-state": "Dodaj stanje nadzorne plošče",
  750 + "no-states-text": "Ni nobene države",
  751 + "state": "Stanje nadzorne plošče",
  752 + "state-name": "Ime",
  753 + "state-name-required": "Ime stanja nadzorne plošče je obvezno.",
  754 + "state-id": "ID države",
  755 + "state-id-required": "ID stanja nadzorne plošče je obvezen.",
  756 + "state-id-exists": "Stanje nadzorne plošče z istim ID že obstaja.",
  757 + "is-root-state": "Izvorno stanje",
  758 + "delete-state-title": "Izbriši stanje nadzorne plošče",
  759 + "delete-state-text": "Ali ste prepričani, da želite izbrisati stanje nadzorne plošče z imenom '{{stateName}}'?",
  760 + "show-details": "Pokaži podrobnosti",
  761 + "hide-details": "Skrij podrobnosti",
  762 + "select-state": "Izberi ciljno stanje",
  763 + "state-controller": "Državni nadzornik",
  764 + "search": "Iskanje po nadzornih ploščah",
  765 + "selected-dashboards": "{ count, plural, 1 {1 dashboard} other {# dashboards} } izbrano"
  766 + },
  767 + "datakey": {
  768 + "settings": "Nastavitve",
  769 + "advanced": "Napredno",
  770 + "label": "Oznaka",
  771 + "color": "Barva",
  772 + "units": "Poseben simbol za prikaz poleg vrednosti",
  773 + "decimals": "Število števk po plavajoči vejici",
  774 + "data-generation-func": "Funkcija ustvarjanja podatkov",
  775 + "use-data-post-processing-func": "Uporabi funkcijo naknadne obdelave podatkov",
  776 + "configuration": "Konfiguracija podatkovnega ključa",
  777 + "timeseries": "Časovne serije",
  778 + "attributes": "Lastnosti",
  779 + "entity-field": "Polje entitete",
  780 + "alarm": "Polja alarma",
  781 + "timeseries-required": "Potrebni so časovni nizi entitet.",
  782 + "timeseries-or-attributes-required": "Potrebni so časovni nizi / atributi entitet.",
  783 + "alarm-fields-timeseries-or-attributes-required": "Polja alarma ali časovni nizi / atributi entitet so obvezni.",
  784 + "maximum-timeseries-or-attributes": "Največ { count, plural, 1 {1 timeseries / attribute is allowed.} other {# timeseries / attributes are allowed} }",
  785 + "alarm-fields-required": "Polja alarma so obvezna.",
  786 + "function-types": "Vrste funkcij",
  787 + "function-types-required": "Zahtevane so vrste funkcij.",
  788 + "maximum-function-types": "Največ dovoljeno je { count, plural, 1 {1 type of function.} other {# vrste funkcij so dovoljene} }",
  789 + "time-description": "časovni žig trenutne vrednosti;",
  790 + "value-description": "trenutna vrednost;",
  791 + "prev-value-description": "rezultat prejšnjega klica funkcije;",
  792 + "time-prev-description": "časovni žig prejšnje vrednosti;",
  793 + "prev-orig-value-description": "prvotna prejšnja vrednost;"
  794 + },
  795 + "datasource": {
  796 + "type": "Vrsta vira podatkov",
  797 + "name": "Ime",
  798 + "add-datasource-prompt": "Prosimo, dodajte vir podatkov"
  799 + },
  800 + "details": {
  801 + "details": "Podrobnosti",
  802 + "edit-mode": "Način urejanja",
  803 + "edit-json": "Uredi JSON",
  804 + "toggle-edit-mode": "Preklopi način urejanja"
  805 + },
  806 + "device": {
  807 + "device": "Naprava",
  808 + "device-required": "Naprava je potrebna.",
  809 + "devices": "Naprave",
  810 + "management": "Upravljanje naprav",
  811 + "view-devices": "Ogled naprav",
  812 + "device-alias": "Vzdevek naprave",
  813 + "aliases": "Vzdevki naprav",
  814 + "no-alias-matching": "'{{alias}}' ni mogoče najti.",
  815 + "no-aliases-found": "Vzdevkov ni bilo mogoče najti.",
  816 + "no-key-matching": "'{{key}}' ni mogoče najti.",
  817 + "no-keys-found": "Ključev ni bilo mogoče najti.",
  818 + "create-new-alias": "Ustvari novega!",
  819 + "create-new-key": "Ustvari novega!",
  820 + "duplicate-alias-error": "Najden je podvojen vzdevek '{{alias}}'. <br> Vzdevki naprav morajo biti na nadzorni plošči edinstveni.",
  821 + "configure-alias": "Konfiguriraj vzdevek '{{alias}}'",
  822 + "no-devices-matching": "Najdena ni bila nobena naprava, ki se ujema z '{{entity}}'.",
  823 + "alias": "Vzdevek",
  824 + "alias-required": "Vzdevek naprave je obvezen.",
  825 + "remove-alias": "Odstrani vzdevek naprave",
  826 + "add-alias": "Dodaj vzdevek naprave",
  827 + "name-starts-with": "Ime naprave se začne z",
  828 + "device-list": "Seznam naprav",
  829 + "use-device-name-filter": "Uporabi filter",
  830 + "device-list-empty": "Izbrana ni nobena naprava.",
  831 + "device-name-filter-required": "Potreben je filter imena naprave.",
  832 + "device-name-filter-no-device-matched": "Najdena ni bila nobena naprava, ki se začne z '{{device}}'.",
  833 + "add": "Dodaj napravo",
  834 + "assign-to-customer": "Dodeli stranki",
  835 + "assign-device-to-customer": "Dodeli naprave strankam",
  836 + "assign-device-to-customer-text": "Izberite naprave, ki jih želite dodeliti stranki",
  837 + "make-public": "Naredi napravo javno",
  838 + "make-private": "Naj bo naprava zasebna",
  839 + "no-devices-text": "Naprave ni bilo mogoče najti",
  840 + "assign-to-customer-text": "Prosimo, izberite stranko, ki bo dodelila naprave (-e)",
  841 + "device-details": "Podrobnosti o napravi",
  842 + "add-device-text": "Dodaj novo napravo",
  843 + "credentials": "Poverilnice",
  844 + "manage-credentials": "Upravljanje poverilnic",
  845 + "delete": "Izbriši napravo",
  846 + "assign-devices": "Dodeli naprave",
  847 + "assign-devices-text": "Kupcu dodeli { count, plural, 1 {1 device} other {# devices} }",
  848 + "delete-devices": "Izbriši naprave",
  849 + "unassign-from-customer": "Odjavi od stranke",
  850 + "unassign-devices": "Odjavi naprave",
  851 + "unassign-devices-action-title": "Odstrani { count, plural, 1 {1 device} other {# devices} } od stranke",
  852 + "assign-new-device": "Dodeli novo napravo",
  853 + "make-public-device-title": "Ali ste prepričani, da želite napravo '{{deviceName}}' narediti javno?",
  854 + "make-public-device-text": "Po potrditvi bo naprava in vsi njeni podatki javno dostopni drugim.",
  855 + "make-private-device-title": "Ali ste prepričani, da želite napravo '{{deviceName}}' narediti zasebno?",
  856 + "make-private-device-text": "Po potrditvi bo naprava in vsi njeni podatki postali zasebni in nedostopni drugim.",
  857 + "view-credentials": "Ogled poverilnic",
  858 + "delete-device-title": "Ali ste prepričani, da želite izbrisati napravo '{{deviceName}}'?",
  859 + "delete-device-text": "Previdno, po potrditvi naprava in vsi povezani podatki postanejo nepopravljivi.",
  860 + "delete-devices-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 device} other {# naprav} }?",
  861 + "delete-devices-action-title": "Izbriši { count, plural, 1 {1 device} other {# devices} }",
  862 + "delete-devices-text": "Bodite previdni, po potrditvi bodo vse izbrane naprave odstranjene in vsi povezani podatki nepopravljivi.",
  863 + "unassign-device-title": "Ali ste prepričani, da želite odstraniti napravo '{{deviceName}}'?",
  864 + "unassign-device-text": "Po potrditvi naprava ne bo dodeljena in nedostopna stranki.",
  865 + "unassign-device": "Preklic dodelitve naprave",
  866 + "unassign-devices-title": "Ali ste prepričani, da želite odstraniti { count, plural, 1 {1 device} other {# naprave} }?",
  867 + "unassign-devices-text": "Po potrditvi bodo vse izbrane naprave nedodeljene in nedostopne stranki.",
  868 + "device-credentials": "Poverilnice naprave",
  869 + "credentials-type": "Vrsta poverilnic",
  870 + "access-token": "Dostopni žeton",
  871 + "access-token-required": "Zahtevan je žeton za dostop.",
  872 + "access-token-invalid": "Dolžina žetona za dostop mora biti od 1 do 20 znakov.",
  873 + "rsa-key": "Javni ključ RSA",
  874 + "rsa-key-required": "Potreben je javni ključ RSA.",
  875 + "client-id": "Client ID",
  876 + "client-id-pattern": "Contains invalid character.",
  877 + "user-name": "User Name",
  878 + "user-name-required": "User Name is required.",
  879 + "client-id-or-user-name-necessary": "Client ID and/or User Name are necessary",
  880 + "password": "Password",
  881 + "secret": "Skrivnost",
  882 + "secret-required": "Potrebna je skrivnost.",
  883 + "device-type": "Vrsta naprave",
  884 + "device-type-required": "Zahtevana je vrsta naprave.",
  885 + "select-device-type": "Izberite vrsto naprave",
  886 + "enter-device-type": "Vnesite vrsto naprave",
  887 + "any-device": "Katerakoli naprava",
  888 + "no-device-types-matching": "Najdena ni bila nobena vrsta naprave, ki se ujema z '{{entitySubtype}}'.",
  889 + "device-type-list-empty": "Izbrana ni nobena vrsta naprave.",
  890 + "device-types": "Vrste naprav",
  891 + "name": "Ime",
  892 + "name-required": "Ime je obvezno.",
  893 + "description": "Opis",
  894 + "label": "Oznaka",
  895 + "events": "Dogodki",
  896 + "details": "Podrobnosti",
  897 + "copyId": "Kopiraj ID naprave",
  898 + "copyAccessToken": "Kopiraj žeton za dostop",
  899 + "copy-mqtt-authentication": "Copy MQTT credentials",
  900 + "idCopiedMessage": "ID naprave je bil kopiran v odložišče",
  901 + "accessTokenCopiedMessage": "Žeton za dostop do naprave je bil kopiran v odložišče",
  902 + "mqtt-authentication-copied-message": "Device MQTT authentication has been copied to clipboard",
  903 + "assignedToCustomer": "Dodeljeno stranki",
  904 + "unable-delete-device-alias-title": "Vzdevka naprave ni mogoče izbrisati",
  905 + "unable-delete-device-alias-text": "Vzdevka naprave '{{deviceAlias}}' ni mogoče izbrisati, saj ga uporabljajo naslednji pripomočki: <br/> {{widgetsList}}",
  906 + "is-gateway": "Je prehod",
  907 + "public": "Javno",
  908 + "device-public": "Naprava je javna",
  909 + "select-device": "Izberi napravo",
  910 + "import": "Uvozi napravo",
  911 + "device-file": "Datoteka naprave",
  912 + "search": "Iskalne naprave",
  913 + "selected-devices": "{ count, plural, 1 {1 device} other {# devices} } izbrano",
  914 + "device-configuration": "Device configuration",
  915 + "transport-configuration": "Transport configuration",
  916 + "wizard": {
  917 + "device-wizard": "Device Wizard",
  918 + "device-details": "Device details",
  919 + "new-device-profile": "Create new device profile",
  920 + "existing-device-profile": "Select existing device profile",
  921 + "specific-configuration": "Specific configuration",
  922 + "customer-to-assign-device": "Customer to assign the device",
  923 + "add-credential": "Add credential"
  924 + }
  925 + },
  926 + "device-profile": {
  927 + "device-profile": "Device profile",
  928 + "device-profiles": "Device profiles",
  929 + "all-device-profiles": "All",
  930 + "add": "Add device profile",
  931 + "edit": "Edit device profile",
  932 + "device-profile-details": "Device profile details",
  933 + "no-device-profiles-text": "No device profiles found",
  934 + "search": "Search device profiles",
  935 + "selected-device-profiles": "{ count, plural, 1 {1 device profile} other {# device profiles} } selected",
  936 + "no-device-profiles-matching": "No device profile matching '{{entity}}' were found.",
  937 + "device-profile-required": "Device profile is required",
  938 + "idCopiedMessage": "Device profile Id has been copied to clipboard",
  939 + "set-default": "Make device profile default",
  940 + "delete": "Delete device profile",
  941 + "copyId": "Copy device profile Id",
  942 + "new-device-profile-name": "Device profile name",
  943 + "new-device-profile-name-required": "Device profile name is required.",
  944 + "name": "Name",
  945 + "name-required": "Name is required.",
  946 + "type": "Profile type",
  947 + "type-required": "Profile type is required.",
  948 + "type-default": "Default",
  949 + "transport-type": "Transport type",
  950 + "transport-type-required": "Transport type is required.",
  951 + "transport-type-default": "Default",
  952 + "transport-type-default-hint": "Supports basic MQTT, HTTP and CoAP transport",
  953 + "transport-type-mqtt": "MQTT",
  954 + "transport-type-mqtt-hint": "Enables advanced MQTT transport settings",
  955 + "transport-type-lwm2m": "LWM2M",
  956 + "transport-type-lwm2m-hint": "LWM2M transport type",
  957 + "description": "Description",
  958 + "default": "Default",
  959 + "profile-configuration": "Profile configuration",
  960 + "transport-configuration": "Transport configuration",
  961 + "default-rule-chain": "Default rule chain",
  962 + "select-queue-hint": "Select from a drop-down list or add a custom name.",
  963 + "delete-device-profile-title": "Are you sure you want to delete the device profile '{{deviceProfileName}}'?",
  964 + "delete-device-profile-text": "Be careful, after the confirmation the device profile and all related data will become unrecoverable.",
  965 + "delete-device-profiles-title": "Are you sure you want to delete { count, plural, 1 {1 device profile} other {# device profiles} }?",
  966 + "delete-device-profiles-text": "Be careful, after the confirmation all selected device profiles will be removed and all related data will become unrecoverable.",
  967 + "set-default-device-profile-title": "Are you sure you want to make the device profile '{{deviceProfileName}}' default?",
  968 + "set-default-device-profile-text": "After the confirmation the device profile will be marked as default and will be used for new devices with no profile specified.",
  969 + "no-device-profiles-found": "No device profiles found.",
  970 + "create-new-device-profile": "Create a new one!",
  971 + "mqtt-device-topic-filters": "MQTT device topic filters",
  972 + "mqtt-device-topic-filters-unique": "MQTT device topic filters need to be unique.",
  973 + "mqtt-device-payload-type": "MQTT device payload",
  974 + "mqtt-device-payload-type-json": "JSON",
  975 + "mqtt-device-payload-type-proto": "Protobuf",
  976 + "mqtt-payload-type-required": "Payload type is required.",
  977 + "support-level-wildcards": "Single <code>[+]</code> and multi-level <code>[#]</code> wildcards supported.",
  978 + "telemetry-topic-filter": "Telemetry topic filter",
  979 + "telemetry-topic-filter-required": "Telemetry topic filter is required.",
  980 + "attributes-topic-filter": "Attributes topic filter",
  981 + "attributes-topic-filter-required": "Attributes topic filter is required.",
  982 + "telemetry-proto-schema": "Telemetry proto schema",
  983 + "telemetry-proto-schema-required": "Telemetry proto schema is required.",
  984 + "attributes-proto-schema": "Attributes proto schema",
  985 + "attributes-proto-schema-required": "Attributes proto schema is required.",
  986 + "rpc-response-topic-filter": "RPC response topic filter",
  987 + "rpc-response-topic-filter-required": "RPC response topic filter is required.",
  988 + "not-valid-pattern-topic-filter": "Not valid pattern topic filter",
  989 + "not-valid-single-character": "Invalid use of a single-level wildcard character",
  990 + "not-valid-multi-character": "Invalid use of a multi-level wildcard character",
  991 + "single-level-wildcards-hint": "<code>[+]</code> is suitable for any topic filter level. Ex.: <b>v1/devices/+/telemetry</b> or <b>+/devices/+/attributes</b>.",
  992 + "multi-level-wildcards-hint": "<code>[#]</code> can replace the topic filter itself and must be the last symbol of the topic. Ex.: <b>#</b> or <b>v1/devices/me/#</b>.",
  993 + "alarm-rules": "Alarm rules",
  994 + "alarm-rules-with-count": "Alarm rules ({{count}})",
  995 + "no-alarm-rules": "No alarm rules configured",
  996 + "add-alarm-rule": "Add alarm rule",
  997 + "edit-alarm-rule": "Edit alarm rule",
  998 + "alarm-type": "Alarm type",
  999 + "alarm-type-required": "Alarm type is required.",
  1000 + "alarm-type-unique": "Alarm type must be unique within the device profile alarm rules.",
  1001 + "create-alarm-pattern": "Create <b>{{alarmType}}</b> alarm",
  1002 + "create-alarm-rules": "Create alarm rules",
  1003 + "no-create-alarm-rules": "No create conditions configured",
  1004 + "add-create-alarm-rule-prompt": "Please add create alarm rule",
  1005 + "clear-alarm-rule": "Clear alarm rule",
  1006 + "no-clear-alarm-rule": "No clear condition configured",
  1007 + "add-create-alarm-rule": "Add create condition",
  1008 + "add-clear-alarm-rule": "Add clear condition",
  1009 + "select-alarm-severity": "Select alarm severity",
  1010 + "alarm-severity-required": "Alarm severity is required.",
  1011 + "condition-duration": "Condition duration",
  1012 + "condition-duration-value": "Duration value",
  1013 + "condition-duration-time-unit": "Time unit",
  1014 + "condition-duration-value-range": "Duration value should be in a range from 1 to 2147483647.",
  1015 + "condition-duration-value-pattern": "Duration value should be integers.",
  1016 + "condition-duration-value-required": "Duration value is required.",
  1017 + "condition-duration-time-unit-required": "Time unit is required.",
  1018 + "advanced-settings": "Advanced settings",
  1019 + "alarm-rule-details": "Details",
  1020 + "add-alarm-rule-details": "Add details",
  1021 + "propagate-alarm": "Propagate alarm",
  1022 + "alarm-rule-relation-types-list": "Relation types to propagate",
  1023 + "alarm-rule-relation-types-list-hint": "If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.",
  1024 + "alarm-details": "Alarm details",
  1025 + "alarm-rule-condition": "Alarm rule condition",
  1026 + "enter-alarm-rule-condition-prompt": "Please add alarm rule condition",
  1027 + "edit-alarm-rule-condition": "Edit alarm rule condition",
  1028 + "device-provisioning": "Device provisioning",
  1029 + "provision-strategy": "Provision strategy",
  1030 + "provision-strategy-required": "Provision strategy is required.",
  1031 + "provision-strategy-disabled": "Disabled",
  1032 + "provision-strategy-created-new": "Allow to create new devices",
  1033 + "provision-strategy-check-pre-provisioned": "Check for pre-provisioned devices",
  1034 + "provision-device-key": "Provision device key",
  1035 + "provision-device-key-required": "Provision device key is required.",
  1036 + "copy-provision-key": "Copy provision key",
  1037 + "provision-key-copied-message": "Provision key has been copied to clipboard",
  1038 + "provision-device-secret": "Provision device secret",
  1039 + "provision-device-secret-required": "Provision device secret is required.",
  1040 + "copy-provision-secret": "Copy provision secret",
  1041 + "provision-secret-copied-message": "Provision secret has been copied to clipboard",
  1042 + "condition": "Condition",
  1043 + "condition-type": "Condition type",
  1044 + "condition-type-simple": "Simple",
  1045 + "condition-type-duration": "Duration",
  1046 + "condition-during": "During {{during}}",
  1047 + "condition-type-repeating": "Repeating",
  1048 + "condition-type-required": "Condition type is required.",
  1049 + "condition-repeating-value": "Count of events",
  1050 + "condition-repeating-value-range": "Count of events should be in a range from 1 to 2147483647.",
  1051 + "condition-repeating-value-pattern": "Count of events should be integers.",
  1052 + "condition-repeating-value-required": "Count of events is required.",
  1053 + "condition-repeat-times": "Repeats { count, plural, 1 {1 time} other {# times} }",
  1054 + "schedule-type": "Scheduler type",
  1055 + "schedule-type-required": "Scheduler type is required.",
  1056 + "schedule": "Schedule",
  1057 + "edit-schedule": "Edit alarm schedule",
  1058 + "schedule-any-time": "Active all the time",
  1059 + "schedule-specific-time": "Active at a specific time",
  1060 + "schedule-custom": "Custom",
  1061 + "schedule-day": {
  1062 + "monday": "Monday",
  1063 + "tuesday": "Tuesday",
  1064 + "wednesday": "Wednesday",
  1065 + "thursday": "Thursday",
  1066 + "friday": "Friday",
  1067 + "saturday": "Saturday",
  1068 + "sunday": "Sunday"
  1069 + },
  1070 + "schedule-days": "Days",
  1071 + "schedule-time": "Time",
  1072 + "schedule-time-from": "From",
  1073 + "schedule-time-to": "To",
  1074 + "schedule-days-of-week-required": "At least one day of week should be selected."
  1075 + },
  1076 + "dialog": {
  1077 + "close": "Zapri pogovorno okno"
  1078 + },
  1079 + "direction": {
  1080 + "column": "Stolpec",
  1081 + "row": "Vrstica"
  1082 + },
  1083 + "error": {
  1084 + "unable-to-connect": "Povezave s strežnikom ni mogoče vzpostaviti! Preverite internetno povezavo.",
  1085 + "unhandled-error-code": "Neobdelana koda napake: {{errorCode}}",
  1086 + "unknown-error": "Neznana napaka"
  1087 + },
  1088 + "entity": {
  1089 + "entity": "Entiteta",
  1090 + "entities": "Subjekti",
  1091 + "aliases": "Vzdevki entitet",
  1092 + "entity-alias": "Vzdevek entitete",
  1093 + "unable-delete-entity-alias-title": "Vzdevka entitete ni mogoče izbrisati",
  1094 + "unable-delete-entity-alias-text": "Vzdevka entitete '{{entityAlias}}' ni mogoče izbrisati, ker ga uporabljajo naslednji pripomočki: <br/> {{widgetsList}}",
  1095 + "duplicate-alias-error": "Najden je podvojen vzdevek '{{alias}}'. <br> Vzdevki entitet morajo biti na nadzorni plošči edinstveni.",
  1096 + "missing-entity-filter-error": "Za vzdevek '{{alias}}' manjka filter.",
  1097 + "configure-alias": "Konfiguriraj vzdevek '{{alias}}'",
  1098 + "alias": "Vzdevek",
  1099 + "alias-required": "Zahtevan je vzdevek entitete.",
  1100 + "remove-alias": "Odstrani vzdevek entitete",
  1101 + "add-alias": "Dodaj vzdevek entitete",
  1102 + "entity-list": "Seznam entitet",
  1103 + "entity-type": "Vrsta entitete",
  1104 + "entity-types": "Vrste entitet",
  1105 + "entity-type-list": "Seznam entitet",
  1106 + "any-entity": "Katerikoli subjekt",
  1107 + "enter-entity-type": "Vnesite vrsto entitete",
  1108 + "no-entities-matching": "Najdena ni bila nobena enota, ki se ujema z '{{entity}}'.",
  1109 + "no-entity-types-matching": "Najdena ni bila nobena vrsta entitete, ki se ujema z '{{entityType}}'.",
  1110 + "name-starts-with": "Ime se začne z",
  1111 + "use-entity-name-filter": "Uporabi filter",
  1112 + "entity-list-empty": "Nobena entiteta ni izbrana.",
  1113 + "entity-type-list-empty": "Izbrana ni nobena vrsta entitete.",
  1114 + "entity-name-filter-required": "Potreben je filter imena entitete.",
  1115 + "entity-name-filter-no-entity-matched": "Najti ni bilo nobene entitete, ki se začne z '{{entity}}'.",
  1116 + "all-subtypes": "Vse",
  1117 + "select-entities": "Izberi entitete",
  1118 + "no-aliases-found": "Vzdevkov ni bilo mogoče najti.",
  1119 + "no-alias-matching": "'{{alias}}' ni mogoče najti.",
  1120 + "create-new-alias": "Ustvari novega!",
  1121 + "key": "Ključ",
  1122 + "key-name": "Ime ključa",
  1123 + "no-keys-found": "Ključev ni bilo mogoče najti.",
  1124 + "no-key-matching": "'{{key}}' ni mogoče najti.",
  1125 + "create-new-key": "Ustvari novega!",
  1126 + "type": "Vrsta",
  1127 + "type-required": "Zahteva se vrsta entitete.",
  1128 + "type-device": "Naprava",
  1129 + "type-devices": "Naprave",
  1130 + "list-of-devices": "{ count, plural, 1 {One device} other {List of # devices} }",
  1131 + "device-name-starts-with": "Naprave, katerih imena se začnejo z '{{prefix}}'",
  1132 + "type-device-profile": "Device profile",
  1133 + "type-device-profiles": "Device profiles",
  1134 + "list-of-device-profiles": "{ count, plural, 1 {One device profile} other {List of # device profiles} }",
  1135 + "device-profile-name-starts-with": "Device profiles whose names start with '{{prefix}}'",
  1136 + "type-asset": "Sredstvo",
  1137 + "type-assets": "Sredstva",
  1138 + "list-of-assets": "{ count, plural, 1 {One asset} other {Seznam # sredstev} }",
  1139 + "asset-name-starts-with": "Sredstva, katerih imena se začnejo z '{{prefix}}'",
  1140 + "type-entity-view": "Pogled entitete",
  1141 + "type-entity-views": "Pogledi entitet",
  1142 + "list-of-entity-views": "{ count, plural, 1 {One entity view} other {Seznam # pogledov entitet} }",
  1143 + "entity-view-name-starts-with": "Pogledi entitet, katerih imena se začnejo z '{{prefix}}'",
  1144 + "type-rule": "Pravilo",
  1145 + "type-rules": "Pravila",
  1146 + "list-of-rules": "{ count, plural, 1 {One rule} other {Seznam # pravil} }",
  1147 + "rule-name-starts-with": "Pravila, katerih imena se začnejo z '{{prefix}}'",
  1148 + "type-plugin": "Vključiti",
  1149 + "type-plugins": "Vtičniki",
  1150 + "list-of-plugins": "{ count, plural, 1 {One plugin} other {List of # plugins} }",
  1151 + "plugin-name-starts-with": "Vtičniki, katerih imena se začnejo z '{{prefix}}'",
  1152 + "type-tenant": "Najemnik",
  1153 + "type-tenants": "Najemniki",
  1154 + "list-of-tenants": "{ count, plural, 1 {One najemnik} other {Seznam # najemnikov} }",
  1155 + "tenant-name-starts-with": "Najemniki, katerih imena se začnejo z '{{prefix}}'",
  1156 + "type-tenant-profile": "Tenant profile",
  1157 + "type-tenant-profiles": "Tenant profiles",
  1158 + "list-of-tenant-profiles": "{ count, plural, 1 {One tenant profile} other {List of # tenant profiles} }",
  1159 + "tenant-profile-name-starts-with": "Tenant profiles whose names start with '{{prefix}}'",
  1160 + "type-customer": "Stranka",
  1161 + "type-customers": "Stranke",
  1162 + "list-of-customers": "{ count, plural, 1 {One customer} other {List of # customers} }",
  1163 + "customer-name-starts-with": "Stranke, katerih imena se začnejo z '{{prefix}}'",
  1164 + "type-user": "Uporabnik",
  1165 + "type-users": "Uporabniki",
  1166 + "list-of-users": "{ count, plural, 1 {One user} other {List of # users} }",
  1167 + "user-name-starts-with": "Uporabniki, katerih imena se začnejo z '{{prefix}}'",
  1168 + "type-dashboard": "Nadzorna plošča",
  1169 + "type-dashboards": "Nadzorne plošče",
  1170 + "list-of-dashboards": "{ count, plural, 1 {One dashboard} other {List of # dashboards} }",
  1171 + "dashboard-name-starts-with": "Nadzorne plošče, katerih imena se začnejo z '{{prefix}}'",
  1172 + "type-alarm": "Alarm",
  1173 + "type-alarms": "Alarmi",
  1174 + "list-of-alarms": "{ count, plural, 1 {One alarms} other {List of # alarms} }",
  1175 + "alarm-name-starts-with": "Alarmi, katerih imena se začnejo z '{{prefix}}'",
  1176 + "type-rulechain": "Veriga pravil",
  1177 + "type-rulechains": "Pravila",
  1178 + "list-of-rulechains": "{ count, plural, 1 {One rule chain} other {List of # rule chains} }",
  1179 + "rulechain-name-starts-with": "Verige pravil, katerih imena se začnejo z '{{prefix}}'",
  1180 + "type-rulenode": "Vozlišče pravila",
  1181 + "type-rulenodes": "Vozlišča pravil",
  1182 + "list-of-rulenodes": "{ count, plural, 1 {One rule node} other {List of # rule nodes} }",
  1183 + "rulenode-name-starts-with": "Vozlišča pravil, katerih imena se začnejo z '{{prefix}}'",
  1184 + "type-current-customer": "Trenutna stranka",
  1185 + "type-current-tenant": "Trenutni najemnik",
  1186 + "type-current-user": "Trenutni uporabnik",
  1187 + "type-current-user-owner": "Trenutni lastnik uporabnika",
  1188 + "search": "Iskanje entitet",
  1189 + "selected-entities": "{ count, plural, 1 {1 entity} other {# entitet} } izbranih",
  1190 + "entity-name": "Ime entitete",
  1191 + "entity-label": "Oznaka entitete",
  1192 + "details": "Podrobnosti o entiteti",
  1193 + "no-entities-prompt": "Ni entitet",
  1194 + "no-data": "Ni podatkov za prikaz",
  1195 + "columns-to-display": "Stolpci za prikaz",
  1196 + "type-api-usage-state": "Api Usage State"
  1197 + },
  1198 + "entity-field": {
  1199 + "created-time": "Čas ustvaritve",
  1200 + "name": "Ime",
  1201 + "type": "Vrsta",
  1202 + "first-name": "Ime",
  1203 + "last-name": "Priimek",
  1204 + "email": "E-naslov",
  1205 + "title": "Naziv",
  1206 + "country": "Država",
  1207 + "state": "Država",
  1208 + "city": "Mesto",
  1209 + "address": "Naslov",
  1210 + "address2": "Naslov 2",
  1211 + "zip": "Poštna številka",
  1212 + "phone": "Telefon",
  1213 + "label": "Oznaka"
  1214 + },
  1215 + "entity-view": {
  1216 + "entity-view": "Pogled entitete",
  1217 + "entity-view-required": "Zahtevan je pogled entitete.",
  1218 + "entity-views": "Pogledi entitet",
  1219 + "management": "Upravljanje entitetnega pogleda",
  1220 + "view-entity-views": "Ogled pogledov entitete",
  1221 + "entity-view-alias": "Vzdevek entitetnega pogleda",
  1222 + "aliases": "Vzdevki entitetnega pogleda",
  1223 + "no-alias-matching": "'{{alias}}' ni mogoče najti.",
  1224 + "no-aliases-found": "Vzdevkov ni bilo mogoče najti.",
  1225 + "no-key-matching": "'{{key}}' ni mogoče najti.",
  1226 + "no-keys-found": "Ključev ni bilo mogoče najti.",
  1227 + "create-new-alias": "Ustvari novega!",
  1228 + "create-new-key": "Ustvari novega!",
  1229 + "duplicate-alias-error": "Najden je podvojen vzdevek '{{alias}}'. <br> Vzdevki entitetnega pogleda na nadzorni plošči morajo biti edinstveni.",
  1230 + "configure-alias": "Konfiguriraj vzdevek '{{alias}}'",
  1231 + "no-entity-views-matching": "Najden ni bil noben pogled entitete, ki se ujema z '{{entity}}'.",
  1232 + "public": "Javno",
  1233 + "alias": "Vzdevek",
  1234 + "alias-required": "Zahtevan je vzdevek pogleda entitete.",
  1235 + "remove-alias": "Odstrani vzdevek pogleda entitete",
  1236 + "add-alias": "Dodaj vzdevek pogleda entitete",
  1237 + "name-starts-with": "Ime entitete se začne z",
  1238 + "entity-view-list": "Seznam entitetnega pogleda",
  1239 + "use-entity-view-name-filter": "Uporabi filter",
  1240 + "entity-view-list-empty": "Izbran ni noben pogled entitete.",
  1241 + "entity-view-name-filter-required": "Potreben je filter imena pogleda entitete.",
  1242 + "entity-view-name-filter-no-entity-view-matched": "Najden ni bil noben pogled entitete, ki se začne z '{{entityView}}'.",
  1243 + "add": "Dodaj pogled entitete",
  1244 + "entity-view-public": "Pogled entitete je javen",
  1245 + "assign-to-customer": "Dodeli stranki",
  1246 + "assign-entity-view-to-customer": "Dodelitev pogledov entitet strankam",
  1247 + "assign-entity-view-to-customer-text": "Izberite poglede entitet, ki jih želite dodeliti stranki",
  1248 + "no-entity-views-text": "Ni najden noben pogled entitete",
  1249 + "assign-to-customer-text": "Izberite stranko, ki bo dodelila poglede entitet",
  1250 + "entity-view-details": "Podrobnosti o pogledu entitete",
  1251 + "add-entity-view-text": "Dodaj nov pogled entitete",
  1252 + "delete": "Izbriši pogled entitete",
  1253 + "assign-entity-views": "Dodelitev pogledov entitete",
  1254 + "assign-entity-views-text": "Stranki dodeli { count, plural, 1 {1 entity view} other {# entity views} }",
  1255 + "delete-entity-views": "Izbriši poglede entitet",
  1256 + "unassign-from-customer": "Odstrani od stranke",
  1257 + "unassign-entity-views": "Odstrani poglede entitete",
  1258 + "unassign-entity-views-action-title": "Od stranke odstrani { count, plural, 1 {1 entity view} other {# entity views} }",
  1259 + "assign-new-entity-view": "Dodeli nov pogled entitete",
  1260 + "delete-entity-view-title": "Ali ste prepričani, da želite izbrisati pogled entitete '{{entityViewName}}'?",
  1261 + "delete-entity-view-text": "Bodite previdni, po potrditvi bodo pogled entitete in vsi povezani podatki nepopravljivi.",
  1262 + "delete-entity-views-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 entity view} other {# entity views} }?",
  1263 + "delete-entity-views-action-title": "Izbriši { count, plural, 1 {1 entity view} other {# entity views} }",
  1264 + "delete-entity-views-text": "Bodite previdni, po potrditvi bodo odstranjeni vsi pogledi izbranih entitet in vsi povezani podatki bodo postali nepopravljivi.",
  1265 + "unassign-entity-view-title": "Ali ste prepričani, da želite odstraniti pogled entitete '{{entityViewName}}?",
  1266 + "unassign-entity-view-text": "Po potrditvi bo pogled entitete odstranjen in nedostopen stranki.",
  1267 + "unassign-entity-view": "Odstrani pogled entitete",
  1268 + "unassign-entity-views-title": "Ali ste prepričani, da želite odstraniti { count, plural, 1 {1 entity view} other {# entity views} }?",
  1269 + "unassign-entity-views-text": "Po potrditvi bodo vsi pogledi izbranih entitet odstranjeni in stranki nedostopni.",
  1270 + "entity-view-type": "Vrsta pogleda entitete",
  1271 + "entity-view-type-required": "Zahteva se vrsta pogleda entitete.",
  1272 + "select-entity-view-type": "Izberi vrsto pogleda entitete",
  1273 + "enter-entity-view-type": "Vnesite vrsto pogleda entitete",
  1274 + "any-entity-view": "Katerikoli pogled entitete",
  1275 + "no-entity-view-types-matching": "Najdena ni bila nobena vrsta pogleda entitete, ki se ujema z '{{entitySubtype}}'.",
  1276 + "entity-view-type-list-empty": "Izbrana ni nobena vrsta pogleda entitete.",
  1277 + "entity-view-types": "Vrste pogleda entitete",
  1278 + "created-time": "Ustvarjeni čas",
  1279 + "name": "Ime",
  1280 + "name-required": "Ime je obvezno.",
  1281 + "description": "Opis",
  1282 + "events": "Dogodki",
  1283 + "details": "Podrobnosti",
  1284 + "copyId": "Kopiraj ID pogleda entitete",
  1285 + "idCopiedMessage": "ID entitete je kopiran v odložišče",
  1286 + "assignedToCustomer": "Dodeljeno stranki",
  1287 + "unable-entity-view-device-alias-title": "Ni mogoče izbrisati vzdevka pogleda entitete",
  1288 + "unable-entity-view-device-alias-text": "Vzdevka naprave '{{entityViewAlias}}' ni mogoče izbrisati, saj ga uporabljajo naslednji pripomočki: <br/> {{widgetsList}}",
  1289 + "select-entity-view": "Izberi pogled entitete",
  1290 + "make-public": "Naj bo pogled entitete javen",
  1291 + "make-private": "Naj bo pogled entitete zaseben",
  1292 + "start-date": "Začetni datum",
  1293 + "start-ts": "Začetni čas",
  1294 + "end-date": "Končni datum",
  1295 + "end-ts": "Končni čas",
  1296 + "date-limits": "Datumske omejitve",
  1297 + "client-attributes": "Atributi odjemalca",
  1298 + "shared-attributes": "Skupni atributi",
  1299 + "server-attributes": "Atributi strežnika",
  1300 + "timeseries": "Časovne serije",
  1301 + "client-attributes-placeholder": "Atributi odjemalca",
  1302 + "shared-attributes-placeholder": "Skupni atributi",
  1303 + "server-attributes-placeholder": "Atributi strežnika",
  1304 + "timeseries-placeholder": "Časovne serije",
  1305 + "target-entity": "Ciljna entiteta",
  1306 + "attributes-propagation": "Propragacija atributov",
  1307 + "attributes-propagation-hint": "Pogled entitete bo samodejno kopiral določene atribute iz ciljne entitete vsakič, ko shranite ali posodobite ta pogled entitete. Zaradi učinkovitosti se atributi ciljne entitete ne propagirajo v pogled entitete pri vsaki spremembi atributa. Samodejno propagacijo lahko omogočite tako, da konfigurirate \" kopiraj v ogled\" vozlišče pravila v verigi pravil in povezovanje sporočil \"Objavi atribute\" in \"Atributi posodobljeni\" na novo vozlišče pravila.",
  1308 + "timeseries-data": "Podatki časovnih serij",
  1309 + "timeseries-data-hint": "Konfigurirajte podatkovne ključe časovnih serij ciljne entitete, ki bodo dostopne pogledu entitete. Ti podatki časovnih serij so samo za branje.",
  1310 + "make-public-entity-view-title": "Ali ste prepričani, da želite pogled entitete '{{entityViewName}}' narediti javen?",
  1311 + "make-public-entity-view-text": "Po potrditvi bodo pogled entitete in vsi njegovi podatki javni in dostopni drugim.",
  1312 + "make-private-entity-view-title": "Ali ste prepričani, da želite pogled entitete '{{entityViewName}}' narediti zaseben?",
  1313 + "make-private-entity-view-text": "Po potrditvi bodo pogled entitete in vsi njegovi podatki postali zasebni in drugim nedostopni.",
  1314 + "search": "Išči poglede entitet",
  1315 + "selected-entity-views": "{ count, plural, 1 {1 entity view} other {# entity views} } izbranih"
  1316 + },
  1317 + "event": {
  1318 + "event-type": "Vrsta dogodka",
  1319 + "type-error": "Napaka",
  1320 + "type-lc-event": "Dogodek življenjskega cikla",
  1321 + "type-stats": "Statistika",
  1322 + "type-debug-rule-node": "Odpravljanje napak",
  1323 + "type-debug-rule-chain": "Odpravljanje napak",
  1324 + "no-events-prompt": "Ni dogodkov",
  1325 + "error": "Napaka",
  1326 + "alarm": "Alarm",
  1327 + "event-time": "Čas dogodka",
  1328 + "server": "Strežnik",
  1329 + "body": "Vsebina",
  1330 + "method": "Metoda",
  1331 + "type": "Vrsta",
  1332 + "entity": "Entiteta",
  1333 + "message-id": "ID sporočila",
  1334 + "message-type": "Vrsta sporočila",
  1335 + "data-type": "Vrsta podatkov",
  1336 + "relation-type": "Vrsta povezave",
  1337 + "metadata": "Metapodatki",
  1338 + "data": "Podatki",
  1339 + "event": "Dogodek",
  1340 + "status": "Stanje",
  1341 + "success": "Uspeh",
  1342 + "failed": "Ni uspelo",
  1343 + "messages-processed": "Obdelana sporočila",
  1344 + "errors-occurred": "Prišlo je do napak"
  1345 + },
  1346 + "extension": {
  1347 + "extensions": "Razširitve",
  1348 + "selected-extensions": "{ count, plural, 1 {1 extension} other {# extensions} } izbrano",
  1349 + "type": "Vrsta",
  1350 + "key": "Ključ",
  1351 + "value": "Vrednost",
  1352 + "id": "ID",
  1353 + "extension-id": "ID razširitve",
  1354 + "extension-type": "Vrsta razširitve",
  1355 + "transformer-json": "JSON *",
  1356 + "unique-id-required": "Trenutni ID razširitve že obstaja.",
  1357 + "delete": "Izbriši razširitev",
  1358 + "add": "Dodaj razširitev",
  1359 + "edit": "Uredi razširitev",
  1360 + "delete-extension-title": "Ali ste prepričani, da želite izbrisati razširitev '{{extensionId}}'?",
  1361 + "delete-extension-text": "Bodite previdni, po potrditvi podaljšanje in vsi povezani podatki ne bodo več obnovljivi.",
  1362 + "delete-extensions-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 extension} other {# extensions} }?",
  1363 + "delete-extensions-text": "Previdno, po potrditvi bodo odstranjene vse izbrane razširitve.",
  1364 + "converters": "Pretvorniki",
  1365 + "converter-id": "ID pretvornika",
  1366 + "configuration": "Konfiguracija",
  1367 + "converter-configurations": "Konfiguracije pretvornika",
  1368 + "token": "Varnostni žeton",
  1369 + "add-converter": "Dodaj pretvornik",
  1370 + "add-config": "Dodaj konfiguracijo pretvornika",
  1371 + "device-name-expression": "Izraz imena naprave",
  1372 + "device-type-expression": "Izraz vrste naprave",
  1373 + "custom": "Po meri",
  1374 + "to-double": "Podvojiti",
  1375 + "transformer": "Transformator",
  1376 + "json-required": "Transformator json je potreben.",
  1377 + "json-parse": "Ni mogoče razčleniti transformatorja json.",
  1378 + "attributes": "Lastnosti",
  1379 + "add-attribute": "Dodaj atribut",
  1380 + "add-map": "Dodaj element preslikave",
  1381 + "timeseries": "Časovne serije",
  1382 + "add-timeseries": "Dodaj časovne vrste",
  1383 + "field-required": "Polje je obvezno",
  1384 + "brokers": "Posredniki",
  1385 + "add-broker": "Dodaj posrednika",
  1386 + "host": "Gostitelj",
  1387 + "port": "Pristanišče",
  1388 + "port-range": "Vrata naj bodo v območju od 1 do 65535.",
  1389 + "ssl": "SSL",
  1390 + "credentials": "Poverilnice",
  1391 + "username": "Uporabniško ime",
  1392 + "password": "Geslo",
  1393 + "retry-interval": "Interval ponovitve v milisekundah",
  1394 + "anonymous": "Anonimno",
  1395 + "basic": "Osnovno",
  1396 + "pem": "PEM",
  1397 + "ca-cert": "Datoteka potrdila CA *",
  1398 + "private-key": "Datoteka z zasebnim ključem *",
  1399 + "cert": "Datoteka s potrdilom *",
  1400 + "no-file": "Izbrana ni nobena datoteka.",
  1401 + "drop-file": "Spustite datoteko ali kliknite, da izberete datoteko za nalaganje.",
  1402 + "mapping": "Preslikava",
  1403 + "topic-filter": "Glavni filter",
  1404 + "converter-type": "Vrsta pretvornika",
  1405 + "converter-json": "Json",
  1406 + "json-name-expression": "Izraz json imena naprave",
  1407 + "topic-name-expression": "Izraz teme imena naprave",
  1408 + "json-type-expression": "Tip naprave json izraz",
  1409 + "topic-type-expression": "Izraz teme naprave",
  1410 + "attribute-key-expression": "Izraz ključnega atributa",
  1411 + "attr-json-key-expression": "Izraz json ključa atributa",
  1412 + "attr-topic-key-expression": "Atribut ključne besede izraza",
  1413 + "request-id-expression": "Zahtevaj izraz za id",
  1414 + "request-id-json-expression": "Zahtevaj izraz json id",
  1415 + "request-id-topic-expression": "Zahtevaj izraz teme za id",
  1416 + "response-topic-expression": "Izraz teme odziva",
  1417 + "value-expression": "Vrednostni izraz",
  1418 + "topic": "Tema",
  1419 + "timeout": "Časovna omejitev v milisekundah",
  1420 + "converter-json-required": "Potreben je pretvornik json.",
  1421 + "converter-json-parse": "Ni mogoče razčleniti pretvornika json.",
  1422 + "filter-expression": "Filtriraj izraz",
  1423 + "connect-requests": "Zahteve za povezovanje",
  1424 + "add-connect-request": "Dodaj zahtevo za povezavo",
  1425 + "disconnect-requests": "Prekini zahteve",
  1426 + "add-disconnect-request": "Dodaj zahtevo za prekinitev povezave",
  1427 + "attribute-requests": "Zahteve za atribute",
  1428 + "add-attribute-request": "Dodaj zahtevo za atribut",
  1429 + "attribute-updates": "Posodobitve atributov",
  1430 + "add-attribute-update": "Dodaj posodobitev atributa",
  1431 + "server-side-rpc": "RPC na strežniški strani",
  1432 + "add-server-side-rpc-request": "Dodaj zahtevo RPC na strani strežnika",
  1433 + "device-name-filter": "Filter imena naprave",
  1434 + "attribute-filter": "Filter atributov",
  1435 + "method-filter": "Filter metode",
  1436 + "request-topic-expression": "Zahtevaj izraz teme",
  1437 + "response-timeout": "Časovna omejitev odziva v milisekundah",
  1438 + "topic-expression": "Izraz teme",
  1439 + "client-scope": "Obseg stranke",
  1440 + "add-device": "Dodaj napravo",
  1441 + "opc-server": "Strežniki",
  1442 + "opc-add-server": "Dodaj strežnik",
  1443 + "opc-add-server-prompt": "Prosimo, dodajte strežnik",
  1444 + "opc-application-name": "Ime aplikacije",
  1445 + "opc-application-uri": "Uri aplikacije",
  1446 + "opc-scan-period-in-seconds": "Obdobje skeniranja v sekundah",
  1447 + "opc-security": "Varnost",
  1448 + "opc-identity": "Identiteta",
  1449 + "opc-keystore": "Trgovina s ključi",
  1450 + "opc-type": "Vrsta",
  1451 + "opc-keystore-type": "Vrsta",
  1452 + "opc-keystore-location": "Lokacija *",
  1453 + "opc-keystore-password": "Geslo",
  1454 + "opc-keystore-alias": "Vzdevek",
  1455 + "opc-keystore-key-password": "Ključno geslo",
  1456 + "opc-device-node-pattern": "Vzorec vozlišča naprave",
  1457 + "opc-device-name-pattern": "Vzorec imena naprave",
  1458 + "modbus-server": "Strežniki / podrejeni",
  1459 + "modbus-add-server": "Dodaj strežnik / podrejen",
  1460 + "modbus-add-server-prompt": "Prosimo, dodajte strežnik / podrejen",
  1461 + "modbus-transport": "Prenos",
  1462 + "modbus-tcp-reconnect": "Samodejno ponovno vzpostavi povezavo",
  1463 + "modbus-rtu-over-tcp": "RTU prek TCP",
  1464 + "modbus-port-name": "Ime serijskih vrat",
  1465 + "modbus-encoding": "Kodiranje",
  1466 + "modbus-parity": "Parnost",
  1467 + "modbus-baudrate": "Hitrost prenosa",
  1468 + "modbus-databits": "Podatkovni bit",
  1469 + "modbus-stopbits": "Stop bits",
  1470 + "modbus-databits-range": "Podatkovni bit mora biti v območju od 7 do 8.",
  1471 + "modbus-stopbits-range": "Stop bitov mora biti v območju od 1 do 2.",
  1472 + "modbus-unit-id": "ID enote",
  1473 + "modbus-unit-id-range": "ID enote mora biti v območju od 1 do 247.",
  1474 + "modbus-device-name": "Ime naprave",
  1475 + "modbus-poll-period": "Obdobje ankete (ms)",
  1476 + "modbus-attributes-poll-period": "Obdobje ankete atributov (ms)",
  1477 + "modbus-timeseries-poll-period": "Obdobje ankete časovnih serij (ms)",
  1478 + "modbus-poll-period-range": "Obdobje ankete mora imeti pozitivno vrednost.",
  1479 + "modbus-tag": "Oznaka",
  1480 + "modbus-function": "Funkcija",
  1481 + "modbus-register-address": "Registrski naslov",
  1482 + "modbus-register-address-range": "Naslov registra mora biti v območju od 0 do 65535.",
  1483 + "modbus-register-bit-index": "Bitni indeks",
  1484 + "modbus-register-bit-index-range": "Bitni indeks naj bo v območju od 0 do 15.",
  1485 + "modbus-register-count": "Število registra",
  1486 + "modbus-register-count-range": "Število registra mora biti pozitivna vrednost.",
  1487 + "modbus-byte-order": "Vrstni red bajtov",
  1488 + "sync": {
  1489 + "status": "Stanje",
  1490 + "sync": "Sinhronizacija",
  1491 + "not-sync": "Ni sinhronizacija",
  1492 + "last-sync-time": "Čas zadnje sinhronizacije",
  1493 + "not-available": "Ni na voljo"
  1494 + },
  1495 + "export-extensions-configuration": "Izvozi konfiguracijo razširitev",
  1496 + "import-extensions-configuration": "Uvozi konfiguracijo razširitev",
  1497 + "import-extensions": "Uvozi razširitve",
  1498 + "import-extension": "Uvozi razširitev",
  1499 + "export-extension": "Razširitev izvoza",
  1500 + "file": "Razširitvena datoteka",
  1501 + "invalid-file-error": "Neveljavna datoteka razširitve"
  1502 + },
  1503 + "filter": {
  1504 + "add": "Dodaj filter",
  1505 + "edit": "Uredi filter",
  1506 + "name": "Ime filtra",
  1507 + "name-required": "Ime filtra je obvezno.",
  1508 + "duplicate-filter": "Filter z istim imenom že obstaja.",
  1509 + "filters": "Filtri",
  1510 + "unable-delete-filter-title": "Filtra ni mogoče izbrisati",
  1511 + "unable-delete-filter-text": "Filtra '{{filter}}' ni mogoče izbrisati, kot ga uporabljajo naslednji pripomočki:<br/>{{widgetsList}}",
  1512 + "duplicate-filter-error": "Najden je podvojen filter '{{filter}}'.<br>Filtri morajo biti na nadzorni plošči edinstveni.",
  1513 + "missing-key-filters-error": "Za filter '{{filter}}' manjkajo ključni filtri.",
  1514 + "filter": "Filter",
  1515 + "editable": "Urejanje",
  1516 + "no-filters-found": "Ni najden noben filter.",
  1517 + "no-filter-text": "No filter specified",
  1518 + "add-filter-prompt": "Please add filter",
  1519 + "no-filter-matching": "'{{filter}}' ni mogoče najti.",
  1520 + "create-new-filter": "Ustvari novega!",
  1521 + "filter-required": "Potreben je filter.",
  1522 + "operation": {
  1523 + "operation": "Dejanje",
  1524 + "equal": "enako",
  1525 + "not-equal": "ni enako",
  1526 + "starts-with": "začne se z",
  1527 + "ends-with": "konča se z",
  1528 + "contains": "vsebuje",
  1529 + "not-contains": "ne vsebuje",
  1530 + "greater": "večji kot",
  1531 + "less": "manj kot",
  1532 + "greater-or-equal": "večje ali enako",
  1533 + "less-or-equal": "manjše ali enako",
  1534 + "and": "in",
  1535 + "or": "ali"
  1536 + },
  1537 + "ignore-case": "Prezri črko",
  1538 + "value": "Vrednost",
  1539 + "remove-filter": "Odstrani filter",
  1540 + "preview": "Predogled filtra",
  1541 + "no-filters": "Ni nastavljenih filtrov",
  1542 + "add-filter": "Dodaj filter",
  1543 + "add-complex-filter": "Dodaj kompleksni filter",
  1544 + "add-complex": "Dodaj kompleks",
  1545 + "complex-filter": "Kompleksni filter",
  1546 + "edit-complex-filter": "Uredi kompleksni filter",
  1547 + "edit-filter-user-params": "Uredi uporabniške parametre predikata filtra",
  1548 + "filter-user-params": "Filter predicate user parameters",
  1549 + "user-parameters": "Uporabniški parametri",
  1550 + "display-label": "Oznaka za prikaz",
  1551 + "autogenerated-label": "Samodejno ustvari oznako",
  1552 + "order-priority": "Prednostni vrstni red",
  1553 + "key-filter": "Ključni filter",
  1554 + "key-filters": "Ključni filtri",
  1555 + "key-name": "Ime ključa",
  1556 + "key-name-required": "Ime ključa je obvezno.",
  1557 + "key-type": {
  1558 + "key-type": "Tip ključa",
  1559 + "attribute": "Atribut",
  1560 + "timeseries": "Časovne serije",
  1561 + "entity-field": "Polje entitete"
  1562 + },
  1563 + "value-type": {
  1564 + "value-type": "Vrsta vrednosti",
  1565 + "string": "Niz",
  1566 + "numeric": "Številska",
  1567 + "boolean": "Logična",
  1568 + "date-time": "Datum in čas"
  1569 + },
  1570 + "value-type-required": "Zahtevana je vrsta vrednosti ključa.",
  1571 + "key-value-type-change-title": "Ali ste prepričani, da želite spremeniti vrsto vrednosti ključa?",
  1572 + "key-value-type-change-message": "Če potrdite novo vrsto vrednosti, bodo odstranjeni vsi vneseni filtri ključev.",
  1573 + "no-key-filters": "Noben ključni filter ni konfiguriran",
  1574 + "add-key-filter": "Dodaj ključni filter",
  1575 + "remove-key-filter": "Odstrani filter ključev",
  1576 + "edit-key-filter": "Uredi filter ključev",
  1577 + "date": "Datum",
  1578 + "time": "Čas",
  1579 + "current-tenant": "Trenutni najemnik",
  1580 + "current-customer": "Trenutna stranka",
  1581 + "current-user": "Trenutni uporabnik",
  1582 + "current-device": "Current device",
  1583 + "default-value": "Privzeta vrednost",
  1584 + "dynamic-source-type": "Dinamična vrsta vira",
  1585 + "no-dynamic-value": "Ni dinamične vrednosti",
  1586 + "source-attribute": "Izvorni atribut",
  1587 + "switch-to-dynamic-value": "Preklopi na dinamično vrednost",
  1588 + "switch-to-default-value": "Preklopi na privzeto vrednost"
  1589 + },
  1590 + "fullscreen": {
  1591 + "expand": "Razširi na celozaslonski način",
  1592 + "exit": "Izhod iz celozaslonskega načina",
  1593 + "toggle": "Preklopi na celozaslonski način",
  1594 + "fullscreen": "Celozaslonski način"
  1595 + },
  1596 + "function": {
  1597 + "function": "Funkcija"
  1598 + },
  1599 + "gateway": {
  1600 + "add-entry": "Dodaj konfiguracijo",
  1601 + "connector-add": "Dodaj nov priključek",
  1602 + "connector-enabled": "Omogoči priključek",
  1603 + "connector-name": "Ime priključka",
  1604 + "connector-name-required": "Ime priključka je obvezno.",
  1605 + "connector-type": "Vrsta priključka",
  1606 + "connector-type-required": "Zahteva se vrsta priključka.",
  1607 + "connectors": "Konfiguracija priključkov",
  1608 + "create-new-gateway": "Ustvari nov prehod",
  1609 + "create-new-gateway-text": "Ali ste prepričani, da želite ustvariti nov prehod z imenom: '{{gatewayName}}'?",
  1610 + "delete": "Izbriši konfiguracijo",
  1611 + "download-tip": "Prenos konfiguracijske datoteke",
  1612 + "gateway": "Prehod",
  1613 + "gateway-exists": "Naprava z istim imenom že obstaja.",
  1614 + "gateway-name": "Ime prehoda",
  1615 + "gateway-name-required": "Ime prehoda je obvezno.",
  1616 + "gateway-saved": "Konfiguracija prehoda je uspešno shranjena.",
  1617 + "json-parse": "Neveljaven JSON.",
  1618 + "json-required": "Polje ne sme biti prazno.",
  1619 + "no-connectors": "Ni priključkov",
  1620 + "no-data": "Brez konfiguracij",
  1621 + "no-gateway-found": "Prehod ni najden.",
  1622 + "no-gateway-matching": " '{{item}}' ni mogoče najti.",
  1623 + "path-logs": "Pot do dnevniških datotek",
  1624 + "path-logs-required": "Pot je obvezna.",
  1625 + "remote": "Oddaljena konfiguracija",
  1626 + "remote-logging-level": "Raven beleženja",
  1627 + "remove-entry": "Odstrani konfiguracijo",
  1628 + "save-tip": "Shrani konfiguracijsko datoteko",
  1629 + "security-type": "Vrsta zaščite",
  1630 + "security-types": {
  1631 + "access-token": "Dostopni žeton",
  1632 + "tls": "TLS"
  1633 + },
  1634 + "storage": "Shramba",
  1635 + "storage-max-file-records": "Največ zapisov v datoteki",
  1636 + "storage-max-files": "Največje število datotek",
  1637 + "storage-max-files-min": "Najmanjše število je 1.",
  1638 + "storage-max-files-pattern": "Številka ni veljavna.",
  1639 + "storage-max-files-required": "Številka je obvezna.",
  1640 + "storage-max-records": "Največ zapisov v pomnilniku",
  1641 + "storage-max-records-min": "Najmanjše število zapisov je 1.",
  1642 + "storage-max-records-pattern": "Številka ni veljavna.",
  1643 + "storage-max-records-required": "Zahtevan je največ zapisov.",
  1644 + "storage-pack-size": "Največja velikost paketa dogodkov",
  1645 + "storage-pack-size-min": "Najmanjše število je 1.",
  1646 + "storage-pack-size-pattern": "Številka ni veljavna.",
  1647 + "storage-pack-size-required": "Zahtevana je največja velikost paketa dogodkov.",
  1648 + "storage-path": "Pot pomnilnika",
  1649 + "storage-path-required": "Zahtevana je pot do pomnilnika.",
  1650 + "storage-type": "Vrsta pomnilnika",
  1651 + "storage-types": {
  1652 + "file-storage": "Shramba datotek",
  1653 + "memory-storage": "Spomin pomnilnika"
  1654 + },
  1655 + "thingsboard": "ThingsBoard",
  1656 + "thingsboard-host": "Gostitelj ThingsBoard",
  1657 + "thingsboard-host-required": "Potreben je gostitelj.",
  1658 + "thingsboard-port": "Vrata ThingsBoard",
  1659 + "thingsboard-port-max": "Največja številka vrat je 65535.",
  1660 + "thingsboard-port-min": "Najmanjša številka vrat je 1.",
  1661 + "thingsboard-port-pattern": "Vrata niso veljavna.",
  1662 + "thingsboard-port-required": "Potrebna so vrata.",
  1663 + "tidy": "Urejeno",
  1664 + "tidy-tip": "Urejena konfiguracija JSON",
  1665 + "title-connectors-json": "Konfiguracija konektorja {{typeName}}",
  1666 + "tls-path-ca-certificate": "Pot do potrdila CA na prehodu",
  1667 + "tls-path-client-certificate": "Pot do potrdila stranke na prehodu",
  1668 + "tls-path-private-key": "Pot do zasebnega ključa na prehodu",
  1669 + "toggle-fullscreen": "Preklop na celozaslonski način",
  1670 + "transformer-json-config": "Konfiguracija JSON *",
  1671 + "update-config": "Dodaj / posodobi konfiguracijo JSON"
  1672 + },
  1673 + "grid": {
  1674 + "delete-item-title": "Ali ste prepričani, da želite izbrisati ta element?",
  1675 + "delete-item-text": "Bodite previdni, po potrditvi bodo ta element in vsi povezani podatki nepopravljivi.",
  1676 + "delete-items-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 item} other {# items} }?",
  1677 + "delete-items-action-title": "Izbriši { count, plural, 1 {1 item} other {# items} }",
  1678 + "delete-items-text": "Bodite previdni, po potrditvi bodo vsi izbrani elementi odstranjeni in vsi povezani podatki nepopravljivi.",
  1679 + "add-item-text": "Dodaj novo postavko",
  1680 + "no-items-text": "Ni najdenih postavk",
  1681 + "item-details": "Podrobnosti o postavki",
  1682 + "delete-item": "Izbriši postavko",
  1683 + "delete-items": "Izbriši postavke",
  1684 + "scroll-to-top": "Pomakni se na vrh"
  1685 + },
  1686 + "help": {
  1687 + "goto-help-page": "Pojdi na stran s pomočjo"
  1688 + },
  1689 + "home": {
  1690 + "home": "Domov",
  1691 + "profile": "Profil",
  1692 + "logout": "Odjava",
  1693 + "menu": "Meni",
  1694 + "avatar": "Avatar",
  1695 + "open-user-menu": "Odpri uporabniški meni"
  1696 + },
  1697 + "import": {
  1698 + "no-file": "Izbrana ni nobena datoteka",
  1699 + "drop-file": "Spustite datoteko JSON ali kliknite, da izberete datoteko za nalaganje.",
  1700 + "drop-file-csv": "Spustite datoteko CSV ali kliknite, da izberete datoteko za nalaganje.",
  1701 + "column-value": "Vrednost",
  1702 + "column-title": "Naslov",
  1703 + "column-example": "Primer vrednosti podatkov",
  1704 + "column-key": "Atribut / ključ telemetrije",
  1705 + "csv-delimiter": "Ločilo CSV",
  1706 + "csv-first-line-header": "Prva vrstica vsebuje imena stolpcev",
  1707 + "csv-update-data": "Posodobi atribute / telemetrijo",
  1708 + "import-csv-number-columns-error": "Datoteka mora vsebovati vsaj dva stolpca",
  1709 + "import-csv-invalid-format-error": "Neveljavna oblika datoteke. Vrstica: '{{line}}'",
  1710 + "column-type": {
  1711 + "name": "Ime",
  1712 + "type": "Vrsta",
  1713 + "label": "Oznaka",
  1714 + "column-type": "Vrsta stolpca",
  1715 + "client-attribute": "Atribut odjemalca",
  1716 + "shared-attribute": "Skupni atribut",
  1717 + "server-attribute": "Atribut strežnika",
  1718 + "timeseries": "Časovne serije",
  1719 + "entity-field": "Polje entitete",
  1720 + "access-token": "Dostopni žeton",
  1721 + "isgateway": "Je prehod",
  1722 + "description": "Opis"
  1723 + },
  1724 + "stepper-text": {
  1725 + "select-file": "Izberi datoteko",
  1726 + "configuration": "Uvozi konfiguracijo",
  1727 + "column-type": "Izberi vrsto stolpca",
  1728 + "creat-entities": "Ustvarjanje novih entitet",
  1729 + "done": "Končano"
  1730 + },
  1731 + "message": {
  1732 + "create-entities": "{{count}} novih entitet je bilo uspešno ustvarjenih.",
  1733 + "update-entities": "{{count}} entitet je bilo uspešno posodobljenih.",
  1734 + "error-entities": "Prišlo je do napake pri ustvarjanju {{count}} entitet."
  1735 + }
  1736 + },
  1737 + "item": {
  1738 + "selected": "Izbrano"
  1739 + },
  1740 + "js-func": {
  1741 + "no-return-error": "Funkcija mora vrniti vrednost!",
  1742 + "return-type-mismatch": "Funkcija mora vrniti vrednost vrste '{{type}}'!",
  1743 + "tidy": "Urejeno",
  1744 + "mini": "Mini"
  1745 + },
  1746 + "key-val": {
  1747 + "key": "Ključ",
  1748 + "value": "Vrednost",
  1749 + "remove-entry": "Odstrani vnos",
  1750 + "add-entry": "Dodaj vnos",
  1751 + "no-data": "Ni vnosov"
  1752 + },
  1753 + "layout": {
  1754 + "layout": "Postavitev",
  1755 + "manage": "Upravljanje postavitev",
  1756 + "settings": "Nastavitve postavitve",
  1757 + "color": "Barva",
  1758 + "main": "Glavni",
  1759 + "right": "Pravi",
  1760 + "select": "Izberi ciljno postavitev"
  1761 + },
  1762 + "legend": {
  1763 + "direction": "Smer legende",
  1764 + "position": "Položaj legende",
  1765 + "sort-legend": "Sort datakeys in legend",
  1766 + "show-max": "Prikaži največjo vrednost",
  1767 + "show-min": "Pokaži najmanjšo vrednost",
  1768 + "show-avg": "Prikaži povprečno vrednost",
  1769 + "show-total": "Prikaži skupno vrednost",
  1770 + "settings": "Nastavitve legende",
  1771 + "min": "najmanj",
  1772 + "max": "največ",
  1773 + "avg": "povprečno",
  1774 + "total": "skupaj",
  1775 + "comparison-time-ago": {
  1776 + "days": "(pretekli dan)",
  1777 + "weeks": "(pretekli teden)",
  1778 + "months": "(pretekli mesec)",
  1779 + "years": "(preteklo leto)"
  1780 + }
  1781 + },
  1782 + "login": {
  1783 + "login": "Vpiši se",
  1784 + "request-password-reset": "Zahtevaj ponastavitev gesla",
  1785 + "reset-password": "Ponastavitev gesla",
  1786 + "create-password": "Ustvari geslo",
  1787 + "passwords-mismatch-error": "Vnesena gesla morajo biti enaka!",
  1788 + "password-again": "Ponovi geslo",
  1789 + "sign-in": "Prosimo, prijavite se",
  1790 + "username": "Uporabniško ime (e-pošta)",
  1791 + "remember-me": "Zapomni si me",
  1792 + "forgot-password": "Ste pozabili geslo?",
  1793 + "password-reset": "Resetiranje gesla",
  1794 + "expired-password-reset-message": "Vaše poverilnice so potekle! Ustvarite novo geslo.",
  1795 + "new-password": "Novo geslo",
  1796 + "new-password-again": "Ponovi novo geslo",
  1797 + "password-link-sent-message": "Povezava za ponastavitev gesla je bila uspešno poslana!",
  1798 + "email": "E-naslov",
  1799 + "login-with": "Prijava z {{name}}",
  1800 + "or": "ali",
  1801 + "error": "Napaka pri prijavi"
  1802 + },
  1803 + "position": {
  1804 + "top": "Zgoraj",
  1805 + "bottom": "Spodaj",
  1806 + "left": "Levo",
  1807 + "right": "Desno"
  1808 + },
  1809 + "profile": {
  1810 + "profile": "Profil",
  1811 + "last-login-time": "Zadnja prijava",
  1812 + "change-password": "Spremeni geslo",
  1813 + "current-password": "Trenutno geslo"
  1814 + },
  1815 + "relation": {
  1816 + "relations": "Odnosi",
  1817 + "direction": "Smer",
  1818 + "search-direction": {
  1819 + "FROM": "Od",
  1820 + "TO": "Za"
  1821 + },
  1822 + "direction-type": {
  1823 + "FROM": "od",
  1824 + "TO": "za"
  1825 + },
  1826 + "from-relations": "Odhodna razmerja",
  1827 + "to-relations": "Vhodna razmerja",
  1828 + "selected-relations": "{ count, plural, 1 {1 relationship} other {# Relations} } izbran",
  1829 + "type": "Vrsta",
  1830 + "to-entity-type": "Za vrsto entitete",
  1831 + "to-entity-name": "Za ime entitete",
  1832 + "from-entity-type": "Od vrste entitete",
  1833 + "from-entity-name": "Od imena entitete",
  1834 + "to-entity": "Za entiteto",
  1835 + "from-entity": "Od entitete",
  1836 + "delete": "Izbriši relacijo",
  1837 + "relation-type": "Vrsta relacije",
  1838 + "relation-type-required": "Vrsta relacije je obvezna.",
  1839 + "any-relation-type": "Katerakoli vrsta",
  1840 + "add": "Dodaj relacijo",
  1841 + "edit": "Uredi relacijo",
  1842 + "delete-to-relation-title": "Ali ste prepričani, da želite izbrisati relacijo z entiteto '{{entityName}}'?",
  1843 + "delete-to-relation-text": "Previdno, po potrditvi entiteta '{{entityName}}' ne bo v relaciji s trenutno entiteto.",
  1844 + "delete-to-relations-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 relacijo} other {# relacije} }?",
  1845 + "delete-to-relations-text": "Bodite previdni, po potrditvi bodo vse izbrane relacije odstranjene in entitete ne bodo povezane med sabo.",
  1846 + "delete-from-relation-title": "Ali ste prepričani, da želite izbrisati relacijo iz entitete '{{entityName}}'?",
  1847 + "delete-from-relation-text": "Bodite previdni, po potrditvi trenutna entiteta ne bo v relacijii z entiteto '{{entityName}}'.",
  1848 + "delete-from-relations-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 relacija} other {# odnosi} }?",
  1849 + "delete-from-relations-text": "Bodite previdni, po potrditvi bodo odstranjene vse izbrane relacije in trenutna entiteta ne bo v relaciji z omenjenimi entitetami.",
  1850 + "remove-relation-filter": "Odstrani relacijski filter",
  1851 + "add-relation-filter": "Dodaj relacijski filter",
  1852 + "any-relation": "Kakršnakoli relacija",
  1853 + "relation-filters": "Relacijski filtri",
  1854 + "additional-info": "Dodatne informacije (JSON)",
  1855 + "invalid-additional-info": "Dodatnih informacij json ni mogoče razčleniti.",
  1856 + "no-relations-text": "Ni najdenih relacij"
  1857 + },
  1858 + "rulechain": {
  1859 + "rulechain": "Veriga pravil",
  1860 + "rulechains": "Pravila",
  1861 + "root": "Izvor",
  1862 + "delete": "Izbriši verigo pravil",
  1863 + "name": "Ime",
  1864 + "name-required": "Ime je obvezno.",
  1865 + "description": "Opis",
  1866 + "add": "Dodaj verigo pravil",
  1867 + "set-root": "Ustvari izvor verige pravil",
  1868 + "set-root-rulechain-title": "Ali ste prepričani, da želite narediti izvorno verigo pravil '{{ruleChainName}}'?",
  1869 + "set-root-rulechain-text": "Po potrditvi bo veriga pravil postala izvorna in bo obravnavala vsa dohodna prometna sporočila.",
  1870 + "delete-rulechain-title": "Ali ste prepričani, da želite izbrisati verigo pravil '{{ruleChainName}}'?",
  1871 + "delete-rulechain-text": "Bodite previdni, po potrditvi bodo veriga pravil in vsi povezani podatki nepopravljivi.",
  1872 + "delete-rulechains-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 rule chain} other {# rule chains} }?",
  1873 + "delete-rulechains-action-title": "Izbriši { count, plural, 1 {1 rule chain} other {# rule chains} }",
  1874 + "delete-rulechains-text": "Bodite previdni, po potrditvi bodo odstranjene vse izbrane verige pravil in vsi povezani podatki bodo postali nepopravljivi.",
  1875 + "add-rulechain-text": "Dodaj novo verigo pravil",
  1876 + "no-rulechains-text": "Nobena veriga pravil ni najdena",
  1877 + "rulechain-details": "Podrobnosti o verigi pravil",
  1878 + "details": "Podrobnosti",
  1879 + "events": "Dogodki",
  1880 + "system": "Sistem",
  1881 + "import": "Uvozi verigo pravil",
  1882 + "export": "Izvozi verigo pravil",
  1883 + "export-failed-error": "Verige pravil ni mogoče izvoziti: {{error}}",
  1884 + "create-new-rulechain": "Ustvari novo verigo pravil",
  1885 + "rulechain-file": "Datoteka verige pravil",
  1886 + "invalid-rulechain-file-error": "Ni mogoče uvoziti verige pravil: neveljavna struktura podatkov verige pravil.",
  1887 + "copyId": "Kopiraj ID verige pravil",
  1888 + "idCopiedMessage": "ID verige pravil je kopiran v odložišče",
  1889 + "select-rulechain": "Izberi verigo pravil",
  1890 + "no-rulechains-matching": "Najdena ni bila nobena veriga pravil, ki se ujema z '{{entity}}'.",
  1891 + "rulechain-required": "Zahtevana je veriga pravil",
  1892 + "management": "Upravljanje pravil",
  1893 + "debug-mode": "Način za odpravljanje napak",
  1894 + "search": "Iskanje verig pravil",
  1895 + "selected-rulechains": "{ count, plural, 1 {1 rule chain} other {# rule chains} } izbrano",
  1896 + "open-rulechain": "Odprta veriga pravil"
  1897 + },
  1898 + "rulenode": {
  1899 + "details": "Podrobnosti",
  1900 + "events": "Dogodki",
  1901 + "search": "Iskanje vozlišč",
  1902 + "open-node-library": "Odpri knjižnico vozlišč",
  1903 + "add": "Dodaj vozlišče pravila",
  1904 + "name": "Ime",
  1905 + "name-required": "Ime je obvezno.",
  1906 + "type": "Vrsta",
  1907 + "description": "Opis",
  1908 + "delete": "Izbriši vozlišče pravila",
  1909 + "select-all-objects": "Izberi vsa vozlišča in povezave",
  1910 + "deselect-all-objects": "Prekliči izbiro vseh vozlišč in povezav",
  1911 + "delete-selected-objects": "Izbriši izbrana vozlišča in povezave",
  1912 + "delete-selected": "Izbriši izbrano",
  1913 + "select-all": "Izberi vse",
  1914 + "copy-selected": "Kopiraj izbrano",
  1915 + "deselect-all": "Prekliči izbor",
  1916 + "rulenode-details": "Podrobnosti vozlišča pravila",
  1917 + "debug-mode": "Način za odpravljanje napak",
  1918 + "configuration": "Konfiguracija",
  1919 + "link": "Povezava",
  1920 + "link-details": "Podrobnosti povezave vozlišča pravila",
  1921 + "add-link": "Dodaj povezavo",
  1922 + "link-label": "Oznaka povezave",
  1923 + "link-label-required": "Oznaka povezave je obvezna.",
  1924 + "custom-link-label": "Oznaka povezave po meri",
  1925 + "custom-link-label-required": "Zahtevana je oznaka povezave po meri.",
  1926 + "link-labels": "Oznake povezav",
  1927 + "link-labels-required": "Oznake povezav so obvezne.",
  1928 + "no-link-labels-found": "Oznak povezav ni bilo mogoče najti",
  1929 + "no-link-label-matching": "'{{label}}' ni mogoče najti.",
  1930 + "create-new-link-label": "Ustvari novega!",
  1931 + "type-filter": "Filter",
  1932 + "type-filter-details": "Filtriraj dohodna sporočila s konfiguriranimi pogoji",
  1933 + "type-enrichment": "Obogatitev",
  1934 + "type-enrichment-details": "Dodaj dodatne informacije v metapodatke sporočila",
  1935 + "type-transformation": "Preobrazba",
  1936 + "type-transformation-details": "Spremeni koristni tovor sporočila in metapodatke",
  1937 + "type-action": "Dejanje",
  1938 + "type-action-details": "Izvedite posebno dejanje",
  1939 + "type-external": "Zunanji",
  1940 + "type-external-details": "Interakcija z zunanjim sistemom",
  1941 + "type-rule-chain": "Veriga pravil",
  1942 + "type-rule-chain-details": "Posredovanje dohodnih sporočil določeni verigi pravil",
  1943 + "type-input": "Vnos",
  1944 + "type-input-details": "Logični vnos verige pravil, posreduje dohodna sporočila naslednjemu povezanemu vozlišču pravila",
  1945 + "type-unknown": "Neznano",
  1946 + "type-unknown-details": "Nerazrešeno vozlišče pravila",
  1947 + "directive-is-not-loaded": "Določena konfiguracijska direktiva '{{DirectiveName}}' ni na voljo.",
  1948 + "ui-resources-load-error": "Napajanje virov uporabniškega vmesnika ni uspelo.",
  1949 + "invalid-target-rulechain": "Ni mogoče razrešiti ciljne verige pravil!",
  1950 + "test-script-function": "Preizkusi funkcijo skripte",
  1951 + "message": "Sporočilo",
  1952 + "message-type": "Vrsta sporočila",
  1953 + "select-message-type": "Izberi vrsto sporočila",
  1954 + "message-type-required": "Zahtevana je vrsta sporočila",
  1955 + "metadata": "Metapodatki",
  1956 + "metadata-required": "Vnosi metapodatkov ne smejo biti prazni.",
  1957 + "output": "Izdelek",
  1958 + "test": "Test",
  1959 + "help": "Pomoč",
  1960 + "reset-debug-mode": "Ponastavi način za odpravljanje napak v vseh vozliščih"
  1961 + },
  1962 + "timezone": {
  1963 + "timezone": "Časovni pas",
  1964 + "select-timezone": "Izberite časovni pas",
  1965 + "no-timezones-matching": "Časovnih pasov, ki se ujemajo z '{{timezone}}', ni bilo mogoče najti.",
  1966 + "timezone-required": "Časovni pas je obvezen."
  1967 + },
  1968 + "queue": {
  1969 + "select_name": "Izberi ime čakalne vrste",
  1970 + "name": "Ime čakalne vrste",
  1971 + "name_required": "Ime čakalne vrste je obvezno!"
  1972 + },
  1973 + "tenant": {
  1974 + "tenant": "Najemnik",
  1975 + "tenants": "Najemniki",
  1976 + "management": "Upravljanje najemnikov",
  1977 + "add": "Dodaj najemnika",
  1978 + "admins": "Skrbniki",
  1979 + "manage-tenant-admins": "Upravljanje skrbnikov najemnikov",
  1980 + "delete": "Izbriši najemnika",
  1981 + "add-tenant-text": "Dodaj novega najemnika",
  1982 + "no-tenants-text": "Najemnikov ni bilo mogoče najti",
  1983 + "tenant-details": "Podrobnosti o najemniku",
  1984 + "delete-tenant-title": "Ali ste prepričani, da želite izbrisati najemnika '{{tenantTitle}}'?",
  1985 + "delete-tenant-text": "Previdno, po potrditvi bodo najemnik in vsi povezani podatki postali nepopravljivi.",
  1986 + "delete-tenants-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 tenant} other {# tenants} }?",
  1987 + "delete-tenants-action-title": "Izbriši { count, plural, 1 {1 tenant} other {# tenants} }",
  1988 + "delete-tenants-text": "Bodite previdni, po potrditvi bodo vsi izbrani najemniki odstranjeni in vsi povezani podatki nepopravljivi.",
  1989 + "title": "Naslov",
  1990 + "title-required": "Naslov je obvezen.",
  1991 + "description": "Opis",
  1992 + "details": "Podrobnosti",
  1993 + "events": "Dogodki",
  1994 + "copyId": "Kopiraj ID najemnika",
  1995 + "idCopiedMessage": "ID najemnika je kopiran v odložišče",
  1996 + "select-tenant": "Izberi najemnika",
  1997 + "no-tenants-matching": "Najden ni bil noben najemnik, ki se ujema z '{{entity}}'.",
  1998 + "tenant-required": "Najemnik je obvezen",
  1999 + "search": "Iskanje najemnikov",
  2000 + "selected-tenants": "{ count, plural, 1 {1 tenant} other {# tenants} } izbran",
  2001 + "isolated-tb-core": "Obdelava v izoliranem odlagališču ThingsBoard Core",
  2002 + "isolated-tb-rule-engine": "Obdelava v izoliranem odlagališču ThingsBoard Rule Engine",
  2003 + "isolated-tb-core-details": "Zahteva ločene mikro storitve na izoliranega najemnika",
  2004 + "isolated-tb-rule-engine-details": "Zahteva ločene mikro storitve na izoliranega najemnika"
  2005 + },
  2006 + "tenant-profile": {
  2007 + "tenant-profile": "Tenant profile",
  2008 + "tenant-profiles": "Tenant profiles",
  2009 + "add": "Add tenant profile",
  2010 + "edit": "Edit tenant profile",
  2011 + "tenant-profile-details": "Tenant profile details",
  2012 + "no-tenant-profiles-text": "No tenant profiles found",
  2013 + "search": "Search tenant profiles",
  2014 + "selected-tenant-profiles": "{ count, plural, 1 {1 tenant profile} other {# tenant profiles} } selected",
  2015 + "no-tenant-profiles-matching": "No tenant profile matching '{{entity}}' were found.",
  2016 + "tenant-profile-required": "Tenant profile is required",
  2017 + "idCopiedMessage": "Tenant profile Id has been copied to clipboard",
  2018 + "set-default": "Make tenant profile default",
  2019 + "delete": "Delete tenant profile",
  2020 + "copyId": "Copy tenant profile Id",
  2021 + "name": "Name",
  2022 + "name-required": "Name is required.",
  2023 + "data": "Profile data",
  2024 + "profile-configuration": "Profile configuration",
  2025 + "description": "Description",
  2026 + "default": "Default",
  2027 + "delete-tenant-profile-title": "Are you sure you want to delete the tenant profile '{{tenantProfileName}}'?",
  2028 + "delete-tenant-profile-text": "Be careful, after the confirmation the tenant profile and all related data will become unrecoverable.",
  2029 + "delete-tenant-profiles-title": "Are you sure you want to delete { count, plural, 1 {1 tenant profile} other {# tenant profiles} }?",
  2030 + "delete-tenant-profiles-text": "Be careful, after the confirmation all selected tenant profiles will be removed and all related data will become unrecoverable.",
  2031 + "set-default-tenant-profile-title": "Are you sure you want to make the tenant profile '{{tenantProfileName}}' default?",
  2032 + "set-default-tenant-profile-text": "After the confirmation the tenant profile will be marked as default and will be used for new tenants with no profile specified.",
  2033 + "no-tenant-profiles-found": "No tenant profiles found.",
  2034 + "create-new-tenant-profile": "Create a new one!",
  2035 + "maximum-devices": "Maximum number of devices (0 - unlimited)",
  2036 + "maximum-devices-required": "Maximum number of devices is required.",
  2037 + "maximum-devices-range": "Minimum number of devices can't be negative",
  2038 + "maximum-assets": "Maximum number of assets (0 - unlimited)",
  2039 + "maximum-assets-required": "Maximum number of assets is required.",
  2040 + "maximum-assets-range": "Maximum number of assets can't be negative",
  2041 + "maximum-customers": "Maximum number of customers (0 - unlimited)",
  2042 + "maximum-customers-required": "Maximum number of customers is required.",
  2043 + "maximum-customers-range": "Maximum number of customers can't be negative",
  2044 + "maximum-users": "Maximum number of users (0 - unlimited)",
  2045 + "maximum-users-required": "Maximum number of users is required.",
  2046 + "maximum-users-range": "Maximum number of users can't be negative",
  2047 + "maximum-dashboards": "Maximum number of dashboards (0 - unlimited)",
  2048 + "maximum-dashboards-required": "Maximum number of dashboards is required.",
  2049 + "maximum-dashboards-range": "Maximum number of dashboards can't be negative",
  2050 + "maximum-rule-chains": "Maximum number of rule chains (0 - unlimited)",
  2051 + "maximum-rule-chains-required": "Maximum number of rule chains is required.",
  2052 + "maximum-rule-chains-range": "Maximum number of rule chains can't be negative",
  2053 + "transport-tenant-msg-rate-limit": "Transport tenant messages rate limit.",
  2054 + "transport-tenant-telemetry-msg-rate-limit": "Transport tenant telemetry messages rate limit.",
  2055 + "transport-tenant-telemetry-data-points-rate-limit": "Transport tenant telemetry data points rate limit.",
  2056 + "transport-device-msg-rate-limit": "Transport device messages rate limit.",
  2057 + "transport-device-telemetry-msg-rate-limit": "Transport device telemetry messages rate limit.",
  2058 + "transport-device-telemetry-data-points-rate-limit": "Transport device telemetry data points rate limit.",
  2059 + "max-transport-messages": "Maximum number of transport messages (0 - unlimited)",
  2060 + "max-transport-messages-required": "Maximum number of transport messages is required.",
  2061 + "max-transport-messages-range": "Maximum number of transport messages can't be negative",
  2062 + "max-transport-data-points": "Maximum number of transport data points (0 - unlimited)",
  2063 + "max-transport-data-points-required": "Maximum number of transport data points is required.",
  2064 + "max-transport-data-points-range": "Maximum number of transport data points can't be negative",
  2065 + "max-r-e-executions": "Maximum number of Rule Engine executions (0 - unlimited)",
  2066 + "max-r-e-executions-required": "Maximum number of Rule Engine executions is required.",
  2067 + "max-r-e-executions-range": "Maximum number of Rule Engine executions can't be negative",
  2068 + "max-j-s-executions": "Maximum number of JavaScript executions (0 - unlimited)",
  2069 + "max-j-s-executions-required": "Maximum number of JavaScript executions is required.",
  2070 + "max-j-s-executions-range": "Maximum number of JavaScript executions can't be negative",
  2071 + "max-d-p-storage-days": "Maximum number of data points storage days (0 - unlimited)",
  2072 + "max-d-p-storage-days-required": "Maximum number of data points storage days is required.",
  2073 + "max-d-p-storage-days-range": "Maximum number of data points storage days can't be negative",
  2074 + "default-storage-ttl-days": "Default storage TTL days (0 - unlimited)",
  2075 + "default-storage-ttl-days-required": "Default storage TTL days is required.",
  2076 + "default-storage-ttl-days-range": "Default storage TTL days can't be negative",
  2077 + "max-rule-node-executions-per-message": "Maximum number of rule node executions per message (0 - unlimited)",
  2078 + "max-rule-node-executions-per-message-required": "Maximum number of rule node executions per message is required.",
  2079 + "max-rule-node-executions-per-message-range": "Maximum number of rule node executions per message can't be negative",
  2080 + "max-emails": "Maximum number of emails sent (0 - unlimited)",
  2081 + "max-emails-required": "Maximum number of emails sent is required.",
  2082 + "max-emails-range": "Maximum number of emails sent can't be negative",
  2083 + "max-sms": "Maximum number of SMS sent (0 - unlimited)",
  2084 + "max-sms-required": "Maximum number of SMS sent is required.",
  2085 + "max-sms-range": "Maximum number of SMS sent can't be negative"
  2086 + },
  2087 + "timeinterval": {
  2088 + "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }",
  2089 + "minutes-interval": "{ minutes, plural, 1 {1 minute} other {# minutes} }",
  2090 + "hours-interval": "{ hours, plural, 1 {1 hour} other {# hours} }",
  2091 + "days-interval": "{ days, plural, 1 {1 day} other {# days} }",
  2092 + "days": "Dnevi",
  2093 + "hours": "Ure",
  2094 + "minutes": "Minute",
  2095 + "seconds": "Sekunde",
  2096 + "advanced": "Napredno"
  2097 + },
  2098 + "timeunit": {
  2099 + "seconds": "Seconds",
  2100 + "minutes": "Minutes",
  2101 + "hours": "Hours",
  2102 + "days": "Days"
  2103 + },
  2104 + "timewindow": {
  2105 + "days": "{ days, plural, 1 {dan} other {# dni} }",
  2106 + "hours": "{ hours, plural, 0 {hour} 1 {1 hour} other {# hours} }",
  2107 + "minutes": "{ minute, plural, 0 {minute} 1 {1 minuta} other {# minut} }",
  2108 + "seconds": "{ seconds, plural, 0 {second} 1 {1 second} other {# seconds} }",
  2109 + "realtime": "V realnem času",
  2110 + "history": "Zgodovina",
  2111 + "last-prefix": "zadnji",
  2112 + "period": "od {{ startTime }} do {{ endTime }}",
  2113 + "edit": "Uredi časovno okno",
  2114 + "date-range": "Časovno obdobje",
  2115 + "last": "Zadnji",
  2116 + "time-period": "Časovno obdobje",
  2117 + "hide": "Skrij"
  2118 + },
  2119 + "user": {
  2120 + "user": "Uporabnik",
  2121 + "users": "Uporabniki",
  2122 + "customer-users": "Uporabniki kupcev",
  2123 + "tenant-admins": "Skrbniki najemnikov",
  2124 + "sys-admin": "Sistemski administrator",
  2125 + "tenant-admin": "Skrbnik najemnika",
  2126 + "customer": "Stranka",
  2127 + "anonymous": "Anonimno",
  2128 + "add": "Dodaj uporabnika",
  2129 + "delete": "Izbriši uporabnika",
  2130 + "add-user-text": "Dodaj novega uporabnika",
  2131 + "no-users-text": "Uporabnikov ni bilo mogoče najti",
  2132 + "user-details": "Podrobnosti o uporabniku",
  2133 + "delete-user-title": "Ali ste prepričani, da želite izbrisati uporabnika '{{userEmail}}'?",
  2134 + "delete-user-text": "Previdno, po potrditvi bodo uporabnik in vsi povezani podatki nepopravljivi.",
  2135 + "delete-users-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 user} other {# users} }?",
  2136 + "delete-users-action-title": "Izbriši { count, plural, 1 {1 user} other {# users} }",
  2137 + "delete-users-text": "Previdno, po potrditvi bodo vsi izbrani uporabniki odstranjeni in vsi povezani podatki nepopravljivi.",
  2138 + "activation-email-sent-message": "Aktivacijsko e-poštno sporočilo je bilo uspešno poslano!",
  2139 + "resend-activation": "Znova pošlji aktivacijo",
  2140 + "email": "E-pošta",
  2141 + "email-required": "E-pošta je obvezna.",
  2142 + "invalid-email-format": "Neveljavna oblika e-pošte.",
  2143 + "first-name": "Ime",
  2144 + "last-name": "Priimek",
  2145 + "description": "Opis",
  2146 + "default-dashboard": "Privzeta nadzorna plošča",
  2147 + "always-fullscreen": "Vedno v celozaslonskem načinu",
  2148 + "select-user": "Izberi uporabnika",
  2149 + "no-users-matching": "Najti ni bilo mogoče nobenega uporabnika, ki se ujema z '{{entity}}'.",
  2150 + "user-required": "Uporabnik je potreben",
  2151 + "activation-method": "Način aktiviranja",
  2152 + "display-activation-link": "Prikaži aktivacijsko povezavo",
  2153 + "send-activation-mail": "Pošlji aktivacijsko pošto",
  2154 + "activation-link": "Povezava za aktiviranje uporabnika",
  2155 + "activation-link-text": "Če želite uporabnika aktivirati, uporabite naslednjo <a href='{{activationLink}}' target='_blank'>aktivacijsko povezavo</a> :",
  2156 + "copy-activation-link": "Kopiraj aktivacijsko povezavo",
  2157 + "activation-link-copied-message": "Povezava za aktiviranje uporabnika je bila kopirana v odložišče",
  2158 + "details": "Podrobnosti",
  2159 + "login-as-tenant-admin": "Prijava kot skrbnik najemnika",
  2160 + "login-as-customer-user": "Prijavi se kot uporabnik stranke",
  2161 + "search": "Iskanje uporabnikov",
  2162 + "selected-users": "{ count, plural, 1 {1 user} other {# users} } izbranih",
  2163 + "disable-account": "Onemogoči uporabniški račun",
  2164 + "enable-account": "Omogoči uporabniški račun",
  2165 + "enable-account-message": "Uporabniški račun je bil uspešno omogočen!",
  2166 + "disable-account-message": "Uporabniški račun je bil uspešno onemogočen!"
  2167 + },
  2168 + "value": {
  2169 + "type": "Vrsta vrednosti",
  2170 + "string": "Niz",
  2171 + "string-value": "Vrednost niza",
  2172 + "string-value-required": "Vrednost niza je potrebna",
  2173 + "integer": "Celota",
  2174 + "integer-value": "Celotna vrednost",
  2175 + "integer-value-required": "Zahtevana je celoštevilčna vrednost",
  2176 + "invalid-integer-value": "Neveljavna celoštevilska vrednost",
  2177 + "double": "Podvojeno",
  2178 + "double-value": "Podvojena vrednost",
  2179 + "double-value-required": "Zahtevana je podvojena vrednost",
  2180 + "boolean": "Logično",
  2181 + "boolean-value": "Logična vrednost",
  2182 + "false": "Napačno",
  2183 + "true": "Pravilno",
  2184 + "long": "Dolgo",
  2185 + "json": "JSON",
  2186 + "json-value": "Vrednost JSON",
  2187 + "json-value-invalid": "Vrednost JSON ima neveljavno obliko",
  2188 + "json-value-required": "Vrednost JSON je potrebna."
  2189 + },
  2190 + "widget": {
  2191 + "widget-library": "Knjižnica pripomočkov",
  2192 + "widget-bundle": "Paket pripomočkov",
  2193 + "select-widgets-bundle": "Izberi paket pripomočkov",
  2194 + "management": "Upravljanje pripomočkov",
  2195 + "editor": "Urejevalnik pripomočkov",
  2196 + "widget-type-not-found": "Težava pri nalaganju konfiguracije pripomočka. <br> Verjetno je bil povezan tip pripomočka odstranjen.",
  2197 + "widget-type-load-error": "Pripomoček ni bil naložen zaradi naslednjih napak:",
  2198 + "remove": "Odstrani pripomoček",
  2199 + "edit": "Uredi pripomoček",
  2200 + "remove-widget-title": "Ali ste prepričani, da želite odstraniti pripomoček '{{widgetTitle}}'?",
  2201 + "remove-widget-text": "Po potrditvi bodo pripomoček in vsi povezani podatki nepopravljivi.",
  2202 + "timeseries": "Časovne serije",
  2203 + "search-data": "Iskanje podatkov",
  2204 + "no-data-found": "Podatkov ni mogoče najti",
  2205 + "latest-values": "Najnovejše vrednosti",
  2206 + "rpc": "Nadzorni pripomoček",
  2207 + "alarm": "Pripomoček za alarm",
  2208 + "static": "Statični pripomoček",
  2209 + "select-widget-type": "Izberi vrsto pripomočka",
  2210 + "missing-widget-title-error": "Navesti je treba pripomoček!",
  2211 + "widget-saved": "Pripomoček je shranjen",
  2212 + "unable-to-save-widget-error": "Pripomočka ni mogoče shraniti! V pripomočku so napake!",
  2213 + "save": "Shrani pripomoček",
  2214 + "saveAs": "Shrani pripomoček kot",
  2215 + "save-widget-type-as": "Shrani vrsto pripomočka kot",
  2216 + "save-widget-type-as-text": "Vnesite nov naslov pripomočka in / ali izberite paket ciljnih pripomočkov",
  2217 + "toggle-fullscreen": "Preklop na celozaslonski način",
  2218 + "run": "Zaženi pripomoček",
  2219 + "title": "Naslov pripomočka",
  2220 + "title-required": "Naslov pripomočka je obvezen.",
  2221 + "type": "Vrsta pripomočka",
  2222 + "resources": "Viri",
  2223 + "resource-url": "URL JavaScript / CSS",
  2224 + "resource-is-module": "Je modul",
  2225 + "remove-resource": "Odstrani vir",
  2226 + "add-resource": "Dodaj vir",
  2227 + "html": "HTML",
  2228 + "tidy": "Urejeno",
  2229 + "css": "CSS",
  2230 + "settings-schema": "Shema nastavitev",
  2231 + "datakey-settings-schema": "Shema nastavitev podatkovnega ključa",
  2232 + "javascript": "Javascript",
  2233 + "js": "JS",
  2234 + "remove-widget-type-title": "Ali ste prepričani, da želite odstraniti vrsto pripomočka '{{widgetName}}'?",
  2235 + "remove-widget-type-text": "Po potrditvi bodo vrsta pripomočka in vsi povezani podatki nepopravljivi.",
  2236 + "remove-widget-type": "Odstrani vrsto pripomočka",
  2237 + "add-widget-type": "Dodaj novo vrsto pripomočka",
  2238 + "widget-type-load-failed-error": "Vrste pripomočka ni bilo mogoče naložiti!",
  2239 + "widget-template-load-failed-error": "Predloge pripomočka ni bilo mogoče naložiti!",
  2240 + "add": "Dodaj pripomoček",
  2241 + "undo": "Razveljavi spremembe pripomočka",
  2242 + "export": "Izvozi pripomoček",
  2243 + "no-data": "Na pripomočku ni podatkov za prikaz",
  2244 + "data-overflow": "Pripomoček prikazuje {{count}} od {{total}} entitet",
  2245 + "alarm-data-overflow": "Pripomoček prikazuje alarme za {{allowedEntities}} (največ dovoljenih) entitet od {{totalEntities}} entitet"
  2246 + },
  2247 + "widget-action": {
  2248 + "header-button": "Gumb glave pripomočka",
  2249 + "open-dashboard-state": "Pomakni se do novega stanja na nadzorni plošči",
  2250 + "update-dashboard-state": "Posodobi trenutno stanje nadzorne plošče",
  2251 + "open-dashboard": "Pomakni se na drugo nadzorno ploščo",
  2252 + "custom": "Dejanje po meri",
  2253 + "custom-pretty": "Dejanje po meri (s predlogo HTML)",
  2254 + "target-dashboard-state": "Ciljno stanje nadzorne plošče",
  2255 + "target-dashboard-state-required": "Ciljno stanje nadzorne plošče je potrebno",
  2256 + "set-entity-from-widget": "Nastavi entiteto iz pripomočka",
  2257 + "target-dashboard": "Ciljna nadzorna plošča",
  2258 + "open-right-layout": "Odpri pravilno postavitev nadzorne plošče (mobilni pogled)"
  2259 + },
  2260 + "widgets-bundle": {
  2261 + "current": "Trenutni paketi",
  2262 + "widgets-bundles": "Paketi pripomočkov",
  2263 + "add": "Dodaj paket pripomočkov",
  2264 + "delete": "Izbriši paket pripomočkov",
  2265 + "title": "Naslov",
  2266 + "title-required": "Naslov je obvezen.",
  2267 + "add-widgets-bundle-text": "Dodaj nov paket pripomočkov",
  2268 + "no-widgets-bundles-text": "Najden ni bil noben paket pripomočkov",
  2269 + "empty": "Paket pripomočkov je prazen",
  2270 + "details": "Podrobnosti",
  2271 + "widgets-bundle-details": "Podrobnosti o paketu pripomočkov",
  2272 + "delete-widgets-bundle-title": "Ali ste prepričani, da želite izbrisati paket pripomočkov '{{widgetsBundleTitle}}'?",
  2273 + "delete-widgets-bundle-text": "Bodite previdni, po potrditvi bodo pripomočki in vsi povezani podatki postali nepopravljivi.",
  2274 + "delete-widgets-bundles-title": "Ali ste prepričani, da želite izbrisati { count, plural, 1 {1 widgets bundle} other {# widgets bundles} }?",
  2275 + "delete-widgets-bundles-action-title": "Izbriši { count, plural, 1 {1 widgets bundle} other {# widgets bundles} }",
  2276 + "delete-widgets-bundles-text": "Bodite previdni, po potrditvi bodo odstranjeni vsi paketi pripomočkov, vsi povezani podatki pa bodo postali nepopravljivi.",
  2277 + "no-widgets-bundles-matching": "Najden ni bil noben paket pripomočkov, ki se ujema z '{{widgetsBundle}}'.",
  2278 + "widgets-bundle-required": "Paket pripomočkov je potreben.",
  2279 + "system": "Sistem",
  2280 + "import": "Uvozi paket pripomočkov",
  2281 + "export": "Izvozi paket pripomočkov",
  2282 + "export-failed-error": "Ni mogoče izvoziti paketa pripomočkov: {{error}}",
  2283 + "create-new-widgets-bundle": "Ustvari nov paket pripomočkov",
  2284 + "widgets-bundle-file": "Datoteka paketa pripomočkov",
  2285 + "invalid-widgets-bundle-file-error": "Ni mogoče uvoziti paketa pripomočkov: neveljavna podatkovna struktura paketa pripomočkov.",
  2286 + "search": "Iskanje po paketih pripomočkov",
  2287 + "selected-widgets-bundles": "{ count, plural, 1 {1 widgets bundle} other {# widgets bundles} } izbranih",
  2288 + "open-widgets-bundle": "Odpri paket pripomočkov"
  2289 + },
  2290 + "widget-config": {
  2291 + "data": "Podatki",
  2292 + "settings": "Nastavitve",
  2293 + "advanced": "Napredno",
  2294 + "title": "Naslov",
  2295 + "title-tooltip": "Opis naslova",
  2296 + "general-settings": "Splošne nastavitve",
  2297 + "display-title": "Prikaži naslov",
  2298 + "drop-shadow": "Spusti senco",
  2299 + "enable-fullscreen": "Omogoči celozaslonski način",
  2300 + "background-color": "Barva ozadja",
  2301 + "text-color": "Barva besedila",
  2302 + "padding": "Oblazinjenje",
  2303 + "margin": "Stopnja",
  2304 + "widget-style": "Slog pripomočkov",
  2305 + "title-style": "Slog naslova",
  2306 + "mobile-mode-settings": "Nastavitve mobilnega načina",
  2307 + "order": "Naročilo",
  2308 + "height": "Višina",
  2309 + "units": "Poseben simbol za prikaz poleg vrednosti",
  2310 + "decimals": "Število številk po plavajoči vejici",
  2311 + "timewindow": "Časovno okno",
  2312 + "use-dashboard-timewindow": "Uporabi časovno okno nadzorne plošče",
  2313 + "display-timewindow": "Prikaži časovno okno",
  2314 + "display-legend": "Prikaži legendo",
  2315 + "datasources": "Viri podatkov",
  2316 + "maximum-datasources": "Največ { count, plural, 1 {1 vir podatkov je dovoljen.} other {# vir podatkov je dovoljen} }",
  2317 + "datasource-type": "Vrsta",
  2318 + "datasource-parameters": "Parametri",
  2319 + "remove-datasource": "Odstrani vir podatkov",
  2320 + "add-datasource": "Dodaj vir podatkov",
  2321 + "target-device": "Ciljna naprava",
  2322 + "alarm-source": "Vir alarma",
  2323 + "actions": "Dejanja",
  2324 + "action": "Dejanje",
  2325 + "add-action": "Dodaj dejanje",
  2326 + "search-actions": "Iskanje dejanj",
  2327 + "no-actions-text": "Ni najdenih dejanj",
  2328 + "action-source": "Vir dejanja",
  2329 + "action-source-required": "Zahtevan je vir dejanj.",
  2330 + "action-name": "Ime",
  2331 + "action-name-required": "Ime dejanja je obvezno.",
  2332 + "action-name-not-unique": "Še eno dejanje z istim imenom že obstaja. <br/> Ime dejanja mora biti enolično v istem viru dejanj.",
  2333 + "action-icon": "Ikona",
  2334 + "action-type": "Vrsta",
  2335 + "action-type-required": "Zahtevana je vrsta dejanja.",
  2336 + "edit-action": "Uredi dejanje",
  2337 + "delete-action": "Izbriši dejanje",
  2338 + "delete-action-title": "Izbriši dejanje pripomočka",
  2339 + "delete-action-text": "Ali ste prepričani, da želite izbrisati dejanje pripomočka z imenom '{{actionName}}'?",
  2340 + "display-icon": "Prikaži ikono naslova",
  2341 + "icon-color": "Barva ikone",
  2342 + "icon-size": "Velikost ikone"
  2343 + },
  2344 + "widget-type": {
  2345 + "import": "Uvozi vrsto pripomočka",
  2346 + "export": "Izvozi vrsto pripomočka",
  2347 + "export-failed-error": "Ni mogoče izvoziti vrste pripomočka: {{error}}",
  2348 + "create-new-widget-type": "Ustvari novo vrsto pripomočka",
  2349 + "widget-type-file": "Datoteka vrste pripomočka",
  2350 + "invalid-widget-type-file-error": "Ne morem uvoziti vrste gradnika: Neveljavna struktura podatkov vrste pripomočka."
  2351 + },
  2352 + "widgets": {
  2353 + "date-range-navigator": {
  2354 + "localizationMap": {
  2355 + "Sun": "Ned",
  2356 + "Mon": "Pon",
  2357 + "Tue": "Tor",
  2358 + "Wed": "Sre",
  2359 + "Thu": "Čet",
  2360 + "Fri": "Pet",
  2361 + "Sat": "Sob",
  2362 + "Jan": "Jan",
  2363 + "Feb": "Feb",
  2364 + "Mar": "Mar",
  2365 + "Apr": "Apr",
  2366 + "May": "Maj",
  2367 + "Jun": "Jun",
  2368 + "Jul": "Jul",
  2369 + "Aug": "Avg",
  2370 + "Sep": "Sep",
  2371 + "Oct": "Okt",
  2372 + "Nov": "Nov",
  2373 + "Dec": "Dec",
  2374 + "January": "Januar",
  2375 + "February": "Februar",
  2376 + "March": "Marec",
  2377 + "April": "April",
  2378 + "June": "Junij",
  2379 + "July": "Julij",
  2380 + "August": "Avgust",
  2381 + "September": "September",
  2382 + "October": "Oktober",
  2383 + "November": "November",
  2384 + "December": "December",
  2385 + "Custom Date Range": "Časovno obdobje po meri",
  2386 + "Date Range Template": "Predloga časovnega obdobja",
  2387 + "Today": "Danes",
  2388 + "Yesterday": "Včeraj",
  2389 + "This Week": "Ta teden",
  2390 + "Last Week": "Prejšnji teden",
  2391 + "This Month": "Ta mesec",
  2392 + "Last Month": "Prejšnji mesec",
  2393 + "Year": "Leto",
  2394 + "This Year": "To leto",
  2395 + "Last Year": "Lansko leto",
  2396 + "Date picker": "Izbirnik datuma",
  2397 + "Hour": "Ura",
  2398 + "Day": "Dan",
  2399 + "Week": "Teden",
  2400 + "2 weeks": "2 tedna",
  2401 + "Month": "Mesec",
  2402 + "3 months": "3 mesece",
  2403 + "6 months": "6 mesecev",
  2404 + "Custom interval": "Interval po meri",
  2405 + "Interval": "Interval",
  2406 + "Step size": "Velikost koraka",
  2407 + "Ok": "V redu"
  2408 + }
  2409 + },
  2410 + "input-widgets": {
  2411 + "attribute-not-allowed": "Parametra atributa v tem pripomočku ni mogoče uporabiti",
  2412 + "blocked-location": "Geolokacija je blokirana v vašem brskalniku",
  2413 + "claim-device": "Zahtevaj napravo",
  2414 + "claim-failed": "Naprave ni bilo mogoče zahtevati!",
  2415 + "claim-not-found": "Naprave ni mogoče najti!",
  2416 + "claim-successful": "Naprava je bila uspešno zahtevana!",
  2417 + "date": "Datum",
  2418 + "device-name": "Ime naprave",
  2419 + "device-name-required": "Ime naprave je obvezno",
  2420 + "discard-changes": "Zavrzi spremembe",
  2421 + "entity-attribute-required": "Zahtevan je atribut entitete",
  2422 + "entity-coordinate-required": "Oba polja, zemljepisna širina in dolžina sta obvezna",
  2423 + "entity-timeseries-required": "Potreben je časovni niz entitet",
  2424 + "get-location": "Pridobi trenutno lokacijo",
  2425 + "invalid-date": "Invalid Date",
  2426 + "latitude": "Zemljepisna širina",
  2427 + "longitude": "Zemljepisna dolžina",
  2428 + "min-value-error": "Min value is {{value}}",
  2429 + "max-value-error": "Max value is {{value}}",
  2430 + "not-allowed-entity": "Izbrana entiteta ne sme imeti atributov v skupni rabi",
  2431 + "no-attribute-selected": "Noben atribut ni izbran",
  2432 + "no-datakey-selected": "Nobena podatkovna tipka ni izbrana",
  2433 + "no-coordinate-specified": "Podatkovni ključ za zemljepisno širino / dolžino ni določen",
  2434 + "no-entity-selected": "Nobena entiteta ni izbrana",
  2435 + "no-image": "Ni slike",
  2436 + "no-support-geolocation": "Vaš brskalnik ne podpira geolokacije",
  2437 + "no-support-web-camera": "Ni podprte spletne kamere",
  2438 + "enable-https-use-widget": "Please enable HTTPS to use this widget",
  2439 + "no-found-your-camera": "Can't find your camera",
  2440 + "no-permission-camera": "Permission was denied by the user / This site doesn't have permission to use the camera",
  2441 + "no-timeseries-selected": "Izbrana ni nobena časovna serija",
  2442 + "secret-key": "Skrivni ključ",
  2443 + "secret-key-required": "Potreben je skrivni ključ",
  2444 + "switch-attribute-value": "Preklopi vrednost atributa entitete",
  2445 + "switch-camera": "Preklopi kamero",
  2446 + "switch-timeseries-value": "Preklopi vrednost časovnega niza entitete",
  2447 + "take-photo": "Fotografiraj",
  2448 + "time": "Čas",
  2449 + "timeseries-not-allowed": "V tem pripomočku ni mogoče uporabiti parametra časovne vrste",
  2450 + "update-failed": "Posodobitev ni uspela",
  2451 + "update-successful": "Posodobitev uspešna",
  2452 + "update-attribute": "Posodobi atribut",
  2453 + "update-timeseries": "Posodobi časovne vrste",
  2454 + "value": "Vrednost"
  2455 + }
  2456 + },
  2457 + "icon": {
  2458 + "icon": "Ikona",
  2459 + "select-icon": "Izberi ikono",
  2460 + "material-icons": "Ikone materiala",
  2461 + "show-all": "Pokaži vse ikone"
  2462 + },
  2463 + "custom": {
  2464 + "widget-action": {
  2465 + "action-cell-button": "Gumb akcijske celice",
  2466 + "row-click": "Klik na vrstico",
  2467 + "polygon-click": "Klik na poligon",
  2468 + "marker-click": "Klik na oznako",
  2469 + "tooltip-tag-action": "Dejanje oznake orodja",
  2470 + "node-selected": "Na izbranem vozlišču",
  2471 + "element-click": "Klik na HTML element",
  2472 + "pie-slice-click": "Klik na rezino",
  2473 + "row-double-click": "Dvojni klik na vrstico"
  2474 + }
  2475 + },
  2476 + "language": {
  2477 + "language": "Jezik"
  2478 + }
  2479 +}
... ...
... ... @@ -24,6 +24,8 @@
24 24
25 25 <meta name="viewport" content="width=device-width, initial-scale=1">
26 26 <link rel="icon" type="image/x-icon" href="thingsboard.ico">
  27 + <link rel="preload" href="assets/fonts/MaterialIcons-Regular.woff2" as="font" type="font/woff2" crossorigin="anonymous" />
  28 + <link rel="stylesheet" href="assets/fonts/material-icons.css" />
27 29 <style type="text/css">
28 30
29 31 body, html {
... ...
... ... @@ -15,7 +15,6 @@
15 15 */
16 16 /* You can add global styles to this file, and also import other style files */
17 17
18   -@import '~material-design-icons/iconfont/material-icons.css';
19 18 @import '~typeface-roboto/index.css';
20 19 @import '~font-awesome/css/font-awesome.min.css';
21 20 @import 'theme.scss';
... ...