Research
Recent Trends in Malicious Packages Targeting Discord
The Socket research team breaks down a sampling of malicious packages that download and execute files, among other suspicious behaviors, targeting the popular Discord platform.
node-red-contrib-ui-contextmenu
Advanced tools
Readme
A Node-RED node to display a popup contextmenu in the Node-RED dashboard
Special thanks to Stephen McLaughlin, my partner in crime for this node!
Run the following npm command in your Node-RED user directory (typically ~/.node-red):
npm install node-red-contrib-ui-contextmenu
:warning: CAUTION:
Remark: all below examples are also available via the Import menu of the Node-RED flow editor.
There are a few common ways to use this node:
Basically this node can be used to display a context menu (on top of any other dashboard widget), totally independent of other nodes. The following flow demonstrates how to show a contextmenu at some location. Both the location and the menu structure can be fixed or dynamic (via an input message):
[{"id":"98f5ee2c.61f16","type":"inject","z":"86187643.ae75e8","name":"Inject menu + position","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"showConfirmation":false,"confirmationLabel":"","x":280,"y":840,"wires":[["131ffbe2.c2f544"]]},{"id":"51a8f9b.d4a2008","type":"change","z":"86187643.ae75e8","name":"","rules":[{"t":"set","p":"position","pt":"msg","to":"{\"x\":100,\"y\":150}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":740,"y":840,"wires":[["dc8a767c.3a3d38"]]},{"id":"131ffbe2.c2f544","type":"change","z":"86187643.ae75e8","name":"","rules":[{"t":"set","p":"menu","pt":"msg","to":"[{\"text\":\"Options\",\"icon\":\"fa-list\",\"sub\":[{\"text\":\"Edit\",\"icon\":\"fa-edit\",\"topic\":\"edit\",\"payload\":[1,2,3,4,5],\"payloadType\":\"JSON\",\"outputField\":\"editArray\"},{\"text\":\"Cut\",\"icon\":\"fa-cut\",\"enabled\":true,\"topic\":\"cut\",\"payload\":\"true\",\"payloadType\":\"bool\"}]},{\"text\":\"---\"},{\"text\":\"Delete\",\"icon\":\"fa-trash\",\"enabled\":true,\"payload\":\"12\",\"payloadType\":\"num\"},{\"text\":\"---\"},{\"text\":\"Quit\",\"icon\":\"fa-times\",\"enabled\":false}]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":520,"y":840,"wires":[["51a8f9b.d4a2008"]]},{"id":"e5e8a905.5a2448","type":"inject","z":"86187643.ae75e8","name":"Inject menu","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"showConfirmation":false,"confirmationLabel":"","x":530,"y":760,"wires":[["7b5faf00.a6b4a"]]},{"id":"912cb88c.2b6e28","type":"inject","z":"86187643.ae75e8","name":"Inject nothing","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"showConfirmation":false,"confirmationLabel":"","x":750,"y":720,"wires":[["c6b11b3e.1d3508"]]},{"id":"7b5faf00.a6b4a","type":"change","z":"86187643.ae75e8","name":"","rules":[{"t":"set","p":"menu","pt":"msg","to":"[{\"text\":\"Options\",\"icon\":\"fa-list\",\"sub\":[{\"text\":\"Edit\",\"icon\":\"fa-edit\",\"topic\":\"edit\",\"payload\":[1,2,3,4,5],\"payloadType\":\"JSON\",\"outputField\":\"editArray\"},{\"text\":\"Cut\",\"icon\":\"fa-cut\",\"enabled\":true,\"topic\":\"cut\",\"payload\":\"true\",\"payloadType\":\"bool\"}]},{\"text\":\"---\"},{\"text\":\"Delete\",\"icon\":\"fa-trash\",\"enabled\":true,\"payload\":\"12\",\"payloadType\":\"num\"},{\"text\":\"---\"},{\"text\":\"Quit\",\"icon\":\"fa-times\",\"enabled\":false}]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":740,"y":760,"wires":[["a1697f44.2f4c8"]]},{"id":"c6b11b3e.1d3508","type":"ui_context_menu","z":"86187643.ae75e8","group":"22787703.a0e968","order":5,"width":0,"height":-1,"fontSize":16,"inputPositionXField":"50","inputPositionXType":"num","inputPositionYField":"60","inputPositionYType":"num","outputField":"payload","inputMenuField":"menu","inputMenuType":"fixed","menuItems":[{"id":"myid","icon":"fa-glass","label":"mylabel","topic":"mytopic","payload":"mypayload","payloadType":"str","visible":true,"enabled":true}],"colors":"native","textColor":"#000000","backgroundColor":"#ffffff","borderColor":"#626262","intervalLength":0,"intervalUnit":"secs","startTimerAtOpen":false,"startTimerAtLeave":true,"stopTimerAtEnter":true,"name":"","x":960,"y":720,"wires":[[]]},{"id":"a1697f44.2f4c8","type":"ui_context_menu","z":"86187643.ae75e8","group":"22787703.a0e968","order":5,"width":0,"height":-1,"fontSize":16,"inputPositionXField":"50","inputPositionXType":"num","inputPositionYField":"60","inputPositionYType":"num","outputField":"payload","inputMenuField":"menu","inputMenuType":"msg","menuItems":[],"colors":"native","textColor":"#000000","backgroundColor":"#ffffff","borderColor":"#626262","intervalLength":0,"intervalUnit":"secs","startTimerAtOpen":false,"startTimerAtLeave":true,"stopTimerAtEnter":true,"name":"","x":960,"y":760,"wires":[[]]},{"id":"66f558a1.128238","type":"inject","z":"86187643.ae75e8","name":"Inject position","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"showConfirmation":false,"confirmationLabel":"","x":530,"y":800,"wires":[["1d3334da.ce9ffb"]]},{"id":"75ccccc.2eca634","type":"ui_context_menu","z":"86187643.ae75e8","group":"22787703.a0e968","order":5,"width":0,"height":-1,"fontSize":16,"inputPositionXField":"position.x","inputPositionXType":"msg","inputPositionYField":"position.y","inputPositionYType":"msg","outputField":"payload","inputMenuField":"menu","inputMenuType":"fixed","menuItems":[{"id":"myid2","icon":"fa-search","label":"mylabel2","topic":"mytopic2","payload":"mypayload2","payloadType":"str","visible":true,"enabled":true}],"colors":"native","textColor":"#000000","backgroundColor":"#ffffff","borderColor":"#626262","intervalLength":0,"intervalUnit":"secs","startTimerAtOpen":false,"startTimerAtLeave":true,"stopTimerAtEnter":true,"name":"","x":960,"y":800,"wires":[[]]},{"id":"dc8a767c.3a3d38","type":"ui_context_menu","z":"86187643.ae75e8","group":"22787703.a0e968","order":5,"width":0,"height":-1,"fontSize":16,"inputPositionXField":"position.x","inputPositionXType":"msg","inputPositionYField":"position.y","inputPositionYType":"msg","outputField":"payload","inputMenuField":"menu","inputMenuType":"msg","menuItems":[],"colors":"native","textColor":"#000000","backgroundColor":"#ffffff","borderColor":"#626262","intervalLength":0,"intervalUnit":"secs","startTimerAtOpen":false,"startTimerAtLeave":true,"stopTimerAtEnter":true,"name":"","x":960,"y":840,"wires":[[]]},{"id":"1d3334da.ce9ffb","type":"change","z":"86187643.ae75e8","name":"","rules":[{"t":"set","p":"position","pt":"msg","to":"{\"x\":100,\"y\":150}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":740,"y":800,"wires":[["75ccccc.2eca634"]]},{"id":"22787703.a0e968","type":"ui_group","z":"","name":"Web push notifications","tab":"80f0e178.bbf4a","order":1,"disp":true,"width":"6","collapse":false},{"id":"80f0e178.bbf4a","type":"ui_tab","z":"","name":"Home","icon":"dashboard","order":1,"disabled":false,"hidden":false}]
Initially we implemented this node to be used in combination with our node-red-contrib-ui-svg node. The following flow explains how a context menu can be displayed on top of a vector graphics drawing:
[{"id":"2d30cf4e.41547","type":"ui_svg_graphics","z":"86187643.ae75e8","group":"2908388b.8114b8","order":1,"width":"14","height":"10","svgString":"<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" preserveAspectRatio=\"none\" x=\"0\" y=\"0\" viewBox=\"0 -0.05780346691608429 900 710.1156005859375\" width=\"100%\" height=\"100%\">\n <rect id=\"svgEditorBackground\" x=\"0\" y=\"0\" width=\"900\" height=\"710\" style=\"fill: none; stroke: none;\" />\n <image width=\"889\" height=\"703\" id=\"background\" xlink:href=\"https://www.roomsketcher.com/wp-content/uploads/2016/10/1-Bedroom-Floor-Plans.jpg\" />\n <text id=\"light_bulb_kitchen\" style=\"fill:blue;\" x=\"180\" y=\"110\" font-family=\"FontAwesome\" fill=\"blue\" stroke=\"black\" font-size=\"60\" text-anchor=\"middle\" alignment-baseline=\"middle\" stroke-width=\"1\">fa-lightbulb-o </text>\n</svg>","clickableShapes":[{"targetId":"#light_bulb_kitchen","action":"click","payload":"light_icon_clicked","payloadType":"str","topic":"light_icon_clicked"}],"smilAnimations":[],"bindings":[{"selector":"#banner","bindSource":"payload.title","bindType":"text","attribute":""},{"selector":"#camera_living","bindSource":"payload.position.x","bindType":"attr","attribute":"x"},{"selector":"#camera_living","bindSource":"payload.camera.colour","bindType":"attr","attribute":"fill"}],"showCoordinates":false,"autoFormatAfterEdit":false,"showBrowserErrors":false,"outputField":"my_output_field","editorUrl":"http://drawsvg.org/drawsvg.html","directory":"","panning":"disabled","zooming":"disabled","panOnlyWhenZoomed":false,"doubleClickZoomEnabled":false,"mouseWheelZoomEnabled":false,"name":"","x":600,"y":1480,"wires":[["24952289.cacf1e","2e6ffce7.748b34"]]},{"id":"24952289.cacf1e","type":"debug","z":"86187643.ae75e8","name":"Clicked SVG shape","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":830,"y":1540,"wires":[]},{"id":"2e6ffce7.748b34","type":"ui_context_menu","z":"86187643.ae75e8","group":"2908388b.8114b8","order":5,"width":0,"height":-1,"fontSize":16,"inputPositionXField":"event.pageX","inputPositionXType":"msg","inputPositionYField":"event.pageY","inputPositionYType":"msg","outputField":"payload","inputMenuField":"","inputMenuType":"fixed","menuItems":[{"id":"ON","icon":"fa-toggle-on","label":"Light on","topic":"light_kitchen_on","payload":"light_kitchen_on","payloadType":"str","visible":true,"enabled":true},{"id":"OFF","icon":"fa-toggle-off","label":"Light off","topic":"light_kitchen_off","payload":"light_kitchen_off","payloadType":"str","visible":true,"enabled":true}],"colors":"native","textColor":"#000000","backgroundColor":"#ffffff","borderColor":"#626262","intervalLength":"0","intervalUnit":"secs","startTimerAtOpen":false,"startTimerAtLeave":true,"stopTimerAtEnter":true,"name":"","x":820,"y":1480,"wires":[["14b0feb6.5bea51"]]},{"id":"14b0feb6.5bea51","type":"debug","z":"86187643.ae75e8","name":"Clicked menu item","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":1050,"y":1480,"wires":[]},{"id":"2908388b.8114b8","type":"ui_group","z":"","name":"Floorplan test","tab":"a28ff08f.3a822","order":1,"disp":true,"width":"14","collapse":false},{"id":"a28ff08f.3a822","type":"ui_tab","z":"","name":"SVG","icon":"dashboard","disabled":false,"hidden":false}]
msg.event.pageX
and msg.event.pageY
which define the mouse click position)!msg.topic
which contains the information about which SVG element has been clicked).A short demo to demonstrate the result:
Other UI nodes (like Button and node-red-contrib-ui-state-trail nodes) also have a msg.event
in their output messages, which means a contextmenu can be displayed when such a UI node is being clicked.
Thanks to Paul Reed for demonstrating how to use a contextmenu - in combination with a dashboard button - to create a hierarchical dashboard menu:
The following (simplified) flow explains how to accomplish something like this, based on the bounding box of the clicked element:
[{"id":"45f42449.b3c8dc","type":"ui_button","z":"86187643.ae75e8","name":"","group":"2908388b.8114b8","order":9,"width":"1","height":"1","passthru":false,"label":"","tooltip":"","color":"","bgcolor":"","icon":"fa-bars","payload":"button_clicked","payloadType":"str","topic":"","x":590,"y":1640,"wires":[["5b2478ff.043158"]]},{"id":"5b2478ff.043158","type":"change","z":"86187643.ae75e8","name":"Get X and Y from bbox","rules":[{"t":"set","p":"position.x","pt":"msg","to":"event.bbox[2]-9","tot":"jsonata"},{"t":"set","p":"position.y","pt":"msg","to":"event.bbox[3]+18","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":780,"y":1640,"wires":[["b3b5bcfc.cf079"]]},{"id":"b3b5bcfc.cf079","type":"ui_context_menu","z":"86187643.ae75e8","group":"2908388b.8114b8","order":5,"width":0,"height":-1,"fontSize":16,"inputPositionXField":"position.x","inputPositionXType":"msg","inputPositionYField":"position.y","inputPositionYType":"msg","outputField":"payload","inputMenuField":"","inputMenuType":"fixed","menuItems":[{"id":"ZOOM_OUT","icon":"fa-search-minus","label":"Light on","topic":"zoom out","payload":"zoom out","payloadType":"str","visible":true,"enabled":true},{"id":"ZOOM_IN","icon":"fa-search-plus","label":"Light off","topic":"zoom in","payload":"zoom in","payloadType":"str","visible":true,"enabled":true}],"colors":"native","textColor":"#000000","backgroundColor":"#ffffff","borderColor":"#626262","intervalLength":"0","intervalUnit":"secs","startTimerAtOpen":false,"startTimerAtLeave":true,"stopTimerAtEnter":true,"name":"","x":1000,"y":1640,"wires":[[]]},{"id":"2908388b.8114b8","type":"ui_group","z":"","name":"Floorplan test","tab":"a28ff08f.3a822","order":1,"disp":true,"width":"14","collapse":false},{"id":"a28ff08f.3a822","type":"ui_tab","z":"","name":"SVG","icon":"dashboard","disabled":false,"hidden":false}]
Remark: in this case it might be useful to hide the standard Node-RED dashboard hamburger menu icon:
The font size of the menu items
Specify how the position of the context menu on the screen will be specified:
Msg: The input message must contain the X/Y coordinate number in the specified msg field. This way the the menu position can be updated dynamically.
Num: The fixed X/Y coordinate number must be specified on the config screen.
Specify where the payload output from clicking a menu item should appear in the msg
.
NOTE: for a msg based menu, this can be set per menu item via the property outputField
(see Message Based example below)
Specify how the menu items of the context menu will be specified:
Fixed: A table will be displayed, to enter the menu items. The following properties can be set for every menu item:
msg.topic
that will be send in the output message.msg.payload
that will be send in the output message. NOTE: if the "Output to" field is set to something other than "payload", then the output will appear in that property of msg
Msg: the input message must contain the menu (as a JSON array of menu items) in the specified msg field. This way the the menu structure can be updated dynamically.
An example input message:
"menu": [
{
"text": "Options",
"icon": "fa-list",
"sub": [
{
"text": "Edit",
"icon": "fa-edit",
"topic": "edit",
"payload": [ 1, 2, 3, 4, 5 ],
"payloadType": "JSON",
"outputField" : "editArray"
},
{
"text": "Cut",
"icon": "fa-cut",
"enabled": true,
"topic": "cut",
"payload": "true",
"payloadType": "bool"
}
]
},
{
"text": "---"
},
{
"text": "Delete",
"icon": "fa-trash",
"enabled": true,
"payload": "12",
"payloadType": "num"
},
{
"text": "---"
},
{
"text": "Quit",
"icon": "fa-times",
"enabled": false
}
]
Example flow with nested sub-menu:
[{"id":"fd05877e.cb0588","type":"ui_svg_graphics","z":"60ad596.8120ba8","group":"9f5b4cff.15cd3","order":1,"width":"14","height":"10","svgString":"<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" preserveAspectRatio=\"none\" x=\"0\" y=\"0\" viewBox=\"0 0 900 710\" width=\"100%\" height=\"100%\">\n <rect id=\"svgEditorBackground\" x=\"0\" y=\"0\" width=\"900\" height=\"710\" style=\"fill:none;stroke:none;\" />\n <image width=\"889\" height=\"703\" id=\"background\" xlink:href=\"https://www.roomsketcher.com/wp-content/uploads/2016/10/1-Bedroom-Floor-Plans.jpg\" />\n <circle id=\"mycircle\" cx=\"182.901\" cy=\"91.4841\" style=\"fill:rosybrown;stroke:black;stroke-width:1px;\" r=\"48\" /></svg>","clickableShapes":[{"targetId":"#mycircle","action":"click","payload":"camera_living","payloadType":"str","topic":"camera_living"}],"smilAnimations":[],"bindings":[{"selector":"#banner","bindSource":"payload.title","bindType":"text","attribute":""},{"selector":"#camera_living","bindSource":"payload.position.x","bindType":"attr","attribute":"x"},{"selector":"#camera_living","bindSource":"payload.camera.colour","bindType":"attr","attribute":"fill"}],"showCoordinates":false,"autoFormatAfterEdit":false,"outputField":"","editorUrl":"http://drawsvg.org/drawsvg.html","directory":"","name":"","x":920,"y":120,"wires":[["a4c84c93.c001d"]]},{"id":"c65edd5a.1c031","type":"ui_context_menu","z":"60ad596.8120ba8","group":"9f5b4cff.15cd3","order":5,"width":0,"height":-1,"fontSize":16,"position":"msg","outputField":"payload","xCoordinate":50,"yCoordinate":50,"menu":"msg","menuItems":[],"colors":"native","textColor":"#000000","backgroundColor":"#ffffff","borderColor":"#626262","name":"","x":1360,"y":120,"wires":[[]]},{"id":"a4c84c93.c001d","type":"change","z":"60ad596.8120ba8","name":"","rules":[{"t":"set","p":"menu","pt":"msg","to":"[{\"text\":\"Options\",\"icon\":\"fa-list\",\"sub\":[{\"text\":\"Edit\",\"icon\":\"fa-edit\",\"topic\":\"edit\",\"payload\":[1,2,3,4,5],\"payloadType\":\"JSON\",\"outputField\":\"editArray\"},{\"text\":\"Cut\",\"icon\":\"fa-cut\",\"enabled\":true,\"topic\":\"cut\",\"payload\":\"true\",\"payloadType\":\"bool\"}]},{\"text\":\"---\"},{\"text\":\"Delete\",\"icon\":\"fa-trash\",\"enabled\":true,\"payload\":\"12\",\"payloadType\":\"num\"},{\"text\":\"---\"},{\"text\":\"Quit\",\"icon\":\"fa-times\",\"enabled\":false}]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":1140,"y":120,"wires":[["c65edd5a.1c031"]]},{"id":"9f5b4cff.15cd3","type":"ui_group","z":"","name":"Floorplan test","tab":"dabfe25b.13bc1","disp":true,"width":"14","collapse":false},{"id":"dabfe25b.13bc1","type":"ui_tab","z":"","name":"SVG","icon":"dashboard","disabled":false,"hidden":false}]
msg.topic
that will be send in the output message. If left empty, topic
will default the the path
of this menu item.msg.payload
that will be send in the output message. If left empty, message
will default the the text
of this menu item.msg.payload
. Allowable types are 'JSON', 'str', 'bool', 'num'. payload
will be converted to this type.payload
to be send to an alternative property of msg
The message-based approach has the advantage that it offers nested menu items, which is currently not available in the config screen!
The label an icon are both optional. This means you can use both or only one of them, to achieve various effects.
0
the context menu will stay visible, until a menu item is selected or outside the menu is being clicked.3 seconds
this means that the context menu will automatically disappear when the context menu is out of focus during at least 3 seconds.
Demo of auto hide in 3 seconds (notice the timer status in the console):
Specify how the colors of the context menu should look like:
Native: The default CSS colors of this node will be used.
Match dashboard theme: The colors of the currently selected dashboard theme will be used.
Custom: Three color pickers will be displayed, which allow you to specify your custom colors.
Demo for the dashboard's built-in dark theme (menu1=custom, menu2=theme, menu2=native):
Demo for the dashboard's built-in light theme (menu1=custom, menu2=theme, menu2=native):
As soon as a menu item has been clicked, an output message will be send. It is up to the next nodes in the flow, to determine how the menu item should be handled.
The output message will contain following fields:
msg
. This can also be overriden per menu item by setting outputField
in the menu itemdsaul whose menu this node is based on https://github.com/dsaul/contextmenujs/
FAQs
A Node-RED widget to show a popup contextmenu in a Node-RED dashboard
The npm package node-red-contrib-ui-contextmenu receives a total of 32 weekly downloads. As such, node-red-contrib-ui-contextmenu popularity was classified as not popular.
We found that node-red-contrib-ui-contextmenu demonstrated a not healthy version release cadence and project activity because the last version was released 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
The Socket research team breaks down a sampling of malicious packages that download and execute files, among other suspicious behaviors, targeting the popular Discord platform.
Security News
Socket CEO Feross Aboukhadijeh joins a16z partners to discuss how modern, sophisticated supply chain attacks require AI-driven defenses and explore the challenges and solutions in leveraging AI for threat detection early in the development life cycle.
Security News
NIST's new AI Risk Management Framework aims to enhance the security and reliability of generative AI systems and address the unique challenges of malicious AI exploits.