Browse Source

Merge branch 'master' into feature/rest_api_node_ssl

pull/3925/head
Viacheslav Kukhtyn 6 years ago
parent
commit
9c3fae2f8e
  1. 15
      application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java
  2. 3
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java
  3. 45
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/MultipleTbMsgsCallbackWrapper.java
  4. 32
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java
  5. 11
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java
  6. 23
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbMsgCallbackWrapper.java
  7. 11
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java
  8. 3
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java
  9. 26
      ui-ngx/angular.json
  10. 10
      ui-ngx/extra-webpack.config.js
  11. 52
      ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.ts
  12. 2
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.html
  13. 17
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.ts
  14. 32
      ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.ts
  15. 1
      ui-ngx/src/app/modules/home/components/widget/lib/analogue-compass.ts
  16. 1
      ui-ngx/src/app/modules/home/components/widget/lib/analogue-linear-gauge.ts
  17. 1
      ui-ngx/src/app/modules/home/components/widget/lib/analogue-radial-gauge.ts
  18. 1
      ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts
  19. 27
      ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts
  20. 20
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts
  21. 77
      ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts
  22. 14
      ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.ts
  23. 10
      ui-ngx/src/app/shared/components/js-func.component.ts
  24. 10
      ui-ngx/src/app/shared/components/json-content.component.ts
  25. 24
      ui-ngx/src/app/shared/components/json-form/react/json-form-ace-editor.tsx
  26. 7
      ui-ngx/src/app/shared/components/json-form/react/json-form-css.tsx
  27. 7
      ui-ngx/src/app/shared/components/json-form/react/json-form-html.tsx
  28. 6
      ui-ngx/src/app/shared/components/json-form/react/json-form-javascript.tsx
  29. 6
      ui-ngx/src/app/shared/components/json-form/react/json-form-json.tsx
  30. 229
      ui-ngx/src/app/shared/components/nav-tree.component.ts
  31. 95
      ui-ngx/src/app/shared/components/time/timezone-select.component.ts
  32. 79
      ui-ngx/src/app/shared/models/beautify.models.ts
  33. 2
      ui-ngx/src/app/shared/models/device.models.ts
  34. 71
      ui-ngx/src/app/shared/models/time/time.models.ts
  35. 12
      ui-ngx/tsconfig.json

15
application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java

@ -30,7 +30,9 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import javax.script.ScriptException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@ -116,14 +118,19 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
}
@Override
public ListenableFuture<TbMsg> executeUpdateAsync(TbMsg msg) {
public ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg) {
ListenableFuture<JsonNode> result = executeScriptAsync(msg);
return Futures.transformAsync(result, json -> {
if (!json.isObject()) {
if (json.isObject()) {
return Futures.immediateFuture(Collections.singletonList(unbindMsg(json, msg)));
} else if (json.isArray()){
List<TbMsg> res = new ArrayList<>(json.size());
json.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg)));
return Futures.immediateFuture(res);
}
else{
log.warn("Wrong result type: {}", json.getNodeType());
return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType()));
} else {
return Futures.immediateFuture(unbindMsg(json, msg));
}
}, MoreExecutors.directExecutor());
}

3
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java

@ -20,13 +20,14 @@ import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.msg.TbMsg;
import javax.script.ScriptException;
import java.util.List;
import java.util.Set;
public interface ScriptEngine {
TbMsg executeUpdate(TbMsg msg) throws ScriptException;
ListenableFuture<TbMsg> executeUpdateAsync(TbMsg msg);
ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg);
TbMsg executeGenerate(TbMsg prevMsg) throws ScriptException;

45
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/MultipleTbMsgsCallbackWrapper.java

@ -0,0 +1,45 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.transform;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.common.msg.queue.TbMsgCallback;
import java.util.concurrent.atomic.AtomicInteger;
public class MultipleTbMsgsCallbackWrapper implements TbMsgCallbackWrapper {
private final AtomicInteger tbMsgsCallbackCount;
private final TbMsgCallback callback;
public MultipleTbMsgsCallbackWrapper(int tbMsgsCallbackCount, TbMsgCallback callback) {
this.tbMsgsCallbackCount = new AtomicInteger(tbMsgsCallbackCount);
this.callback = callback;
}
@Override
public void onSuccess() {
if (tbMsgsCallbackCount.decrementAndGet() <= 0) {
callback.onSuccess();
}
}
@Override
public void onFailure(Throwable t) {
callback.onFailure(new RuleEngineException(t.getMessage()));
}
}

32
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java

