Hello
I need some help.
I am currently using ui template to display my chart and it works just fine. But when I refresh webpage it disappeared.
After Deploy
After Refresh
[
{
"id": "auto_inject_trigger_01",
"type": "inject",
"z": "4e40fbd2a88aea91",
"name": "Daily Total ",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "900",
"crontab": "",
"once": true,
"onceDelay": 0.1,
"topic": "triggerAutoDailyTotal",
"payload": "",
"payloadType": "date",
"x": 250,
"y": 620,
"wires": [
[
"auto_func_set_date_sql_01"
]
]
},
{
"id": "auto_func_set_date_sql_01",
"type": "function",
"z": "4e40fbd2a88aea91",
"name": "Set Current UTC Date & SQL",
"func": "const now = new Date();\n\nconst year = now.getUTCFullYear();\nconst month_0idx = now.getUTCMonth();\nconst day = now.getUTCDate();\n\nconst reportDateUTCObj = new Date(Date.UTC(year, month_0idx, day, 0, 0, 0, 0));\n\nconst dateForQuery = reportDateUTCObj.getUTCFullYear() + '-' +\n (reportDateUTCObj.getUTCMonth() + 1).toString().padStart(2, '0') + '-' +\n reportDateUTCObj.getUTCDate().toString().padStart(2, '0');\n\nconst startTimeSQL = `${dateForQuery} 00:00:00.000`;\nconst endTimeSQL = `${dateForQuery} 23:59:59.999`;\n\nmsg.payload = `\n SELECT\n [Time],\n SUM(Total) AS Total \nFROM\n dbo.batch\nWHERE\n [Time] >= CONVERT(datetime, '${startTimeSQL}', 120)\n AND [Time] <= CONVERT(datetime, '${endTimeSQL}', 120)\nGROUP BY\n [Time]\nORDER BY\n [Time] ASC;\n`;\n\nmsg.reportDateForUTCDay = reportDateUTCObj; \nconst displayDateForStatus = reportDateUTCObj.toLocaleDateString('en-GB', { timeZone: 'UTC' });\n\nnode.log(\"AutoDailyTotal: Querying for \" + displayDateForStatus + \" (UTC Day)\");\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 480,
"y": 620,
"wires": [
[
"auto_mssql_data_01"
]
]
},
{
"id": "auto_mssql_data_01",
"type": "MSSQL",
"z": "4e40fbd2a88aea91",
"mssqlCN": "8baf7e1e28030a6d",
"name": "Get Current Day Data",
"outField": "payload",
"returnType": 0,
"throwErrors": "1",
"query": "",
"modeOpt": "queryMode",
"modeOptType": "query",
"queryOpt": "payload",
"queryOptType": "editor",
"paramsOpt": "queryParams",
"paramsOptType": "none",
"rows": "",
"rowsType": "msg",
"parseMustache": true,
"params": [],
"x": 780,
"y": 620,
"wires": [
[
"auto_func_calculate_sum_01",
"36f6f7e1b733d31b"
]
]
},
{
"id": "auto_func_calculate_sum_01",
"type": "function",
"z": "4e40fbd2a88aea91",
"name": "Calc Auto Daily Sum (7AM-7PM UTC)",
"func": "const batchData = msg.payload;\nconst reportDateUTCObjInput = msg.reportDateForUTCDay;\n\nlet reportDateStringForDisplay = \"an unspecified UTC date\";\nlet reportDateUTCObj = null;\n\nif (reportDateUTCObjInput) {\n reportDateUTCObj = new Date(reportDateUTCObjInput);\n if (isNaN(reportDateUTCObj.getTime())) {\n reportDateUTCObj = null;\n } else {\n reportDateStringForDisplay = reportDateUTCObj.toLocaleDateString('en-GB', { timeZone: 'UTC' });\n }\n}\n\nif (!reportDateUTCObj) {\n msg.payload = \"Today's Total (7AM-7PM UTC): Error - Date Invalid\";\n node.warn(\"AutoDailySum Calc: Invalid date object received.\");\n return msg;\n}\n\nif (!Array.isArray(batchData)) {\n node.warn(\"AutoDailySum Calc: Input batchData is not an array for \" + reportDateStringForDisplay + \" (UTC).\");\n msg.payload = \"Today's Total (7AM-7PM UTC) for \" + reportDateStringForDisplay + \": Error fetching data\";\n return msg;\n}\n\nlet dailyTotalOutputSum = 0;\nlet dataFoundInTimeRange = false;\n\nbatchData.forEach((item, index) => {\n if (!item || typeof item.Time === 'undefined' || item.Total === null || typeof item.Total === 'undefined') {\n return;\n }\n const itemTime = new Date(item.Time);\n if (isNaN(itemTime.getTime())) {\n return;\n }\n\n // SQL already filters by day, but double-check for safety if needed (usually not with this setup)\n // if (itemTime.getUTCFullYear() !== reportDateUTCObj.getUTCFullYear() || ... ) return;\n\n const itemHourUTC = itemTime.getUTCHours();\n const itemTotal = parseFloat(item.Total);\n\n if (itemHourUTC >= 7 && itemHourUTC < 19) { // Sum for 07:00 UTC to 18:59 UTC\n if (!isNaN(itemTotal)) {\n dailyTotalOutputSum += itemTotal;\n dataFoundInTimeRange = true;\n }\n }\n});\n\nlet dailyTotalString;\nif (batchData.length === 0 && !dataFoundInTimeRange) { // No data returned from SQL at all for the day\n dailyTotalString = \"Current Output: No data\";\n} else if (!dataFoundInTimeRange) { // Data existed for the day, but none in the 7AM-7PM range\n dailyTotalString = \"Current Output: 0\";\n} else {\n dailyTotalString = \"Current Output: \" + dailyTotalOutputSum.toLocaleString();\n}\n\nmsg.payload = dailyTotalString;\nnode.log(\"AutoDailySum Calc: \" + msg.payload);\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1090,
"y": 620,
"wires": [
[
"78a071dec8d0aca5"
]
]
},
{
"id": "78a071dec8d0aca5",
"type": "ui-text",
"z": "4e40fbd2a88aea91",
"group": "1f528672fb91aed2",
"order": 8,
"width": 0,
"height": 0,
"name": "Output",
"label": "",
"format": "{{msg.payload}}",
"layout": "row-center",
"style": true,
"font": "",
"fontSize": "35",
"color": "#000000",
"wrapText": false,
"className": "",
"x": 1350,
"y": 620,
"wires": []
},
{
"id": "36f6f7e1b733d31b",
"type": "function",
"z": "4e40fbd2a88aea91",
"name": "Daily Hourly Chart Data",
"func": "const rawData = msg.payload;\n\n// --- Logic for data preparation (same core logic as before) ---\nif (!Array.isArray(rawData)) {\n node.status({ fill: \"red\", shape: \"ring\", text: \"Input data is not an array\" });\n // Send empty data for the template to handle\n msg.payload = { labels: [], datasets: [] };\n msg.options = {\n plugins: { title: { display: true, text: 'Hourly Output vs. Target (Data Error)' } }\n };\n return msg;\n}\n\nconst hourlyTotals = {};\nconst desiredHoursUTC = [7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18];\nconst xLabels = [\n \"7AM-8AM\", \"8AM-9AM\", \"9AM-10AM\", \"10AM-11AM\",\n \"11AM-12PM\", \"12PM-1PM\", \"1PM-2PM\", \"2PM-3PM\",\n \"3PM-4PM\", \"4PM-5PM\", \"5PM-6PM\", \"6PM-7PM\"\n];\n\ndesiredHoursUTC.forEach(hour => {\n hourlyTotals[hour] = 0;\n});\n\nlet dataFoundForChart = false;\nrawData.forEach(item => {\n if (!item || typeof item.Time === 'undefined' || item.Total === null || typeof item.Total === 'undefined') {\n return;\n }\n const itemTime = new Date(item.Time);\n if (isNaN(itemTime.getTime())) {\n return;\n }\n const itemHourUTC = itemTime.getUTCHours();\n if (itemHourUTC >= 7 && itemHourUTC < 19) { // UTC 7:00 to 18:59\n const itemTotal = parseFloat(item.Total);\n if (!isNaN(itemTotal)) {\n if (hourlyTotals.hasOwnProperty(itemHourUTC)) {\n hourlyTotals[itemHourUTC] += itemTotal;\n dataFoundForChart = true;\n }\n }\n }\n});\n\nconst actualOutputValues = [];\ndesiredHoursUTC.forEach(hour => {\n actualOutputValues.push(hourlyTotals[hour]);\n});\n\nconst committedHourlyTarget = flow.get(\"currentHourlyTarget\");\nlet targetOutputValues = new Array(xLabels.length).fill(null);\n\nif (committedHourlyTarget !== null && typeof committedHourlyTarget === 'number' && !isNaN(committedHourlyTarget)) {\n targetOutputValues = new Array(xLabels.length).fill(committedHourlyTarget);\n}\n\nif (!dataFoundForChart && rawData.length > 0 && committedHourlyTarget === null) {\n node.status({ fill: \"yellow\", shape: \"ring\", text: \"Actuals out of range, no target\" });\n} else if (!dataFoundForChart && committedHourlyTarget === null) {\n node.status({ fill: \"yellow\", shape: \"ring\", text: \"No actuals data, no target\" });\n} else {\n node.status({ fill: \"green\", shape: \"dot\", text: \"Combined chart data prepared\" });\n}\nmsg.payload = {\n labels: xLabels,\n datasets: [\n {\n label: \"Hourly Output\", // BAR chart\n data: actualOutputValues,\n type: 'bar',\n backgroundColor: 'rgba(54, 162, 235, 0.5)', // Blue\n borderColor: 'rgb(54, 162, 235)',\n borderWidth: 1,\n yAxisID: 'yBar'\n },\n {\n label: \"Target Output\", // LINE chart\n data: targetOutputValues,\n type: 'line',\n fill: false,\n borderColor: 'rgb(255, 99, 132)', // Red\n tension: 0.1,\n yAxisID: 'yLine'\n }\n ]\n};\n\nmsg.options = { // Chart.js options\n responsive: true,\n maintainAspectRatio: false,\n interaction: {\n mode: 'index',\n intersect: false,\n },\n stacked: false,\n plugins: {\n title: {\n display: true,\n text: 'Hourly Output vs. Target Output (7AM-7PM UTC)'\n },\n legend: {\n position: 'top',\n }\n },\n scales: {\n x: {\n display: true,\n title: {\n display: false,\n text: 'Time Slot'\n }\n },\n yBar: {\n type: 'linear',\n display: true,\n position: 'left',\n title: {\n display: true,\n text: 'Hourly Output'\n },\n beginAtZero: true\n },\n yLine: {\n type: 'linear',\n display: true,\n position: 'right',\n title: {\n display: true,\n text: 'Target Output'\n },\n grid: {\n drawOnChartArea: false,\n },\n beginAtZero: true\n }\n }\n};\n\nreturn msg;\n/*\n// TEMPORARY TEST CODE FOR \"Daily Hourly Chart Data\" (36f6f7e1b733d31b)\nnode.warn(\"Sending TEST message from Output Chart Function\");\nmsg.payload = {\n testProperty: \"This is a TEST payload from the server function for output chart\",\n labels: [\"Test1\", \"Test2\"],\n datasets: [{label: \"Test Data\", data: [1,2], type: 'line'}]\n};\nmsg.options = { testOption: \"Test option value\" };\nmsg.distinctMarker = \"FROM_OUTPUT_CHART_FUNCTION_36f6f7e1b733d31b\"; // Add a very unique marker\nreturn msg;\n*/",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1070,
"y": 800,
"wires": [
[
"47dc19ff18262555",
"0d7f850d62581469"
]
]
},
{
"id": "47dc19ff18262555",
"type": "ui-template",
"z": "4e40fbd2a88aea91",
"group": "1f528672fb91aed2",
"page": "",
"ui": "",
"name": "",
"order": 1,
"width": "12",
"height": "12",
"head": "",
"format": "<script src=\"https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js\"></script>\n<div style=\"width: 100%; height: 100%; position: relative;\">\n <canvas ref=\"chartCanvasElement\"></canvas>\n</div>\n\n<script>\n let chartInstance = null;\n\n const renderOrUpdateChart = (currentMsg) => {\n console.log('Output Chart Template - renderOrUpdateChart CALLED with currentMsg:', JSON.parse(JSON.stringify(currentMsg || {note: \"currentMsg was null/undefined\"})));\n this.$nextTick(() => {\n if (!this.$refs.chartCanvasElement) {\n console.warn('Output Chart Template: Canvas element not found.');\n return;\n }\n const canvas = this.$refs.chartCanvasElement;\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n console.error('Output Chart Template: Failed to get 2D context.');\n return;\n }\n if (chartInstance) {\n chartInstance.destroy();\n chartInstance = null;\n }\n if (typeof Chart === 'undefined') {\n console.error('Output Chart Template: Chart.js not loaded!');\n ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.font = \"16px Arial\"; ctx.fillStyle = \"red\"; ctx.textAlign = \"center\"; ctx.fillText(\"Chart.js not loaded.\", canvas.width / 2, canvas.height / 2);\n return;\n }\n\n const hasMsg = !!currentMsg;\n const hasPayload = !!(currentMsg && currentMsg.payload);\n const hasDatasets = !!(currentMsg && currentMsg.payload && typeof currentMsg.payload.datasets !== 'undefined' && currentMsg.payload.datasets !== null && Array.isArray(currentMsg.payload.datasets));\n const hasLabels = !!(currentMsg && currentMsg.payload && typeof currentMsg.payload.labels !== 'undefined' && currentMsg.payload.labels !== null && Array.isArray(currentMsg.payload.labels));\n console.log(`Output Chart Template - DATA CHECK: hasMsg: ${hasMsg}, hasPayload: ${hasPayload}, hasDatasets: ${hasDatasets}, hasLabels: ${hasLabels}`);\n if(hasPayload) {\n console.log('Output Chart Template - currentMsg.payload content:', JSON.parse(JSON.stringify(currentMsg.payload)));\n }\n\n if (hasMsg && hasPayload && hasDatasets && hasLabels && currentMsg.payload.datasets.length > 0) {\n console.log('Output Chart Template: Valid data found. Creating chart.');\n // ... (rest of your chart creation logic using currentMsg.payload and currentMsg.options)\n const chartData = { labels: currentMsg.payload.labels, datasets: currentMsg.payload.datasets };\n const chartOptions = currentMsg.options || { responsive: true, maintainAspectRatio: false, plugins: { title: { display: true, text: 'Chart' } } };\n chartInstance = new Chart(ctx, { data: chartData, options: chartOptions });\n } else {\n console.log('Output Chart Template: No valid data to draw. Displaying \"Waiting for data...\"');\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.font = \"16px Arial\"; ctx.fillStyle = \"grey\"; ctx.textAlign = \"center\";\n ctx.fillText(\"Waiting for data...\", canvas.width / 2, canvas.height / 2);\n }\n });\n };\n\n console.log('Output Chart Template - Script evaluating. Initial this.msg:', JSON.parse(JSON.stringify(this.msg || {note: \"this.msg was null/undefined\"})));\n \n if (this.msg && this.msg.payload && this.msg.payload.datasets) { // Check if msg has data from resendOnRefresh\n renderOrUpdateChart(this.msg);\n } else {\n // If no valid initial data (e.g. from resendOnRefresh), display \"Waiting\" and request it.\n renderOrUpdateChart(null); \n if (this.send) { // this.send is available in Dashboard 2.0 ui-template\n console.log('Output Chart Template: Requesting initial data...');\n this.send({topic: \"REQUEST_OUTPUT_CHART_DATA\", requestTime: Date.now()});\n } else {\n console.error('Output Chart Template: this.send is not available to request data.');\n }\n }\n</script>",
"storeOutMessages": true,
"passthru": true,
"resendOnRefresh": true,
"templateScope": "local",
"className": "",
"x": 1280,
"y": 800,
"wires": [
[
"80b9dc6e0aa7906f"
]
]
},
{
"id": "71f2f0954ec54534",
"type": "link in",
"z": "4e40fbd2a88aea91",
"name": "Trigger output",
"links": [
"c91128631132c36f"
],
"x": 285,
"y": 680,
"wires": [
[
"auto_func_set_date_sql_01"
]
]
},
{
"id": "c91128631132c36f",
"type": "link out",
"z": "4e40fbd2a88aea91",
"name": "Send Trigger for Output Chart Refresh",
"mode": "link",
"links": [
"71f2f0954ec54534"
],
"x": 1555,
"y": 800,
"wires": []
},
{
"id": "80b9dc6e0aa7906f",
"type": "switch",
"z": "4e40fbd2a88aea91",
"name": "",
"property": "topic",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "REQUEST_OUTPUT_CHART_DATA",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 1430,
"y": 800,
"wires": [
[
"c91128631132c36f"
]
]
},
{
"id": "0d7f850d62581469",
"type": "debug",
"z": "4e40fbd2a88aea91",
"name": "debug 4",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 1280,
"y": 920,
"wires": []
},
{
"id": "8baf7e1e28030a6d",
"type": "MSSQL-CN",
"tdsVersion": "7_4",
"name": "i4.0",
"server": "10.183.175.8",
"port": "1433",
"encyption": true,
"trustServerCertificate": true,
"database": "i4.0",
"useUTC": true,
"connectTimeout": "15000",
"requestTimeout": "15000",
"cancelTimeout": "5000",
"pool": "10",
"parseJSON": false,
"enableArithAbort": true,
"readOnlyIntent": false
},
{
"id": "1f528672fb91aed2",
"type": "ui-group",
"name": "Delta SA 03",
"page": "8ad4c897bc98235a",
"width": "6",
"height": 1,
"order": 3,
"showTitle": false,
"className": "",
"visible": "true",
"disabled": "false",
"groupType": "default"
},
{
"id": "8ad4c897bc98235a",
"type": "ui-page",
"name": "Main Page",
"ui": "e37b297fa64e1312",
"path": "/page3",
"icon": "home",
"layout": "grid",
"theme": "8796486dd6098e1f",
"breakpoints": [
{
"name": "Default",
"px": "0",
"cols": "3"
},
{
"name": "Tablet",
"px": "576",
"cols": "6"
},
{
"name": "Small Desktop",
"px": "768",
"cols": "9"
},
{
"name": "Desktop",
"px": "1024",
"cols": "12"
}
],
"order": 1,
"className": "",
"visible": "true",
"disabled": "false"
},
{
"id": "e37b297fa64e1312",
"type": "ui-base",
"name": "My Dashboard",
"path": "/dashboard",
"appIcon": "",
"includeClientData": true,
"acceptsClientConfig": [
"ui-notification",
"ui-control",
"ui-text",
"ui-text-input"
],
"showPathInSidebar": false,
"headerContent": "page",
"navigationStyle": "default",
"titleBarStyle": "fixed",
"showReconnectNotification": true,
"notificationDisplayTime": 1,
"showDisconnectNotification": true,
"allowInstall": true
},
{
"id": "8796486dd6098e1f",
"type": "ui-theme",
"name": "Theme 2",
"colors": {
"surface": "#0f0f0f",
"primary": "#050505",
"bgPage": "#767474",
"groupBg": "#d8d4d4",
"groupOutline": "#ffffff"
},
"sizes": {
"density": "default",
"pagePadding": "12px",
"groupGap": "4px",
"groupBorderRadius": "4px",
"widgetGap": "1px"
}
}
]
I did tried using the link in and link out but I don't know if I'm using it right.
Any advice would be appreciated.