
Research
Two Malicious Rust Crates Impersonate Popular Logger to Steal Wallet Keys
Socket uncovers malicious Rust crates impersonating fast_log to steal Solana and Ethereum wallet keys from source code.
svelte-integration-red
Advanced tools
Integrates Svelte into Node-RED so that the editor-part of a node can be developed using Svelte templates.
SIR integrates Svelte into Node-RED and allows to write the editor-part of your node using Svelte templates.
Svelte itself is only needed during development. Anyone using your node will use the generated HTML file. This file may be a bit bigger than a hand-written version. Anyway, the development of node editors gets way easier and neat. Especially if your editor is quite complex or has dynamic parts. And furthermore you can easily write editor components and reuse them.
Install SIR global with
npm install -g svelte-integration-red
Then you can run the build-process with the command sir
in your project's main directory. SIR will analyze your package.json
and check if it finds a .svelte
template and a .doc.html
for any node mentioned within. It then compiles these to HTML files that comply with Node-RED's HTML file format. If your node is not in the same directory than your package.json you can also submit the path to it's folder as a command line parameter (like the test-node example below).
Compile single files with sir myNodeName.svelte
The html file will automatically be minified. If you need a more readable version for debugging purposes use sir -m=false
.
Creating a Node-RED node with SIR is mostly similiar to a normal Node-RED node.
Register your node in the package.json
{
"name": "my-node",
"version": "0.0.1",
"description": "My awesome node",
"node-red": {
"nodes": {
"my-node-name": "my-node-name.js",
}
}
}
Create a .js file with the following code:
module.exports = function (RED) {
function MyNodeName (config) {
RED.nodes.createNode(this, config)
const node = this
node.name = config.name
}
RED.nodes.registerType('my-node-name', MyNodeName)
}
And instead of the .html file, which will be created later by SIR you need a .svelte file.
Attention: The Node-RED part must be stated in <script context="module">
and you must state the three functions render, update, revert.
If you want to set your node to a minimum width, you can add that in the render option object => e.g. render(this, { minWidth: "600px" }).
<script context="module">
/* This is mostly identical to a standard Node-RED node. Important: It must be stated in script context="module"! */
RED.nodes.registerType("my-node-name", {
category: "common",
defaults: {
name: { value: "", label: "Name", placeholder: "Enter a name for this node" },
_version: { value: ""}
},
inputs: 0,
outputs: 1,
color: "#CEC0DE",
icon: "font-awesome/fa-tag",
label: function() {
if (this.name) return this.name;
return "No name set";
},
oneditprepare: function () {
render(this)
},
oneditsave: function () {
update(this)
},
oneditcancel: function () {
revert(this)
},
onadd: function () {
addCurrentNodeVersion(this)
}
});
</script>
<script>
// get your node variable from extern and import the needed components from SIR
export let node
import { Input, Button } from 'svelte-integration-red/components'
// then add your javascript functions
const myButtonFunc = () => alert('The button was pressed')
</script>
<!-- Now enter your svelte code -->
<!-- just bind node and set the property name which you have stated above in the defaults variable -->
<Input bind:node prop="name" />
<Button icon="plus" label="Click me" on:click={myButtonFunc}/>
This will be the result:
Documentation and your node code are separated. Just create a my-node-name.doc.html for a html or a my-node-name.doc.md for a markdown documentation file and SIR will merge it later. Further examples can be found here.
<p>This is a documentation for my node in html.</p>
<h3>Inputs</h3>
<dl class="message-properties">
<dt>payload
<span class="property-type">string | buffer</span>
</dt>
<dd> the payload of the message to publish. </dd>
<dt class="optional">topic
<span class="property-type">string</span>
</dt>
<dd> the MQTT topic to publish to.</dd>
</dl>
<h3>Details</h3>
<p>Documentation is very important!</p>
This is a markdown documentation.
### Inputs
: payload (string | buffer) : the payload of the message to publish.
: *topic* (string) : the MQTT topic to publish to.
### Details
Documentation is very important!
With SIR you can easily use the following components to create your awesome Node-RED Node.
You can also find more and complexer examples in the test-node.svelte file. There are also detailed comments for each component: source code.
This component creates buttons. Just set the needed properties:
false
) or to set the path to a specific translation file.Events:
<script>
or directly like the example below.<Button small icon='edit' on:click={() => alert('You clicked edit.')} />
This component creates a button group similar to a toggle group. Main difference is that we place the button directly inside the button group and have therefor full control over the styling. A button is also not selected after clicking.
<ButtonGroup label="A button group">
<Button inline icon="truck" label="We are also indented" />
<Button inline icon="globe" label="I am number 2" />
<Button inline icon="trash" label="I am the third button" />
</ButtonGroup>
Callouts are info boxes which will highlight important information.
<Callout type="info">
<span slot="header">I am a callout infobox</span>
You can use <b>html</b> <u>styling</u> and fill me with useful tips!'
</Callout>
Callout properties:
The Collapsible component is great if you want to hide content that is seldomly needed or in case you want to define a kind of tree editor. For the latter case you should set the indented flag.
<Collapsible collapsed label="Click me">
<Input maximize value="Some content."/>
<Collapsible indented label="More content with extra long label">
<Input maximize value="More content. :)" />
<Input maximize value="And even more! :o" />
</Collapsible>
</Collapsible>
false
) or to set the path to a specific translation file.The config node is a special <Input>
component. Just set in the defaults in type the name of your config node and in the Input component the type "config".
How to create custom nodes can be found here:.
If you are editing a node which is using the config node input and you want to react directly after changing the config node, you can use the Node-RED filter function. Please note that SIR uses two properties to enable changing the active node values (see example below).
The following properties are valid and are identical to the normal <Input>
component: node, prop, icon, label, id, disabled, maximize, i18n.
defaults: {
// mqtt-broker is a standard Node-RED config node
broker: { type: 'mqtt-broker', label: 'Broker', filter: function (configNode, node) {
// a function to react after changing the config node in your node
console.log(configNode, node)
}},
}
<Input type='config' {node} prop="broker"/>
The editable list component helps you to visualize and to edit array variables. Unlike most components we use 'default' only to save the value of the array. Note: This component won't use jQuery since version 1.0.4 which leads to a different sorting animation as the standard Node-Red EditableList.
defaults: {
values: { value: ["Hello", "world", "here", "I", "am."] },
}
<EditableList {node} label="Values" icon="list" bind:elements={node.values} let:element={value} let:index sortable removable addButton >
<Input inline maximize {value} on:change={(e) => node.values[index] = e.detail.value} placeholder="Value name" disabled={node.disableInput}></Input>
</EditableList>
Editable list properties:
false
) or to set the path to a specific translation file.Events
Slots
Attention: The bound elements are automatically iterated using their index as id. This may lead to unexpected behaviour when removing elements from the list. To prevent this, your elements may define an id property that is used as an alternative to the index.
To render an element, assign the element to a variable (here: value). You can then render them in any way you like within the EditableList's HTML content. Keep in mind that you may not bind to the node from within here as your elements are not direct children of your node. Use the value property and the change event instead.
Groups allow to render a border around other components.
<Group label="Just grouped content" icon="minus">
<Input maximize label="First" value="First input" disabled={node.disableInput}/>
<Input maximize label = "Second" value="Second input" disabled={node.disableInput}/>
</Group>
Group properties:
false
) or to set the path to a specific translation file.Creates a Input field. If you want to refer to one of your 'default' properties like name in this example
RED.nodes.registerType("my-node-name", {
category: "common",
defaults: {
name: { value: "", label: "Name" }
},...
you can create your Input field simply with:
<Input bind:node prop="name"/>}
Input has the following properties. Properties markes with '*' can be stated in 'default':
true
false
) or to set the path to a specific translation file.false
if you don't want a label.<script>
tag.Prepare your credenctials like in the credentials readme.
Use instead of the Node-RED html input this code.
<Input bind:node prop="username" credentials />
<Input bind:node prop="password" credentials type="password" />
Creates a Menu Entry. Together with Popover you can create a context menu like element (which have to be open with the left mouse button).
<MenuEntry on:click={ () => alert('you clicked entry 1') }>Entry 1</MenuEntry>
<MenuEntry on:click={ () => alert('you clicked entry 2') }>Entry 2</MenuEntry>
<MenuEntry expandable title="Header 1">
<MenuEntry on:click={ () => alert('you clicked entry 3') }>Entry 3</MenuEntry>
<MenuEntry on:click={ () => alert('you clicked entry 4') }>Entry 4</MenuEntry>
</MenuEntry>
MenuEntry properties:
A Panel is a Component with 2 boxes which are resizable. Just enter your content in either the top or bottom slot.
<Panel label="Resizable Panel" height="300" topHeight="180" border>
<div slot="top" style="padding: 5px;">
<Input label="Top input" placeholder="I am the top input"/>
<Callout type="info">
Hello world!
</Callout>
</div>
<div slot="bottom" style="padding: 5px;">
<Input label="Bottom input" placeholder="I am the bottom input"/>
<Callout type="warning">
Goodbye world!
</Callout>
</div>
</Panel>
Panel properties:
false
) or to set the path to a specific translation file.Creates a popup element at it's button position. If used with modal, it will close if clicked outside the popover element.
<Popover label={showPopover ? "Close Menu" : "Open Menu"} bind:showPopover={showPopover}>
<Input inline placeholder="I am a dummy input field"/>
<Button inline on:click={() => showPopover = false} label="Close Popover Menu"/>
</Popover>
Popover properties:
Create easy but complex Node-RED popups. You just need something to trigger the popup, like a button. If the popup is fixed you must import the closePopup function to close the popup again :).
Simple warning/error popups are also possible, but those are propably more easy to create with the RED.notify()
Translations within a Popup should be made with the RED._() function instead of data-i18n!
<script>
import Popup, { closePopup } from 'svelte-integration-red/components/Popup.svelte' // <-- neccessary import to close popups if fixed
let showRiddlePopup = false
let keysPopup = {
enter: () => {
if (node.insidePopup === 'friend') {
closePopup("riddlePopup")
} else {
alert('think again Gandalf!')
}
},
escape: () => {
alert('You pressed escape. The popup will close now')
closePopup("riddlePopup")
},
a: () => {
alert('There is no a in friend!')
}
}
</script>
<Button label='Show riddle popup' on:click={ () => showRiddlePopup = true }></Button>
<Popup id="riddlePopup" modal fixed bind:showPopup={showRiddlePopup} focus="insidePopup" keyboard={keysPopup}>
<h2>Speak "friend" and enter!</h2>
<!-- use RED._(...) instead of <span data-i18n="..." /> for translating texts -->
<span>{RED._('my-node/my-node:popup.text')}</span>
<Input bind:node type='text' prop='insidePopup'></Input>
<!-- use svelte:fragment to remove unneccessary slot DOM -->
<svelte:fragment slot="buttons">
<Button label='Okay' primary disabled={node.insidePopup !== 'friend'} on:click={() => closePopup("riddlePopup")} />
<Button label='Cancel' on:click={cancelPopup} />
<Button label='Give me a hint' on:click={ () => alert("It's literal 'friend' Gandalf! And don't speak it out, but type it in the field...") } />
</svelte:fragment>
</Popup>
Popup properties:
This is just a simple row. Usually components use this component automatically to keep the same distance.
If you want to combine input elements within one row you must state the row component and set the input elements inside. Within your Row some elements like the input field must set to 'inline'.
<Row>
<Input type='text' inline maximize {node} prop="myVariable" />
<Button icon="trash" on:click={() => alert('You clicked on the trash can button')} />
<Button icon="plus" on:click={() => alert('You clicked on the plus button')} />
<Button icon="close" on:click={() => alert('You clicked on the close button')} />
</Row>
Row properties:
Events: click, dblclick, mouseenter, mouseleave
Select let the user choose from a dropdown list. The options can also be created by svelte #each.
defaults: {
selectionTwo: { value: "hello", label: "Selection two" }
}
<Select bind:node prop="selectionTwo">
<option value="hello">Hello</option>
<option value="world">World</option>
</Select>
Select properties (Properties with '*' can be stated in 'default'):
false
) or to set the path to a specific translation file.false
if you don't want a label.<script>
tag.With the help of those two components you can create tabs for your node. TabbedPane is the outer box of your Tabs. TabContent will hold the components that will be shown, if the tab is active.
Define Tabs by using an object where the key is the tabs name. The tab object key is the identifing name for the TabContent, the value can either be a string (label) or an object to show a label and an icon.
If you do not want to prevent rendering non active tabs use the "active" property at the TabContent component. This should only be necessary if your node has a tab with a complex structure (reduce loading times) or special stuff like recreate on opening.
<script>
let tabs = { "props": "Properties", "buttons": { name: "Buttons", icon: "truck" }, "list": "List", "groups": "Groups", "callouts": "Callouts", "popups": "Popups", "table": "Table" }
let activeTab
</script>
<TabbedPane bind:tabs bind:active={activeTab}>
<TabContent tab="props" active={activeTab}>
... <-- here is your content that will be shown if the tab is active
</TabContent>
<TabContent tab="buttons" active={activeTab}>
...
</TabContent>
...
</TabbedPane>
TabbedPane properties:
false
) or to set the path to a specific translation file.TabbedContent properties:
Create a table which can also edit its values.
defaults: {
myTable: {
value: {
header: ['col1', 'col2', 'col3', 'col4'],
rows: [
['hello', 'world', 'this is', 'my first row'],
['hello', 'world', 'this is', 'my second row'],
['hello', 'world', 'this is', 'my third row'],
['hello', 'world', 'this is', 'my fourth row']
]
}
},
}
<Table bind:node prop='myTable' editable={true} on:afterAddRow={(event) => console.log(event.detail)/>
Table properties:
false
) or to set the path to a specific translation file.Table has the following events:
Creates a multiline input field.
defaults: {
textarea: {value: 'This is a multiline input field', label: 'Textarea', placeholder: 'Please enter something'},
}
<Textarea bind:node prop='textarea' />
Textarea properties. (Properties markes with '*' can be stated in 'default'):
false
) or to set the path to a specific translation file.false
if you don't want a label.<script>
tag.With ToggleGroup you can select one or multiple related options. You can choose between buttons (standard), checkbox or radio input fields. With radio input can only choose one option!
Important: If multiple options are possible the result will always be stated in an array.
defaults: {
toggleSingle: { value: 'left', label:"Select one", icon:"check"},
toggleMulti: { value: ['you', 'more', 'one'], label:"Select multiple", icon:"list-ol"},
}
<script>
const groupedInputOptions = [
{ icon: 'align-left', label: 'label.left', value: 'left' },
{ icon: 'align-center', label: 'label.center', value: 'center' },
{ icon: 'align-right', label: 'label.right', value: 'right' },
{ icon: 'align-justify', label: 'label.justify', value: 'justify' }
]
const groupedInputOptionsMulti = [ 'you', 'can', 'select', 'more', 'than', 'one']
</script>
<ToggleGroup bind:node prop="toggleSingle" options={groupedInputOptions}/>
<ToggleGroup bind:node prop="toggleMulti" options={groupedInputOptionsMulti} gap={false} multiselect={true} />
ToggleGroup properties (Properties with '*' can be stated in 'default'):
false
) or to set the path to a specific translation file.false
if you don't want a label.<script>
tag.Add a tooltip to a custom component. Most Sir-Components have this component included and only needs the property "tooltip" and optional "tooltipOptions".
Tooltip properties:
Open a Node-RED tray editor. On closing it returns the modified data. On cancel it returns null.
<script>
import { openTray } from 'svelte-integration-red/components/utils/tray.js'
import MyTraySvelteComponent from './MyTraySvelteComponent.svelte'
const showTray = async () => {
const myData = { prop1: '', prop2: 2, prop3: true }
const result = await openTray(myData, { title: 'My tray editor', fixed: true }, MyTraySvelteComponent)
if (!result) {
RED.notify('Canceled', { type: 'warning', timeout: 30000 })
}
return result
}
A tree node is a hoverable and selectable component which is often used to show a hierarchical or repitite structure. A tree node can be the root element, a branch or the leaf. Bind selected and hovered to a variable. The hovering and selection of the row works for the tree headers automatically. As the structure of the tree childs is unknown you must set the id of the child row to the selected / hovered variable. Most of the times this can be done by get the closest ".sir-Row".
<script>
const myTree = [
{ label: "tree 1", icon: "search"},
{ label: "tree 2", icon: "truck"},
{ label: "tree 3", icon: "bullhorn"},
{ label: "tree 4", icon: "camera"}
]
let treeSelect
let treeHover
const getRow = (e) => e.target?.closest(".sir-Row")?.id
</script>
<style>
.myRow { padding: 2px 0; }
.myButton { margin-right: 6px; }
</style>
{#each myTree as { label, icon }, i}
<TreeNode {label} bind:selected={treeSelect} bind:hovered={treeHover}>
<Row clazz="myRow" maximize on:mouseenter={(e) => treeHover = getRow(e)} on:click={(e) => treeSelect = getRow(e)} >
<Button {icon} small inline clazz="myButton" on:click={(e) => treeSelect = getRow(e)} />
Welcome to {label}
</Row>
<TreeNode label="{label} subtree" bind:selected={treeSelect} bind:hovered={treeHover}>
<Input label="{label} input" />
</TreeNode>
</TreeNode>
{/each}
ToggleGroup has the following properties. Properties markes with '*' can be stated in 'default':
TypedInputs are Node-RED specific fields which combine a selection field and an input field.
defaults: {
content: { value: '', label: 'Content', validate: RED.validators.typedInput("contentType") },
contentType: { value: 'str', types: ["str", "bool", "num"] },
}
<TypedInput {node} prop="content" typeProp="contentType" bind:types={contentTypes} disabled={node.disableInput}/>
TypedInput has the following properties. Properties markes with '*' can be stated in 'default':
clazz: Add a class to this component.
disabled (boolean): Deactivate the in
i18n: This property can be used to either deactivate translation (set to false
) or to set the path to a specific translation file.
icon*: Set a Font-Awesome icon for your label. Just enter the icon name, without "fa fa-".
label*: Enter your label name, if empty it will state the key name. Set to false
if you don't want a label.
fading (boolean | number): Fades the component when hiding or showing (not on opening).
id: Set a custom id for your input field. The id will always have the prefix 'node-input-'. If no id is stated it will take the key name or create an uuid.
inline (boolean): Use this option if you want to put multiple items in one row. (Disables sir-Row class)
indented (boolean): Will shift your input field to the same position as it would have a label. put field.
maximize (boolean): Set your input field to the highest available width within your Row.
multiple (boolean): Allows multiple selection of values
multipleSelectionText (string): Set a custom text of the typedInput multi selection (Default: "x selected")
node: Bind your node property to get access to most features.
prop: The key name of your property within 'default'.
showLeftOptions (boolean): Show / Hide the type selection input on the left (Default: true
)
tooltip: Shows a tooltip on label / label icon.
tooltipOptions: See details under Tooltip component.
typeProp: Set the value of the (left) selection field.
type: Instead of typeProp: bind the value of the (left) selection field.
types: Instead of typeProp: bind the selectable values of the (left) selection field.
value*: Set the value of the (right) input field. If you use a custom variable it must be bound (bind:value=myVariable). This variable must be created within the svelte <script>
tag.
You can use SIR to automatically update your nodes if critical changes were made. This is useful if you changed a variable name, the type of a variable or the variable needs a (new) default value which must be set in all existing nodes. Just follow these steps:
// update existing node values => version === currrent node version
const clientUpdate = (version, node) => {
// version = {major: 1, minor: 2, patch: 3, tag: 'beta-1', string: '1.2.3-beta-1', aggregated: 1002003, parse = function('string') }
// version below 1.2.0
if (version.aggregated < version.parse('1.2').aggregated) {
// overwrite all node names
node.name = "my new name"
}
// lower than 2.1.3
if (version.aggregated < version.parse('2.1.3').aggregated) {
// node.myProp is depreceated and is now node.anotherProp
node.anotherProp = node.myProp // Attention: Add my prop in the addLegacyProperties function to enable getting the value a last time.
}
return node
}
// add temporarily node default keys
// this function is handy if you rename or split old values
const addLegacyProperties = () => {
const legacyProps = []
legacyProps.push('myProp')
return legacyProps
}
module.exports = {
clientUpdate,
legacyProps
}
// my-node-name-update.js
const serverUpdate = (config) => {
// TODO find a good way to put the version parse automatically here...
// const currentVersion = config._version
return config
}
// my-node-name.js
config = require('./test-node-update').serverUpdate(config)
If you want to test SIR you may run this project within Gitpod which is integrated into GitLab and just a click away. After your workspace is up and running, you need to run the following commands in order to compile the test-node's svelte template and run node-red:
gitpod /workspace/svelte-integration-red $ sir test-node
Found svelte-file test-node/test-node.svelte for node test-node.
Created HTML version of test-node/test-node.svelte
gitpod /workspace/svelte-integration-red $ node-red
You will see a message that a new port has been opened. Just click on "Open Browser" and Node-RED will open in a new browser tab where you can try the test node's behaviour. Feel free to change the template, recompile it and re-run Node-RED to get a feeling for SIR.
This repository and the code inside it is licensed under the MIT License. Read LICENSE for more information.
The following components / features have will be implemented
FAQs
Integrates Svelte into Node-RED so that the editor-part of a node can be developed using Svelte templates.
We found that svelte-integration-red demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Socket uncovers malicious Rust crates impersonating fast_log to steal Solana and Ethereum wallet keys from source code.
Research
A malicious package uses a QR code as steganography in an innovative technique.
Research
/Security News
Socket identified 80 fake candidates targeting engineering roles, including suspected North Korean operators, exposing the new reality of hiring as a security function.