@ -17,16 +17,19 @@ package org.thingsboard.rule.engine.transform;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.common.msg.queue.TbMsgCallback;
import java.util.List;
import static org.thingsboard.common.util.DonAsynchron.withCallback;
import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE;
import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
/**
* Created by ashvayka on 19.01.18.
@ -61,7 +64,30 @@ public abstract class TbAbstractTransformNode implements TbNode {
}
}
protected abstract ListenableFuture<TbMsg> transform(TbContext ctx, TbMsg msg);
protected void transformSuccess(TbContext ctx, TbMsg msg, List<TbMsg> msgs) {
if (msgs != null && !msgs.isEmpty()) {
if (msgs.size() == 1) {
ctx.tellSuccess(msgs.get(0));
} else {
TbMsgCallbackWrapper wrapper = new MultipleTbMsgsCallbackWrapper(msgs.size(), new TbMsgCallback() {
@Override
public void onSuccess() {
ctx.ack(msg);
}
@Override
public void onFailure(RuleEngineException e) {
ctx.tellFailure(msg, e);
}
});
msgs.forEach(newMsg -> ctx.enqueueForTellNext(newMsg, "Success", wrapper::onSuccess, wrapper::onFailure));
}
} else {
ctx.tellNext(msg, FAILURE);
}
}
protected abstract ListenableFuture<List<TbMsg>> transform(TbContext ctx, TbMsg msg);
public void setConfig(TbTransformNodeConfiguration config) {
this.config = config;

11
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java

@ -15,16 +15,15 @@
*/
package org.thingsboard.rule.engine.transform;
import com.google.common.base.Function;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.rule.engine.util.EntitiesAlarmOriginatorIdAsyncLoader;
import org.thingsboard.rule.engine.util.EntitiesCustomerIdAsyncLoader;
import org.thingsboard.rule.engine.util.EntitiesRelatedEntityIdAsyncLoader;
@ -33,7 +32,9 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@Slf4j
@RuleNode(
@ -65,13 +66,13 @@ public class TbChangeOriginatorNode extends TbAbstractTransformNode {
}
@Override
protected ListenableFuture<TbMsg> transform(TbContext ctx, TbMsg msg) {
protected ListenableFuture<List<TbMsg>> transform(TbContext ctx, TbMsg msg) {
ListenableFuture<? extends EntityId> newOriginator = getNewOriginator(ctx, msg.getOriginator());
return Futures.transform(newOriginator, (Function<EntityId, TbMsg>) n -> {
return Futures.transform(newOriginator, n -> {
if (n == null || n.isNullUid()) {
return null;
}
return ctx.transformMsg(msg, msg.getType(), n, msg.getMetaData(), msg.getData());
return Collections.singletonList((ctx.transformMsg(msg, msg.getType(), n, msg.getMetaData(), msg.getData())));
}, ctx.getDbCallbackExecutor());
}

23
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbMsgCallbackWrapper.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.transform;
public interface TbMsgCallbackWrapper {
void onSuccess();
void onFailure(Throwable t);
}

11
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java

@ -16,13 +16,16 @@
package org.thingsboard.rule.engine.transform;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.ScriptEngine;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.rule.engine.api.*;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;
import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE;
import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
import java.util.List;
@RuleNode(
type = ComponentType.TRANSFORMATION,
@ -51,7 +54,7 @@ public class TbTransformMsgNode extends TbAbstractTransformNode {
}
@Override
protected ListenableFuture<TbMsg> transform(TbContext ctx, TbMsg msg) {
protected ListenableFuture<List<TbMsg>> transform(TbContext ctx, TbMsg msg) {
ctx.logJsEvalRequest();
return jsEngine.executeUpdateAsync(msg);
}

3
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java

@ -38,6 +38,7 @@ import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import javax.script.ScriptException;
import java.util.Collections;
import java.util.concurrent.Callable;
import static org.junit.Assert.assertEquals;
@ -72,7 +73,7 @@ public class TbTransformMsgNodeTest {
TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON,rawJson, ruleChainId, ruleNodeId);
TbMsg transformedMsg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, "{new}", ruleChainId, ruleNodeId);
mockJsExecutor();
when(scriptEngine.executeUpdateAsync(msg)).thenReturn(Futures.immediateFuture(transformedMsg));
when(scriptEngine.executeUpdateAsync(msg)).thenReturn(Futures.immediateFuture(Collections.singletonList(transformedMsg)));
node.onMsg(ctx, msg);
verify(ctx).getDbCallbackExecutor();

26
ui-ngx/angular.json

@ -86,25 +86,9 @@
]
},
"scripts": [
"node_modules/jquery/dist/jquery.min.js",
"node_modules/jquery.terminal/js/jquery.terminal.min.js",
"node_modules/flot/lib/jquery.colorhelpers.js",
"node_modules/flot/src/jquery.flot.js",
"node_modules/flot/src/plugins/jquery.flot.time.js",
"node_modules/flot/src/plugins/jquery.flot.selection.js",
"node_modules/flot/src/plugins/jquery.flot.pie.js",
"node_modules/flot/src/plugins/jquery.flot.crosshair.js",
"node_modules/flot/src/plugins/jquery.flot.stack.js",
"node_modules/flot/src/plugins/jquery.flot.symbol.js",
"node_modules/flot.curvedlines/curvedLines.js",
"node_modules/tinycolor2/dist/tinycolor-min.js",
"node_modules/tooltipster/dist/js/tooltipster.bundle.min.js",
"node_modules/split.js/dist/split.js",
"node_modules/js-beautify/js/lib/beautify.js",
"node_modules/js-beautify/js/lib/beautify-css.js",
"node_modules/js-beautify/js/lib/beautify-html.js",
"node_modules/systemjs/dist/system.js",
"node_modules/jstree/dist/jstree.min.js"
"node_modules/split.js/dist/split.min.js",
"node_modules/systemjs/dist/system.js"
],
"customWebpackConfig": {
"path": "./extra-webpack.config.js"
@ -130,7 +114,11 @@
"tinycolor2",
"json-schema-defaults",
"leaflet-providers",
"lodash"
"lodash",
"jquery",
"jquery.terminal",
"tooltipster",
"jstree"
]
},
"configurations": {

10
ui-ngx/extra-webpack.config.js

@ -34,6 +34,13 @@ module.exports = (config, options) => {
SUPPORTED_LANGS: JSON.stringify(langs),
})
);
config.plugins.push(
new webpack.ProvidePlugin(
{
$: "jquery"
}
)
);
config.plugins.push(
new CompressionPlugin({
filename: "[path][base].gz[query]",
@ -44,6 +51,9 @@ module.exports = (config, options) => {
deleteOriginalAssets: false,
})
);
config.plugins.push(
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
);
if (config.mode === 'production') {
const index = config.plugins.findIndex(p => p instanceof AngularCompilerPlugin.AngularCompilerPlugin);

52
ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.ts

@ -24,6 +24,9 @@ import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router';
import { ContentType, contentTypesMap } from '@shared/models/constants';
import { getAce } from '@shared/models/ace/ace.models';
import { Observable } from 'rxjs/internal/Observable';
import { beautifyJs } from '@shared/models/beautify.models';
import { of } from 'rxjs';
export interface EventContentDialogData {
content: string;
@ -64,33 +67,42 @@ export class EventContentDialogComponent extends DialogComponent<EventContentDia
createEditor(editorElementRef: ElementRef, content: string) {
const editorElement = editorElementRef.nativeElement;
let mode = 'java';
let content$: Observable<string> = null;
if (this.contentType) {
mode = contentTypesMap.get(this.contentType).code;
if (this.contentType === ContentType.JSON && content) {
content = js_beautify(content, {indent_size: 4});
content$ = beautifyJs(content, {indent_size: 4});
}
}
let editorOptions: Partial<Ace.EditorOptions> = {
mode: `ace/mode/${mode}`,
theme: 'ace/theme/github',
showGutter: false,
showPrintMargin: false,
readOnly: true
};
if (!content$) {
content$ = of(content);
}
content$.subscribe(
(processedContent) => {
let editorOptions: Partial<Ace.EditorOptions> = {
mode: `ace/mode/${mode}`,
theme: 'ace/theme/github',
showGutter: false,
showPrintMargin: false,
readOnly: true
};
const advancedOptions = {
enableSnippets: false,
enableBasicAutocompletion: false,
enableLiveAutocompletion: false
};
const advancedOptions = {
enableSnippets: false,
enableBasicAutocompletion: false,
enableLiveAutocompletion: false
};
editorOptions = {...editorOptions, ...advancedOptions};
getAce().subscribe(
(ace) => {
const editor = ace.edit(editorElement, editorOptions);
editor.session.setUseWrapMode(false);
editor.setValue(content, -1);
this.updateEditorSize(editorElement, content, editor);
editorOptions = {...editorOptions, ...advancedOptions};
getAce().subscribe(
(ace) => {
const editor = ace.edit(editorElement, editorOptions);
editor.session.setUseWrapMode(false);
editor.setValue(processedContent, -1);
this.updateEditorSize(editorElement, processedContent, editor);
}
);
}
);
}

2
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.html

@ -29,7 +29,7 @@
</mat-form-field>
<div *ngIf="alarmScheduleForm.get('type').value !== alarmScheduleType.ANY_TIME">
<tb-timezone-select
[defaultTimezone]="defaultTimezone"
userTimezoneByDefault
required
formControlName="timezone">
</tb-timezone-select>

17
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.ts

@ -32,11 +32,14 @@ import {
AlarmSchedule,
AlarmScheduleType,
AlarmScheduleTypeTranslationMap,
dayOfWeekTranslations, getAlarmScheduleRangeText, timeOfDayToUTCTimestamp, utcTimestampToTimeOfDay
dayOfWeekTranslations,
getAlarmScheduleRangeText,
timeOfDayToUTCTimestamp,
utcTimestampToTimeOfDay
} from '@shared/models/device.models';
import { isDefined, isDefinedAndNotNull } from '@core/utils';
import * as _moment from 'moment-timezone';
import { MatCheckboxChange } from '@angular/material/checkbox';
import { getDefaultTimezone } from '@shared/models/time/time.models';
@Component({
selector: 'tb-alarm-schedule',
@ -58,8 +61,6 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator,
alarmScheduleForm: FormGroup;
defaultTimezone = _moment.tz.guess();
alarmScheduleTypes = Object.keys(AlarmScheduleType);
alarmScheduleType = AlarmScheduleType;
alarmScheduleTypeTranslate = AlarmScheduleTypeTranslationMap;
@ -93,9 +94,11 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator,
items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i)), this.validateItems)
});
this.alarmScheduleForm.get('type').valueChanges.subscribe((type) => {
this.alarmScheduleForm.reset({type, items: this.defaultItems, timezone: this.defaultTimezone}, {emitEvent: false});
this.updateValidators(type, true);
this.alarmScheduleForm.updateValueAndValidity();
getDefaultTimezone().subscribe((defaultTimezone) => {
this.alarmScheduleForm.reset({type, items: this.defaultItems, timezone: defaultTimezone}, {emitEvent: false});
this.updateValidators(type, true);
this.alarmScheduleForm.updateValueAndValidity();
});
});
this.alarmScheduleForm.valueChanges.subscribe(() => {
this.updateModel();

32
ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.ts

@ -34,13 +34,13 @@ import { AppState } from '@core/core.state';
import { CustomActionDescriptor } from '@shared/models/widget.models';
import { Ace } from 'ace-builds';
import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
import { css_beautify, html_beautify } from 'js-beautify';
import { ResizeObserver } from '@juggle/resize-observer';
import { CustomPrettyActionEditorCompleter } from '@home/components/widget/action/custom-action.models';
import { Observable } from 'rxjs/internal/Observable';
import { forkJoin, from } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { getAce } from '@shared/models/ace/ace.models';
import { beautifyCss, beautifyHtml } from '@shared/models/beautify.models';
@Component({
selector: 'tb-custom-action-pretty-resources-tabs',
@ -140,21 +140,27 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl
}
public beautifyCss(): void {
const res = css_beautify(this.action.customCss, {indent_size: 4});
if (this.action.customCss !== res) {
this.action.customCss = res;
this.cssEditor.setValue(this.action.customCss ? this.action.customCss : '', -1);
this.notifyActionUpdated();
}
beautifyCss(this.action.customCss, {indent_size: 4}).subscribe(
(res) => {
if (this.action.customCss !== res) {
this.action.customCss = res;
this.cssEditor.setValue(this.action.customCss ? this.action.customCss : '', -1);
this.notifyActionUpdated();
}
}
);
}
public beautifyHtml(): void {
const res = html_beautify(this.action.customHtml, {indent_size: 4, wrap_line_length: 60});
if (this.action.customHtml !== res) {
this.action.customHtml = res;
this.htmlEditor.setValue(this.action.customHtml ? this.action.customHtml : '', -1);
this.notifyActionUpdated();
}
beautifyHtml(this.action.customHtml, {indent_size: 4, wrap_line_length: 60}).subscribe(
(res) => {
if (this.action.customHtml !== res) {
this.action.customHtml = res;
this.htmlEditor.setValue(this.action.customHtml ? this.action.customHtml : '', -1);
this.notifyActionUpdated();
}
}
);
}
private initAceEditors(): Observable<any> {

1
ui-ngx/src/app/modules/home/components/widget/lib/analogue-compass.ts

@ -30,6 +30,7 @@ import RadialGauge = CanvasGauges.RadialGauge;
const analogueCompassSettingsSchemaValue = analogueCompassSettingsSchema;
// @dynamic
export class TbAnalogueCompass extends TbBaseGauge<AnalogueCompassSettings, RadialGaugeOptions> {
static get settingsSchema(): JsonSettingsSchema {

1
ui-ngx/src/app/modules/home/components/widget/lib/analogue-linear-gauge.ts

@ -32,6 +32,7 @@ const tinycolor = tinycolor_;
const analogueLinearGaugeSettingsSchemaValue = getAnalogueLinearGaugeSettingsSchema();
// @dynamic
export class TbAnalogueLinearGauge extends TbAnalogueGauge<AnalogueLinearGaugeSettings, LinearGaugeOptions>{
static get settingsSchema(): JsonSettingsSchema {

1
ui-ngx/src/app/modules/home/components/widget/lib/analogue-radial-gauge.ts

@ -28,6 +28,7 @@ import BaseGauge = CanvasGauges.BaseGauge;
const analogueRadialGaugeSettingsSchemaValue = getAnalogueRadialGaugeSettingsSchema();
// @dynamic
export class TbAnalogueRadialGauge extends TbAnalogueGauge<AnalogueRadialGaugeSettings, RadialGaugeOptions>{
static get settingsSchema(): JsonSettingsSchema {

1
ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts

@ -46,6 +46,7 @@ const tinycolor = tinycolor_;
const digitalGaugeSettingsSchemaValue = digitalGaugeSettingsSchema;
// @dynamic
export class TbCanvasDigitalGauge {
static get settingsSchema(): JsonSettingsSchema {

27
ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts

@ -111,11 +111,32 @@ export class WidgetComponentService {
const initSubject = new ReplaySubject();
this.init$ = initSubject.asObservable();
(window as any).tinycolor = tinycolor;
(window as any).cssjs = cssjs;
(window as any).moment = moment;
const w = (this.window as any);
w.tinycolor = tinycolor;
w.cssjs = cssjs;
w.moment = moment;
w.$ = $;
w.jQuery = $;
const widgetModulesTasks: Observable<any>[] = [];
widgetModulesTasks.push(from(import('jquery.terminal')));
widgetModulesTasks.push(from(import('flot/src/jquery.flot.js')).pipe(
mergeMap(() => {
const flotJsPluginsTasks: Observable<any>[] = [];
flotJsPluginsTasks.push(from(import('flot/lib/jquery.colorhelpers.js')));
flotJsPluginsTasks.push(from(import('flot/src/plugins/jquery.flot.time.js')));
flotJsPluginsTasks.push(from(import('flot/src/plugins/jquery.flot.selection.js')));
flotJsPluginsTasks.push(from(import('flot/src/plugins/jquery.flot.pie.js')));
flotJsPluginsTasks.push(from(import('flot/src/plugins/jquery.flot.crosshair.js')));
flotJsPluginsTasks.push(from(import('flot/src/plugins/jquery.flot.stack.js')));
flotJsPluginsTasks.push(from(import('flot/src/plugins/jquery.flot.symbol.js')));
flotJsPluginsTasks.push(from(import('flot.curvedlines/curvedLines.js')));
return forkJoin(flotJsPluginsTasks);
})
));
widgetModulesTasks.push(from(import('@home/components/widget/lib/flot-widget')).pipe(
tap((mod) => {
(window as any).TbFlot = mod.TbFlot;

20
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts

@ -29,7 +29,7 @@ import {
import { EntitiesTableComponent } from '../../components/entity/entities-table.component';
import { Authority } from '@shared/models/authority.enum';
import { RuleChainsTableConfigResolver } from '@modules/home/pages/rulechain/rulechains-table-config.resolver';
import { Observable } from 'rxjs';
import { from, Observable } from 'rxjs';
import { BreadCrumbConfig, BreadCrumbLabelFunction } from '@shared/components/breadcrumb';
import { ResolvedRuleChainMetaData, RuleChain } from '@shared/models/rule-chain.models';
import { RuleChainService } from '@core/http/rule-chain.service';
@ -75,6 +75,17 @@ export class RuleNodeComponentsResolver implements Resolve<Array<RuleNodeCompone
}
}
@Injectable()
export class TooltipsterResolver implements Resolve<any> {
constructor() {
}
resolve(route: ActivatedRouteSnapshot): Observable<any> {
return from(import('tooltipster'));
}
}
@Injectable()
export class RuleChainImportGuard implements CanActivate {
@ -144,7 +155,8 @@ const routes: Routes = [
resolve: {
ruleChain: RuleChainResolver,
ruleChainMetaData: ResolvedRuleChainMetaDataResolver,
ruleNodeComponents: RuleNodeComponentsResolver
ruleNodeComponents: RuleNodeComponentsResolver,
tooltipster: TooltipsterResolver
}
},
{
@ -162,7 +174,8 @@ const routes: Routes = [
import: true
},
resolve: {
ruleNodeComponents: RuleNodeComponentsResolver
ruleNodeComponents: RuleNodeComponentsResolver,
tooltipster: TooltipsterResolver
}
}
]
@ -178,6 +191,7 @@ const routes: Routes = [
RuleChainResolver,
ResolvedRuleChainMetaDataResolver,
RuleNodeComponentsResolver,
TooltipsterResolver,
RuleChainImportGuard
]
})

77
ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts

@ -34,7 +34,6 @@ import { TranslateService } from '@ngx-translate/core';
import { getCurrentIsLoading } from '@app/core/interceptors/load.selectors';
import { Ace } from 'ace-builds';
import { getAce, Range } from '@shared/models/ace/ace.models';
import { css_beautify, html_beautify } from 'js-beautify';
import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
import { WINDOW } from '@core/services/window.service';
import { WindowMessage } from '@shared/models/window-message.model';
@ -51,6 +50,7 @@ import Timeout = NodeJS.Timeout;
import { widgetEditorCompleter } from '@home/pages/widget/widget-editor.models';
import { Observable } from 'rxjs/internal/Observable';
import { map, tap } from 'rxjs/operators';
import { beautifyCss, beautifyHtml, beautifyJs } from '@shared/models/beautify.models';
// @dynamic
@Component({
@ -618,48 +618,63 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
}
beautifyCss(): void {
const res = css_beautify(this.widget.templateCss, {indent_size: 4});
if (this.widget.templateCss !== res) {
this.isDirty = true;
this.widget.templateCss = res;
this.cssEditor.setValue(this.widget.templateCss ? this.widget.templateCss : '', -1);
}
beautifyCss(this.widget.templateCss, {indent_size: 4}).subscribe(
(res) => {
if (this.widget.templateCss !== res) {
this.isDirty = true;
this.widget.templateCss = res;
this.cssEditor.setValue(this.widget.templateCss ? this.widget.templateCss : '', -1);
}
}
);
}
beautifyHtml(): void {
const res = html_beautify(this.widget.templateHtml, {indent_size: 4, wrap_line_length: 60});
if (this.widget.templateHtml !== res) {
this.isDirty = true;
this.widget.templateHtml = res;
this.htmlEditor.setValue(this.widget.templateHtml ? this.widget.templateHtml : '', -1);
}
beautifyHtml(this.widget.templateHtml, {indent_size: 4, wrap_line_length: 60}).subscribe(
(res) => {
if (this.widget.templateHtml !== res) {
this.isDirty = true;
this.widget.templateHtml = res;
this.htmlEditor.setValue(this.widget.templateHtml ? this.widget.templateHtml : '', -1);
}
}
);
}
beautifyJson(): void {
const res = js_beautify(this.widget.settingsSchema, {indent_size: 4});
if (this.widget.settingsSchema !== res) {
this.isDirty = true;
this.widget.settingsSchema = res;
this.jsonSettingsEditor.setValue(this.widget.settingsSchema ? this.widget.settingsSchema : '', -1);
}
beautifyJs(this.widget.settingsSchema, {indent_size: 4}).subscribe(
(res) => {
if (this.widget.settingsSchema !== res) {
this.isDirty = true;
this.widget.settingsSchema = res;
this.jsonSettingsEditor.setValue(this.widget.settingsSchema ? this.widget.settingsSchema : '', -1);
}
}
);
}
beautifyDataKeyJson(): void {
const res = js_beautify(this.widget.dataKeySettingsSchema, {indent_size: 4});
if (this.widget.dataKeySettingsSchema !== res) {
this.isDirty = true;
this.widget.dataKeySettingsSchema = res;
this.dataKeyJsonSettingsEditor.setValue(this.widget.dataKeySettingsSchema ? this.widget.dataKeySettingsSchema : '', -1);
}
beautifyJs(this.widget.dataKeySettingsSchema, {indent_size: 4}).subscribe(
(res) => {
if (this.widget.dataKeySettingsSchema !== res) {
this.isDirty = true;
this.widget.dataKeySettingsSchema = res;
this.dataKeyJsonSettingsEditor.setValue(this.widget.dataKeySettingsSchema ? this.widget.dataKeySettingsSchema : '', -1);
}
}
);
}
beautifyJs(): void {
const res = js_beautify(this.widget.controllerScript, {indent_size: 4, wrap_line_length: 60});
if (this.widget.controllerScript !== res) {
this.isDirty = true;
this.widget.controllerScript = res;
this.jsEditor.setValue(this.widget.controllerScript ? this.widget.controllerScript : '', -1);
}
beautifyJs(this.widget.controllerScript, {indent_size: 4, wrap_line_length: 60}).subscribe(
(res) => {
if (this.widget.controllerScript !== res) {
this.isDirty = true;
this.widget.controllerScript = res;
this.jsEditor.setValue(this.widget.controllerScript ? this.widget.controllerScript : '', -1);
}
}
);
}
removeResource(index: number) {

14
ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.ts

@ -41,6 +41,7 @@ import { TestScriptInputParams } from '@shared/models/rule-node.models';
import { RuleChainService } from '@core/http/rule-chain.service';
import { mergeMap } from 'rxjs/operators';
import { ActionNotificationShow } from '@core/notification/notification.actions';
import { beautifyJs } from '@shared/models/beautify.models';
export interface NodeScriptTestDialogData {
script: string;
@ -110,12 +111,17 @@ export class NodeScriptTestDialogComponent extends DialogComponent<NodeScriptTes
this.nodeScriptTestFormGroup = this.fb.group({
payload: this.fb.group({
msgType: [this.data.msgType, [Validators.required]],
msg: [js_beautify(JSON.stringify(this.data.msg), {indent_size: 4}), []],
msg: [null, []],
}),
metadata: [this.data.metadata, [Validators.required]],
script: [this.data.script, []],
output: ['', []]
});
beautifyJs(JSON.stringify(this.data.msg), {indent_size: 4}).subscribe(
(res) => {
this.nodeScriptTestFormGroup.get('payload').get('msg').patchValue(res, {emitEvent: false});
}
);
}
ngAfterViewInit(): void {
@ -166,7 +172,11 @@ export class NodeScriptTestDialogComponent extends DialogComponent<NodeScriptTes
test(): void {
this.testNodeScript().subscribe((output) => {
this.nodeScriptTestFormGroup.get('output').setValue(js_beautify(output, {indent_size: 4}));
beautifyJs(output, {indent_size: 4}).subscribe(
(res) => {
this.nodeScriptTestFormGroup.get('output').setValue(res);
}
);
});
}

10
ui-ngx/src/app/shared/components/js-func.component.ts

@ -37,6 +37,7 @@ import { TranslateService } from '@ngx-translate/core';
import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
import { ResizeObserver } from '@juggle/resize-observer';
import { TbEditorCompleter } from '@shared/models/ace/completion.models';
import { beautifyJs } from '@shared/models/beautify.models';
@Component({
selector: 'tb-js-func',
@ -214,9 +215,12 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
}
beautifyJs() {
const res = js_beautify(this.modelValue, {indent_size: 4, wrap_line_length: 60});
this.jsEditor.setValue(res ? res : '', -1);
this.updateView();
beautifyJs(this.modelValue, {indent_size: 4, wrap_line_length: 60}).subscribe(
(res) => {
this.jsEditor.setValue(res ? res : '', -1);
this.updateView();
}
);
}
validateOnSubmit(): void {

10
ui-ngx/src/app/shared/components/json-content.component.ts

@ -36,6 +36,7 @@ import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
import { guid } from '@core/utils';
import { ResizeObserver } from '@juggle/resize-observer';
import { getAce } from '@shared/models/ace/ace.models';
import { beautifyJs } from '@shared/models/beautify.models';
@Component({
selector: 'tb-json-content',
@ -286,9 +287,12 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid
}
beautifyJSON() {
const res = js_beautify(this.contentBody, {indent_size: 4, wrap_line_length: 60});
this.jsonEditor.setValue(res ? res : '', -1);
this.updateView();
beautifyJs(this.contentBody, {indent_size: 4, wrap_line_length: 60}).subscribe(
(res) => {
this.jsonEditor.setValue(res ? res : '', -1);
this.updateView();
}
);
}
minifyJSON() {

24
ui-ngx/src/app/shared/components/json-form/react/json-form-ace-editor.tsx

@ -22,6 +22,7 @@ import { IEditorProps } from 'react-ace/src/types';
import { map, mergeMap } from 'rxjs/operators';
import { loadAceDependencies } from '@shared/models/ace/ace.models';
import { from } from 'rxjs';
import { Observable } from 'rxjs/internal/Observable';
const ReactAce = React.lazy(() => {
return loadAceDependencies().pipe(
@ -33,7 +34,7 @@ const ReactAce = React.lazy(() => {
interface ThingsboardAceEditorProps extends JsonFormFieldProps {
mode: string;
onTidy: (value: string) => string;
onTidy: (value: string) => Observable<string>;
}
interface ThingsboardAceEditorState extends JsonFormFieldState {
@ -84,15 +85,18 @@ class ThingsboardAceEditor extends React.Component<ThingsboardAceEditorProps, Th
onTidy() {
if (!this.props.form.readonly) {
let value = this.state.value;
value = this.props.onTidy(value);
this.setState({
value
});
this.props.onChangeValidate({
target: {
value
}
});
this.props.onTidy(value).subscribe(
(processedValue) => {
this.setState({
value: processedValue
});
this.props.onChangeValidate({
target: {
value: processedValue
}
});
}
);
}
}

7
ui-ngx/src/app/shared/components/json-form/react/json-form-css.tsx

@ -16,7 +16,8 @@
import * as React from 'react';
import ThingsboardAceEditor from './json-form-ace-editor';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
import { css_beautify } from 'js-beautify';
import { Observable } from 'rxjs/internal/Observable';
import { beautifyCss } from '@shared/models/beautify.models';
class ThingsboardCss extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
@ -25,8 +26,8 @@ class ThingsboardCss extends React.Component<JsonFormFieldProps, JsonFormFieldSt
this.onTidyCss = this.onTidyCss.bind(this);
}
onTidyCss(css: string): string {
return css_beautify(css, {indent_size: 4});
onTidyCss(css: string): Observable<string> {
return beautifyCss(css, {indent_size: 4});
}
render() {

7
ui-ngx/src/app/shared/components/json-form/react/json-form-html.tsx

@ -15,8 +15,9 @@
*/
import * as React from 'react';
import ThingsboardAceEditor from './json-form-ace-editor';
import { html_beautify } from 'js-beautify';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
import { Observable } from 'rxjs/internal/Observable';
import { beautifyHtml } from '@shared/models/beautify.models';
class ThingsboardHtml extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
@ -25,8 +26,8 @@ class ThingsboardHtml extends React.Component<JsonFormFieldProps, JsonFormFieldS
this.onTidyHtml = this.onTidyHtml.bind(this);
}
onTidyHtml(html: string): string {
return html_beautify(html, {indent_size: 4});
onTidyHtml(html: string): Observable<string> {
return beautifyHtml(html, {indent_size: 4});
}
render() {

6
ui-ngx/src/app/shared/components/json-form/react/json-form-javascript.tsx

@ -16,6 +16,8 @@
import * as React from 'react';
import ThingsboardAceEditor from './json-form-ace-editor';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
import { Observable } from 'rxjs/internal/Observable';
import { beautifyJs } from '@shared/models/beautify.models';
class ThingsboardJavaScript extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
@ -24,8 +26,8 @@ class ThingsboardJavaScript extends React.Component<JsonFormFieldProps, JsonForm
this.onTidyJavascript = this.onTidyJavascript.bind(this);
}
onTidyJavascript(javascript: string): string {
return js_beautify(javascript, {indent_size: 4, wrap_line_length: 60});
onTidyJavascript(javascript: string): Observable<string> {
return beautifyJs(javascript, {indent_size: 4, wrap_line_length: 60});
}
render() {

6
ui-ngx/src/app/shared/components/json-form/react/json-form-json.tsx

@ -16,6 +16,8 @@
import * as React from 'react';
import ThingsboardAceEditor from './json-form-ace-editor';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
import { Observable } from 'rxjs/internal/Observable';
import { beautifyJs } from '@shared/models/beautify.models';
class ThingsboardJson extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
@ -24,8 +26,8 @@ class ThingsboardJson extends React.Component<JsonFormFieldProps, JsonFormFieldS
this.onTidyJson = this.onTidyJson.bind(this);
}
onTidyJson(json: string): string {
return js_beautify(json, {indent_size: 4});
onTidyJson(json: string): Observable<string> {
return beautifyJs(json, {indent_size: 4});
}
render() {

229
ui-ngx/src/app/shared/components/nav-tree.component.ts

@ -145,128 +145,131 @@ export class NavTreeComponent implements OnInit {
};
}
this.treeElement = $('.tb-nav-tree-container', this.elementRef.nativeElement).jstree(config);
import('jstree').then(() => {
this.treeElement.on('changed.jstree', (e: any, data) => {
const node: NavTreeNode = data.instance.get_selected(true)[0];
if (this.onNodeSelected) {
this.ngZone.run(() => this.onNodeSelected(node, e as Event));
}
});
this.treeElement.on('model.jstree', (e: any, data) => {
if (this.onNodesInserted) {
this.ngZone.run(() => this.onNodesInserted(data.nodes, data.parent));
}
});
this.treeElement = $('.tb-nav-tree-container', this.elementRef.nativeElement).jstree(config);
if (this.editCallbacks) {
this.editCallbacks.selectNode = id => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
this.treeElement.jstree('deselect_all', true);
this.treeElement.jstree('select_node', node);
}
};
this.editCallbacks.deselectAll = () => {
this.treeElement.jstree('deselect_all');
};
this.editCallbacks.getNode = (id) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
return node;
};
this.editCallbacks.getParentNodeId = (id) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
return this.treeElement.jstree('get_parent', node);
this.treeElement.on('changed.jstree', (e: any, data) => {
const node: NavTreeNode = data.instance.get_selected(true)[0];
if (this.onNodeSelected) {
this.ngZone.run(() => this.onNodeSelected(node, e as Event));
}
};
this.editCallbacks.openNode = (id, cb) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
this.treeElement.jstree('open_node', node, cb);
}
};
this.editCallbacks.nodeIsOpen = (id) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
return this.treeElement.jstree('is_open', node);
} else {
return true;
}
};
this.editCallbacks.nodeIsLoaded = (id) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
return this.treeElement.jstree('is_loaded', node);
} else {
return true;
});
this.treeElement.on('model.jstree', (e: any, data) => {
if (this.onNodesInserted) {
this.ngZone.run(() => this.onNodesInserted(data.nodes, data.parent));
}
};
this.editCallbacks.refreshNode = (id) => {
if (id === '#') {
this.treeElement.jstree('refresh');
this.treeElement.jstree('redraw');
} else {
});
if (this.editCallbacks) {
this.editCallbacks.selectNode = id => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
this.treeElement.jstree('deselect_all', true);
this.treeElement.jstree('select_node', node);
}
};
this.editCallbacks.deselectAll = () => {
this.treeElement.jstree('deselect_all');
};
this.editCallbacks.getNode = (id) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
return node;
};
this.editCallbacks.getParentNodeId = (id) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
const opened = this.treeElement.jstree('is_open', node);
this.treeElement.jstree('refresh_node', node);
return this.treeElement.jstree('get_parent', node);
}
};
this.editCallbacks.openNode = (id, cb) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
this.treeElement.jstree('open_node', node, cb);
}
};
this.editCallbacks.nodeIsOpen = (id) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
return this.treeElement.jstree('is_open', node);
} else {
return true;
}
};
this.editCallbacks.nodeIsLoaded = (id) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
return this.treeElement.jstree('is_loaded', node);
} else {
return true;
}
};
this.editCallbacks.refreshNode = (id) => {
if (id === '#') {
this.treeElement.jstree('refresh');
this.treeElement.jstree('redraw');
if (node.children && opened/* && !node.children.length*/) {
this.treeElement.jstree('open_node', node);
} else {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
const opened = this.treeElement.jstree('is_open', node);
this.treeElement.jstree('refresh_node', node);
this.treeElement.jstree('redraw');
if (node.children && opened/* && !node.children.length*/) {
this.treeElement.jstree('open_node', node);
}
}
}
}
};
this.editCallbacks.updateNode = (id, newName) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
this.treeElement.jstree('rename_node', node, newName);
}
};
this.editCallbacks.createNode = (parentId, node, pos) => {
const parentNode: NavTreeNode = this.treeElement.jstree('get_node', parentId);
if (parentNode) {
this.treeElement.jstree('create_node', parentNode, node, pos);
}
};
this.editCallbacks.deleteNode = (id) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
this.treeElement.jstree('delete_node', node);
}
};
this.editCallbacks.disableNode = (id) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
this.treeElement.jstree('disable_node', node);
}
};
this.editCallbacks.enableNode = (id) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
this.treeElement.jstree('enable_node', node);
}
};
this.editCallbacks.setNodeHasChildren = (id, hasChildren) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
if (!node.children || (Array.isArray(node.children) && !node.children.length)) {
node.children = hasChildren;
node.state.loaded = !hasChildren;
node.state.opened = false;
this.treeElement.jstree('_node_changed', node.id);
this.treeElement.jstree('redraw');
};
this.editCallbacks.updateNode = (id, newName) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
this.treeElement.jstree('rename_node', node, newName);
}
}
};
this.editCallbacks.search = (searchText) => {
this.treeElement.jstree('search', searchText);
};
this.editCallbacks.clearSearch = () => {
this.treeElement.jstree('clear_search');
};
}
};
this.editCallbacks.createNode = (parentId, node, pos) => {
const parentNode: NavTreeNode = this.treeElement.jstree('get_node', parentId);
if (parentNode) {
this.treeElement.jstree('create_node', parentNode, node, pos);
}
};
this.editCallbacks.deleteNode = (id) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
this.treeElement.jstree('delete_node', node);
}
};
this.editCallbacks.disableNode = (id) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
this.treeElement.jstree('disable_node', node);
}
};
this.editCallbacks.enableNode = (id) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
this.treeElement.jstree('enable_node', node);
}
};
this.editCallbacks.setNodeHasChildren = (id, hasChildren) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) {
if (!node.children || (Array.isArray(node.children) && !node.children.length)) {
node.children = hasChildren;
node.state.loaded = !hasChildren;
node.state.opened = false;
this.treeElement.jstree('_node_changed', node.id);
this.treeElement.jstree('redraw');
}
}
};
this.editCallbacks.search = (searchText) => {
this.treeElement.jstree('search', searchText);
};
this.editCallbacks.clearSearch = () => {
this.treeElement.jstree('clear_search');
};
}
});
}
}

