Merge remote-tracking branch 'origin/master' into develop/2.5

This commit is contained in:
Andrii Shvaika 2020-04-14 16:17:27 +03:00
commit 7c24b850fb
23 changed files with 97 additions and 50 deletions

View File

@ -330,13 +330,13 @@
"name": "Web Camera Input", "name": "Web Camera Input",
"descriptor": { "descriptor": {
"type": "latest", "type": "latest",
"sizeX": 9.5, "sizeX": 7.5,
"sizeY": 6.5, "sizeY": 3,
"resources": [], "resources": [],
"templateHtml": "<tb-web-camera-widget ctx=\"ctx\">\n</tb-web-camera-widget>", "templateHtml": "<tb-web-camera-widget ctx=\"ctx\">\n</tb-web-camera-widget>",
"templateCss": "#container {\n overflow: auto;\n}\n\n.tbDatasource-container {\n margin: 5px;\n padding: 8px;\n}\n\n.tbDatasource-title {\n font-size: 1.200rem;\n font-weight: 500;\n padding-bottom: 10px;\n}\n\n.tbDatasource-table {\n width: 100%;\n box-shadow: 0 0 10px #ccc;\n border-collapse: collapse;\n white-space: nowrap;\n font-size: 1.000rem;\n color: #757575;\n}\n\n.tbDatasource-table td {\n position: relative;\n border-top: 1px solid rgba(0, 0, 0, 0.12);\n border-bottom: 1px solid rgba(0, 0, 0, 0.12);\n padding: 0px 18px;\n box-sizing: border-box;\n}", "templateCss": "",
"controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n}\n", "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{}", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Web Camera\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"imageFormat\": {\n \"title\": \"Image Format\",\n \"type\": \"string\",\n \"default\": \"image/png\"\n },\n \"imageQuality\":{\n \"title\":\"Image quality that use lossy compression such as jpeg and webp\",\n \"type\":\"number\",\n \"default\": 0.92,\n \"min\": 0,\n \"max\": 1\n },\n \"maxWidth\": {\n \"title\": \"The maximal image width\",\n \"type\": \"number\",\n \"default\": 640\n }, \n \"maxHeight\": {\n \"title\": \"The maximal image heigth\",\n \"type\": \"number\",\n \"default\": 480\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n {\n \"key\": \"imageFormat\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"image/jpeg\",\n \"label\": \"JPEG\"\n },\n {\n \"value\": \"image/png\",\n \"label\": \"PNG\"\n },\n {\n \"value\": \"image/webp\",\n \"label\": \"WEBP\"\n }\n ]\n },\n \"imageQuality\",\n \"maxWidth\",\n \"maxHeight\"\n ]\n}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Web Camera Input\",\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showLegend\":false,\"actions\":{}}" "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Web Camera Input\",\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
} }

View File

@ -201,6 +201,7 @@ public class AuthController extends BaseController {
@ResponseBody @ResponseBody
public JsonNode activateUser( public JsonNode activateUser(
@RequestBody JsonNode activateRequest, @RequestBody JsonNode activateRequest,
@RequestParam(required = false, defaultValue = "true") boolean sendActivationMail,
HttpServletRequest request) throws ThingsboardException { HttpServletRequest request) throws ThingsboardException {
try { try {
String activateToken = activateRequest.get("activateToken").asText(); String activateToken = activateRequest.get("activateToken").asText();
@ -215,11 +216,13 @@ public class AuthController extends BaseController {
String loginUrl = String.format("%s/login", baseUrl); String loginUrl = String.format("%s/login", baseUrl);
String email = user.getEmail(); String email = user.getEmail();
if (sendActivationMail) {
try { try {
mailService.sendAccountActivatedEmail(loginUrl, email); mailService.sendAccountActivatedEmail(loginUrl, email);
} catch (Exception e) { } catch (Exception e) {
log.info("Unable to send account activation email [{}]", e.getMessage()); log.info("Unable to send account activation email [{}]", e.getMessage());
} }
}
JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);

View File

@ -277,6 +277,7 @@ public class DefaultMailService implements MailService {
} else { } else {
message = exception.getMessage(); message = exception.getMessage();
} }
log.warn("Unable to send mail: {}", message);
return new ThingsboardException(String.format("Unable to send mail: %s", message), return new ThingsboardException(String.format("Unable to send mail: %s", message),
ThingsboardErrorCode.GENERAL); ThingsboardErrorCode.GENERAL);
} }

View File

@ -435,6 +435,10 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
if (user.authority === 'CUSTOMER_USER') { if (user.authority === 'CUSTOMER_USER') {
entityId.id = user.customerId; entityId.id = user.customerId;
} }
} else if (entityType === types.aliasEntityType.current_tenant){
let user = userService.getCurrentUser();
entityId.entityType = types.entityType.tenant;
entityId.id = user.tenantId;
} }
return entityId; return entityId;
} }
@ -806,6 +810,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
entityTypes.dashboard = types.entityType.dashboard; entityTypes.dashboard = types.entityType.dashboard;
if (useAliasEntityTypes) { if (useAliasEntityTypes) {
entityTypes.current_customer = types.aliasEntityType.current_customer; entityTypes.current_customer = types.aliasEntityType.current_customer;
entityTypes.current_tenant = types.aliasEntityType.current_tenant;
} }
break; break;
case 'CUSTOMER_USER': case 'CUSTOMER_USER':

