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 org.thingsboard.server.common.msg.TbMsgMetaData;
import javax.script.ScriptException; import javax.script.ScriptException;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -116,14 +118,19 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
} }
@Override @Override
public ListenableFuture<TbMsg> executeUpdateAsync(TbMsg msg) { public ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg) {
ListenableFuture<JsonNode> result = executeScriptAsync(msg); ListenableFuture<JsonNode> result = executeScriptAsync(msg);
return Futures.transformAsync(result, json -> { 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()); log.warn("Wrong result type: {}", json.getNodeType());
return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType())); return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType()));
} else {
return Futures.immediateFuture(unbindMsg(json, msg));
} }
}, MoreExecutors.directExecutor()); }, 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 org.thingsboard.server.common.msg.TbMsg;
import javax.script.ScriptException; import javax.script.ScriptException;
import java.util.List;
import java.util.Set; import java.util.Set;
public interface ScriptEngine { public interface ScriptEngine {
TbMsg executeUpdate(TbMsg msg) throws ScriptException; TbMsg executeUpdate(TbMsg msg) throws ScriptException;
ListenableFuture<TbMsg> executeUpdateAsync(TbMsg msg); ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg);
TbMsg executeGenerate(TbMsg prevMsg) throws ScriptException; 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 com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; 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.TbContext;
import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; 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.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.common.util.DonAsynchron.withCallback;
import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE; 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. * 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) { public void setConfig(TbTransformNodeConfiguration config) {
this.config = 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; package org.thingsboard.rule.engine.transform;
import com.google.common.base.Function;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; 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.RuleNode;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; 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.EntitiesAlarmOriginatorIdAsyncLoader;
import org.thingsboard.rule.engine.util.EntitiesCustomerIdAsyncLoader; import org.thingsboard.rule.engine.util.EntitiesCustomerIdAsyncLoader;
import org.thingsboard.rule.engine.util.EntitiesRelatedEntityIdAsyncLoader; 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.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
@Slf4j @Slf4j
@RuleNode( @RuleNode(
@ -65,13 +66,13 @@ public class TbChangeOriginatorNode extends TbAbstractTransformNode {
} }
@Override @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()); 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()) { if (n == null || n.isNullUid()) {
return null; 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()); }, 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; package org.thingsboard.rule.engine.transform;
import com.google.common.util.concurrent.ListenableFuture; 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.util.TbNodeUtils;
import org.thingsboard.rule.engine.api.*;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE; import java.util.List;
import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
@RuleNode( @RuleNode(
type = ComponentType.TRANSFORMATION, type = ComponentType.TRANSFORMATION,
@ -51,7 +54,7 @@ public class TbTransformMsgNode extends TbAbstractTransformNode {
} }
@Override @Override
protected ListenableFuture<TbMsg> transform(TbContext ctx, TbMsg msg) { protected ListenableFuture<List<TbMsg>> transform(TbContext ctx, TbMsg msg) {
ctx.logJsEvalRequest(); ctx.logJsEvalRequest();
return jsEngine.executeUpdateAsync(msg); 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 org.thingsboard.server.common.msg.TbMsgMetaData;
import javax.script.ScriptException; import javax.script.ScriptException;
import java.util.Collections;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import static org.junit.Assert.assertEquals; 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 msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON,rawJson, ruleChainId, ruleNodeId);
TbMsg transformedMsg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, "{new}", ruleChainId, ruleNodeId); TbMsg transformedMsg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, "{new}", ruleChainId, ruleNodeId);
mockJsExecutor(); mockJsExecutor();
when(scriptEngine.executeUpdateAsync(msg)).thenReturn(Futures.immediateFuture(transformedMsg)); when(scriptEngine.executeUpdateAsync(msg)).thenReturn(Futures.immediateFuture(Collections.singletonList(transformedMsg)));
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
verify(ctx).getDbCallbackExecutor(); verify(ctx).getDbCallbackExecutor();

26
ui-ngx/angular.json

@ -86,25 +86,9 @@
] ]
}, },
"scripts": [ "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/tinycolor2/dist/tinycolor-min.js",
"node_modules/tooltipster/dist/js/tooltipster.bundle.min.js", "node_modules/split.js/dist/split.min.js",
"node_modules/split.js/dist/split.js", "node_modules/systemjs/dist/system.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"
], ],
"customWebpackConfig": { "customWebpackConfig": {
"path": "./extra-webpack.config.js" "path": "./extra-webpack.config.js"
@ -130,7 +114,11 @@
"tinycolor2", "tinycolor2",
"json-schema-defaults", "json-schema-defaults",
"leaflet-providers", "leaflet-providers",
"lodash" "lodash",
"jquery",
"jquery.terminal",
"tooltipster",
"jstree"
] ]
}, },
"configurations": { "configurations": {

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

@ -34,6 +34,13 @@ module.exports = (config, options) => {
SUPPORTED_LANGS: JSON.stringify(langs), SUPPORTED_LANGS: JSON.stringify(langs),
}) })
); );
config.plugins.push(
new webpack.ProvidePlugin(
{
$: "jquery"
}
)
);
config.plugins.push( config.plugins.push(
new CompressionPlugin({ new CompressionPlugin({
filename: "[path][base].gz[query]", filename: "[path][base].gz[query]",
@ -44,6 +51,9 @@ module.exports = (config, options) => {
deleteOriginalAssets: false, deleteOriginalAssets: false,
}) })
); );
config.plugins.push(
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
);
if (config.mode === 'production') { if (config.mode === 'production') {
const index = config.plugins.findIndex(p => p instanceof AngularCompilerPlugin.AngularCompilerPlugin); 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 { Router } from '@angular/router';
import { ContentType, contentTypesMap } from '@shared/models/constants'; import { ContentType, contentTypesMap } from '@shared/models/constants';
import { getAce } from '@shared/models/ace/ace.models'; 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 { export interface EventContentDialogData {
content: string; content: string;
@ -64,33 +67,42 @@ export class EventContentDialogComponent extends DialogComponent<EventContentDia
createEditor(editorElementRef: ElementRef, content: string) { createEditor(editorElementRef: ElementRef, content: string) {
const editorElement = editorElementRef.nativeElement; const editorElement = editorElementRef.nativeElement;
let mode = 'java'; let mode = 'java';
let content$: Observable<string> = null;
if (this.contentType) { if (this.contentType) {
mode = contentTypesMap.get(this.contentType).code; mode = contentTypesMap.get(this.contentType).code;
if (this.contentType === ContentType.JSON && content) { if (this.contentType === ContentType.JSON && content) {
content = js_beautify(content, {indent_size: 4}); content$ = beautifyJs(content, {indent_size: 4});
} }
} }
let editorOptions: Partial<Ace.EditorOptions> = { if (!content$) {
mode: `ace/mode/${mode}`, content$ = of(content);
theme: 'ace/theme/github', }
showGutter: false,
showPrintMargin: false, content$.subscribe(
readOnly: true (processedContent) => {
}; let editorOptions: Partial<Ace.EditorOptions> = {
mode: `ace/mode/${mode}`,
theme: 'ace/theme/github',
showGutter: false,
showPrintMargin: false,
readOnly: true
};
const advancedOptions = { const advancedOptions = {
enableSnippets: false, enableSnippets: false,
enableBasicAutocompletion: false, enableBasicAutocompletion: false,
enableLiveAutocompletion: false enableLiveAutocompletion: false
}; };
editorOptions = {...editorOptions, ...advancedOptions}; editorOptions = {...editorOptions, ...advancedOptions};
getAce().subscribe( getAce().subscribe(
(ace) => { (ace) => {
const editor = ace.edit(editorElement, editorOptions); const editor = ace.edit(editorElement, editorOptions);
editor.session.setUseWrapMode(false); editor.session.setUseWrapMode(false);
editor.setValue(content, -1); editor.setValue(processedContent, -1);
this.updateEditorSize(editorElement, content, editor); 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> </mat-form-field>
<div *ngIf="alarmScheduleForm.get('type').value !== alarmScheduleType.ANY_TIME"> <div *ngIf="alarmScheduleForm.get('type').value !== alarmScheduleType.ANY_TIME">
<tb-timezone-select <tb-timezone-select
[defaultTimezone]="defaultTimezone" userTimezoneByDefault
required required
formControlName="timezone"> formControlName="timezone">
</tb-timezone-select> </tb-timezone-select>

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

@ -32,11 +32,14 @@ import {
AlarmSchedule, AlarmSchedule,
AlarmScheduleType, AlarmScheduleType,
AlarmScheduleTypeTranslationMap, AlarmScheduleTypeTranslationMap,
dayOfWeekTranslations, getAlarmScheduleRangeText, timeOfDayToUTCTimestamp, utcTimestampToTimeOfDay dayOfWeekTranslations,
getAlarmScheduleRangeText,
timeOfDayToUTCTimestamp,
utcTimestampToTimeOfDay
} from '@shared/models/device.models'; } from '@shared/models/device.models';
import { isDefined, isDefinedAndNotNull } from '@core/utils'; import { isDefined, isDefinedAndNotNull } from '@core/utils';
import * as _moment from 'moment-timezone';
import { MatCheckboxChange } from '@angular/material/checkbox'; import { MatCheckboxChange } from '@angular/material/checkbox';
import { getDefaultTimezone } from '@shared/models/time/time.models';
@Component({ @Component({
selector: 'tb-alarm-schedule', selector: 'tb-alarm-schedule',
@ -58,8 +61,6 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator,
alarmScheduleForm: FormGroup; alarmScheduleForm: FormGroup;
defaultTimezone = _moment.tz.guess();
alarmScheduleTypes = Object.keys(AlarmScheduleType); alarmScheduleTypes = Object.keys(AlarmScheduleType);
alarmScheduleType = AlarmScheduleType; alarmScheduleType = AlarmScheduleType;
alarmScheduleTypeTranslate = AlarmScheduleTypeTranslationMap; 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) items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i)), this.validateItems)
}); });
this.alarmScheduleForm.get('type').valueChanges.subscribe((type) => { this.alarmScheduleForm.get('type').valueChanges.subscribe((type) => {
this.alarmScheduleForm.reset({type, items: this.defaultItems, timezone: this.defaultTimezone}, {emitEvent: false}); getDefaultTimezone().subscribe((defaultTimezone) => {
this.updateValidators(type, true); this.alarmScheduleForm.reset({type, items: this.defaultItems, timezone: defaultTimezone}, {emitEvent: false});
this.alarmScheduleForm.updateValueAndValidity(); this.updateValidators(type, true);
this.alarmScheduleForm.updateValueAndValidity();
});
}); });
this.alarmScheduleForm.valueChanges.subscribe(() => { this.alarmScheduleForm.valueChanges.subscribe(() => {
this.updateModel(); 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 { CustomActionDescriptor } from '@shared/models/widget.models';
import { Ace } from 'ace-builds'; import { Ace } from 'ace-builds';
import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
import { css_beautify, html_beautify } from 'js-beautify';
import { ResizeObserver } from '@juggle/resize-observer'; import { ResizeObserver } from '@juggle/resize-observer';
import { CustomPrettyActionEditorCompleter } from '@home/components/widget/action/custom-action.models'; import { CustomPrettyActionEditorCompleter } from '@home/components/widget/action/custom-action.models';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { forkJoin, from } from 'rxjs'; import { forkJoin, from } from 'rxjs';
import { map, tap } from 'rxjs/operators'; import { map, tap } from 'rxjs/operators';
import { getAce } from '@shared/models/ace/ace.models'; import { getAce } from '@shared/models/ace/ace.models';
import { beautifyCss, beautifyHtml } from '@shared/models/beautify.models';
@Component({ @Component({
selector: 'tb-custom-action-pretty-resources-tabs', selector: 'tb-custom-action-pretty-resources-tabs',
@ -140,21 +140,27 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl
} }
public beautifyCss(): void { public beautifyCss(): void {
const res = css_beautify(this.action.customCss, {indent_size: 4}); beautifyCss(this.action.customCss, {indent_size: 4}).subscribe(
if (this.action.customCss !== res) { (res) => {
this.action.customCss = res; if (this.action.customCss !== res) {
this.cssEditor.setValue(this.action.customCss ? this.action.customCss : '', -1); this.action.customCss = res;
this.notifyActionUpdated(); this.cssEditor.setValue(this.action.customCss ? this.action.customCss : '', -1);
} this.notifyActionUpdated();
}
}
);
} }
public beautifyHtml(): void { public beautifyHtml(): void {
const res = html_beautify(this.action.customHtml, {indent_size: 4, wrap_line_length: 60}); beautifyHtml(this.action.customHtml, {indent_size: 4, wrap_line_length: 60}).subscribe(
if (this.action.customHtml !== res) { (res) => {
this.action.customHtml = res; if (this.action.customHtml !== res) {
this.htmlEditor.setValue(this.action.customHtml ? this.action.customHtml : '', -1); this.action.customHtml = res;
this.notifyActionUpdated(); this.htmlEditor.setValue(this.action.customHtml ? this.action.customHtml : '', -1);
} this.notifyActionUpdated();
}
}
);
} }
private initAceEditors(): Observable<any> { 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; const analogueCompassSettingsSchemaValue = analogueCompassSettingsSchema;
// @dynamic
export class TbAnalogueCompass extends TbBaseGauge<AnalogueCompassSettings, RadialGaugeOptions> { export class TbAnalogueCompass extends TbBaseGauge<AnalogueCompassSettings, RadialGaugeOptions> {
static get settingsSchema(): JsonSettingsSchema { 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(); const analogueLinearGaugeSettingsSchemaValue = getAnalogueLinearGaugeSettingsSchema();
// @dynamic
export class TbAnalogueLinearGauge extends TbAnalogueGauge<AnalogueLinearGaugeSettings, LinearGaugeOptions>{ export class TbAnalogueLinearGauge extends TbAnalogueGauge<AnalogueLinearGaugeSettings, LinearGaugeOptions>{
static get settingsSchema(): JsonSettingsSchema { 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(); const analogueRadialGaugeSettingsSchemaValue = getAnalogueRadialGaugeSettingsSchema();
// @dynamic
export class TbAnalogueRadialGauge extends TbAnalogueGauge<AnalogueRadialGaugeSettings, RadialGaugeOptions>{ export class TbAnalogueRadialGauge extends TbAnalogueGauge<AnalogueRadialGaugeSettings, RadialGaugeOptions>{
static get settingsSchema(): JsonSettingsSchema { 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; const digitalGaugeSettingsSchemaValue = digitalGaugeSettingsSchema;
// @dynamic
export class TbCanvasDigitalGauge { export class TbCanvasDigitalGauge {
static get settingsSchema(): JsonSettingsSchema { 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(); const initSubject = new ReplaySubject();
this.init$ = initSubject.asObservable(); this.init$ = initSubject.asObservable();
(window as any).tinycolor = tinycolor; const w = (this.window as any);
(window as any).cssjs = cssjs;
(window as any).moment = moment; w.tinycolor = tinycolor;
w.cssjs = cssjs;
w.moment = moment;
w.$ = $;
w.jQuery = $;
const widgetModulesTasks: Observable<any>[] = []; 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( widgetModulesTasks.push(from(import('@home/components/widget/lib/flot-widget')).pipe(
tap((mod) => { tap((mod) => {
(window as any).TbFlot = mod.TbFlot; (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 { EntitiesTableComponent } from '../../components/entity/entities-table.component';
import { Authority } from '@shared/models/authority.enum'; import { Authority } from '@shared/models/authority.enum';
import { RuleChainsTableConfigResolver } from '@modules/home/pages/rulechain/rulechains-table-config.resolver'; 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 { BreadCrumbConfig, BreadCrumbLabelFunction } from '@shared/components/breadcrumb';
import { ResolvedRuleChainMetaData, RuleChain } from '@shared/models/rule-chain.models'; import { ResolvedRuleChainMetaData, RuleChain } from '@shared/models/rule-chain.models';
import { RuleChainService } from '@core/http/rule-chain.service'; 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() @Injectable()
export class RuleChainImportGuard implements CanActivate { export class RuleChainImportGuard implements CanActivate {
@ -144,7 +155,8 @@ const routes: Routes = [
resolve: { resolve: {
ruleChain: RuleChainResolver, ruleChain: RuleChainResolver,
ruleChainMetaData: ResolvedRuleChainMetaDataResolver, ruleChainMetaData: ResolvedRuleChainMetaDataResolver,
ruleNodeComponents: RuleNodeComponentsResolver ruleNodeComponents: RuleNodeComponentsResolver,
tooltipster: TooltipsterResolver
} }
}, },
{ {
@ -162,7 +174,8 @@ const routes: Routes = [
import: true import: true
}, },
resolve: { resolve: {
ruleNodeComponents: RuleNodeComponentsResolver ruleNodeComponents: RuleNodeComponentsResolver,
tooltipster: TooltipsterResolver
} }
} }
] ]
@ -178,6 +191,7 @@ const routes: Routes = [
RuleChainResolver, RuleChainResolver,
ResolvedRuleChainMetaDataResolver, ResolvedRuleChainMetaDataResolver,
RuleNodeComponentsResolver, RuleNodeComponentsResolver,
TooltipsterResolver,
RuleChainImportGuard 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 { getCurrentIsLoading } from '@app/core/interceptors/load.selectors';
import { Ace } from 'ace-builds'; import { Ace } from 'ace-builds';
import { getAce, Range } from '@shared/models/ace/ace.models'; 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 { CancelAnimationFrame, RafService } from '@core/services/raf.service';
import { WINDOW } from '@core/services/window.service'; import { WINDOW } from '@core/services/window.service';
import { WindowMessage } from '@shared/models/window-message.model'; 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 { widgetEditorCompleter } from '@home/pages/widget/widget-editor.models';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { map, tap } from 'rxjs/operators'; import { map, tap } from 'rxjs/operators';
import { beautifyCss, beautifyHtml, beautifyJs } from '@shared/models/beautify.models';
// @dynamic // @dynamic
@Component({ @Component({
@ -618,48 +618,63 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
} }
beautifyCss(): void { beautifyCss(): void {
const res = css_beautify(this.widget.templateCss, {indent_size: 4}); beautifyCss(this.widget.templateCss, {indent_size: 4}).subscribe(
if (this.widget.templateCss !== res) { (res) => {
this.isDirty = true; if (this.widget.templateCss !== res) {
this.widget.templateCss = res; this.isDirty = true;
this.cssEditor.setValue(this.widget.templateCss ? this.widget.templateCss : '', -1); this.widget.templateCss = res;
} this.cssEditor.setValue(this.widget.templateCss ? this.widget.templateCss : '', -1);
}
}
);
} }
beautifyHtml(): void { beautifyHtml(): void {
const res = html_beautify(this.widget.templateHtml, {indent_size: 4, wrap_line_length: 60}); beautifyHtml(this.widget.templateHtml, {indent_size: 4, wrap_line_length: 60}).subscribe(
if (this.widget.templateHtml !== res) { (res) => {
this.isDirty = true; if (this.widget.templateHtml !== res) {
this.widget.templateHtml = res; this.isDirty = true;
this.htmlEditor.setValue(this.widget.templateHtml ? this.widget.templateHtml : '', -1); this.widget.templateHtml = res;
} this.htmlEditor.setValue(this.widget.templateHtml ? this.widget.templateHtml : '', -1);
}
}
);
} }
beautifyJson(): void { beautifyJson(): void {
const res = js_beautify(this.widget.settingsSchema, {indent_size: 4}); beautifyJs(this.widget.settingsSchema, {indent_size: 4}).subscribe(
if (this.widget.settingsSchema !== res) { (res) => {
this.isDirty = true; if (this.widget.settingsSchema !== res) {
this.widget.settingsSchema = res; this.isDirty = true;
this.jsonSettingsEditor.setValue(this.widget.settingsSchema ? this.widget.settingsSchema : '', -1); this.widget.settingsSchema = res;
} this.jsonSettingsEditor.setValue(this.widget.settingsSchema ? this.widget.settingsSchema : '', -1);
}
}
);
} }
beautifyDataKeyJson(): void { beautifyDataKeyJson(): void {
const res = js_beautify(this.widget.dataKeySettingsSchema, {indent_size: 4}); beautifyJs(this.widget.dataKeySettingsSchema, {indent_size: 4}).subscribe(
if (this.widget.dataKeySettingsSchema !== res) { (res) => {
this.isDirty = true; if (this.widget.dataKeySettingsSchema !== res) {
this.widget.dataKeySettingsSchema = res; this.isDirty = true;
this.dataKeyJsonSettingsEditor.setValue(this.widget.dataKeySettingsSchema ? this.widget.dataKeySettingsSchema : '', -1); this.widget.dataKeySettingsSchema = res;
} this.dataKeyJsonSettingsEditor.setValue(this.widget.dataKeySettingsSchema ? this.widget.dataKeySettingsSchema : '', -1);
}
}
);
} }
beautifyJs(): void { beautifyJs(): void {
const res = js_beautify(this.widget.controllerScript, {indent_size: 4, wrap_line_length: 60}); beautifyJs(this.widget.controllerScript, {indent_size: 4, wrap_line_length: 60}).subscribe(
if (this.widget.controllerScript !== res) { (res) => {
this.isDirty = true; if (this.widget.controllerScript !== res) {
this.widget.controllerScript = res; this.isDirty = true;
this.jsEditor.setValue(this.widget.controllerScript ? this.widget.controllerScript : '', -1); this.widget.controllerScript = res;
} this.jsEditor.setValue(this.widget.controllerScript ? this.widget.controllerScript : '', -1);
}
}
);
} }
removeResource(index: number) { 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 { RuleChainService } from '@core/http/rule-chain.service';
import { mergeMap } from 'rxjs/operators'; import { mergeMap } from 'rxjs/operators';
import { ActionNotificationShow } from '@core/notification/notification.actions'; import { ActionNotificationShow } from '@core/notification/notification.actions';
import { beautifyJs } from '@shared/models/beautify.models';
export interface NodeScriptTestDialogData { export interface NodeScriptTestDialogData {
script: string; script: string;
@ -110,12 +111,17 @@ export class NodeScriptTestDialogComponent extends DialogComponent<NodeScriptTes
this.nodeScriptTestFormGroup = this.fb.group({ this.nodeScriptTestFormGroup = this.fb.group({
payload: this.fb.group({ payload: this.fb.group({
msgType: [this.data.msgType, [Validators.required]], 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]], metadata: [this.data.metadata, [Validators.required]],
script: [this.data.script, []], script: [this.data.script, []],
output: ['', []] output: ['', []]
}); });
beautifyJs(JSON.stringify(this.data.msg), {indent_size: 4}).subscribe(
(res) => {
this.nodeScriptTestFormGroup.get('payload').get('msg').patchValue(res, {emitEvent: false});
}
);
} }
ngAfterViewInit(): void { ngAfterViewInit(): void {
@ -166,7 +172,11 @@ export class NodeScriptTestDialogComponent extends DialogComponent<NodeScriptTes
test(): void { test(): void {
this.testNodeScript().subscribe((output) => { 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 { CancelAnimationFrame, RafService } from '@core/services/raf.service';
import { ResizeObserver } from '@juggle/resize-observer'; import { ResizeObserver } from '@juggle/resize-observer';
import { TbEditorCompleter } from '@shared/models/ace/completion.models'; import { TbEditorCompleter } from '@shared/models/ace/completion.models';
import { beautifyJs } from '@shared/models/beautify.models';
@Component({ @Component({
selector: 'tb-js-func', selector: 'tb-js-func',
@ -214,9 +215,12 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
} }
beautifyJs() { beautifyJs() {
const res = js_beautify(this.modelValue, {indent_size: 4, wrap_line_length: 60}); beautifyJs(this.modelValue, {indent_size: 4, wrap_line_length: 60}).subscribe(
this.jsEditor.setValue(res ? res : '', -1); (res) => {
this.updateView(); this.jsEditor.setValue(res ? res : '', -1);
this.updateView();
}
);
} }
validateOnSubmit(): void { 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 { guid } from '@core/utils';
import { ResizeObserver } from '@juggle/resize-observer'; import { ResizeObserver } from '@juggle/resize-observer';
import { getAce } from '@shared/models/ace/ace.models'; import { getAce } from '@shared/models/ace/ace.models';
import { beautifyJs } from '@shared/models/beautify.models';
@Component({ @Component({
selector: 'tb-json-content', selector: 'tb-json-content',
@ -286,9 +287,12 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid
} }
beautifyJSON() { beautifyJSON() {
const res = js_beautify(this.contentBody, {indent_size: 4, wrap_line_length: 60}); beautifyJs(this.contentBody, {indent_size: 4, wrap_line_length: 60}).subscribe(
this.jsonEditor.setValue(res ? res : '', -1); (res) => {
this.updateView(); this.jsonEditor.setValue(res ? res : '', -1);
this.updateView();
}
);
} }
minifyJSON() { 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 { map, mergeMap } from 'rxjs/operators';
import { loadAceDependencies } from '@shared/models/ace/ace.models'; import { loadAceDependencies } from '@shared/models/ace/ace.models';
import { from } from 'rxjs'; import { from } from 'rxjs';
import { Observable } from 'rxjs/internal/Observable';
const ReactAce = React.lazy(() => { const ReactAce = React.lazy(() => {
return loadAceDependencies().pipe( return loadAceDependencies().pipe(
@ -33,7 +34,7 @@ const ReactAce = React.lazy(() => {
interface ThingsboardAceEditorProps extends JsonFormFieldProps { interface ThingsboardAceEditorProps extends JsonFormFieldProps {
mode: string; mode: string;
onTidy: (value: string) => string; onTidy: (value: string) => Observable<string>;
} }
interface ThingsboardAceEditorState extends JsonFormFieldState { interface ThingsboardAceEditorState extends JsonFormFieldState {
@ -84,15 +85,18 @@ class ThingsboardAceEditor extends React.Component<ThingsboardAceEditorProps, Th
onTidy() { onTidy() {
if (!this.props.form.readonly) { if (!this.props.form.readonly) {
let value = this.state.value; let value = this.state.value;
value = this.props.onTidy(value); this.props.onTidy(value).subscribe(
this.setState({ (processedValue) => {
value this.setState({
}); value: processedValue
this.props.onChangeValidate({ });
target: { this.props.onChangeValidate({
value 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 * as React from 'react';
import ThingsboardAceEditor from './json-form-ace-editor'; import ThingsboardAceEditor from './json-form-ace-editor';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; 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> { class ThingsboardCss extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
@ -25,8 +26,8 @@ class ThingsboardCss extends React.Component<JsonFormFieldProps, JsonFormFieldSt
this.onTidyCss = this.onTidyCss.bind(this); this.onTidyCss = this.onTidyCss.bind(this);
} }
onTidyCss(css: string): string { onTidyCss(css: string): Observable<string> {
return css_beautify(css, {indent_size: 4}); return beautifyCss(css, {indent_size: 4});
} }
render() { 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 * as React from 'react';
import ThingsboardAceEditor from './json-form-ace-editor'; 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 { 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> { class ThingsboardHtml extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
@ -25,8 +26,8 @@ class ThingsboardHtml extends React.Component<JsonFormFieldProps, JsonFormFieldS
this.onTidyHtml = this.onTidyHtml.bind(this); this.onTidyHtml = this.onTidyHtml.bind(this);
} }
onTidyHtml(html: string): string { onTidyHtml(html: string): Observable<string> {
return html_beautify(html, {indent_size: 4}); return beautifyHtml(html, {indent_size: 4});
} }
render() { 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 * as React from 'react';
import ThingsboardAceEditor from './json-form-ace-editor'; import ThingsboardAceEditor from './json-form-ace-editor';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; 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> { class ThingsboardJavaScript extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
@ -24,8 +26,8 @@ class ThingsboardJavaScript extends React.Component<JsonFormFieldProps, JsonForm
this.onTidyJavascript = this.onTidyJavascript.bind(this); this.onTidyJavascript = this.onTidyJavascript.bind(this);
} }
onTidyJavascript(javascript: string): string { onTidyJavascript(javascript: string): Observable<string> {
return js_beautify(javascript, {indent_size: 4, wrap_line_length: 60}); return beautifyJs(javascript, {indent_size: 4, wrap_line_length: 60});
} }
render() { 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 * as React from 'react';
import ThingsboardAceEditor from './json-form-ace-editor'; import ThingsboardAceEditor from './json-form-ace-editor';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; 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> { class ThingsboardJson extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
@ -24,8 +26,8 @@ class ThingsboardJson extends React.Component<JsonFormFieldProps, JsonFormFieldS
this.onTidyJson = this.onTidyJson.bind(this); this.onTidyJson = this.onTidyJson.bind(this);
} }
onTidyJson(json: string): string { onTidyJson(json: string): Observable<string> {
return js_beautify(json, {indent_size: 4}); return beautifyJs(json, {indent_size: 4});
} }
render() { 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) => { this.treeElement = $('.tb-nav-tree-container', this.elementRef.nativeElement).jstree(config);
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));
}
});
if (this.editCallbacks) { this.treeElement.on('changed.jstree', (e: any, data) => {
this.editCallbacks.selectNode = id => { const node: NavTreeNode = data.instance.get_selected(true)[0];
const node: NavTreeNode = this.treeElement.jstree('get_node', id); if (this.onNodeSelected) {
if (node) { this.ngZone.run(() => this.onNodeSelected(node, e as Event));
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.editCallbacks.openNode = (id, cb) => {
const node: NavTreeNode = this.treeElement.jstree('get_node', id); this.treeElement.on('model.jstree', (e: any, data) => {
if (node) { if (this.onNodesInserted) {
this.treeElement.jstree('open_node', node, cb); this.ngZone.run(() => this.onNodesInserted(data.nodes, data.parent));
}
};
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 === '#') { if (this.editCallbacks) {
this.treeElement.jstree('refresh'); this.editCallbacks.selectNode = id => {
this.treeElement.jstree('redraw'); const node: NavTreeNode = this.treeElement.jstree('get_node', id);
} else { 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); const node: NavTreeNode = this.treeElement.jstree('get_node', id);
if (node) { if (node) {
const opened = this.treeElement.jstree('is_open', node); return this.treeElement.jstree('get_parent', node);
this.treeElement.jstree('refresh_node', 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'); this.treeElement.jstree('redraw');
if (node.children && opened/* && !node.children.length*/) { } else {
this.treeElement.jstree('open_node', node); 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) => {
this.editCallbacks.updateNode = (id, newName) => { const node: NavTreeNode = this.treeElement.jstree('get_node', id);
const node: NavTreeNode = this.treeElement.jstree('get_node', id); if (node) {
if (node) { this.treeElement.jstree('rename_node', node, newName);
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.createNode = (parentId, node, pos) => {
this.editCallbacks.search = (searchText) => { const parentNode: NavTreeNode = this.treeElement.jstree('get_node', parentId);
this.treeElement.jstree('search', searchText); if (parentNode) {
}; this.treeElement.jstree('create_node', parentNode, node, pos);
this.editCallbacks.clearSearch = () => { }
this.treeElement.jstree('clear_search'); };
}; 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 { AfterViewInit, Component, forwardRef, Input, NgZone, OnInit, ViewChild } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; 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 { map, mergeMap, share, tap } from 'rxjs/operators';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state'; import { AppState } from '@app/core/core.state';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { coerceBooleanProperty } from '@angular/cdk/coercion';
import * as _moment from 'moment-timezone';
import { MatAutocompleteTrigger } from '@angular/material/autocomplete'; import { MatAutocompleteTrigger } from '@angular/material/autocomplete';
import { getTimezoneInfo, getTimezones, TimezoneInfo } from '@shared/models/time/time.models';
interface TimezoneInfo {
id: string;
name: string;
offset: string;
nOffset: number;
}
@Component({ @Component({
selector: 'tb-timezone-select', selector: 'tb-timezone-select',
@ -50,28 +43,14 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
defaultTimezoneId: string = null; defaultTimezoneId: string = null;
defaultTimezoneInfo: TimezoneInfo = null; timezones$ = getTimezones().pipe(
share()
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()
}
});
@Input() @Input()
set defaultTimezone(timezone: string) { set defaultTimezone(timezone: string) {
if (this.defaultTimezoneId !== timezone) { if (this.defaultTimezoneId !== timezone) {
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); this.requiredValue = coerceBooleanProperty(value);
} }
private userTimezoneByDefaultValue: boolean;
get userTimezoneByDefault(): boolean {
return this.userTimezoneByDefaultValue;
}
@Input()
set userTimezoneByDefault(value: boolean) {
this.userTimezoneByDefaultValue = coerceBooleanProperty(value);
}
@Input() @Input()
disabled: boolean; disabled: boolean;
@ -150,24 +138,23 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
writeValue(value: string | null): void { writeValue(value: string | null): void {
this.searchText = ''; this.searchText = '';
let foundTimezone: TimezoneInfo = null; getTimezoneInfo(value, this.defaultTimezoneId, this.userTimezoneByDefaultValue).subscribe(
if (value !== null) { (foundTimezone) => {
foundTimezone = this.timezones.find(timezoneInfo => timezoneInfo.id === value); if (foundTimezone !== null) {
} this.selectTimezoneFormGroup.get('timezone').patchValue(foundTimezone, {emitEvent: false});
if (foundTimezone !== null) { if (foundTimezone.id !== value) {
this.modelValue = value; setTimeout(() => {
this.selectTimezoneFormGroup.get('timezone').patchValue(foundTimezone, {emitEvent: false}); this.updateView(foundTimezone.id);
} else { }, 0);
if (this.defaultTimezoneInfo) { } else {
this.selectTimezoneFormGroup.get('timezone').patchValue(this.defaultTimezoneInfo, {emitEvent: false}); this.modelValue = value;
setTimeout(() => { }
this.updateView(this.defaultTimezoneInfo.id); } else {
}, 0); this.modelValue = null;
} else { this.selectTimezoneFormGroup.get('timezone').patchValue('', {emitEvent: false});
this.modelValue = null; }
this.selectTimezoneFormGroup.get('timezone').patchValue('', {emitEvent: false});
} }
} );
this.dirty = true; this.dirty = true;
} }
@ -182,10 +169,15 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
if (this.ignoreClosePanel) { if (this.ignoreClosePanel) {
this.ignoreClosePanel = false; this.ignoreClosePanel = false;
} else { } else {
if (!this.modelValue && this.defaultTimezoneInfo) { if (!this.modelValue && (this.defaultTimezoneId || this.userTimezoneByDefaultValue)) {
this.ngZone.run(() => { getTimezoneInfo(this.defaultTimezoneId, this.defaultTimezoneId, this.userTimezoneByDefaultValue).subscribe(
this.selectTimezoneFormGroup.get('timezone').reset(this.defaultTimezoneInfo, {emitEvent: true}); (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>> { fetchTimezones(searchText?: string): Observable<Array<TimezoneInfo>> {
this.searchText = searchText; this.searchText = searchText;
let result = this.timezones;
if (searchText && searchText.length) { if (searchText && searchText.length) {
result = this.timezones.filter((timezoneInfo) => return getTimezones().pipe(
timezoneInfo.name.toLowerCase().includes(searchText.toLowerCase())); map((timezones) => timezones.filter((timezoneInfo) =>
timezoneInfo.name.toLowerCase().includes(searchText.toLowerCase())))
);
} }
return of(result); return getTimezones();
} }
clear() { 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 { EntityInfoData } from '@shared/models/entity.models';
import { KeyFilter } from '@shared/models/query/query.models'; import { KeyFilter } from '@shared/models/query/query.models';
import { TimeUnit } from '@shared/models/time/time.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'; import { AbstractControl, ValidationErrors } from '@angular/forms';
export enum DeviceProfileType { 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 { TimeService } from '@core/services/time.service';
import { deepClone, isDefined, isUndefined } from '@app/core/utils'; import { deepClone, isDefined, isUndefined } from '@app/core/utils';
import * as moment_ from 'moment'; 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_; const moment = moment_;
@ -481,3 +484,71 @@ export const timeUnitTranslationMap = new Map<TimeUnit, string>(
[TimeUnit.DAYS, 'timeunit.days'] [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": [ "ace": [
"node_modules/ace-builds/src-noconflict/ace.js" "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": [ "lib": [

Loading…
Cancel
Save