95
ui-ngx/src/app/shared/components/time/timezone-select.component.ts

@ -16,21 +16,14 @@
import { AfterViewInit, Component, forwardRef, Input, NgZone, OnInit, ViewChild } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Observable, of } from 'rxjs';
import { Observable } from 'rxjs';
import { map, mergeMap, share, tap } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { TranslateService } from '@ngx-translate/core';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import * as _moment from 'moment-timezone';
import { MatAutocompleteTrigger } from '@angular/material/autocomplete';
interface TimezoneInfo {
id: string;
name: string;
offset: string;
nOffset: number;
}
import { getTimezoneInfo, getTimezones, TimezoneInfo } from '@shared/models/time/time.models';
@Component({
selector: 'tb-timezone-select',
@ -50,28 +43,14 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
defaultTimezoneId: string = null;
defaultTimezoneInfo: TimezoneInfo = null;
timezones: TimezoneInfo[] = _moment.tz.names().map((zoneName) => {
const tz = _moment.tz(zoneName);
return {
id: zoneName,
name: zoneName.replace(/_/g, ' '),
offset: `UTC${tz.format('Z')}`,
nOffset: tz.utcOffset()
}
});
timezones$ = getTimezones().pipe(
share()
);
@Input()
set defaultTimezone(timezone: string) {
if (this.defaultTimezoneId !== timezone) {
this.defaultTimezoneId = timezone;
if (this.defaultTimezoneId) {
this.defaultTimezoneInfo =
this.timezones.find((timezoneInfo) => timezoneInfo.id === this.defaultTimezoneId);
} else {
this.defaultTimezoneInfo = null;
}
}
}
@ -84,6 +63,15 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
this.requiredValue = coerceBooleanProperty(value);
}
private userTimezoneByDefaultValue: boolean;
get userTimezoneByDefault(): boolean {
return this.userTimezoneByDefaultValue;
}
@Input()
set userTimezoneByDefault(value: boolean) {
this.userTimezoneByDefaultValue = coerceBooleanProperty(value);
}
@Input()
disabled: boolean;
@ -150,24 +138,23 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
writeValue(value: string | null): void {
this.searchText = '';
let foundTimezone: TimezoneInfo = null;
if (value !== null) {
foundTimezone = this.timezones.find(timezoneInfo => timezoneInfo.id === value);
}
if (foundTimezone !== null) {
this.modelValue = value;
this.selectTimezoneFormGroup.get('timezone').patchValue(foundTimezone, {emitEvent: false});
} else {
if (this.defaultTimezoneInfo) {
this.selectTimezoneFormGroup.get('timezone').patchValue(this.defaultTimezoneInfo, {emitEvent: false});
setTimeout(() => {
this.updateView(this.defaultTimezoneInfo.id);
}, 0);
} else {
this.modelValue = null;
this.selectTimezoneFormGroup.get('timezone').patchValue('', {emitEvent: false});
getTimezoneInfo(value, this.defaultTimezoneId, this.userTimezoneByDefaultValue).subscribe(
(foundTimezone) => {
if (foundTimezone !== null) {
this.selectTimezoneFormGroup.get('timezone').patchValue(foundTimezone, {emitEvent: false});
if (foundTimezone.id !== value) {
setTimeout(() => {
this.updateView(foundTimezone.id);
}, 0);
} else {
this.modelValue = value;
}
} else {
this.modelValue = null;
this.selectTimezoneFormGroup.get('timezone').patchValue('', {emitEvent: false});
}
}
}
);
this.dirty = true;
}
@ -182,10 +169,15 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
if (this.ignoreClosePanel) {
this.ignoreClosePanel = false;
} else {
if (!this.modelValue && this.defaultTimezoneInfo) {
this.ngZone.run(() => {
this.selectTimezoneFormGroup.get('timezone').reset(this.defaultTimezoneInfo, {emitEvent: true});
});
if (!this.modelValue && (this.defaultTimezoneId || this.userTimezoneByDefaultValue)) {
getTimezoneInfo(this.defaultTimezoneId, this.defaultTimezoneId, this.userTimezoneByDefaultValue).subscribe(
(defaultTimezoneInfo) => {
if (defaultTimezoneInfo !== null) {
this.ngZone.run(() => {
this.selectTimezoneFormGroup.get('timezone').reset(defaultTimezoneInfo, {emitEvent: true});
});
}
});
}
}
}
@ -203,12 +195,13 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
fetchTimezones(searchText?: string): Observable<Array<TimezoneInfo>> {
this.searchText = searchText;
let result = this.timezones;
if (searchText && searchText.length) {
result = this.timezones.filter((timezoneInfo) =>
timezoneInfo.name.toLowerCase().includes(searchText.toLowerCase()));
return getTimezones().pipe(
map((timezones) => timezones.filter((timezoneInfo) =>
timezoneInfo.name.toLowerCase().includes(searchText.toLowerCase())))
);
}
return of(result);
return getTimezones();
}
clear() {

79
ui-ngx/src/app/shared/models/beautify.models.ts

@ -0,0 +1,79 @@
///
/// Copyright © 2016-2020 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Observable } from 'rxjs/internal/Observable';
import { from, of } from 'rxjs';
import { map, tap } from 'rxjs/operators';
let jsBeautifyModule: any;
let htmlBeautifyModule: any;
let cssBeautifyModule: any;
function loadJsBeautify(): Observable<any> {
if (jsBeautifyModule) {
return of(jsBeautifyModule);
} else {
return from(import('js-beautify/js/lib/beautify.js')).pipe(
tap((module) => {
jsBeautifyModule = module;
})
);
}
}
function loadHtmlBeautify(): Observable<any> {
if (htmlBeautifyModule) {
return of(htmlBeautifyModule);
} else {
return from(import('js-beautify/js/lib/beautify-html.js')).pipe(
tap((module) => {
htmlBeautifyModule = module;
})
);
}
}
function loadCssBeautify(): Observable<any> {
if (cssBeautifyModule) {
return of(cssBeautifyModule);
} else {
return from(import('js-beautify/js/lib/beautify-css.js')).pipe(
tap((module) => {
cssBeautifyModule = module;
})
);
}
}
export function beautifyJs(source: string, options?: any): Observable<string> {
return loadJsBeautify().pipe(
map((mod) => {
return mod.js_beautify(source, options);
})
);
}
export function beautifyCss(source: string, options?: any): Observable<string> {
return loadCssBeautify().pipe(
map((mod) => mod.css_beautify(source, options))
);
}
export function beautifyHtml(source: string, options?: any): Observable<string> {
return loadHtmlBeautify().pipe(
map((mod) => mod.html_beautify(source, options))
);
}

2
ui-ngx/src/app/shared/models/device.models.ts

@ -25,7 +25,7 @@ import { RuleChainId } from '@shared/models/id/rule-chain-id';
import { EntityInfoData } from '@shared/models/entity.models';
import { KeyFilter } from '@shared/models/query/query.models';
import { TimeUnit } from '@shared/models/time/time.models';
import * as _moment from 'moment-timezone';
import * as _moment from 'moment';
import { AbstractControl, ValidationErrors } from '@angular/forms';
export enum DeviceProfileType {

71
ui-ngx/src/app/shared/models/time/time.models.ts

@ -17,6 +17,9 @@
import { TimeService } from '@core/services/time.service';
import { deepClone, isDefined, isUndefined } from '@app/core/utils';
import * as moment_ from 'moment';
import { Observable } from 'rxjs/internal/Observable';
import { from, of } from 'rxjs';
import { map, mergeMap, tap } from 'rxjs/operators';
const moment = moment_;
@ -481,3 +484,71 @@ export const timeUnitTranslationMap = new Map<TimeUnit, string>(
[TimeUnit.DAYS, 'timeunit.days']
]
);
export interface TimezoneInfo {
id: string;
name: string;
offset: string;
nOffset: number;
}
let timezones: TimezoneInfo[] = null;
let defaultTimezone: string = null;
export function getTimezones(): Observable<TimezoneInfo[]> {
if (timezones) {
return of(timezones);
} else {
return from(import('moment-timezone')).pipe(
map((monentTz) => {
return monentTz.tz.names().map((zoneName) => {
const tz = monentTz.tz(zoneName);
return {
id: zoneName,
name: zoneName.replace(/_/g, ' '),
offset: `UTC${tz.format('Z')}`,
nOffset: tz.utcOffset()
};
});
}),
tap((zones) => {
timezones = zones;
})
);
}
}
export function getTimezoneInfo(timezoneId: string, defaultTimezoneId?: string, userTimezoneByDefault?: boolean): Observable<TimezoneInfo> {
return getTimezones().pipe(
mergeMap((timezoneList) => {
let foundTimezone = timezoneList.find(timezoneInfo => timezoneInfo.id === timezoneId);
if (!foundTimezone) {
if (userTimezoneByDefault) {
return getDefaultTimezone().pipe(
map((userTimezone) => {
return timezoneList.find(timezoneInfo => timezoneInfo.id === userTimezone);
})
);
} else if (defaultTimezoneId) {
foundTimezone = timezoneList.find(timezoneInfo => timezoneInfo.id === defaultTimezoneId);
}
}
return of(foundTimezone);
})
);
}
export function getDefaultTimezone(): Observable<string> {
if (defaultTimezone) {
return of(defaultTimezone);
} else {
return from(import('moment-timezone')).pipe(
map((monentTz) => {
return monentTz.tz.guess();
}),
tap((zone) => {
defaultTimezone = zone;
})
);
}
}

12
ui-ngx/tsconfig.json

@ -37,6 +37,18 @@
],
"ace": [
"node_modules/ace-builds/src-noconflict/ace.js"
],
"jquery": [
"node_modules/jquery/dist/jquery.min.js"
],
"jquery.terminal": [
"node_modules/jquery.terminal/js/jquery.terminal.js"
],
"tooltipster": [
"node_modules/tooltipster/dist/js/tooltipster.bundle.min.js"
],
"jstree": [
"node_modules/jstree/dist/jstree.min.js"
]
},
"lib": [

Loading…
Cancel
Save