Device overview dashboard2

Inspired by:

and

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

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 :smiley:

Reference links:

[{"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"}}]

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.

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 :slight_smile: ) 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.

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:

<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>
1 Like

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