|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
@ -0,0 +1,65 @@ |
|||
/** |
|||
* Copyright © 2016-2024 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.server.service.update; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.rule.engine.api.NotificationCenter; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.notification.info.GeneralNotificationInfo; |
|||
import org.thingsboard.server.common.data.notification.targets.platform.SystemAdministratorsFilter; |
|||
import org.thingsboard.server.dao.notification.DefaultNotifications; |
|||
import org.thingsboard.server.queue.util.AfterStartUp; |
|||
|
|||
import java.util.Map; |
|||
|
|||
@Service |
|||
@Slf4j |
|||
@RequiredArgsConstructor |
|||
public class DeprecationService { |
|||
|
|||
private final NotificationCenter notificationCenter; |
|||
|
|||
@Value("${queue.type}") |
|||
private String queueType; |
|||
|
|||
@AfterStartUp(order = Integer.MAX_VALUE) |
|||
public void checkDeprecation() { |
|||
checkQueueTypeDeprecation(); |
|||
} |
|||
|
|||
private void checkQueueTypeDeprecation() { |
|||
String queueTypeName; |
|||
switch (queueType) { |
|||
case "aws-sqs" -> queueTypeName = "AWS SQS"; |
|||
case "pubsub" -> queueTypeName = "PubSub"; |
|||
case "service-bus" -> queueTypeName = "Azure Service Bus"; |
|||
case "rabbitmq" -> queueTypeName = "RabbitMQ"; |
|||
default -> { |
|||
return; |
|||
} |
|||
} |
|||
|
|||
log.warn("WARNING: {} queue type is deprecated and will be removed in ThingsBoard 4.0. Please migrate to Apache Kafka", queueTypeName); |
|||
notificationCenter.sendGeneralWebNotification(TenantId.SYS_TENANT_ID, new SystemAdministratorsFilter(), |
|||
DefaultNotifications.queueTypeDeprecation.toTemplate(), new GeneralNotificationInfo(Map.of( |
|||
"queueType", queueTypeName |
|||
))); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
/** |
|||
* Copyright © 2016-2024 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.server.common.data.notification.info; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
|
|||
import java.util.Map; |
|||
|
|||
@Data |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
public class GeneralNotificationInfo implements RuleOriginatedNotificationInfo { |
|||
|
|||
private Map<String, String> data; |
|||
|
|||
@Override |
|||
public Map<String, String> getTemplateData() { |
|||
return data; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,104 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 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 type { Plugin, PluginBuild, OutputFile } from 'esbuild'; |
|||
import dirTree from 'directory-tree'; |
|||
import * as packageJson from '../package.json'; |
|||
import { gzip } from 'node:zlib'; |
|||
import * as path from 'node:path'; |
|||
|
|||
const defineTbVariablesPlugin: Plugin = { |
|||
name: 'tb-define-variables', |
|||
setup(build: PluginBuild) { |
|||
const options = build.initialOptions; |
|||
|
|||
const langs: string[] = []; |
|||
|
|||
dirTree("./src/assets/locale/", {extensions: /\.json$/}, (item) => { |
|||
/* It is expected what the name of a locale file has the following format: */ |
|||
/* 'locale.constant-LANG_CODE[_REGION_CODE].json', e.g. locale.constant-es.json or locale.constant-zh_CN.json*/ |
|||
langs.push(item.name.slice(item.name.lastIndexOf("-") + 1, -5)); |
|||
}); |
|||
options.define.TB_VERSION = JSON.stringify(packageJson.version); |
|||
options.define.SUPPORTED_LANGS = JSON.stringify(langs); |
|||
options.define.ngJitMode = 'true'; |
|||
}, |
|||
}; |
|||
|
|||
const resolveJQueryPlugin: Plugin = { |
|||
name: 'tb-resolve-jquery-plugin', |
|||
setup(build: PluginBuild) { |
|||
if (isProduction()) { |
|||
const jQueryPath = require.resolve('jquery'); |
|||
build.onResolve({filter: /^(jquery|\$)$/}, () => { |
|||
return {path: jQueryPath}; |
|||
}) |
|||
} |
|||
} |
|||
}; |
|||
|
|||
const compressFileTypes = ['.js', '.css', '.html', '.svg', '.png', '.jpg', '.ttf', '.gif', '.woff', '.woff2', '.eot', '.json']; |
|||
const compressThreshold = 10240; |
|||
|
|||
const compressorPlugin: Plugin = { |
|||
name: 'tb-compressor-plugin', |
|||
setup(build) { |
|||
build.onEnd(async result => { |
|||
if (!result.outputFiles || !isProduction()) return; |
|||
const outputExt = '.gz'; |
|||
const gzippedFiles: OutputFile[] = []; |
|||
for (const file of result.outputFiles) { |
|||
if (!compressFileTypes.some((ext) => ext === path.extname(file.path))) continue; |
|||
if (file.contents.byteLength <= compressThreshold) continue; |
|||
const compressedContent = await gzipContent(file.contents); |
|||
const compressedFilePath = `${file.path}${outputExt}`; |
|||
gzippedFiles.push( |
|||
{ |
|||
path: compressedFilePath, |
|||
hash: file.hash, |
|||
contents: new Uint8Array(compressedContent), |
|||
text: '', |
|||
} |
|||
); |
|||
} |
|||
result.outputFiles.push(...gzippedFiles); |
|||
}); |
|||
}, |
|||
}; |
|||
|
|||
async function gzipContent(content): Promise<Buffer> { |
|||
return new Promise((resolve, reject) => { |
|||
gzip(content, (error, result) => { |
|||
if (error) { |
|||
reject(error); |
|||
} else { |
|||
resolve(result); |
|||
} |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
function isProduction(): boolean { |
|||
const configurationIndex = process.argv.indexOf('--configuration'); |
|||
let production = false; |
|||
if (configurationIndex > -1) { |
|||
const configurationValue = process.argv[configurationIndex + 1]; |
|||
production = configurationValue === 'production'; |
|||
} |
|||
return production; |
|||
} |
|||
|
|||
export default [defineTbVariablesPlugin, resolveJQueryPlugin, compressorPlugin]; |
|||
@ -1,100 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2024 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. |
|||
*/ |
|||
const CompressionPlugin = require("compression-webpack-plugin"); |
|||
const JavaScriptOptimizerPlugin = require("@angular-devkit/build-angular/src/tools/webpack/plugins/javascript-optimizer-plugin").JavaScriptOptimizerPlugin; |
|||
const webpack = require("webpack"); |
|||
const dirTree = require("directory-tree"); |
|||
const ngWebpack = require('@ngtools/webpack'); |
|||
const keysTransformer = require('ts-transformer-keys/transformer').default; |
|||
|
|||
var langs = []; |
|||
|
|||
dirTree("./src/assets/locale/", {extensions: /\.json$/}, (item) => { |
|||
/* It is expected what the name of a locale file has the following format: */ |
|||
/* 'locale.constant-LANG_CODE[_REGION_CODE].json', e.g. locale.constant-es.json or locale.constant-zh_CN.json*/ |
|||
langs.push(item.name.slice(item.name.lastIndexOf("-") + 1, -5)); |
|||
}); |
|||
|
|||
module.exports = (config, options) => { |
|||
|
|||
config.ignoreWarnings.push(/Usage of '~' in imports is deprecated/); |
|||
config.ignoreWarnings.push(/Did you mean "left" instead?/); |
|||
config.ignoreWarnings.push(/autoprefixer/); |
|||
|
|||
config.plugins.push( |
|||
new webpack.DefinePlugin({ |
|||
TB_VERSION: JSON.stringify(require("./package.json").version), |
|||
SUPPORTED_LANGS: JSON.stringify(langs), |
|||
}) |
|||
); |
|||
config.plugins.push( |
|||
new webpack.ProvidePlugin( |
|||
{ |
|||
$: "jquery" |
|||
} |
|||
) |
|||
); |
|||
config.plugins.push( |
|||
new CompressionPlugin({ |
|||
filename: "[path][base].gz[query]", |
|||
algorithm: "gzip", |
|||
test: /\.js$|\.css$|\.html$|\.svg?.+$|\.jpg$|\.ttf?.+$|\.woff?.+$|\.eot?.+$|\.json$/, |
|||
threshold: 10240, |
|||
minRatio: 0.8, |
|||
deleteOriginalAssets: false, |
|||
}) |
|||
); |
|||
config.plugins.push( |
|||
new webpack.IgnorePlugin({ |
|||
resourceRegExp: /^\.\/locale$/, |
|||
contextRegExp: /moment$/, |
|||
}) |
|||
); |
|||
|
|||
config.module.rules[2].use[0].options.aot = false; |
|||
const index = config.plugins.findIndex(p => p instanceof ngWebpack.AngularWebpackPlugin); |
|||
let angularWebpackPlugin = config.plugins[index]; |
|||
if (config.mode === 'production') { |
|||
const angularCompilerOptions = angularWebpackPlugin.pluginOptions; |
|||
angularCompilerOptions.emitClassMetadata = true; |
|||
angularCompilerOptions.emitNgModuleScope = true; |
|||
config.plugins.splice(index, 1); |
|||
angularWebpackPlugin = new ngWebpack.AngularWebpackPlugin(angularCompilerOptions); |
|||
config.plugins.push(angularWebpackPlugin); |
|||
const javascriptOptimizerOptions = config.optimization.minimizer[0].options; |
|||
delete javascriptOptimizerOptions.define.ngJitMode; |
|||
config.optimization.minimizer.splice(0, 1); |
|||
config.optimization.minimizer.unshift(new JavaScriptOptimizerPlugin(javascriptOptimizerOptions)); |
|||
} |
|||
|
|||
addTransformerToAngularWebpackPlugin(angularWebpackPlugin, keysTransformer); |
|||
|
|||
return config; |
|||
}; |
|||
|
|||
function addTransformerToAngularWebpackPlugin(plugin, transformer) { |
|||
const originalCreateFileEmitter = plugin.createFileEmitter; // private method
|
|||
plugin.createFileEmitter = function (program, transformers, getExtraDependencies, onAfterEmit) { |
|||
if (!transformers) { |
|||
transformers = {}; |
|||
} |
|||
if (!transformers.before) { |
|||
transformers = { before: [] }; |
|||
} |
|||
transformers.before.push(transformer(program.getProgram())); |
|||
return originalCreateFileEmitter.apply(plugin, [program, transformers, getExtraDependencies, onAfterEmit]); |
|||
}; |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
diff --git a/node_modules/@angular/build/src/tools/angular/compilation/angular-compilation.js b/node_modules/@angular/build/src/tools/angular/compilation/angular-compilation.js
|
|||
index 625c621..4fc8bd8 100755
|
|||
--- a/node_modules/@angular/build/src/tools/angular/compilation/angular-compilation.js
|
|||
+++ b/node_modules/@angular/build/src/tools/angular/compilation/angular-compilation.js
|
|||
@@ -68,8 +68,6 @@ class AngularCompilation {
|
|||
allowEmptyCodegenFiles: false, |
|||
annotationsAs: 'decorators', |
|||
enableResourceInlining: false, |
|||
- supportTestBed: false,
|
|||
- supportJitMode: false,
|
|||
})); |
|||
} |
|||
async diagnoseFiles(modes = DiagnosticModes.All) { |
|||
diff --git a/node_modules/@angular/build/src/tools/esbuild/angular/compiler-plugin.js b/node_modules/@angular/build/src/tools/esbuild/angular/compiler-plugin.js
|
|||
index b1bb6ea..c76b4c6 100755
|
|||
--- a/node_modules/@angular/build/src/tools/esbuild/angular/compiler-plugin.js
|
|||
+++ b/node_modules/@angular/build/src/tools/esbuild/angular/compiler-plugin.js
|
|||
@@ -79,7 +79,7 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
|
|||
sourcemap: !!pluginOptions.sourcemap, |
|||
thirdPartySourcemaps: pluginOptions.thirdPartySourcemaps, |
|||
advancedOptimizations: pluginOptions.advancedOptimizations, |
|||
- jit: pluginOptions.jit,
|
|||
+ jit: true, // pluginOptions.jit,
|
|||
}, environment_options_1.maxWorkers, cacheStore?.createCache('jstransformer')); |
|||
// Setup defines based on the values used by the Angular compiler-cli |
|||
build.initialOptions.define ??= {}; |
|||
@@ -377,12 +377,14 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
|
|||
async function hasSideEffects(path) { |
|||
if (!pluginOptions.advancedOptimizations) { |
|||
return undefined; |
|||
+ } else {
|
|||
+ return true;
|
|||
} |
|||
- const { sideEffects } = await build.resolve(path, {
|
|||
+ /*const { sideEffects } = await build.resolve(path, {
|
|||
kind: 'import-statement', |
|||
resolveDir: build.initialOptions.absWorkingDir ?? '', |
|||
}); |
|||
- return sideEffects;
|
|||
+ return sideEffects;*/
|
|||
} |
|||
}, |
|||
}; |
|||
@ -1,23 +0,0 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 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 { Inject, Type } from '@angular/core'; |
|||
|
|||
export function TbInject<T>(token: any): (target: Type<T>, key: any, paramIndex: number) => void { |
|||
return (target: Type<T>, key: any, paramIndex: number) => { |
|||
Inject(token)(target, key, paramIndex); |
|||
}; |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
### Address filter field |
|||
|
|||
It is used to filter the allowed IP addresses to connect to the connector. |
|||
|
|||
### **Examples of IP addresses filtering** |
|||
|
|||
Let’s review more examples of IP addresses filtering: |
|||
|
|||
For example, we have a device that has the following IP address: 192.168.0.120:5001. Now let's look at examples of the configuration of the field to allow the connection of different variants of IP addresses: |
|||
|
|||
1. Only one device with a specified IP address and port can connect: |
|||
|
|||
**Address filter**: 192.168.0.120:5001 |
|||
2. Allow any devices with any IP address and only port 5001: |
|||
|
|||
**Address filter:** *:5001 |
|||
|
|||
3. Allow all devices that have the IP address 192.168.0.120 with any port: |
|||
|
|||
**Address filter:** 192.168.0.120:* |
|||
|
|||
4. Allow any devices: |
|||
|
|||
**Address filter:** *:* |
|||
@ -0,0 +1,21 @@ |
|||
### **Attribute name expression field** |
|||
|
|||
The expression that is used to get the name of the requested attribute from the received data. |
|||
|
|||
### **Examples of data converting** |
|||
|
|||
Let’s review example of data converting: |
|||
|
|||
We have a device that measures temperature and humidity. And for example, you want to send a request for a shared attribute that stores the firmware version of the device. To do this, we need to specify exactly where in the message the attribute name is located, the value of which we want to get. |
|||
|
|||
In our case, let the requested attribute name is “**FirmwareVersion**”. |
|||
|
|||
Accordingly, the field will contain the following value: |
|||
|
|||
`[16:]` |
|||
|
|||
And if the device sends a message with the following payload: |
|||
|
|||
`myShrAttrRequestFirmwareVersion` |
|||
|
|||
The connector, according to the configuration above, will take bytes starting from **16** to the **end** (from **0** to **16** is the “**Request expression**”) and send a request to the platform for the dispersed name of the shared attribute. |
|||
@ -0,0 +1,35 @@ |
|||
## Byte field |
|||
|
|||
The byte field is used to slice received data from the specific index. |
|||
|
|||
### Examples of data converting |
|||
|
|||
Let’s review more examples of data converting: |
|||
We have a device that measures temperature and humidity. Device has charasteristic that can be |
|||
read and when we receive data from her, the data combine temperature and humidity. So, data |
|||
from device have the next view:b’\x08<\x08\x00’ and in human readable format:[8, 34](first array |
|||
element is temperature and the second is humidity). |
|||
|
|||
1. We want to read only temperature value |
|||
|
|||
**“Bytes from”: “0”** |
|||
|
|||
**“Bytes to”: “1”** |
|||
|
|||
Data to platform: **8** |
|||
|
|||
2. We want to read only humidity value |
|||
|
|||
**“Bytes from”: “1”** |
|||
|
|||
**“Bytes to”: “-1”** |
|||
|
|||
Data to platform: **34** |
|||
|
|||
3. We want to read all values |
|||
|
|||
**“Bytes from”: “0”** |
|||
|
|||
**“Bytes to”: “-1”** |
|||
|
|||
Data to platform:**834** |
|||
@ -0,0 +1,21 @@ |
|||
### **Request expression field** |
|||
|
|||
The expression that is used to know if the request from the device is “**Attribute Request**” or not. |
|||
|
|||
### **Examples of data converting** |
|||
|
|||
Let’s review example of data converting: |
|||
|
|||
We have a device that measures temperature and humidity. And for example, you want to send a request for a shared attribute that stores the firmware version of the device. In order for the connector to understand that the received message refers to "**Attribute request**" and not telemetry type, we must specify in the configuration what the message should begin with. |
|||
|
|||
In our case, let the beginning of the message contain “**myShrAttrRequest**”. |
|||
|
|||
Accordingly, the field will contain the following value: |
|||
|
|||
`${[0:16]==myShrAttrRequest}` |
|||
|
|||
And if the device sends a message with the following payload: |
|||
|
|||
`myShrAttrRequestFirmwareVersion` |
|||
|
|||
The connector will take the specified range from 0 to 16 bytes and see that this message belongs to the “**Attribute request**” type. |
|||