A quick share from my ongoing Home dashboard (that uses UIBUILDER rather than D1 or D2).
I publish a number of npm packages and I've been keeping track of their stats from the start. I keep these in a persistent global context variable in Node-RED.
The Home dashboard uses UIBUILDER's front-end router library to manage multiple "pages" as a single-page app. Each route is stored as an HTML fragment file. That file can contain styles, HMTL and JavaScript. Just like you might see when using a front-end framework but no framework is needed here.
This particular route is mostly dynamically created in the route file and I simply send the Node-RED variable through to it when the route is requested.
The script in the route file creates the HTML table from the data. I could have used a helper fn from the uibuilder client library to build the table but honestly, simply and quickly writing the code, especially with the help of GitHub Copilot, was trivial. It only took a few minutes to get it working.
The script also uses one of UIBUILDER's client library helper functions uibuilder.formatNumber
to turn the numbers into Locale formatted number strings.
Here is what the page looks like:
And here is the route file:
<style>
.text-right {
text-align: right;
}
</style>
<h2>Totally Information's Package Download Statistics</h2>
<div>This page displays the average download statistics for my packages on the npm site.</div>
<div id="statsContainer">
<!-- Stats table will be dynamically inserted here -->
</div>
<script>
'use strict'
// Only ever run this once per page load
if (!window.statsSetup) {
console.log('Running initial setup for stats route - wont be run again until page reload')
window.statsSetup = {}
/** Listener function to handle the 'sendStats' message from uibuilder
* @param {object} msg - The message object containing route data
*/
window.statsSetup.rcvStats = uibuilder.onTopic('sendStats', (msg) => {
console.log('Received stats data:', msg)
const fmt = uibuilder.formatNumber
if (msg && msg.payload) {
const stats = msg.payload
const statsContainer = document.getElementById('statsContainer')
statsContainer.innerHTML = '' // Clear previous content
// Create a table to display the stats
const table = document.createElement('table')
table.id = 'statsTable'
table.innerHTML = /* html */`
<thead>
<tr>
<th>Package</th>
<th class="text-right">Avg/Day</th>
<th class="text-right">Avg/Month</th>
<th class="text-right">Avg/Year</th>
<th title="When was the package first published?">From</th>
</tr>
</thead>
<tbody>
</tbody>
`
const tbody = table.querySelector('tbody')
Object.entries(stats).forEach(([pkg, data]) => {
const row = document.createElement('tr')
row.innerHTML = /* html */`
<td title="Last update: ${data.end}">${pkg}</td>
<td class="text-right">${fmt(data.avgDlPerDay)}</td>
<td class="text-right">${fmt(data.avgDlPerMonth)}</td>
<td class="text-right">${fmt(data.avgDlPerYear)}</td>
<td title="${data.years} years">${data.start}</td>
`
tbody.appendChild(row)
})
statsContainer.appendChild(table)
} else {
console.error('No data received in message:', msg)
}
})
}
</script>