View File

@ -85,9 +85,12 @@ function LoginService($http, $q) {
return deferred.promise; return deferred.promise;
} }
function activate(activateToken, password) { function activate(activateToken, password, sendActivationMail) {
var deferred = $q.defer(); var deferred = $q.defer();
var url = '/api/noauth/activate'; var url = '/api/noauth/activate';
if(sendActivationMail === true || sendActivationMail === false) {
url += '?sendActivationMail=' + sendActivationMail;
}
$http.post(url, {activateToken: activateToken, password: password}).then(function success(response) { $http.post(url, {activateToken: activateToken, password: password}).then(function success(response) {
deferred.resolve(response); deferred.resolve(response);
}, function fail() { }, function fail() {

View File

@ -408,7 +408,8 @@ export default angular.module('thingsboard.types', [])
} }
}, },
aliasEntityType: { aliasEntityType: {
current_customer: "CURRENT_CUSTOMER" current_customer: "CURRENT_CUSTOMER",
current_tenant: "CURRENT_TENANT"
}, },
entityTypeTranslations: { entityTypeTranslations: {
"DEVICE": { "DEVICE": {
@ -474,6 +475,10 @@ export default angular.module('thingsboard.types', [])
"CURRENT_CUSTOMER": { "CURRENT_CUSTOMER": {
type: 'entity.type-current-customer', type: 'entity.type-current-customer',
list: 'entity.type-current-customer' list: 'entity.type-current-customer'
},
"CURRENT_TENANT": {
type: 'entity.type-current-tenant',
list: 'entity.type-current-tenant'
} }
}, },
entityField: { entityField: {

View File

@ -83,9 +83,9 @@ class ThingsboardAceEditor extends React.Component {
fixAceEditor(editor); fixAceEditor(editor);
} }
onToggleFull(groupId) { onToggleFull() {
this.setState({ isFull: !this.state.isFull }); this.setState({ isFull: !this.state.isFull });
this.props.onToggleFullscreen(groupId); this.props.onToggleFullscreen();
this.updateAceEditorSize = true; this.updateAceEditorSize = true;
} }
@ -140,7 +140,7 @@ class ThingsboardAceEditor extends React.Component {
<div className="title-panel"> <div className="title-panel">
<label>{this.props.mode}</label> <label>{this.props.mode}</label>
<FlatButton style={ styles.tidyButtonStyle } className="tidy-button" label={'Tidy'} onTouchTap={this.onTidy}/> <FlatButton style={ styles.tidyButtonStyle } className="tidy-button" label={'Tidy'} onTouchTap={this.onTidy}/>
<FlatButton style={ styles.tidyButtonStyle } className="tidy-button" label={this.state.isFull ? 'Exit fullscreen' : 'Fullscreen'} onTouchTap={() => this.onToggleFull(this.props.groupId)}/> <FlatButton style={ styles.tidyButtonStyle } className="tidy-button" label={this.state.isFull ? 'Exit fullscreen' : 'Fullscreen'} onTouchTap={this.onToggleFull}/>
</div> </div>
<AceEditor mode={this.props.mode} <AceEditor mode={this.props.mode}
height={this.state.isFull ? "100%" : "150px"} height={this.state.isFull ? "100%" : "150px"}

View File

@ -131,7 +131,7 @@ class ThingsboardArray extends React.Component {
} }
let forms = this.props.form.items.map(function(form, index){ let forms = this.props.form.items.map(function(form, index){
var copy = this.copyWithIndex(form, i); var copy = this.copyWithIndex(form, i);
return this.props.builder(copy, this.props.groupId, this.props.model, index, this.props.onChange, this.props.onColorClick, this.props.onIconClick, this.props.onToggleFullscreen, this.props.mapper, this.props.builder); return this.props.builder(copy, this.props.model, index, this.props.onChange, this.props.onColorClick, this.props.onIconClick, this.props.onToggleFullscreen, this.props.mapper, this.props.builder);
}.bind(this)); }.bind(this));
arrays.push( arrays.push(
<li key={keys[i]} className="list-group-item"> <li key={keys[i]} className="list-group-item">

View File

@ -19,7 +19,7 @@ class ThingsboardFieldSet extends React.Component {
render() { render() {
let forms = this.props.form.items.map(function(form, index){ let forms = this.props.form.items.map(function(form, index){
return this.props.builder(form, this.props.groupId, this.props.model, index, this.props.onChange, this.props.onColorClick, this.props.onIconClick, this.props.onToggleFullscreen, this.props.mapper, this.props.builder); return this.props.builder(form, this.props.model, index, this.props.onChange, this.props.onColorClick, this.props.onIconClick, this.props.onToggleFullscreen, this.props.mapper, this.props.builder);
}.bind(this)); }.bind(this));
return ( return (

View File

@ -40,9 +40,6 @@ class ThingsboardSchemaForm extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {
groupId: null,
};
this.mapper = { this.mapper = {
'number': ThingsboardNumber, 'number': ThingsboardNumber,
@ -88,15 +85,12 @@ class ThingsboardSchemaForm extends React.Component {
this.props.onIconClick(event); this.props.onIconClick(event);
} }
onToggleFullscreen(groupId) { onToggleFullscreen() {
this.setState({
groupId: groupId
});
this.props.onToggleFullscreen(); this.props.onToggleFullscreen();
} }
builder(form, groupId, model, index, onChange, onColorClick, onIconClick, onToggleFullscreen, mapper) { builder(form, model, index, onChange, onColorClick, onIconClick, onToggleFullscreen, mapper) {
var type = form.type; var type = form.type;
let Field = this.mapper[type]; let Field = this.mapper[type];
if(!Field) { if(!Field) {
@ -109,21 +103,21 @@ class ThingsboardSchemaForm extends React.Component {
return null; return null;
} }
} }
return <Field model={model} groupId={groupId} form={form} key={index} onChange={onChange} onColorClick={onColorClick} onIconClick={onIconClick} onToggleFullscreen={onToggleFullscreen} mapper={mapper} builder={this.builder}/> return <Field model={model} form={form} key={index} onChange={onChange} onColorClick={onColorClick} onIconClick={onIconClick} onToggleFullscreen={onToggleFullscreen} mapper={mapper} builder={this.builder}/>
} }
createSchema(theForm, groupId) { createSchema(theForm) {
let merged = utils.merge(this.props.schema, theForm, this.props.ignore, this.props.option); let merged = utils.merge(this.props.schema, theForm, this.props.ignore, this.props.option);
let mapper = this.mapper; let mapper = this.mapper;
if(this.props.mapper) { if(this.props.mapper) {
mapper = _.merge(this.mapper, this.props.mapper); mapper = _.merge(this.mapper, this.props.mapper);
} }
let forms = merged.map(function(form, index) { let forms = merged.map(function(form, index) {
return this.builder(form, groupId, this.props.model, index, this.onChange, this.onColorClick, this.onIconClick, this.onToggleFullscreen, mapper); return this.builder(form, this.props.model, index, this.onChange, this.onColorClick, this.onIconClick, this.onToggleFullscreen, mapper);
}.bind(this)); }.bind(this));
let formClass = 'SchemaForm'; let formClass = 'SchemaForm';
if (this.props.isFullscreen && groupId === this.state.groupId) { if (this.props.isFullscreen) {
formClass += ' SchemaFormFullscreen'; formClass += ' SchemaFormFullscreen';
} }
@ -136,7 +130,7 @@ class ThingsboardSchemaForm extends React.Component {
if(this.props.groupInfoes&&this.props.groupInfoes.length>0){ if(this.props.groupInfoes&&this.props.groupInfoes.length>0){
let content=[]; let content=[];
for(let info of this.props.groupInfoes){ for(let info of this.props.groupInfoes){
let forms = this.createSchema(this.props.form[info.formIndex], info.formIndex); let forms = this.createSchema(this.props.form[info.formIndex]);
let item = <ThingsboardSchemaGroup key={content.length} forms={forms} info={info}></ThingsboardSchemaGroup>; let item = <ThingsboardSchemaGroup key={content.length} forms={forms} info={info}></ThingsboardSchemaGroup>;
content.push(item); content.push(item);
} }

View File

@ -24,15 +24,12 @@ $input-label-float-scale: .75 !default;
.tb-fullscreen { .tb-fullscreen {
[name="ReactSchemaForm"] { [name="ReactSchemaForm"] {
.SchemaForm { .SchemaForm {
display: none;
&.SchemaFormFullscreen { &.SchemaFormFullscreen {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
display: block;
> div:not(.fullscreen-form-field) { > div:not(.fullscreen-form-field) {
display: none !important; display: none !important;

View File

@ -152,7 +152,7 @@ export default function WidgetController($scope, $state, $timeout, $window, $ocL
var entityInfo = getActiveEntityInfo(); var entityInfo = getActiveEntityInfo();
var entityId = entityInfo ? entityInfo.entityId : null; var entityId = entityInfo ? entityInfo.entityId : null;
var entityName = entityInfo ? entityInfo.entityName : null; var entityName = entityInfo ? entityInfo.entityName : null;
var entityLabel = entityInfo && entityInfo.label ? entityInfo.label : null; var entityLabel = entityInfo && entityInfo.entityLabel ? entityInfo.entityLabel : null;
handleWidgetAction($event, this.descriptor, entityId, entityName, null, entityLabel); handleWidgetAction($event, this.descriptor, entityId, entityName, null, entityLabel);
} }
widgetContext.customHeaderActions.push(headerAction); widgetContext.customHeaderActions.push(headerAction);

View File

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
--> -->
<md-dialog aria-label="{{ 'dashboard.manage-states' | translate }}" style="width: 620px;"> <md-dialog aria-label="{{ 'dashboard.manage-states' | translate }}" style="min-width: 600px;">
<form name="theForm" ng-submit="vm.save()"> <form name="theForm" ng-submit="vm.save()">
<md-toolbar> <md-toolbar>
<div class="md-toolbar-tools"> <div class="md-toolbar-tools">
@ -72,7 +72,7 @@
</md-toolbar> </md-toolbar>
<md-table-container> <md-table-container>
<table md-table> <table md-table>
<thead fix-head md-head md-order="vm.query.order" md-on-reorder="vm.onReorder"> <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
<tr md-row> <tr md-row>
<th md-column md-order-by="name"><span translate>dashboard.state-name</span></th> <th md-column md-order-by="name"><span translate>dashboard.state-name</span></th>
<th md-column md-order-by="id"><span translate>dashboard.state-id</span></th> <th md-column md-order-by="id"><span translate>dashboard.state-id</span></th>

View File

@ -22,13 +22,14 @@ import entitySelectTemplate from './entity-select.tpl.html';
/* eslint-enable import/no-unresolved, import/default */ /* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/ /*@ngInject*/
export default function EntitySelect($compile, $templateCache, entityService) { export default function EntitySelect($compile, $templateCache, entityService, types) {
var linker = function (scope, element, attrs, ngModelCtrl) { var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(entitySelectTemplate); var template = $templateCache.get(entitySelectTemplate);
element.html(template); element.html(template);
scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false; scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
scope.entityTypeCurrentTenant = types.aliasEntityType.current_tenant;
var entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes, scope.useAliasEntityTypes); var entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes, scope.useAliasEntityTypes);
@ -48,7 +49,8 @@ export default function EntitySelect($compile, $templateCache, entityService) {
scope.updateView = function () { scope.updateView = function () {
if (!scope.disabled) { if (!scope.disabled) {
var value = ngModelCtrl.$viewValue; var value = ngModelCtrl.$viewValue;
if (scope.model && scope.model.entityType && scope.model.entityId) { if (scope.model && scope.model.entityType &&
(scope.model.entityId || scope.model.entityType === scope.entityTypeCurrentTenant)) {
if (!value) { if (!value) {
value = {}; value = {};
} }

View File

@ -25,7 +25,7 @@
allowed-entity-types="allowedEntityTypes" allowed-entity-types="allowedEntityTypes"
ng-model="model.entityType"> ng-model="model.entityType">
</tb-entity-type-select> </tb-entity-type-select>
<tb-entity-autocomplete flex ng-if="model.entityType" <tb-entity-autocomplete flex ng-if="model.entityType && model.entityType !== entityTypeCurrentTenant"
the-form="theForm" the-form="theForm"
ng-disabled="disabled" ng-disabled="disabled"
tb-required="tbRequired" tb-required="tbRequired"

View File

@ -770,6 +770,7 @@
"list-of-rulenodes": "{ count, plural, 1 {Jeden uzel pravidla} other {Seznam # uzlů pravidel} }", "list-of-rulenodes": "{ count, plural, 1 {Jeden uzel pravidla} other {Seznam # uzlů pravidel} }",
"rulenode-name-starts-with": "Uzly pravidel, jejichž název začíná '{{prefix}}'", "rulenode-name-starts-with": "Uzly pravidel, jejichž název začíná '{{prefix}}'",
"type-current-customer": "Stávající zákazník", "type-current-customer": "Stávající zákazník",
"type-current-tenant": "Stávající tenant",
"search": "Vyhledat entity", "search": "Vyhledat entity",
"selected-entities": "{ count, plural, 1 {1 entita} other {# entit} } zvoleno", "selected-entities": "{ count, plural, 1 {1 entita} other {# entit} } zvoleno",
"entity-name": "Název entity", "entity-name": "Název entity",

View File

@ -811,6 +811,7 @@
"list-of-rulenodes": "{ count, plural, 1 {One rule node} other {List of # rule nodes} }", "list-of-rulenodes": "{ count, plural, 1 {One rule node} other {List of # rule nodes} }",
"rulenode-name-starts-with": "Rule nodes whose names start with '{{prefix}}'", "rulenode-name-starts-with": "Rule nodes whose names start with '{{prefix}}'",
"type-current-customer": "Current Customer", "type-current-customer": "Current Customer",
"type-current-tenant": "Current Tenant",
"search": "Search entities", "search": "Search entities",
"selected-entities": "{ count, plural, 1 {1 entity} other {# entities} } selected", "selected-entities": "{ count, plural, 1 {1 entity} other {# entities} } selected",
"entity-name": "Entity name", "entity-name": "Entity name",

View File

@ -809,6 +809,7 @@
"list-of-rulenodes": "{ count, plural, 1 {Одно правило} other {Список из # правил} }", "list-of-rulenodes": "{ count, plural, 1 {Одно правило} other {Список из # правил} }",
"rulenode-name-starts-with": "Правила, чьи названия начинаются с '{{prefix}}'", "rulenode-name-starts-with": "Правила, чьи названия начинаются с '{{prefix}}'",
"type-current-customer": "Текущий клиент", "type-current-customer": "Текущий клиент",
"type-current-tenant": "Текущий владелец",
"search": "Поиск объектов", "search": "Поиск объектов",
"selected-entities": "Выбран(ы) { count, plural, 1 {1 объект} few {# объекта} other {# объектов} }", "selected-entities": "Выбран(ы) { count, plural, 1 {1 объект} few {# объекта} other {# объектов} }",
"entity-name": "Название объекта", "entity-name": "Название объекта",

View File

@ -942,6 +942,7 @@
"list-of-rulenodes": "{ count, plural, 1 {Одне правило} other {Список # правил} }", "list-of-rulenodes": "{ count, plural, 1 {Одне правило} other {Список # правил} }",
"rulenode-name-starts-with": "Список правил, імена яких починаються '{{prefix}}'", "rulenode-name-starts-with": "Список правил, імена яких починаються '{{prefix}}'",
"type-current-customer": "Поточний клієнт", "type-current-customer": "Поточний клієнт",
"type-current-tenant": "Поточний власник",
"search": "Пошук сутностей", "search": "Пошук сутностей",
"selected-entities": "{ count, plural, 1 {1 сутність} other {# сутності} } вибрано", "selected-entities": "{ count, plural, 1 {1 сутність} other {# сутності} } вибрано",
"entity-name": "Ім'я сутності", "entity-name": "Ім'я сутності",

View File

@ -20,7 +20,7 @@ import logoSvg from '../../svg/logo_title_white.svg';
/* eslint-enable import/no-unresolved, import/default */ /* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/ /*@ngInject*/
export default function LoginController(toast, loginService, userService, types, $state/*, $rootScope, $log, $translate*/) { export default function LoginController(toast, loginService, userService, types, $state, $stateParams/*, $rootScope, $log, $translate*/) {
var vm = this; var vm = this;
vm.logoSvg = logoSvg; vm.logoSvg = logoSvg;
@ -32,6 +32,12 @@ export default function LoginController(toast, loginService, userService, types,
vm.login = login; vm.login = login;
if ($stateParams.username && $stateParams.password) {
vm.user.name = $stateParams.username;
vm.user.password = $stateParams.password;
doLogin();
}
function doLogin() { function doLogin() {
loginService.login(vm.user).then(function success(response) { loginService.login(vm.user).then(function success(response) {
var token = response.data.token; var token = response.data.token;

View File

@ -25,7 +25,7 @@ import createPasswordTemplate from './create-password.tpl.html';
/*@ngInject*/ /*@ngInject*/
export default function LoginRoutes($stateProvider) { export default function LoginRoutes($stateProvider) {
$stateProvider.state('login', { $stateProvider.state('login', {
url: '/login', url: '/login?username&password',
module: 'public', module: 'public',
views: { views: {
"@": { "@": {

View File

@ -52,6 +52,11 @@ function WebCameraWidgetController($element, $scope, $window, types, utils, attr
let canvas = null; let canvas = null;
let photoCamera = null; let photoCamera = null;
let dataKeyType = ""; let dataKeyType = "";
let width = 640;
let height = 480;
const DEFAULT_IMAGE_TYPE = 'image/jpeg';
const DEFAULT_IMAGE_QUALITY = 0.92;
vm.getStream = getStream; vm.getStream = getStream;
vm.createPhoto = createPhoto; vm.createPhoto = createPhoto;
@ -79,6 +84,8 @@ function WebCameraWidgetController($element, $scope, $window, types, utils, attr
vm.isEntityDetected = true; vm.isEntityDetected = true;
} }
} }
width = vm.ctx.settings.maxWidth ? vm.ctx.settings.maxWidth : 640;
height = vm.ctx.settings.maxHeight ? vm.ctx.settings.maxWidth : 480;
if (datasource.dataKeys.length) { if (datasource.dataKeys.length) {
$scope.currentKey = datasource.dataKeys[0].name; $scope.currentKey = datasource.dataKeys[0].name;
dataKeyType = datasource.dataKeys[0].type; dataKeyType = datasource.dataKeys[0].type;
@ -93,6 +100,24 @@ function WebCameraWidgetController($element, $scope, $window, types, utils, attr
} }
}); });
function getVideoAspectRatio() {
if (videoElement.videoWidth && videoElement.videoWidth > 0 &&
videoElement.videoHeight && videoElement.videoHeight > 0) {
return videoElement.videoWidth / videoElement.videoHeight;
}
return width / height;
}
vm.videoWidth = function() {
const videoRatio = getVideoAspectRatio();
return Math.min(width, height * videoRatio);
}
vm.videoHeight = function() {
const videoRatio = getVideoAspectRatio();
return Math.min(height, width / videoRatio);
}
function hasGetUserMedia() { function hasGetUserMedia() {
return !!($window.navigator.mediaDevices && $window.navigator.mediaDevices.getUserMedia); return !!($window.navigator.mediaDevices && $window.navigator.mediaDevices.getUserMedia);
} }
@ -157,10 +182,12 @@ function WebCameraWidgetController($element, $scope, $window, types, utils, attr
} }
function createPhoto() { function createPhoto() {
canvas.width = videoElement.videoWidth; canvas.width = vm.videoWidth();
canvas.height = videoElement.videoHeight; canvas.height = vm.videoHeight();
canvas.getContext('2d').drawImage(videoElement, 0, 0); canvas.getContext('2d').drawImage(videoElement, 0, 0, vm.videoWidth(), vm.videoHeight());
vm.previewPhoto = canvas.toDataURL('image/png'); const mimeType = vm.ctx.settings.imageFormat ? vm.ctx.settings.imageFormat : DEFAULT_IMAGE_TYPE;
const quality = vm.ctx.settings.imageQuality ? vm.ctx.settings.imageQuality : DEFAULT_IMAGE_QUALITY;
vm.previewPhoto = canvas.toDataURL(mimeType, quality);
vm.isPreviewPhoto = true; vm.isPreviewPhoto = true;
} }