# Device overview dashboard2

**URL:** <https://discourse.nodered.org/t/device-overview-dashboard2/89911>\
**Category:** General\
**Tags:** dashboard-2, uibuilder\
**Created:** [31 July 2024 16:13 UTC](https://discourse.nodered.org/t/device-overview-dashboard2/89911 "2024-07-31T16:13:15Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![KarolisL](https://avatars.discourse-cdn.com/v4/letter/k/a4c791/32.png) [@KarolisL](https://discourse.nodered.org/u/KarolisL)\
**Post date:** [31 July 2024 16:13 UTC](https://discourse.nodered.org/t/device-overview-dashboard2/89911/1 "2024-07-31T16:13:15Z")

</div>

Inspired by:

> [@Bookmark page with reachability verification](https://discourse.nodered.org/t/bookmark-page-with-reachability-verification/75728):
>
> I wanted a clean bookmark page for my internal webpages that also verifies if the pages are reachable (ie. servers are running). This flow will produce a responsive endpoint on http://\<node-red-host:port\>/bookmarks that looks good on any device (or at least during my testing). Indicators: green reachable, red (subtle blinking) unreachable, transparent is not checked. Links open in new tabs. The flow Edit the "bookmarks" node to setup the links (in yaml format): - title: P…

and

> [@Web endpoint status dashboard - uibuilder zero-code example](https://discourse.nodered.org/t/web-endpoint-status-dashboard-uibuilder-zero-code-example/75740):
>
> Another advantage to using uibuilder over manually created endpoints is that uibuilder endpoints - including their websockets - adjust to Node-RED's configuration. On my dev machine for example, your connection to the websocket does not work without adjusting the web page because I deliberately set httpNodeRoot to /nr in order to test that URL's still work. The constant refresh also seems to do something odd to my browser giving a regular flash - I think it was coming from the Editor but it was…

My use case: I don't know how many devices are active (each project has different amount of clients) I just listen for incoming MQTT reports and present latest state of data.

The main purpose was to play around with flex-grid and dynamically show newly appearing devices.

![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/8/f/8ff83a26a013b50b117fa4084608d8057fd1ae0c.png)

Just wanted to share, hoping it might save somebody few hours of googling and chatGPT'ing

P.S I really like dialogs to be able to show that extra bit of information 😃

Reference links:

- [https://codepen.io/LeoKi12/pen/zLKmBz](https://codepen.io/LeoKi12/pen/zLKmBz)
- [Vuetify Example Pen (codepen.io)](https://codepen.io/betanet/pen/YzENBRv)
- [https://codepen.io/luizarusso/pen/MWYbZVP](https://codepen.io/luizarusso/pen/MWYbZVP) - really nice example how to combine multiple stuff

```auto
[{"id":"d29341aed8bcf076","type":"inject","z":"f9678504054d3675","name":"Test","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"plc\":\"F001\",\"status\":[{\"title\":\"PLC connection status\",\"icon\":\"server\",\"name\":\"PLC state\",\"state\":\"on\"},{\"title\":\"Camera connection status\",\"icon\":\"camera\",\"name\":\"Camera state\",\"state\":\"off\"}],\"data\":{\"plc\":{\"lastHeartBeat\":\"2024-08-02 13:00:00.123\"},\"camera\":{\"lastHeartBeat\":\"2024-08-01 12:00:00.123\"}}},{\"plc\":\"F002\",\"status\":[{\"title\":\"PLC connection status\",\"icon\":\"server\",\"name\":\"PLC state\",\"state\":\"on\"},{\"title\":\"Camera connection status\",\"icon\":\"camera\",\"name\":\"Camera state\",\"state\":\"on\"}],\"data\":{\"plc\":{\"lastHeartBeat\":\"2024-08-02 13:00:00.123\"},\"camera\":{\"lastHeartBeat\":\"2024-08-01 12:00:00.123\"}}},{\"plc\":\"F003\"},{\"plc\":\"F004\"},{\"plc\":\"F005\"},{\"plc\":\"F006\"},{\"plc\":\"F007\"},{\"plc\":\"F008\"}]","payloadType":"json","x":130,"y":200,"wires":[["9e88cf9f9c542282","a9eea5ec13ab29cc"]]},{"id":"a9eea5ec13ab29cc","type":"ui-template","z":"f9678504054d3675","group":"dc2f0f89e050476e","page":"","ui":"","name":"SystemOverview","order":0,"width":"12","height":"1","head":"","format":"<template>\n <v-row align-items=\"start\">\n <v-col v-for=\"(item, index) in items\" :key=\"item.id\">\n <v-card class=\"mx-auto\" min-width=\"300px\" max-width=\"100%\">\n <v-card-title>\n <h4 class=\"headline mb-0\" v-text=\"item.plc\"></h4>\n <v-btn class=\"ml-auto\" color=\"blue-grey-lighten-5\" icon=\"mdi-information-variant\" @click=\"showInfo(item)\"></v-btn>\n </v-card-title>\n <v-card-text class=\"text--primary\">\n\n <v-treeview v-model=\"tree\" :items=\"item.status\">\n <template v-slot:append=\"{ item}\">\n <v-icon :style=\"{color: getColor(item.state)}\"> {{ getIcon(item.icon) }} </v-icon>\n </template>\n </v-treeview>\n\n <v-dialog v-model=\"dialog\" max-width=\"22%\">\n <v-card>\n <v-card-title>\n {{plcName}} data\n </v-card-title>\n <v-card-text>\n <pre style=\"overflow:auto\">{{ infoData }} </pre>\n </v-card-text>\n <v-card-actions>\n <v-btn color=\"black\" text @click=\"dialog = false\">Close</v-btn>\n </v-card-actions>\n </v-card>\n </v-dialog>\n\n </v-card-text>\n </v-card>\n </v-col>\n </v-row>\n\n</template>\n\n<script>\nexport default {\n data: () => ({\n fd: [],\n plcName: '',\n selectedFile: null,\n dialog: false,\n infoData: {},\n initiallyOpen: ['public'],\n icons: {\n server: 'mdi-check-network',\n camera: 'mdi-check-network',\n },\n tree: [],\n items: [],\n editedIndex: -1,\n }),\n\n methods: {\n getIcon(icon) {\n return this.icons[icon] || 'mdi-connection';\n },\n getColor(state) {\n if (state === 'on') {\n return 'green';\n } else if (state === 'off') {\n return 'red';\n } else {\n return 'grey'; // default color\n }\n },\n showInfo(data) {\n this.plcName = data.plc\n this.infoData = data.data || 'No data available';\n this.dialog = true;\n }\n },\n\n watch: {\n msg: function(){\n const items = this.msg.payload\n this.items = items\n },\n dialog(val) {\n val || this.close();\n },\n },\n};\n</script>\n\n<style>\n .v-col {\n flex: 0 0 100px; /* Ensure the minimum width of each column */\n }\n .v-card-title {\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n</style>\n","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":330,"y":200,"wires":[[]]},{"id":"dc2f0f89e050476e","type":"ui-group","name":"System overview","page":"9f326ef91194bc5d","width":"12","height":"1","order":1,"showTitle":true,"className":"","visible":"true","disabled":"false"},{"id":"9f326ef91194bc5d","type":"ui-page","name":"Overview","ui":"65cf368b48fdb5f6","path":"/Overview","icon":"home","layout":"grid","theme":"d1f899a55e3aa9ab","order":4,"className":"","visible":"true","disabled":"false"},{"id":"65cf368b48fdb5f6","type":"ui-base","name":"My Dashboard","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control","ui-file-input"],"showPathInSidebar":false,"showPageTitle":true,"navigationStyle":"default","titleBarStyle":"default"},{"id":"d1f899a55e3aa9ab","type":"ui-theme","name":"Default Theme","colors":{"surface":"#ffffff","primary":"#c2c5c7","bgPage":"#f2f2f2","groupBg":"#ffffff","groupOutline":"#e0e0e0"},"sizes":{"pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px"}}]

```

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [31 July 2024 16:38 UTC](https://discourse.nodered.org/t/device-overview-dashboard2/89911/2 "2024-07-31T16:38:00Z")

</div>

My version was inspired by the 1st post and I liked the card format (which was also inspired elsewhere) so I added it to the UIBUILDER default CSS to make it easily reusable. Indeed, I've even used it for work to illustrate the Gartner TIME analysis applied to our catalogue of apps and services. It was well liked.

This is an updated version. This is the test version but I have another which I use in my home dashboard.

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/6/f/6f589b878bfe74c8abbed177132a0e9d53510e5b.png)

In my case, I know what devices, services and websites I want to monitor and I keep everything in the original post's `flow.bookmarks` context variable.

Where you have a more rapidly changing set, you will, of course, want to dynamically update that using a separate flow and would then want the (2) part of the flow to be run whenever the variable changes.

While I did this as a zero-code (and yes, I realise that this does stretch the meaning of that phrase 🙂 ) example, for live use I personally would do the visual processing in the front-end rather than in Node-RED - that tends to be easier if you know some basic HTML and JavaScript. Visual programming is OK for some stuff but details can easily bog it down.

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/9/a/9a64ece2240a6394c997e707cab049fface5a694.png)

The content of the cards is easily modified but if you wanted the format you shared, that is pretty easy as well. This is the HTML for a single card:

```html
<a href="https://nas.knightnet.co.uk:5001/" target="_blank" class="status-link">
  <div id="st-NAS" class="box flex surface4" data-topic="telegraf/ping/192.168.1.161" title="MQTT Topic: telegraf/ping/192.168.1.161">
  <div class="success status-side-panel" title="Last update: 2024-07-31T16:34:03.274Z" data-updated="2024-07-31T16:34:03.274Z" data-updatedby="monitor"></div>
    <div>
      <div>NAS</div>
      <div class="text-smaller">nas.knightnet.co.uk:5001</div>
    </div>
  </div>
</a>

```

---

<div class="post-metadata">

**Author:** ![system](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/1X/d073cd938eafa2e558d7c2cd59003b3ef4963033.png) [@system](https://discourse.nodered.org/u/system)\
**Post date:** [29 October 2024 16:38 UTC](https://discourse.nodered.org/t/device-overview-dashboard2/89911/3 "2024-10-29T16:38:55Z")

</div>

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.
