Merge pull request #5117 from node-red/5102-telemetry
Add update notification
@@ -1280,5 +1280,15 @@
|
||||
"environment": "Environment",
|
||||
"header": "Global Environment Variables",
|
||||
"revert": "Revert"
|
||||
},
|
||||
"telemetry": {
|
||||
"label": "Update Notifications",
|
||||
"settingsTitle": "Enable Update Notifications",
|
||||
"settingsDescription": "<p>Node-RED can notify you when there is a new version available. This ensures you keep up to date with the latest features and fixes.</p><p>This requires sending anonymised data back to the Node-RED team. It does not include any details of your flows or users.</p><p>For full information on what information is collected and how it is used, please see the <a href=\"https://nodered.org/docs/telemetry\" target=\"_blank\">documentation</a>.</p>",
|
||||
"settingsDescription2": "<p>You can change this setting at any time in the editor settings.</p>",
|
||||
"enableLabel": "Yes, enable notifications",
|
||||
"disableLabel": "No, do not enable notifications",
|
||||
"updateAvailable": "Update available",
|
||||
"updateAvailableDesc": "Node-RED __version__ is now available"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,7 +358,10 @@ var RED = (function() {
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (notificationId === "update-available") {
|
||||
// re-emit as an event to be handled in editor-client/src/js/ui/palette-editor.js
|
||||
RED.events.emit("notification/update-available", msg)
|
||||
}
|
||||
if (msg.text) {
|
||||
msg.default = msg.text;
|
||||
var text = RED._(msg.text,msg);
|
||||
@@ -672,14 +675,48 @@ var RED = (function() {
|
||||
|
||||
setTimeout(function() {
|
||||
loader.end();
|
||||
checkFirstRun(function() {
|
||||
if (showProjectWelcome) {
|
||||
RED.projects.showStartup();
|
||||
}
|
||||
});
|
||||
checkTelemetry(function () {
|
||||
checkFirstRun(function() {
|
||||
if (showProjectWelcome) {
|
||||
RED.projects.showStartup();
|
||||
}
|
||||
});
|
||||
})
|
||||
},100);
|
||||
}
|
||||
|
||||
function checkTelemetry(done) {
|
||||
const telemetrySettings = RED.settings.telemetryEnabled;
|
||||
// Can only get telemetry permission from a user with permission to modify settings
|
||||
if (RED.user.hasPermission("settings.write") && telemetrySettings === undefined) {
|
||||
|
||||
const dialog = RED.popover.dialog({
|
||||
title: RED._("telemetry.settingsTitle"),
|
||||
content: `${RED._("telemetry.settingsDescription")}${RED._("telemetry.settingsDescription2")}`,
|
||||
closeButton: false,
|
||||
buttons: [
|
||||
{
|
||||
text: RED._("telemetry.enableLabel"),
|
||||
click: () => {
|
||||
RED.settings.set("telemetryEnabled", true)
|
||||
dialog.close()
|
||||
done()
|
||||
}
|
||||
},
|
||||
{
|
||||
text: RED._("telemetry.disableLabel"),
|
||||
click: () => {
|
||||
RED.settings.set("telemetryEnabled", false)
|
||||
dialog.close()
|
||||
done()
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
} else {
|
||||
done()
|
||||
}
|
||||
}
|
||||
function checkFirstRun(done) {
|
||||
if (RED.settings.theme("tours") === false) {
|
||||
done();
|
||||
|
||||
@@ -163,13 +163,18 @@ RED.popover = (function() {
|
||||
}
|
||||
|
||||
var timer = null;
|
||||
let isOpen = false
|
||||
var active;
|
||||
var div;
|
||||
var contentDiv;
|
||||
var currentStyle;
|
||||
|
||||
var openPopup = function(instant) {
|
||||
if (isOpen) {
|
||||
return
|
||||
}
|
||||
if (active) {
|
||||
isOpen = true
|
||||
var existingPopover = target.data("red-ui-popover");
|
||||
if (options.tooltip && existingPopover) {
|
||||
active = false;
|
||||
@@ -334,6 +339,7 @@ RED.popover = (function() {
|
||||
|
||||
}
|
||||
var closePopup = function(instant) {
|
||||
isOpen = false
|
||||
$(document).off('mousedown.red-ui-popover');
|
||||
if (!active) {
|
||||
if (div) {
|
||||
@@ -673,6 +679,74 @@ RED.popover = (function() {
|
||||
show:show,
|
||||
hide:hide
|
||||
}
|
||||
},
|
||||
dialog: function(options) {
|
||||
|
||||
const dialogContent = $('<div style="position:relative"></div>');
|
||||
|
||||
if (options.closeButton !== false) {
|
||||
$('<button type="button" class="red-ui-button red-ui-button-small" style="float: right; margin-top: -4px; margin-right: -4px;"><i class="fa fa-times"></i></button>').appendTo(dialogContent).click(function(evt) {
|
||||
evt.preventDefault();
|
||||
close();
|
||||
})
|
||||
}
|
||||
|
||||
const dialogBody = $('<div class="red-ui-dialog-body"></div>').appendTo(dialogContent);
|
||||
if (options.title) {
|
||||
$('<h2>').text(options.title).appendTo(dialogBody);
|
||||
}
|
||||
$('<div>').css("text-align","left").html(options.content).appendTo(dialogBody);
|
||||
|
||||
const stepToolbar = $('<div>',{class:"red-ui-dialog-toolbar"}).appendTo(dialogContent);
|
||||
|
||||
if (options.buttons) {
|
||||
options.buttons.forEach(button => {
|
||||
const btn = $('<button type="button" class="red-ui-button"></button>').text(button.text).appendTo(stepToolbar);
|
||||
if (button.class) {
|
||||
btn.addClass(button.class);
|
||||
}
|
||||
if (button.click) {
|
||||
btn.on('click', function(evt) {
|
||||
evt.preventDefault();
|
||||
button.click();
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
const width = 500;
|
||||
const maxWidth = Math.min($(window).width()-10,Math.max(width || 0, 300));
|
||||
|
||||
let shade = $('<div class="red-ui-shade" style="z-index: 2000"></div>').appendTo(document.body);
|
||||
shade.fadeIn()
|
||||
|
||||
let popover = RED.popover.create({
|
||||
target: $(".red-ui-editor"),
|
||||
width: width || "auto",
|
||||
maxWidth: maxWidth+"px",
|
||||
direction: "inset",
|
||||
class: "red-ui-dialog",
|
||||
trigger: "manual",
|
||||
content: dialogContent
|
||||
}).open()
|
||||
|
||||
function close() {
|
||||
if (shade) {
|
||||
shade.fadeOut(() => {
|
||||
shade.remove()
|
||||
shade = null
|
||||
})
|
||||
}
|
||||
if (popover) {
|
||||
popover.close()
|
||||
popover = null
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
close
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -815,6 +815,14 @@ RED.palette.editor = (function() {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
RED.events.on("notification/update-available", function (msg) {
|
||||
const updateKnownAbout = updateStatusState.version === msg.version
|
||||
updateStatusState.version = msg.version
|
||||
if (updateStatusWidgetPopover && !updateKnownAbout) {
|
||||
setTimeout(() => { updateStatusWidgetPopover.open(); setTimeout(() => updateStatusWidgetPopover.close(), 20000) }, 1000)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getSettingsPane() {
|
||||
@@ -1604,28 +1612,43 @@ RED.palette.editor = (function() {
|
||||
}
|
||||
|
||||
const updateStatusWidget = $('<button type="button" class="red-ui-footer-button red-ui-update-status"></button>');
|
||||
let updateStatusWidgetPopover;
|
||||
const updateStatusState = { moduleCount: 0 }
|
||||
let updateAvailable = [];
|
||||
|
||||
function addUpdateInfoToStatusBar() {
|
||||
updateStatusWidget.on("click", function (evt) {
|
||||
RED.actions.invoke("core:manage-palette", {
|
||||
view: "nodes",
|
||||
filter: '"' + updateAvailable.join('", "') + '"'
|
||||
});
|
||||
});
|
||||
|
||||
RED.popover.tooltip(updateStatusWidget, function () {
|
||||
const count = updateAvailable.length || 0;
|
||||
return RED._("palette.editor.updateCount", { count: count });
|
||||
updateStatusWidgetPopover = RED.popover.create({
|
||||
target: updateStatusWidget,
|
||||
trigger: "click",
|
||||
interactive: true,
|
||||
direction: "bottom",
|
||||
content: function () {
|
||||
const count = updateAvailable.length || 0;
|
||||
const content = $('<div style="display: flex; flex-direction: column; gap: 5px;"></div>');
|
||||
if (updateStatusState.version) {
|
||||
$(`<a class='red-ui-button' href="https://github.com/node-red/node-red/releases/tag/${updateStatusState.version}" target="_blank">${RED._("telemetry.updateAvailableDesc", updateStatusState)}</a>`).appendTo(content)
|
||||
}
|
||||
if (count > 0) {
|
||||
$(`<button type="button" class="red-ui-button"><i class="fa fa-cube"></i> ${RED._("palette.editor.updateCount", { count: count })}</button>`).on("click", function (evt) {
|
||||
updateStatusWidgetPopover.close()
|
||||
RED.actions.invoke("core:manage-palette", {
|
||||
view: "nodes",
|
||||
filter: '"' + updateAvailable.join('", "') + '"'
|
||||
});
|
||||
}).appendTo(content)
|
||||
}
|
||||
return content
|
||||
},
|
||||
delay: { show: 750, hide: 250 }
|
||||
});
|
||||
|
||||
RED.statusBar.add({
|
||||
id: "update",
|
||||
id: "red-ui-status-package-update",
|
||||
align: "right",
|
||||
element: updateStatusWidget
|
||||
});
|
||||
|
||||
updateStatus({ count: 0 });
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
let pendingRefreshTimeout
|
||||
@@ -1648,18 +1671,22 @@ RED.palette.editor = (function() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateStatus({ count: updateAvailable.length });
|
||||
updateStatusState.moduleCount = updateAvailable.length;
|
||||
updateStatus();
|
||||
}, 200)
|
||||
}
|
||||
|
||||
function updateStatus(opts) {
|
||||
if (opts.count) {
|
||||
RED.statusBar.show("update");
|
||||
function updateStatus() {
|
||||
if (updateStatusState.moduleCount || updateStatusState.version) {
|
||||
updateStatusWidget.empty();
|
||||
$('<span><i class="fa fa-cube"></i> ' + opts.count + '</span>').appendTo(updateStatusWidget);
|
||||
let count = updateStatusState.moduleCount || 0;
|
||||
if (updateStatusState.version) {
|
||||
count ++
|
||||
}
|
||||
$(`<span><i class="fa fa-cube"></i> ${RED._("telemetry.updateAvailable", { count: count })}</span>`).appendTo(updateStatusWidget);
|
||||
RED.statusBar.show("red-ui-status-package-update");
|
||||
} else {
|
||||
RED.statusBar.hide("update");
|
||||
RED.statusBar.hide("red-ui-status-package-update");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -435,10 +435,15 @@ RED.tourGuide = (function() {
|
||||
|
||||
function listTour() {
|
||||
return [
|
||||
{
|
||||
id: "4_1",
|
||||
label: "4.1",
|
||||
path: "./tours/welcome.js"
|
||||
},
|
||||
{
|
||||
id: "4_0",
|
||||
label: "4.0",
|
||||
path: "./tours/welcome.js"
|
||||
path: "./tours/4.0/welcome.js"
|
||||
},
|
||||
{
|
||||
id: "3_1",
|
||||
|
||||
@@ -144,6 +144,18 @@ RED.userSettings = (function() {
|
||||
{setting:"view-node-show-label",label:"menu.label.showNodeLabelDefault",default: true, toggle:true}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "telemetry.label",
|
||||
options: [
|
||||
{
|
||||
global: true,
|
||||
setting: "telemetryEnabled",
|
||||
label: "telemetry.settingsTitle",
|
||||
description: "telemetry.settingsDescription",
|
||||
toggle: true
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "menu.label.other",
|
||||
options: [
|
||||
@@ -170,13 +182,20 @@ RED.userSettings = (function() {
|
||||
var initialState;
|
||||
if (opt.local) {
|
||||
initialState = localStorage.getItem(opt.setting);
|
||||
} else if (opt.global) {
|
||||
initialState = RED.settings.get(opt.setting);
|
||||
} else {
|
||||
initialState = currentEditorSettings.view[opt.setting];
|
||||
}
|
||||
var row = $('<div class="red-ui-settings-row"></div>').appendTo(pane);
|
||||
var input;
|
||||
if (opt.toggle) {
|
||||
input = $('<label for="user-settings-'+opt.setting+'"><input id="user-settings-'+opt.setting+'" type="checkbox"> '+RED._(opt.label)+'</label>').appendTo(row).find("input");
|
||||
let label = RED._(opt.label)
|
||||
if (opt.description) {
|
||||
label = `<p>${label}</p>${RED._(opt.description)}`;
|
||||
}
|
||||
input = $('<input id="user-settings-'+opt.setting+'" type="checkbox">').appendTo(row)
|
||||
$('<label for="user-settings-'+opt.setting+'">'+label+'</label>').appendTo(row)
|
||||
input.prop('checked',initialState);
|
||||
} else if (opt.options) {
|
||||
$('<label for="user-settings-'+opt.setting+'">'+RED._(opt.label)+'</label>').appendTo(row);
|
||||
@@ -210,6 +229,8 @@ RED.userSettings = (function() {
|
||||
var opt = allSettings[id];
|
||||
if (opt.local) {
|
||||
localStorage.setItem(opt.setting,value);
|
||||
} else if (opt.global) {
|
||||
RED.settings.set(opt.setting, value)
|
||||
} else {
|
||||
var currentEditorSettings = RED.settings.get('editor') || {};
|
||||
currentEditorSettings.view = currentEditorSettings.view || {};
|
||||
@@ -238,7 +259,7 @@ RED.userSettings = (function() {
|
||||
|
||||
addPane({
|
||||
id:'view',
|
||||
title: RED._("menu.label.view.view"),
|
||||
title: RED._("menu.label.settings"),
|
||||
get: createViewPane,
|
||||
close: function() {
|
||||
viewSettings.forEach(function(section) {
|
||||
|
||||
@@ -205,3 +205,39 @@
|
||||
background: var(--red-ui-secondary-background);
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
|
||||
.red-ui-popover.red-ui-dialog {
|
||||
z-index: 2003;
|
||||
--red-ui-popover-background: var(--red-ui-secondary-background);
|
||||
--red-ui-popover-border: var(--red-ui-tourGuide-border);
|
||||
--red-ui-popover-color: var(--red-ui-primary-text-color);
|
||||
|
||||
.red-ui-popover-content {
|
||||
h2 {
|
||||
text-align: center;
|
||||
margin-top: 0px;
|
||||
line-height: 1.2em;
|
||||
color: var(--red-ui-tourGuide-heading-color);
|
||||
i.fa {
|
||||
font-size: 1.5em
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.red-ui-dialog-toolbar {
|
||||
min-height: 36px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
.red-ui-dialog-body {
|
||||
padding: 20px 40px 10px;
|
||||
a {
|
||||
color: var(--red-ui-text-color-link) !important;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +70,14 @@
|
||||
overflow-y: auto;
|
||||
}
|
||||
.red-ui-settings-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items:flex-start;
|
||||
padding: 5px 10px 2px;
|
||||
}
|
||||
.red-ui-settings-row input[type="checkbox"] {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.red-ui-settings-section {
|
||||
position: relative;
|
||||
&:after {
|
||||
|
||||
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
231
packages/node_modules/@node-red/editor-client/src/tours/4.0/welcome.js
vendored
Normal file
@@ -0,0 +1,231 @@
|
||||
export default {
|
||||
version: "4.0.0",
|
||||
steps: [
|
||||
{
|
||||
titleIcon: "fa fa-map-o",
|
||||
title: {
|
||||
"en-US": "Welcome to Node-RED 4.0!",
|
||||
"ja": "Node-RED 4.0 へようこそ!",
|
||||
"fr": "Bienvenue dans Node-RED 4.0!"
|
||||
},
|
||||
description: {
|
||||
"en-US": "<p>Let's take a moment to discover the new features in this release.</p>",
|
||||
"ja": "<p>本リリースの新機能を見つけてみましょう。</p>",
|
||||
"fr": "<p>Prenons un moment pour découvrir les nouvelles fonctionnalités de cette version.</p>"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Multiplayer Mode",
|
||||
"ja": "複数ユーザ同時利用モード",
|
||||
"fr": "Mode Multi-utilisateur"
|
||||
},
|
||||
image: 'images/nr4-multiplayer-location.png',
|
||||
description: {
|
||||
"en-US": `<p>This release includes the first small steps towards making Node-RED easier
|
||||
to work with when you have multiple people editing flows at the same time.</p>
|
||||
<p>When this feature is enabled, you will now see who else has the editor open and some
|
||||
basic information on where they are in the editor.</p>
|
||||
<p>Check the release post for details on how to enable this feature in your settings file.</p>`,
|
||||
"ja": `<p>本リリースには、複数ユーザが同時にフローを編集する時に、Node-REDをより使いやすくするのための最初の微修正が入っています。</p>
|
||||
<p>本機能を有効にすると、誰がエディタを開いているか、その人がエディタ上のどこにいるかの基本的な情報が表示されます。</p>
|
||||
<p>設定ファイルで本機能を有効化する方法の詳細は、リリースの投稿を確認してください。</p>`,
|
||||
"fr": `<p>Cette version inclut les premières étapes visant à rendre Node-RED plus facile à utiliser
|
||||
lorsque plusieurs personnes modifient des flux en même temps.</p>
|
||||
<p>Lorsque cette fonctionnalité est activée, vous pourrez désormais voir si d’autres utilisateurs ont
|
||||
ouvert l'éditeur. Vous pourrez également savoir où ces utilisateurs se trouvent dans l'éditeur.</p>
|
||||
<p>Consultez la note de publication pour plus de détails sur la façon d'activer cette fonctionnalité
|
||||
dans votre fichier de paramètres.</p>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Better background deploy handling",
|
||||
"ja": "バックグラウンドのデプロイ処理の改善",
|
||||
"fr": "Meilleure gestion du déploiement en arrière-plan"
|
||||
},
|
||||
image: 'images/nr4-background-deploy.png',
|
||||
description: {
|
||||
"en-US": `<p>If another user deploys changes whilst you are editing, we now use a more discrete notification
|
||||
that doesn't stop you continuing your work - especially if they are being very productive and deploying lots
|
||||
of changes.</p>`,
|
||||
"ja": `他のユーザが変更をデプロイした時に、特に変更が多い生産的な編集作業を妨げないように通知するようになりました。`,
|
||||
"fr": `<p>Si un autre utilisateur déploie des modifications pendant que vous êtes en train de modifier, vous recevrez
|
||||
une notification plus discrète qu'auparavant qui ne vous empêche pas de continuer votre travail.</p>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Improved flow diffs",
|
||||
"ja": "フローの差分表示の改善",
|
||||
"fr": "Amélioration des différences de flux"
|
||||
},
|
||||
image: 'images/nr4-diff-update.png',
|
||||
description: {
|
||||
"en-US": `<p>When viewing changes made to a flow, Node-RED now distinguishes between nodes that have had configuration
|
||||
changes and those that have only been moved.<p>
|
||||
<p>When faced with a long list of changes to look at, this makes it much easier to focus on more significant items.</p>`,
|
||||
"ja": `<p>フローの変更内容を表示する時に、Node-REDは設定が変更されたノードと、移動されただけのノードを区別するようになりました。<p>
|
||||
<p>これによって、多くの変更内容を確認する際に、重要な項目に焦点を当てることができます。</p>`,
|
||||
"fr": `<p>Lors de l'affichage des modifications apportées à un flux, Node-RED fait désormais la distinction entre les
|
||||
noeuds qui ont changé de configuration et ceux qui ont seulement été déplacés.<p>
|
||||
<p>Face à une longue liste de changements à examiner, il est beaucoup plus facile de se concentrer sur les éléments les
|
||||
plus importants.</p>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Better Configuration Node UX",
|
||||
"ja": "設定ノードのUXが向上",
|
||||
"fr": "Meilleure expérience utilisateur du noeud de configuration"
|
||||
},
|
||||
image: 'images/nr4-config-select.png',
|
||||
description: {
|
||||
"en-US": `<p>The Configuration node selection UI has had a small update to have a dedicated 'add' button
|
||||
next to the select box.</p>
|
||||
<p>It's a small change, but should make it easier to work with your config nodes.</p>`,
|
||||
"ja": `<p>設定ノードを選択するUIが修正され、選択ボックスの隣に専用の「追加」ボタンが追加されました。</p>
|
||||
<p>微修正ですが設定ノードの操作が容易になります。</p>`,
|
||||
"fr": `<p>L'interface utilisateur de la sélection du noeud de configuration a fait l'objet d'une petite
|
||||
mise à jour afin de disposer d'un bouton « Ajouter » à côté de la zone de sélection.</p>
|
||||
<p>C'est un petit changement, mais cela devrait faciliter le travail avec vos noeuds de configuration.</p>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Timestamp formatting options",
|
||||
"ja": "タイムスタンプの形式の項目",
|
||||
"fr": "Options de formatage de l'horodatage"
|
||||
},
|
||||
image: 'images/nr4-timestamp-formatting.png',
|
||||
description: {
|
||||
"en-US": `<p>Nodes that let you set a timestamp now have options on what format that timestamp should be in.</p>
|
||||
<p>We're keeping it simple to begin with by providing three options:<p>
|
||||
<ul>
|
||||
<li>Milliseconds since epoch - this is existing behaviour of the timestamp option</li>
|
||||
<li>ISO 8601 - a common format used by many systems</li>
|
||||
<li>JavaScript Date Object</li>
|
||||
</ul>`,
|
||||
"ja": `<p>タイムスタンプを設定するノードに、タイムスタンプの形式を指定できる項目が追加されました。</p>
|
||||
<p>次の3つの項目を追加したことで、簡単に選択できるようになりました:<p>
|
||||
<ul>
|
||||
<li>エポックからのミリ秒 - 従来動作と同じになるタイムスタンプの項目</li>
|
||||
<li>ISO 8601 - 多くのシステムで使用されている共通の形式</li>
|
||||
<li>JavaScript日付オブジェクト</li>
|
||||
</ul>`,
|
||||
"fr": `<p>Les noeuds qui vous permettent de définir un horodatage disposent désormais d'options sur le format dans lequel cet horodatage peut être défini.</p>
|
||||
<p>Nous gardons les choses simples en proposant trois options :<p>
|
||||
<ul>
|
||||
<li>Millisecondes depuis l'époque : il s'agit du comportement existant de l'option d'horodatage</li>
|
||||
<li>ISO 8601 : un format commun utilisé par de nombreux systèmes</li>
|
||||
<li>Objet Date JavaScript</li>
|
||||
</ul>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Auto-complete of flow/global and env types",
|
||||
"ja": "フロー/グローバル、環境変数の型の自動補完",
|
||||
"fr": "Saisie automatique des types de flux/global et env"
|
||||
},
|
||||
image: 'images/nr4-auto-complete.png',
|
||||
description: {
|
||||
"en-US": `<p>The <code>flow</code>/<code>global</code> context inputs and the <code>env</code> input
|
||||
now all include auto-complete suggestions based on the live state of your flows.</p>
|
||||
`,
|
||||
"ja": `<p><code>flow</code>/<code>global</code>コンテキストや<code>env</code>の入力を、現在のフローの状態をもとに自動補完で提案するようになりました。</p>
|
||||
`,
|
||||
"fr": `<p>Les entrées contextuelles <code>flow</code>/<code>global</code> et l'entrée <code>env</code>
|
||||
incluent désormais des suggestions de saisie semi-automatique basées sur l'état actuel de vos flux.</p>
|
||||
`,
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Config node customisation in Subflows",
|
||||
"ja": "サブフローでの設定ノードのカスタマイズ",
|
||||
"fr": "Personnalisation du noeud de configuration dans les sous-flux"
|
||||
},
|
||||
image: 'images/nr4-sf-config.png',
|
||||
description: {
|
||||
"en-US": `<p>Subflows can now be customised to allow each instance to use a different
|
||||
config node of a selected type.</p>
|
||||
<p>For example, each instance of a subflow that connects to an MQTT Broker and does some post-processing
|
||||
of the messages received can be pointed at a different broker.</p>
|
||||
`,
|
||||
"ja": `<p>サブフローをカスタマイズして、選択した型の異なる設定ノードを各インスタンスが使用できるようになりました。</p>
|
||||
<p>例えば、MQTTブローカへ接続し、メッセージ受信と後処理を行うサブフローの各インスタンスに異なるブローカを指定することも可能です。</p>
|
||||
`,
|
||||
"fr": `<p>Les sous-flux peuvent désormais être personnalisés pour permettre à chaque instance d'utiliser un
|
||||
noeud de configuration d'un type sélectionné.</p>
|
||||
<p>Par exemple, chaque instance d'un sous-flux qui se connecte à un courtier MQTT et effectue un post-traitement
|
||||
des messages reçus peut être pointée vers un autre courtier.</p>
|
||||
`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Remembering palette state",
|
||||
"ja": "パレットの状態を維持",
|
||||
"fr": "Mémorisation de l'état de la palette"
|
||||
},
|
||||
description: {
|
||||
"en-US": `<p>The palette now remembers what categories you have hidden between reloads - as well as any
|
||||
filter you have applied.</p>`,
|
||||
"ja": `<p>パレット上で非表示にしたカテゴリや適用したフィルタが、リロードしても記憶されるようになりました。</p>`,
|
||||
"fr": `<p>La palette se souvient désormais des catégories que vous avez masquées entre les rechargements,
|
||||
ainsi que le filtre que vous avez appliqué.</p>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Plugins shown in the Palette Manager",
|
||||
"ja": "パレット管理にプラグインを表示",
|
||||
"fr": "Affichage des Plugins dans le gestionnaire de palettes"
|
||||
},
|
||||
image: 'images/nr4-plugins.png',
|
||||
description: {
|
||||
"en-US": `<p>The palette manager now shows any plugin modules you have installed, such as
|
||||
<code>node-red-debugger</code>. Previously they would only be shown if the plugins include
|
||||
nodes for the palette.</p>`,
|
||||
"ja": `<p>パレットの管理に <code>node-red-debugger</code> の様なインストールしたプラグインが表示されます。以前はプラグインにパレット向けのノードが含まれている時のみ表示されていました。</p>`,
|
||||
"fr": `<p>Le gestionnaire de palettes affiche désormais tous les plugins que vous avez installés,
|
||||
tels que <code>node-red-debugger</code>. Auparavant, ils n'étaient affichés que s'ils contenaient
|
||||
des noeuds pour la palette.</p>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Node Updates",
|
||||
"ja": "ノードの更新",
|
||||
"fr": "Mises à jour des noeuds"
|
||||
},
|
||||
// image: "images/",
|
||||
description: {
|
||||
"en-US": `<p>The core nodes have received lots of minor fixes, documentation updates and
|
||||
small enhancements. Check the full changelog in the Help sidebar for a full list.</p>
|
||||
<ul>
|
||||
<li>A fully RFC4180 compliant CSV mode</li>
|
||||
<li>Customisable headers on the WebSocket node</li>
|
||||
<li>Split node now can operate on any message property</li>
|
||||
<li>and lots more...</li>
|
||||
</ul>`,
|
||||
"ja": `<p>コアノードには沢山の軽微な修正、ドキュメント更新、小さな機能拡張が入っています。全リストはヘルプサイドバーにある変更履歴を参照してください。</p>
|
||||
<ul>
|
||||
<li>RFC4180に完全に準拠したCSVモード</li>
|
||||
<li>WebSocketノードのカスタマイズ可能なヘッダ</li>
|
||||
<li>Splitノードは、メッセージプロパティで操作できるようになりました</li>
|
||||
<li>他にも沢山あります...</li>
|
||||
</ul>`,
|
||||
"fr": `<p>Les noeuds principaux ont reçu de nombreux correctifs mineurs ainsi que des améliorations. La documentation a été mise à jour.
|
||||
Consultez le journal des modifications dans la barre latérale d'aide pour une liste complète. Ci-dessous, les changements les plus importants :</p>
|
||||
<ul>
|
||||
<li>Un mode CSV entièrement conforme à la norme RFC4180</li>
|
||||
<li>En-têtes personnalisables pour le noeud WebSocket</li>
|
||||
<li>Le noeud Split peut désormais fonctionner sur n'importe quelle propriété de message</li>
|
||||
<li>Et bien plus encore...</li>
|
||||
</ul>`
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
export default {
|
||||
version: "4.0.0",
|
||||
version: "4.1.0",
|
||||
steps: [
|
||||
{
|
||||
titleIcon: "fa fa-map-o",
|
||||
title: {
|
||||
"en-US": "Welcome to Node-RED 4.0!",
|
||||
"ja": "Node-RED 4.0 へようこそ!",
|
||||
"fr": "Bienvenue dans Node-RED 4.0!"
|
||||
"en-US": "Welcome to Node-RED 4.1!",
|
||||
"ja": "Node-RED 4.1 へようこそ!",
|
||||
"fr": "Bienvenue dans Node-RED 4.1!"
|
||||
},
|
||||
description: {
|
||||
"en-US": "<p>Let's take a moment to discover the new features in this release.</p>",
|
||||
@@ -16,216 +16,45 @@ export default {
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Multiplayer Mode",
|
||||
"ja": "複数ユーザ同時利用モード",
|
||||
"fr": "Mode Multi-utilisateur"
|
||||
"en-US": "Something new",
|
||||
},
|
||||
image: 'images/nr4-multiplayer-location.png',
|
||||
// image: 'images/nr4-multiplayer-location.png',
|
||||
description: {
|
||||
"en-US": `<p>This release includes the first small steps towards making Node-RED easier
|
||||
to work with when you have multiple people editing flows at the same time.</p>
|
||||
<p>When this feature is enabled, you will now see who else has the editor open and some
|
||||
basic information on where they are in the editor.</p>
|
||||
<p>Check the release post for details on how to enable this feature in your settings file.</p>`,
|
||||
"ja": `<p>本リリースには、複数ユーザが同時にフローを編集する時に、Node-REDをより使いやすくするのための最初の微修正が入っています。</p>
|
||||
<p>本機能を有効にすると、誰がエディタを開いているか、その人がエディタ上のどこにいるかの基本的な情報が表示されます。</p>
|
||||
<p>設定ファイルで本機能を有効化する方法の詳細は、リリースの投稿を確認してください。</p>`,
|
||||
"fr": `<p>Cette version inclut les premières étapes visant à rendre Node-RED plus facile à utiliser
|
||||
lorsque plusieurs personnes modifient des flux en même temps.</p>
|
||||
<p>Lorsque cette fonctionnalité est activée, vous pourrez désormais voir si d’autres utilisateurs ont
|
||||
ouvert l'éditeur. Vous pourrez également savoir où ces utilisateurs se trouvent dans l'éditeur.</p>
|
||||
<p>Consultez la note de publication pour plus de détails sur la façon d'activer cette fonctionnalité
|
||||
dans votre fichier de paramètres.</p>`
|
||||
"en-US": `<p>Something new</p>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Better background deploy handling",
|
||||
"ja": "バックグラウンドのデプロイ処理の改善",
|
||||
"fr": "Meilleure gestion du déploiement en arrière-plan"
|
||||
},
|
||||
image: 'images/nr4-background-deploy.png',
|
||||
description: {
|
||||
"en-US": `<p>If another user deploys changes whilst you are editing, we now use a more discrete notification
|
||||
that doesn't stop you continuing your work - especially if they are being very productive and deploying lots
|
||||
of changes.</p>`,
|
||||
"ja": `他のユーザが変更をデプロイした時に、特に変更が多い生産的な編集作業を妨げないように通知するようになりました。`,
|
||||
"fr": `<p>Si un autre utilisateur déploie des modifications pendant que vous êtes en train de modifier, vous recevrez
|
||||
une notification plus discrète qu'auparavant qui ne vous empêche pas de continuer votre travail.</p>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Improved flow diffs",
|
||||
"ja": "フローの差分表示の改善",
|
||||
"fr": "Amélioration des différences de flux"
|
||||
},
|
||||
image: 'images/nr4-diff-update.png',
|
||||
description: {
|
||||
"en-US": `<p>When viewing changes made to a flow, Node-RED now distinguishes between nodes that have had configuration
|
||||
changes and those that have only been moved.<p>
|
||||
<p>When faced with a long list of changes to look at, this makes it much easier to focus on more significant items.</p>`,
|
||||
"ja": `<p>フローの変更内容を表示する時に、Node-REDは設定が変更されたノードと、移動されただけのノードを区別するようになりました。<p>
|
||||
<p>これによって、多くの変更内容を確認する際に、重要な項目に焦点を当てることができます。</p>`,
|
||||
"fr": `<p>Lors de l'affichage des modifications apportées à un flux, Node-RED fait désormais la distinction entre les
|
||||
noeuds qui ont changé de configuration et ceux qui ont seulement été déplacés.<p>
|
||||
<p>Face à une longue liste de changements à examiner, il est beaucoup plus facile de se concentrer sur les éléments les
|
||||
plus importants.</p>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Better Configuration Node UX",
|
||||
"ja": "設定ノードのUXが向上",
|
||||
"fr": "Meilleure expérience utilisateur du noeud de configuration"
|
||||
},
|
||||
image: 'images/nr4-config-select.png',
|
||||
description: {
|
||||
"en-US": `<p>The Configuration node selection UI has had a small update to have a dedicated 'add' button
|
||||
next to the select box.</p>
|
||||
<p>It's a small change, but should make it easier to work with your config nodes.</p>`,
|
||||
"ja": `<p>設定ノードを選択するUIが修正され、選択ボックスの隣に専用の「追加」ボタンが追加されました。</p>
|
||||
<p>微修正ですが設定ノードの操作が容易になります。</p>`,
|
||||
"fr": `<p>L'interface utilisateur de la sélection du noeud de configuration a fait l'objet d'une petite
|
||||
mise à jour afin de disposer d'un bouton « Ajouter » à côté de la zone de sélection.</p>
|
||||
<p>C'est un petit changement, mais cela devrait faciliter le travail avec vos noeuds de configuration.</p>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Timestamp formatting options",
|
||||
"ja": "タイムスタンプの形式の項目",
|
||||
"fr": "Options de formatage de l'horodatage"
|
||||
},
|
||||
image: 'images/nr4-timestamp-formatting.png',
|
||||
description: {
|
||||
"en-US": `<p>Nodes that let you set a timestamp now have options on what format that timestamp should be in.</p>
|
||||
<p>We're keeping it simple to begin with by providing three options:<p>
|
||||
<ul>
|
||||
<li>Milliseconds since epoch - this is existing behaviour of the timestamp option</li>
|
||||
<li>ISO 8601 - a common format used by many systems</li>
|
||||
<li>JavaScript Date Object</li>
|
||||
</ul>`,
|
||||
"ja": `<p>タイムスタンプを設定するノードに、タイムスタンプの形式を指定できる項目が追加されました。</p>
|
||||
<p>次の3つの項目を追加したことで、簡単に選択できるようになりました:<p>
|
||||
<ul>
|
||||
<li>エポックからのミリ秒 - 従来動作と同じになるタイムスタンプの項目</li>
|
||||
<li>ISO 8601 - 多くのシステムで使用されている共通の形式</li>
|
||||
<li>JavaScript日付オブジェクト</li>
|
||||
</ul>`,
|
||||
"fr": `<p>Les noeuds qui vous permettent de définir un horodatage disposent désormais d'options sur le format dans lequel cet horodatage peut être défini.</p>
|
||||
<p>Nous gardons les choses simples en proposant trois options :<p>
|
||||
<ul>
|
||||
<li>Millisecondes depuis l'époque : il s'agit du comportement existant de l'option d'horodatage</li>
|
||||
<li>ISO 8601 : un format commun utilisé par de nombreux systèmes</li>
|
||||
<li>Objet Date JavaScript</li>
|
||||
</ul>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Auto-complete of flow/global and env types",
|
||||
"ja": "フロー/グローバル、環境変数の型の自動補完",
|
||||
"fr": "Saisie automatique des types de flux/global et env"
|
||||
},
|
||||
image: 'images/nr4-auto-complete.png',
|
||||
description: {
|
||||
"en-US": `<p>The <code>flow</code>/<code>global</code> context inputs and the <code>env</code> input
|
||||
now all include auto-complete suggestions based on the live state of your flows.</p>
|
||||
`,
|
||||
"ja": `<p><code>flow</code>/<code>global</code>コンテキストや<code>env</code>の入力を、現在のフローの状態をもとに自動補完で提案するようになりました。</p>
|
||||
`,
|
||||
"fr": `<p>Les entrées contextuelles <code>flow</code>/<code>global</code> et l'entrée <code>env</code>
|
||||
incluent désormais des suggestions de saisie semi-automatique basées sur l'état actuel de vos flux.</p>
|
||||
`,
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Config node customisation in Subflows",
|
||||
"ja": "サブフローでの設定ノードのカスタマイズ",
|
||||
"fr": "Personnalisation du noeud de configuration dans les sous-flux"
|
||||
},
|
||||
image: 'images/nr4-sf-config.png',
|
||||
description: {
|
||||
"en-US": `<p>Subflows can now be customised to allow each instance to use a different
|
||||
config node of a selected type.</p>
|
||||
<p>For example, each instance of a subflow that connects to an MQTT Broker and does some post-processing
|
||||
of the messages received can be pointed at a different broker.</p>
|
||||
`,
|
||||
"ja": `<p>サブフローをカスタマイズして、選択した型の異なる設定ノードを各インスタンスが使用できるようになりました。</p>
|
||||
<p>例えば、MQTTブローカへ接続し、メッセージ受信と後処理を行うサブフローの各インスタンスに異なるブローカを指定することも可能です。</p>
|
||||
`,
|
||||
"fr": `<p>Les sous-flux peuvent désormais être personnalisés pour permettre à chaque instance d'utiliser un
|
||||
noeud de configuration d'un type sélectionné.</p>
|
||||
<p>Par exemple, chaque instance d'un sous-flux qui se connecte à un courtier MQTT et effectue un post-traitement
|
||||
des messages reçus peut être pointée vers un autre courtier.</p>
|
||||
`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Remembering palette state",
|
||||
"ja": "パレットの状態を維持",
|
||||
"fr": "Mémorisation de l'état de la palette"
|
||||
},
|
||||
description: {
|
||||
"en-US": `<p>The palette now remembers what categories you have hidden between reloads - as well as any
|
||||
filter you have applied.</p>`,
|
||||
"ja": `<p>パレット上で非表示にしたカテゴリや適用したフィルタが、リロードしても記憶されるようになりました。</p>`,
|
||||
"fr": `<p>La palette se souvient désormais des catégories que vous avez masquées entre les rechargements,
|
||||
ainsi que le filtre que vous avez appliqué.</p>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Plugins shown in the Palette Manager",
|
||||
"ja": "パレット管理にプラグインを表示",
|
||||
"fr": "Affichage des Plugins dans le gestionnaire de palettes"
|
||||
},
|
||||
image: 'images/nr4-plugins.png',
|
||||
description: {
|
||||
"en-US": `<p>The palette manager now shows any plugin modules you have installed, such as
|
||||
<code>node-red-debugger</code>. Previously they would only be shown if the plugins include
|
||||
nodes for the palette.</p>`,
|
||||
"ja": `<p>パレットの管理に <code>node-red-debugger</code> の様なインストールしたプラグインが表示されます。以前はプラグインにパレット向けのノードが含まれている時のみ表示されていました。</p>`,
|
||||
"fr": `<p>Le gestionnaire de palettes affiche désormais tous les plugins que vous avez installés,
|
||||
tels que <code>node-red-debugger</code>. Auparavant, ils n'étaient affichés que s'ils contenaient
|
||||
des noeuds pour la palette.</p>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Node Updates",
|
||||
"ja": "ノードの更新",
|
||||
"fr": "Mises à jour des noeuds"
|
||||
},
|
||||
// image: "images/",
|
||||
description: {
|
||||
"en-US": `<p>The core nodes have received lots of minor fixes, documentation updates and
|
||||
small enhancements. Check the full changelog in the Help sidebar for a full list.</p>
|
||||
<ul>
|
||||
<li>A fully RFC4180 compliant CSV mode</li>
|
||||
<li>Customisable headers on the WebSocket node</li>
|
||||
<li>Split node now can operate on any message property</li>
|
||||
<li>and lots more...</li>
|
||||
</ul>`,
|
||||
"ja": `<p>コアノードには沢山の軽微な修正、ドキュメント更新、小さな機能拡張が入っています。全リストはヘルプサイドバーにある変更履歴を参照してください。</p>
|
||||
<ul>
|
||||
<li>RFC4180に完全に準拠したCSVモード</li>
|
||||
<li>WebSocketノードのカスタマイズ可能なヘッダ</li>
|
||||
<li>Splitノードは、メッセージプロパティで操作できるようになりました</li>
|
||||
<li>他にも沢山あります...</li>
|
||||
</ul>`,
|
||||
"fr": `<p>Les noeuds principaux ont reçu de nombreux correctifs mineurs ainsi que des améliorations. La documentation a été mise à jour.
|
||||
Consultez le journal des modifications dans la barre latérale d'aide pour une liste complète. Ci-dessous, les changements les plus importants :</p>
|
||||
<ul>
|
||||
<li>Un mode CSV entièrement conforme à la norme RFC4180</li>
|
||||
<li>En-têtes personnalisables pour le noeud WebSocket</li>
|
||||
<li>Le noeud Split peut désormais fonctionner sur n'importe quelle propriété de message</li>
|
||||
<li>Et bien plus encore...</li>
|
||||
</ul>`
|
||||
}
|
||||
}
|
||||
// {
|
||||
// title: {
|
||||
// "en-US": "Node Updates",
|
||||
// "ja": "ノードの更新",
|
||||
// "fr": "Mises à jour des noeuds"
|
||||
// },
|
||||
// // image: "images/",
|
||||
// description: {
|
||||
// "en-US": `<p>The core nodes have received lots of minor fixes, documentation updates and
|
||||
// small enhancements. Check the full changelog in the Help sidebar for a full list.</p>
|
||||
// <ul>
|
||||
// <li>A fully RFC4180 compliant CSV mode</li>
|
||||
// <li>Customisable headers on the WebSocket node</li>
|
||||
// <li>Split node now can operate on any message property</li>
|
||||
// <li>and lots more...</li>
|
||||
// </ul>`,
|
||||
// "ja": `<p>コアノードには沢山の軽微な修正、ドキュメント更新、小さな機能拡張が入っています。全リストはヘルプサイドバーにある変更履歴を参照してください。</p>
|
||||
// <ul>
|
||||
// <li>RFC4180に完全に準拠したCSVモード</li>
|
||||
// <li>WebSocketノードのカスタマイズ可能なヘッダ</li>
|
||||
// <li>Splitノードは、メッセージプロパティで操作できるようになりました</li>
|
||||
// <li>他にも沢山あります...</li>
|
||||
// </ul>`,
|
||||
// "fr": `<p>Les noeuds principaux ont reçu de nombreux correctifs mineurs ainsi que des améliorations. La documentation a été mise à jour.
|
||||
// Consultez le journal des modifications dans la barre latérale d'aide pour une liste complète. Ci-dessous, les changements les plus importants :</p>
|
||||
// <ul>
|
||||
// <li>Un mode CSV entièrement conforme à la norme RFC4180</li>
|
||||
// <li>En-têtes personnalisables pour le noeud WebSocket</li>
|
||||
// <li>Le noeud Split peut désormais fonctionner sur n'importe quelle propriété de message</li>
|
||||
// <li>Et bien plus encore...</li>
|
||||
// </ul>`
|
||||
// }
|
||||
// }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -161,6 +161,8 @@ var api = module.exports = {
|
||||
safeSettings.diagnostics.ui = false; // cannot have UI without endpoint
|
||||
}
|
||||
|
||||
safeSettings.telemetryEnabled = runtime.telemetry.isEnabled()
|
||||
|
||||
safeSettings.runtimeState = {
|
||||
//unless runtimeState.ui and runtimeState.enabled are explicitly true, they will default to false.
|
||||
enabled: !!runtime.settings.runtimeState && runtime.settings.runtimeState.enabled === true,
|
||||
@@ -213,7 +215,19 @@ var api = module.exports = {
|
||||
}
|
||||
var currentSettings = runtime.settings.getUserSettings(username)||{};
|
||||
currentSettings = extend(currentSettings, opts.settings);
|
||||
|
||||
try {
|
||||
if (currentSettings.hasOwnProperty("telemetryEnabled")) {
|
||||
// This is a global setting that is being set by the user. It should
|
||||
// not be stored per-user as it applies to the whole runtime.
|
||||
const telemetryEnabled = currentSettings.telemetryEnabled;
|
||||
delete currentSettings.telemetryEnabled;
|
||||
if (telemetryEnabled) {
|
||||
runtime.telemetry.enable()
|
||||
} else {
|
||||
runtime.telemetry.disable()
|
||||
}
|
||||
}
|
||||
return runtime.settings.setUserSettings(username, currentSettings).then(function() {
|
||||
runtime.log.audit({event: "settings.update",username:username}, opts.req);
|
||||
return;
|
||||
|
||||
@@ -23,6 +23,7 @@ var library = require("./library");
|
||||
var plugins = require("./plugins");
|
||||
var settings = require("./settings");
|
||||
const multiplayer = require("./multiplayer");
|
||||
const telemetry = require("./telemetry");
|
||||
|
||||
var express = require("express");
|
||||
var path = require('path');
|
||||
@@ -135,6 +136,7 @@ function start() {
|
||||
return i18n.registerMessageCatalog("runtime",path.resolve(path.join(__dirname,"..","locales")),"runtime.json")
|
||||
.then(function() { return storage.init(runtime)})
|
||||
.then(function() { return settings.load(storage)})
|
||||
.then(function() { return telemetry.init(runtime)})
|
||||
.then(function() { return library.init(runtime)})
|
||||
.then(function() { return multiplayer.init(runtime)})
|
||||
.then(function() {
|
||||
@@ -337,6 +339,7 @@ var runtime = {
|
||||
library: library,
|
||||
exec: exec,
|
||||
util: util,
|
||||
telemetry: telemetry,
|
||||
get adminApi() { return adminApi },
|
||||
get adminApp() { return adminApp },
|
||||
get nodeApp() { return nodeApp },
|
||||
|
||||
208
packages/node_modules/@node-red/runtime/lib/telemetry/index.js
vendored
Normal file
@@ -0,0 +1,208 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs/promises')
|
||||
const semver = require('semver')
|
||||
const cronosjs = require('cronosjs')
|
||||
|
||||
const METRICS_DIR = path.join(__dirname, 'metrics')
|
||||
const INITIAL_PING_DELAY = 1000 * 60 * 30 // 30 minutes from startup
|
||||
|
||||
let runtime
|
||||
|
||||
let scheduleTask
|
||||
|
||||
async function gather () {
|
||||
let metricFiles = await fs.readdir(METRICS_DIR)
|
||||
metricFiles = metricFiles.filter(name => /^\d+-.*\.js$/.test(name))
|
||||
metricFiles.sort()
|
||||
|
||||
const metrics = {}
|
||||
|
||||
for (let i = 0, l = metricFiles.length; i < l; i++) {
|
||||
const metricModule = require(path.join(METRICS_DIR, metricFiles[i]))
|
||||
let result = metricModule(runtime)
|
||||
if (!!result && (typeof result === 'object' || typeof result === 'function') && typeof result.then === 'function') {
|
||||
result = await result
|
||||
}
|
||||
const keys = Object.keys(result)
|
||||
keys.forEach(key => {
|
||||
const keyParts = key.split('.')
|
||||
let p = metrics
|
||||
keyParts.forEach((part, index) => {
|
||||
if (index < keyParts.length - 1) {
|
||||
if (!p[part]) {
|
||||
p[part] = {}
|
||||
}
|
||||
p = p[part]
|
||||
} else {
|
||||
p[part] = result[key]
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
return metrics
|
||||
}
|
||||
|
||||
async function report () {
|
||||
if (!isTelemetryEnabled()) {
|
||||
return
|
||||
}
|
||||
// If enabled, gather metrics
|
||||
const metrics = await gather()
|
||||
console.log(JSON.stringify(metrics, null, 2))
|
||||
|
||||
// Post metrics to endpoint - handle any error silently
|
||||
|
||||
const { got } = await import('got')
|
||||
runtime.log.debug('Sending telemetry')
|
||||
const response = await got.post('https://telemetry.nodered.org/ping', {
|
||||
json: metrics,
|
||||
responseType: 'json',
|
||||
headers: {
|
||||
'User-Agent': `Node-RED/${runtime.settings.version}`
|
||||
}
|
||||
}).json().catch(err => {
|
||||
// swallow errors
|
||||
runtime.log.debug('Failed to send telemetry: ' + err.toString())
|
||||
})
|
||||
// Example response:
|
||||
// { 'node-red': { latest: '4.0.9', next: '4.1.0-beta.1.9' } }
|
||||
runtime.log.debug(`Telemetry response: ${JSON.stringify(response)}`)
|
||||
// Get response from endpoint
|
||||
if (response?.['node-red']) {
|
||||
const currentVersion = metrics.env['node-red']
|
||||
if (semver.valid(currentVersion)) {
|
||||
const latest = response['node-red'].latest
|
||||
const next = response['node-red'].next
|
||||
let updatePayload
|
||||
if (semver.lt(currentVersion, latest)) {
|
||||
// Case one: current < latest
|
||||
runtime.log.info(`A new version of Node-RED is available: ${latest}`)
|
||||
updatePayload = { version: latest }
|
||||
} else if (semver.gt(currentVersion, latest) && semver.lt(currentVersion, next)) {
|
||||
// Case two: current > latest && current < next
|
||||
runtime.log.info(`A new beta version of Node-RED is available: ${next}`)
|
||||
updatePayload = { version: next }
|
||||
}
|
||||
|
||||
if (updatePayload && isUpdateNotificationEnabled()) {
|
||||
runtime.events.emit("runtime-event",{id:"update-available", payload: updatePayload, retain: true});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isTelemetryEnabled () {
|
||||
// If NODE_RED_DISABLE_TELEMETRY was set, or --no-telemetry was specified,
|
||||
// the settings object will have been updated to disable telemetry explicitly
|
||||
|
||||
// If there are no telemetry settings then the user has not had a chance
|
||||
// to opt out yet - so keep it disabled until they do
|
||||
|
||||
let telemetrySettings
|
||||
try {
|
||||
telemetrySettings = runtime.settings.get('telemetry')
|
||||
} catch (err) {
|
||||
// Settings not available
|
||||
}
|
||||
let runtimeTelemetryEnabled
|
||||
try {
|
||||
runtimeTelemetryEnabled = runtime.settings.get('telemetryEnabled')
|
||||
} catch (err) {
|
||||
// Settings not available
|
||||
}
|
||||
|
||||
if (telemetrySettings === undefined && runtimeTelemetryEnabled === undefined) {
|
||||
// No telemetry settings - so keep it disabled
|
||||
return undefined
|
||||
}
|
||||
|
||||
// User has made a choice; defer to that
|
||||
if (runtimeTelemetryEnabled !== undefined) {
|
||||
return runtimeTelemetryEnabled
|
||||
}
|
||||
|
||||
// If there are telemetry settings, use what it says
|
||||
if (telemetrySettings && telemetrySettings.enabled !== undefined) {
|
||||
return telemetrySettings.enabled
|
||||
}
|
||||
|
||||
// At this point, we have no sign the user has consented to telemetry, so
|
||||
// keep disabled - but return undefined as a false-like value to distinguish
|
||||
// it from the explicit disable above
|
||||
return undefined
|
||||
}
|
||||
|
||||
function isUpdateNotificationEnabled () {
|
||||
const telemetrySettings = runtime.settings.get('telemetry') || {}
|
||||
return telemetrySettings.updateNotification !== false
|
||||
}
|
||||
/**
|
||||
* Start the telemetry schedule
|
||||
*/
|
||||
function startTelemetry () {
|
||||
if (scheduleTask) {
|
||||
// Already scheduled - nothing left to do
|
||||
return
|
||||
}
|
||||
|
||||
const pingTime = new Date(Date.now() + INITIAL_PING_DELAY)
|
||||
const pingMinutes = pingTime.getMinutes()
|
||||
const pingHours = pingTime.getHours()
|
||||
const pingSchedule = `${pingMinutes} ${pingHours} * * *`
|
||||
|
||||
runtime.log.debug(`Telemetry enabled. Schedule: ${pingSchedule}`)
|
||||
|
||||
scheduleTask = cronosjs.scheduleTask(pingSchedule, () => {
|
||||
report()
|
||||
})
|
||||
}
|
||||
|
||||
function stopTelemetry () {
|
||||
if (scheduleTask) {
|
||||
runtime.log.debug(`Telemetry disabled`)
|
||||
scheduleTask.stop()
|
||||
scheduleTask = null
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: (_runtime) => {
|
||||
runtime = _runtime
|
||||
|
||||
if (isTelemetryEnabled()) {
|
||||
startTelemetry()
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Enable telemetry via user opt-in in the editor
|
||||
*/
|
||||
enable: () => {
|
||||
if (runtime.settings.available()) {
|
||||
runtime.settings.set('telemetryEnabled', true)
|
||||
}
|
||||
startTelemetry()
|
||||
},
|
||||
|
||||
/**
|
||||
* Disable telemetry via user opt-in in the editor
|
||||
*/
|
||||
disable: () => {
|
||||
if (runtime.settings.available()) {
|
||||
runtime.settings.set('telemetryEnabled', false)
|
||||
}
|
||||
stopTelemetry()
|
||||
},
|
||||
|
||||
/**
|
||||
* Get telemetry enabled status
|
||||
* @returns {boolean} true if telemetry is enabled, false if disabled, undefined if not set
|
||||
*/
|
||||
isEnabled: isTelemetryEnabled,
|
||||
|
||||
stop: () => {
|
||||
if (scheduleTask) {
|
||||
scheduleTask.stop()
|
||||
scheduleTask = null
|
||||
}
|
||||
}
|
||||
}
|
||||
5
packages/node_modules/@node-red/runtime/lib/telemetry/metrics/01-core.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = (runtime) => {
|
||||
return {
|
||||
instanceId: runtime.settings.get('instanceId')
|
||||
}
|
||||
}
|
||||
9
packages/node_modules/@node-red/runtime/lib/telemetry/metrics/02-os.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
const os = require('os')
|
||||
|
||||
module.exports = (_) => {
|
||||
return {
|
||||
'os.type': os.type(),
|
||||
'os.release': os.release(),
|
||||
'os.arch': os.arch()
|
||||
}
|
||||
}
|
||||
8
packages/node_modules/@node-red/runtime/lib/telemetry/metrics/03-env.js
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
const process = require('process')
|
||||
|
||||
module.exports = (runtime) => {
|
||||
return {
|
||||
'env.nodejs': process.version.replace(/^v/, ''),
|
||||
'env.node-red': runtime.settings.version
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,11 @@
|
||||
"@node-red/util": "4.1.0-beta.0",
|
||||
"async-mutex": "0.5.0",
|
||||
"clone": "2.1.2",
|
||||
"cronosjs": "1.7.1",
|
||||
"express": "4.21.2",
|
||||
"fs-extra": "11.3.0",
|
||||
"json-stringify-safe": "5.0.1",
|
||||
"rfdc": "^1.3.1"
|
||||
"rfdc": "^1.3.1",
|
||||
"semver": "7.7.1"
|
||||
}
|
||||
}
|
||||
|
||||
8
packages/node_modules/node-red/red.js
vendored
@@ -63,7 +63,8 @@ var knownOpts = {
|
||||
"verbose": Boolean,
|
||||
"safe": Boolean,
|
||||
"version": Boolean,
|
||||
"define": [String, Array]
|
||||
"define": [String, Array],
|
||||
"no-telemetry": Boolean
|
||||
};
|
||||
var shortHands = {
|
||||
"?":["--help"],
|
||||
@@ -97,6 +98,7 @@ if (parsedArgs.help) {
|
||||
console.log(" --safe enable safe mode");
|
||||
console.log(" -D, --define X=Y overwrite value in settings file");
|
||||
console.log(" --version show version information");
|
||||
console.log(" --no-telemetry do not share usage data with the Node-RED project");
|
||||
console.log(" -?, --help show this help");
|
||||
console.log(" admin <command> run an admin command");
|
||||
console.log("");
|
||||
@@ -222,6 +224,10 @@ if (process.env.NODE_RED_ENABLE_TOURS) {
|
||||
settings.editorTheme.tours = !/^false$/i.test(process.env.NODE_RED_ENABLE_TOURS);
|
||||
}
|
||||
|
||||
if (parsedArgs.telemetry === false || process.env.NODE_RED_DISABLE_TELEMETRY) {
|
||||
settings.telemetry = settings.telemetry || {};
|
||||
settings.telemetry.enabled = false;
|
||||
}
|
||||
|
||||
var defaultServerSettings = {
|
||||
"x-powered-by": false
|
||||
|
||||
17
packages/node_modules/node-red/settings.js
vendored
@@ -273,6 +273,7 @@ module.exports = {
|
||||
* Runtime Settings
|
||||
* - lang
|
||||
* - runtimeState
|
||||
* - telemetry
|
||||
* - diagnostics
|
||||
* - logging
|
||||
* - contextStorage
|
||||
@@ -311,6 +312,22 @@ module.exports = {
|
||||
/** show or hide runtime stop/start options in the node-red editor. Must be set to `false` to hide */
|
||||
ui: false,
|
||||
},
|
||||
telemetry: {
|
||||
/**
|
||||
* By default, telemetry is disabled until the user provides consent the first
|
||||
* time they open the editor.
|
||||
*
|
||||
* The following property can be uncommented and set to true/false to enable/disable
|
||||
* telemetry without seeking further consent in the editor.
|
||||
* The user can override this setting via the user settings dialog within the editor
|
||||
*/
|
||||
// enabled: true,
|
||||
/**
|
||||
* If telemetry is enabled, the editor will notify the user if a new version of Node-RED
|
||||
* is available. Set the following property to false to disable this notification.
|
||||
*/
|
||||
// updateNotification: true
|
||||
},
|
||||
/** Configure the logging output */
|
||||
logging: {
|
||||
/** Only console logging is currently supported */
|
||||
|
||||