Dashboard 1.0 - URL redirect flow (workaround)

I'm aware Dashboard 1.0 is deprecated, however....

Problem
You maintain links to Dashboard 1.0 pages ("tabs") from your browser bookmarks, or maybe your mobile device. e.g. to take you straight to a specific page of your dashboard.

Node-RED dashboard generates URLs that end like this:

/ui/#!/0
/ui/#!/1
/ui/#!/2

Those numbers are array indexes, not persistent identifiers. When you delete or reorder tabs, Dashboard rebuilds the internal array, so your links break.

Solution
Import this tiny flow (3 nodes). You can set up mappings yourself (in the function node), then it will automatically map e.g.

http://your_nodered_server:1880/red/ui/bedroom_lights

to

http://your_nodered_server:1880/red/ui/#!/1

(Yes I know it's not rocket science, but I had been meaning to do this for a while, so thought I'd post it here in case anyone else wanted this workaround...)

[{"id":"http_in_router","type":"http in","z":"a09201df.15c79","name":"Stable URL router","url":"/red/ui/:page","method":"get","upload":false,"swaggerDoc":"","x":250,"y":100,"wires":[["fn_router"]]},{"id":"fn_router","type":"function","z":"a09201df.15c79","name":"Lookup + build redirect","func":"// http://your_server:1880/red/nice_url_you_choose redirects to the \n// first NodeRED dashboard page\n\n// So when IDs change, just come here and update them\n\n const map = {\n    \"nice_url_you_choose\": \"0\",\n    \"test\": \"1\"\n};\n\nconst page = msg.req.params.page;\nconst tab = map[page];\n\nif (!tab) {\n    msg.statusCode = 404;\n    msg.payload = `Unknown page: ${page}`;\n    return msg;\n}\n\nmsg.statusCode = 302;\nmsg.headers = {\n    \"Location\": `/red/ui/#!/${tab}`\n};\nmsg.payload = \"\";\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":510,"y":100,"wires":[["http_response_router"]]},{"id":"http_response_router","type":"http response","z":"a09201df.15c79","name":"Send redirect","statusCode":"","headers":{},"x":770,"y":100,"wires":[]}]

Nice! And a good use of the http-in/-response nodes.

BTW, corrected an issue with your pasted flow - there was an extra line-break.

I've been looking at a couple of examples of mini-proxies recently that do something similar but outside of Node-RED. They have the advantage of being able to remove port numbers. But the disadvantage of requiring extra setup of course.

I great idea, but you still need to remember the indexes if delete or reorder tabs. I was thinking you were reading in the actual tab names from the config, and using those as the URLs.

Would result in, for example,

http://your_nodered_server:1880/red/ui/shelly_lightbulb
http://your_nodered_server:1880/red/ui/music_-_study
etc

I may have a look to see if this is possible.
Thanks for the idea.

True, but you can just go and find out the indexes after deleting and reordering tabs then update the mapping. Yes it’s very rudimentary :slight_smile:

Let me know if it’s possible to retrieve the tab name from the ID. That would be great. I know it’s possible to refer to groups by name using the UI Control node, but haven’t seen if there are other means.

Maybe there’s another really dirty hack which could be to periodically scan your flows.json file and read the tab names from there, as they are ordered in the json file it’s possible to determine the page number…

Thanks for correcting that. I installed Traefik to do various things like that, but - as always - didn’t get around to it!

Having looked into this, it seems it was a very common request to use names instead of changing ids to access the dashboard. I found about 10 such topics on this board alone.

It never got "fixed" and there is no way of getting the tab list without a code change, which I believe never got done.

Looks like your method, and others that are very similar is the only way to do what you want to do.

:frowning:

It is possible that Dave (who is really the last maintainer of D1 I think) might accept a pull request if you were able to offer one? But there isn't anyone actively maintaining it otherwise I'm afraid.

It is possible to get a ordered array of ui_tab names after you reorder dashboard tabs.
e.g. using flow.json

[{"id":"de763f434970e28c","type":"file in","z":"613df62afc8a16bf","name":"","filename":".node-red/flows.json","filenameType":"str","format":"utf8","chunk":false,"sendError":false,"encoding":"none","allProps":false,"x":355,"y":720,"wires":[["2780b0c44c48a134"]]},{"id":"b01384206e718489","type":"inject","z":"613df62afc8a16bf","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":165,"y":735,"wires":[["de763f434970e28c"]]},{"id":"2780b0c44c48a134","type":"json","z":"613df62afc8a16bf","name":"","property":"payload","action":"","pretty":false,"x":410,"y":765,"wires":[["46baed34edba7a60"]]},{"id":"46baed34edba7a60","type":"change","z":"613df62afc8a16bf","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"$$.payload[$.type = \"ui_tab\"]^(order).name","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":550,"y":765,"wires":[["b050b81619cf2680"]]},{"id":"b050b81619cf2680","type":"debug","z":"613df62afc8a16bf","name":"debug 18","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":525,"y":810,"wires":[]}]

Thanks to @E1cid and based off @hazymat's work, this should now work.

using /ui/{pagename} will redirect to the correct tab index. It does not take into account any hidden tabs.

Everything is converted to lowercase and spaces replaced with underscores (_). So using my screen shot above:

"Music - Group" becomes /ui/music_-_group
"Shelly H&T" becomes /ui/shelly_h&t

[{"id":"de763f434970e28c","type":"file in","z":"c07b047ac4d1031c","name":"Get Flow Data","filename":"/data/flows.json","filenameType":"str","format":"utf8","chunk":false,"sendError":false,"encoding":"none","allProps":false,"x":300,"y":360,"wires":[["2780b0c44c48a134"]]},{"id":"2780b0c44c48a134","type":"json","z":"c07b047ac4d1031c","name":"JSON","property":"payload","action":"","pretty":false,"x":470,"y":360,"wires":[["46baed34edba7a60"]]},{"id":"46baed34edba7a60","type":"change","z":"c07b047ac4d1031c","name":"Get Tab Names","rules":[{"t":"set","p":"payload","pt":"msg","to":"$$.payload[$.type = \"ui_tab\"]^(order).name","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":640,"y":360,"wires":[["fn_router"]]},{"id":"http_in_router","type":"http in","z":"c07b047ac4d1031c","name":"/ui/:page","url":"/ui/:page","method":"get","upload":false,"skipBodyParsing":false,"swaggerDoc":"","x":120,"y":360,"wires":[["de763f434970e28c"]]},{"id":"fn_router","type":"function","z":"c07b047ac4d1031c","name":"Redirect","func":"const payload = msg.payload;\nconst pageNames = payload.map(e => e.toLowerCase());\nconst pageReq = msg.req.params.page.replaceAll(\"_\",\" \");\n\nconst reqTab = pageNames.indexOf(pageReq.toLowerCase());\n\nif ((!reqTab) || (reqTab == -1)) {\n    msg.statusCode = 404;\n    msg.payload = `Unknown page: ${pageReq}`;\n    return msg;\n}\n\nmsg.statusCode = 302;\nmsg.headers = {\"Location\": `http://home.lan:1880/ui/#!/${reqTab}`};\nmsg.payload = \"\";\n\nreturn msg;\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":820,"y":360,"wires":[["http_response_router"]]},{"id":"http_response_router","type":"http response","z":"c07b047ac4d1031c","name":"Response","statusCode":"","headers":{},"x":980,"y":360,"wires":[]}]

Super, finally solved one of those minor (and possibly niche) annoyances I've had with NodeRED dashboard for years :slight_smile: