Cron-plus: not showing entries in config?

Hi,

I'm trying to use cron-plus but I note that when I send a job to it via a msg, it doesn't show in the config - is this deliberate? It would be helpful if you could see what had been sent.

Example flow:

[{"id":"1b4b05f0.7fdf3a","type":"ui_text_input","z":"63281c77.40a064","name":"Start Time","label":"Time","tooltip":"","group":"a68d92f5.9fe6c","order":2,"width":0,"height":0,"passthru":false,"mode":"time","delay":300,"topic":"start","x":170,"y":1540,"wires":[["4009168d.75e6e8"]]},{"id":"818949b1.1eff98","type":"ui_text_input","z":"63281c77.40a064","name":"End Time","label":"End","tooltip":"","group":"fa3eee85.b8eb1","order":4,"width":0,"height":0,"passthru":false,"mode":"time","delay":300,"topic":"end","x":160,"y":1580,"wires":[["4009168d.75e6e8"]]},{"id":"c4ba8cf4.10962","type":"cronplus","z":"63281c77.40a064","name":"","outputField":"payload","timeZone":"","options":[],"x":560,"y":1580,"wires":[["6575fe66.a80cf","2c96c428.4553dc"]]},{"id":"6575fe66.a80cf","type":"debug","z":"63281c77.40a064","name":"DO STUFF","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":810,"y":1520,"wires":[]},{"id":"c9446616.4e01d8","type":"inject","z":"63281c77.40a064","name":"status-all","topic":"","payload":"{\"command\":\"status-all\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":400,"y":1520,"wires":[["c4ba8cf4.10962"]]},{"id":"90f40087.345","type":"switch","z":"63281c77.40a064","name":"Feedback","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"start","vt":"str"},{"t":"eq","v":"end","vt":"str"},{"t":"else"}],"checkall":"false","repair":false,"outputs":3,"x":420,"y":1780,"wires":[["6cd48422.ae206c"],["11ca0a2a.796d96"],["c0bd63a5.f0ff5"]]},{"id":"d17f6e44.9488","type":"ui_date_picker","z":"63281c77.40a064","name":"Start Date","label":"Date","group":"a68d92f5.9fe6c","order":1,"width":0,"height":0,"passthru":false,"topic":"start-date","x":160,"y":1500,"wires":[[]]},{"id":"3da641fe.a9526e","type":"ui_date_picker","z":"63281c77.40a064","name":"End Date","label":"Date","group":"fa3eee85.b8eb1","order":3,"width":0,"height":0,"passthru":false,"topic":"end-date","x":160,"y":1620,"wires":[[]]},{"id":"5db2fac5.6b4004","type":"link in","z":"63281c77.40a064","name":"Start In","links":["6cd48422.ae206c"],"x":35,"y":1520,"wires":[["d17f6e44.9488","1b4b05f0.7fdf3a"]]},{"id":"a0f2b195.54b33","type":"link in","z":"63281c77.40a064","name":"End In","links":["11ca0a2a.796d96"],"x":35,"y":1600,"wires":[["818949b1.1eff98","3da641fe.a9526e"]]},{"id":"6cd48422.ae206c","type":"link out","z":"63281c77.40a064","name":"Start Out","links":["5db2fac5.6b4004"],"x":575,"y":1740,"wires":[]},{"id":"11ca0a2a.796d96","type":"link out","z":"63281c77.40a064","name":"End Out","links":["a0f2b195.54b33"],"x":575,"y":1780,"wires":[]},{"id":"4009168d.75e6e8","type":"function","z":"63281c77.40a064","name":"Convert to CRON+","func":"const dt = new Date(msg.payload)\n\nmsg.payload = [\n    {\n        'command': 'add',\n        'name': msg.topic,\n        'expression': `0 ${dt.getUTCMinutes()} ${dt.getUTCHours()} * * *`,\n        'payload': msg.topic,\n        'type': 'str',\n    },\n    {\n        'command': 'status',\n        'name': msg.topic,\n    },\n]\n\nreturn msg\n","outputs":1,"noerr":0,"x":370,"y":1580,"wires":[["c4ba8cf4.10962"]]},{"id":"441a82d0.4de61c","type":"function","z":"63281c77.40a064","name":"Next datetime","func":"//only output next date/time\n\nvar next = ''\n\nif ( msg.hasOwnProperty('cronplus') ) {\n    msg.payload = msg.cronplus.status.nextDate\n    msg.enabled = true\n    \n    return msg\n}\n\n","outputs":1,"noerr":0,"x":240,"y":1780,"wires":[["90f40087.345","5d53a615.d6b948"]]},{"id":"b693b1a.42aaa5","type":"inject","z":"63281c77.40a064","name":"remove-all","topic":"","payload":"{\"command\":\"remove-all\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":400,"y":1480,"wires":[["c4ba8cf4.10962"]]},{"id":"a6fc1cb6.fc0c9","type":"comment","z":"63281c77.40a064","name":"--- Simple Schedule Webpage ---","info":"","x":230,"y":1420,"wires":[]},{"id":"2c96c428.4553dc","type":"link out","z":"63281c77.40a064","name":"Cron+ Out","links":["aea41e65.d3687"],"x":695,"y":1580,"wires":[]},{"id":"aea41e65.d3687","type":"link in","z":"63281c77.40a064","name":"Feedback","links":["2c96c428.4553dc"],"x":120,"y":1780,"wires":[["441a82d0.4de61c"]]},{"id":"c0bd63a5.f0ff5","type":"debug","z":"63281c77.40a064","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":630,"y":1820,"wires":[]},{"id":"5d53a615.d6b948","type":"debug","z":"63281c77.40a064","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":430,"y":1700,"wires":[]},{"id":"a68d92f5.9fe6c","type":"ui_group","z":"","name":"Start","tab":"70bd0808.2b4f68","disp":true,"width":"6","collapse":false},{"id":"fa3eee85.b8eb1","type":"ui_group","z":"","name":"End","tab":"70bd0808.2b4f68","disp":true,"width":"4","collapse":false},{"id":"70bd0808.2b4f68","type":"ui_tab","z":"","name":"Schedule-Test","icon":"dashboard","disabled":false,"hidden":false}]

What shows up in config:
image

Flow:

it is and it isnt :slight_smile:

The entries in the table are stored (static) in the nodes config (and thus exported with the flow)

Dynamic entries are not actually in the config (design time) they are only present in runtime.

I'm not certain (perhaps you know) is there a way I could display the dynamically created entries in the design time environment? I suspect not.

The only way to do this is to use the messaging and send a "command":"status-all"

As it happens, I'm just playing with it - got around to knocking up a demo with dashboard (would love someone to do a ui_builder/vue version too :slight_smile: )

I'l post the flow to the flow library shortly but here is a taster...

Yes, you can use context storage and merge the incoming commands with your config data. Don't forget that everything in your node's js file outside the part that handles the incoming msg is active all of the time, so you can actually just define the variable there. You will most likely need to provide an API however so that when a user opens the config panel, it can query the current list and build the display. I do this in uibuilder for the list of known packages in the package manager since I cannot predict if someone may have updated the packages externally to a flow.

I've got that part - however, that raises another issue about timezones. But I will wait for your example so that I can see how you've got it working before I raise that since I am probably just being thick.

I'm up for that! It shouldn't be too difficult once you've shared your flow.

Thanks for the Info. I'm not quite grasping this ATM (being thick myself). Do you have any demo code / sample or somewhere I can see this in action?

Here is my dashboard demo.

I left timezone out of it for now (defaults to system).

Dynamic cron schedules / timers via dashboard control

Thanks for that, I will have a look.

Here is my code. Not all of it works as expected because I was playing around. The problem I have is that I can't find a combination of timezones/utc that works. Both the Pi and my laptop should be set to EU/London which is currently UTC+1 (BST) but I'm getting times that are 1hr out - possibly because I send an ISO timestamp back to Dashboard.

[{"id":"1b4b05f0.7fdf3a","type":"ui_text_input","z":"63281c77.40a064","name":"Start Time","label":"Time","tooltip":"","group":"a68d92f5.9fe6c","order":1,"width":0,"height":0,"passthru":false,"mode":"time","delay":300,"topic":"start","x":170,"y":1540,"wires":[["4009168d.75e6e8"]]},{"id":"818949b1.1eff98","type":"ui_text_input","z":"63281c77.40a064","name":"End Time","label":"End","tooltip":"","group":"fa3eee85.b8eb1","order":1,"width":0,"height":0,"passthru":false,"mode":"time","delay":300,"topic":"end","x":160,"y":1580,"wires":[["4009168d.75e6e8"]]},{"id":"c4ba8cf4.10962","type":"cronplus","z":"63281c77.40a064","name":"","outputField":"payload","timeZone":"Europe/London","options":[],"x":560,"y":1580,"wires":[["6575fe66.a80cf","2c96c428.4553dc","6c119b09.25b174"]]},{"id":"6575fe66.a80cf","type":"debug","z":"63281c77.40a064","name":"DO STUFF","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":810,"y":1520,"wires":[]},{"id":"c9446616.4e01d8","type":"inject","z":"63281c77.40a064","name":"status-all","topic":"","payload":"{\"command\":\"status-all\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":400,"y":1520,"wires":[["c4ba8cf4.10962"]]},{"id":"90f40087.345","type":"switch","z":"63281c77.40a064","name":"Feedback","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"start","vt":"str"},{"t":"eq","v":"end","vt":"str"},{"t":"else"}],"checkall":"false","repair":false,"outputs":3,"x":420,"y":1780,"wires":[["6cd48422.ae206c","62cb5f86.e508f"],["11ca0a2a.796d96","4b520867.3536b8"],["c0bd63a5.f0ff5"]]},{"id":"5db2fac5.6b4004","type":"link in","z":"63281c77.40a064","name":"Start In","links":["6cd48422.ae206c"],"x":55,"y":1540,"wires":[["1b4b05f0.7fdf3a"]]},{"id":"a0f2b195.54b33","type":"link in","z":"63281c77.40a064","name":"End In","links":["11ca0a2a.796d96"],"x":55,"y":1580,"wires":[["818949b1.1eff98"]]},{"id":"6cd48422.ae206c","type":"link out","z":"63281c77.40a064","name":"Start Out","links":["5db2fac5.6b4004"],"x":575,"y":1740,"wires":[]},{"id":"11ca0a2a.796d96","type":"link out","z":"63281c77.40a064","name":"End Out","links":["a0f2b195.54b33"],"x":575,"y":1780,"wires":[]},{"id":"4009168d.75e6e8","type":"function","z":"63281c77.40a064","name":"Convert to CRON+","func":"const dt = new Date(msg.payload)\n\nmsg.payload = [\n    {\n        'command': 'add',\n        'name': msg.topic,\n        //'expression': `0 ${dt.getUTCMinutes()} ${dt.getUTCHours()} * * *`,\n        'expression': `0 ${dt.getMinutes()} ${dt.getHours()} * * *`,\n        'payload': msg.topic,\n        'type': 'str',\n    },\n    {\n        'command': 'status',\n        'name': msg.topic,\n    },\n]\n\nreturn msg\n","outputs":1,"noerr":0,"x":370,"y":1580,"wires":[["c4ba8cf4.10962"]]},{"id":"441a82d0.4de61c","type":"function","z":"63281c77.40a064","name":"Next datetime","func":"//only output next date/time\n\nvar next = '', msg1 = {topic:msg.topic}\n\ntry {\n\n    if ( msg.payload.hasOwnProperty('result') && (msg.payload.command.command === 'status') ) {\n        msg1.payload = msg.payload.result.status.nextDate\n        msg1.description = msg.payload.result.status.nextDescription\n    \n        return msg1\n    }\n} catch (e) {\n    node.warn('1')\n    node.warn(msg)\n    node.warn(e)\n}\n    \ntry {\n\n    if ( msg.hasOwnProperty('cronplus') ) {\n        if ( (msg.cronplus.status.nextDate !== '')  && (msg.cronplus.status.nextDate !== null)) {\n            msg1.payload = msg.cronplus.status.nextDate\n            msg1.description = msg.cronplus.status.nextDescription\n        \n            return msg1\n        }\n    }\n\n} catch (e) {\n    node.warn('2')\n    node.warn(msg)\n    node.warn(e)\n}\n","outputs":1,"noerr":0,"x":240,"y":1780,"wires":[["90f40087.345","3eb59c01.aae764"]]},{"id":"b693b1a.42aaa5","type":"inject","z":"63281c77.40a064","name":"remove-all","topic":"","payload":"{\"command\":\"remove-all\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":400,"y":1480,"wires":[["c4ba8cf4.10962"]]},{"id":"a6fc1cb6.fc0c9","type":"comment","z":"63281c77.40a064","name":"--- Simple Schedule Webpage ---","info":"","x":230,"y":1420,"wires":[]},{"id":"2c96c428.4553dc","type":"link out","z":"63281c77.40a064","name":"Cron+ Out","links":["aea41e65.d3687"],"x":695,"y":1580,"wires":[]},{"id":"aea41e65.d3687","type":"link in","z":"63281c77.40a064","name":"Feedback","links":["2c96c428.4553dc"],"x":120,"y":1780,"wires":[["441a82d0.4de61c","e6e1cd4c.4d20a"]]},{"id":"c0bd63a5.f0ff5","type":"debug","z":"63281c77.40a064","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":610,"y":1820,"wires":[]},{"id":"6c119b09.25b174","type":"switch","z":"63281c77.40a064","name":"Start/End?","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"start","vt":"str"},{"t":"eq","v":"end","vt":"str"},{"t":"else"}],"checkall":"false","repair":false,"outputs":3,"x":830,"y":1580,"wires":[["858a374d.571018"],["858a374d.571018"],[]]},{"id":"858a374d.571018","type":"ui_led","z":"63281c77.40a064","group":"f75dae43.60996","order":1,"width":0,"height":0,"label":"","labelPlacement":"left","labelAlignment":"left","colorForValue":[{"color":"green","value":"start","valueType":"str"}],"allowColorForValueInMessage":false,"name":"Start/End","x":1000,"y":1580,"wires":[]},{"id":"62cb5f86.e508f","type":"ui_text","z":"63281c77.40a064","group":"a68d92f5.9fe6c","order":2,"width":0,"height":0,"name":"","label":"Next","format":"{{msg.payload.replace(':00.000Z','').replace('T',' ')}}<br>{{description}}","layout":"row-spread","x":610,"y":1720,"wires":[]},{"id":"4b520867.3536b8","type":"ui_text","z":"63281c77.40a064","group":"fa3eee85.b8eb1","order":2,"width":0,"height":0,"name":"","label":"Next","format":"{{msg.payload.replace(':00.000Z','').replace('T',' ')}}<br>{{description}}","layout":"row-spread","x":650,"y":1780,"wires":[]},{"id":"e6e1cd4c.4d20a","type":"debug","z":"63281c77.40a064","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":210,"y":1720,"wires":[]},{"id":"3eb59c01.aae764","type":"debug","z":"63281c77.40a064","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":410,"y":1840,"wires":[]},{"id":"a68d92f5.9fe6c","type":"ui_group","z":"","name":"Start","tab":"70bd0808.2b4f68","order":2,"disp":true,"width":3,"collapse":false},{"id":"fa3eee85.b8eb1","type":"ui_group","z":"","name":"End","tab":"70bd0808.2b4f68","order":3,"disp":true,"width":3,"collapse":false},{"id":"f75dae43.60996","type":"ui_group","z":"","name":"LED","tab":"70bd0808.2b4f68","order":1,"disp":false,"width":1,"collapse":false},{"id":"70bd0808.2b4f68","type":"ui_tab","z":"","name":"Schedule-Test","icon":"dashboard","order":2,"disabled":false,"hidden":false}]

I can't get minutes <=12 to show correctly on your example, it seems to pick it up as month:

image
image

Also, when an event fires, I get an error and the output isn't shown:

image

I believe what you are seeing matches the behaviour explained here: UPDATE: V1.2.1 node-red-contrib-cron-plus scheduler (incl solar events and Timezone support) - #46 by Steve-Mcl

Not sure:

image

According to that other thread, if I understood it correctly, if I use 6 or 7 parts, it should work?

But it doesn't.

And this doesn't work either:

image

But this does
image

Ah, OK, I can't count!

A slightly different issue there then. There is no check for >7 elements.

Argh! cron is hard!

1 Like

Getting the same problems with my own front-end too:

I have also checked cronisjs directly and I don't get the same issues so it isn't that library:

image

I need to check the cronstrue library next but I need to go pick up my wife.

I'm not seeing these issues - but I remember I began some improvements about a month ago & think I might have spotted / fixed a bug.

I'll publish my changes soon & update NPM.

OK, there are definitely differences between the outputs from your dependencies and outputs from cron-plus:

Test flow - produces the left-hand column (with the help of uibuilder of course :wink:) :

[{"id":"9389855e.785518","type":"cronplus","z":"63281c77.40a064","name":"","outputField":"payload","timeZone":"","options":[],"x":560,"y":3200,"wires":[["392b23b5.c8ef7c"]]},{"id":"2d1a4e8a.9f6d92","type":"inject","z":"63281c77.40a064","name":"","topic":"test","payload":"","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":210,"y":3200,"wires":[["c2da6d69.e515"]]},{"id":"c2da6d69.e515","type":"function","z":"63281c77.40a064","name":"describe cmd","func":"msg.payload = [\n    {\n        \"command\": \"describe\",\n        \"expression\": '0 0 1 * *'\n    },\n    {\n        \"command\": \"describe\",\n        \"expression\": '0 1 1 * *'\n    },\n    {\n        \"command\": \"describe\",\n        \"expression\": '0 14 1 * *'\n    },\n    {\n        \"command\": \"describe\",\n        \"expression\": '0 4 1 * *'\n    },\n    {\n        \"command\": \"describe\",\n        \"expression\": '0 0 1 * * *'\n    },\n    {\n        \"command\": \"describe\",\n        \"expression\": '0 1 1 * * *'\n    },\n    {\n        \"command\": \"describe\",\n        \"expression\": '0 14 1 * * *'\n    },\n    {\n        \"command\": \"describe\",\n        \"expression\": '0 4 1 * * *'\n    },\n    {\n        \"command\": \"describe\",\n        \"expression\": '0 0 1 * * * *'\n    },\n    {\n        \"command\": \"describe\",\n        \"expression\": '0 1 1 * * * *'\n    },\n    {\n        \"command\": \"describe\",\n        \"expression\": '0 14 1 * * * *'\n    },\n    {\n        \"command\": \"describe\",\n        \"expression\": '0 4 1 * * * *'\n    },\n]\n\nreturn msg;","outputs":1,"noerr":0,"x":390,"y":3200,"wires":[["9389855e.785518"]]},{"id":"3f74672c.cf13c8","type":"debug","z":"63281c77.40a064","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1270,"y":3200,"wires":[]},{"id":"2fe4e66c.8e320a","type":"join","z":"63281c77.40a064","name":"","mode":"custom","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"4","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":930,"y":3200,"wires":[["3ee0061b.981d0a"]]},{"id":"392b23b5.c8ef7c","type":"change","z":"63281c77.40a064","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"\t    \"expression: \" & payload.command.expression & \" \\n\" &\t    \"nextDate: \" & payload.result.nextDate & \" \\n\" &\t    \"description: \" & payload.result.description & \" \\n\" &\t    \"nextDescription: \" & payload.result.nextDescription & \" \\n\" &\t    ' - - - - - - - - - - - - - - - - - '\t","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":740,"y":3200,"wires":[["2fe4e66c.8e320a"]]},{"id":"3ee0061b.981d0a","type":"join","z":"63281c77.40a064","name":"","mode":"custom","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n============================\\n","joinerType":"str","accumulate":false,"timeout":"","count":"3","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":1090,"y":3200,"wires":[["3f74672c.cf13c8","dee69c83.8a411"]]},{"id":"dee69c83.8a411","type":"link out","z":"63281c77.40a064","name":"test-cron","links":["6f114bd6.d008d4"],"x":1260,"y":3240,"wires":[]}]

And test script for the raw output (the right-hand column):

#!/usr/bin/env node
'use strict'

var { validate, CronosExpression } = require('cronosjs')
var cronstrue = require('cronstrue')
const prettyMs = require('pretty-ms')

let nowMs = Date.now().valueOf()

function doit(cString) {
    var nextMS = CronosExpression.parse(cString).nextDate()
    console.log('expression: ', cString)
    console.log('nextDate: ', nextMS )
    console.log('description: ', cronstrue.toString(cString) )
    console.log('nextDescription: ', `in ${prettyMs( (nextMS - nowMs), { secondsDecimalDigits: 0, verbose: true })}` )
    console.log(' - - - - - - - - - - - - - - - - - ')    
}

console.log('FIVE - - - - - - - - - - - - - - - - - ')
var cString = '0 0 1 * *'
doit(cString)
var cString = '0 1 1 * *'
doit(cString)
var cString = '0 14 1 * *'
doit(cString)
var cString = '0 4 1 * *'
doit(cString)

console.log('SIX - - - - - - - - - - - - - - - - - ')
var cString = '0 0 1 * * *'
doit(cString)
var cString = '0 1 1 * * *'
doit(cString)
var cString = '0 14 1 * * *'
doit(cString)
var cString = '0 4 1 * * *'
doit(cString)

console.log('SEVEN - - - - - - - - - - - - - - - - - ')
var cString = '0 0 1 * * * *'
doit(cString)
var cString = '0 1 1 * * * *'
doit(cString)
var cString = '0 14 1 * * * *'
doit(cString)
var cString = '0 4 1 * * * *'
doit(cString)
1 Like

Thanks for all your efforts & providing good info for me.

I had (by accident) fixed this last month but never finished my main intent.

image

image

image

should be posting an update soon.

1 Like

Thanks :wine_glass:

In the meantime, just to whet your appetite, here is some starter code for uibuilder - not much done yet because I keep getting side-tracked as usual - but a start anyway:

Flow:

[{"id":"e6afd4b0.91d068","type":"debug","z":"63281c77.40a064","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":940,"y":2900,"wires":[]},{"id":"db553021.e64ac","type":"uibuilder","z":"63281c77.40a064","name":"","topic":"","url":"cron","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":true,"showfolder":false,"x":390,"y":2920,"wires":[["478c4836.d0fa98","2482e719.803068"],[]]},{"id":"755458a1.0e2c28","type":"function","z":"63281c77.40a064","name":"describe cmd","func":"msg.payload = {\n    \"command\": \"describe\",\n    \"expression\": msg.payload\n}\nreturn msg;","outputs":1,"noerr":0,"x":430,"y":3020,"wires":[["29d47dae.6be632","bfda6f87.ffbb1"]]},{"id":"29d47dae.6be632","type":"cronplus","z":"63281c77.40a064","name":"","outputField":"payload","timeZone":"","options":[],"x":620,"y":3020,"wires":[["7c319937.5dc158","5487012f.84a5e"]]},{"id":"478c4836.d0fa98","type":"switch","z":"63281c77.40a064","name":"","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"describe","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":560,"y":2900,"wires":[["a768cfd6.d5677","58877c4f.ce50f4"],[]]},{"id":"2dfd1241.3fee8e","type":"link in","z":"63281c77.40a064","name":"describe","links":["a768cfd6.d5677"],"x":315,"y":3020,"wires":[["755458a1.0e2c28"]]},{"id":"a768cfd6.d5677","type":"link out","z":"63281c77.40a064","name":"describe-out","links":["2dfd1241.3fee8e"],"x":675,"y":2860,"wires":[]},{"id":"2482e719.803068","type":"debug","z":"63281c77.40a064","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":570,"y":2820,"wires":[]},{"id":"58877c4f.ce50f4","type":"debug","z":"63281c77.40a064","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":770,"y":2860,"wires":[]},{"id":"bfda6f87.ffbb1","type":"debug","z":"63281c77.40a064","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":630,"y":3080,"wires":[]},{"id":"7c319937.5dc158","type":"debug","z":"63281c77.40a064","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":770,"y":2980,"wires":[]},{"id":"5487012f.84a5e","type":"link out","z":"63281c77.40a064","name":"cron-out","links":["6f114bd6.d008d4"],"x":780,"y":3040,"wires":[]},{"id":"6f114bd6.d008d4","type":"link in","z":"63281c77.40a064","name":"uib-cron-in","links":["5487012f.84a5e","dee69c83.8a411"],"x":295,"y":2920,"wires":[["db553021.e64ac"]]}]

index.html

<!doctype html><html lang="en"><head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">

    <title>uibuilder cron-plus example</title>
    <meta name="description" content="Node-RED UI Builder - cron-plus example">
    <link rel="icon" href="./images/node-blue.ico">

    <link rel="preload" as="font" href="../ui/fonts/fontawesome-webfont.woff2" type="font/woff2" crossorigin="anonymous">
    <link type="text/css" rel="stylesheet" href="../uibuilder/vendor/bootstrap/dist/css/bootstrap.min.css" />
    <link type="text/css" rel="stylesheet" href="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.css" />
    <link type="text/css" rel="stylesheet" href="../vendor/font-awesome/css/font-awesome.min.css" />
    
    <link rel="stylesheet" href="./index.css" media="all">
</head><body>

    <script type="text/x-template" id="cron-input-template">
        <div>
                <b-form inline>
                    <b-form-input
                        id="input-name"
                        v-model="inputName"
                        type="text"
                        required autofocus
                        placeholder="Name for this job"
                    ></b-form-input>
                    <b-form-input
                        id="input-expression"
                        v-model="inputExpression"
                        type="text"
                        required
                        placeholder="CRON expression"
                    ></b-form-input>
                    <b-form-input
                        id="input-payload"
                        v-model="inputPayload"
                        type="text"
                        required
                        placeholder="Output payload"
                    ></b-form-input>
                    <b-button>Add</b-button>
                    <b-button>Refresh</b-button>
                </b-form>
                <b-card>
                    Expression Description ... <br>
                    <b>{{expressionDescription}}</b>
                    <h5>Next</h5>
                    <div>Date: {{nextDate}}</div>
                    <div>Description: {{nextDescription}}</div>
                </b-card>
                <!-- <b-card><pre>{{testOutput}}</pre></b-card> -->
        </div>
    </script>
    <script type="text/x-template" id="cron-schedules-template">
        <div>

        </div>
    </script>
    <script type="text/x-template" id="cron-help-template">
        <div>
            <h5 v-b-toggle.collapse-1 tabindex="0" role="button" class="pointer">
                &#x21f5;
                Help
            </h5>
            <b-collapse id="collapse-1" class="mt-2">
                <b-card>
                    <pre>
*  *  *  *  *  *  *    Field              Allowed values    Special symbols
|  |  |  |  |  |  |    -----------------  ---------------   ---------------
`--|--|--|--|--|--|->  Second (optional)  0-59              * / , -
   `--|--|--|--|--|->  Minute             0-59              * / , -
      `--|--|--|--|->  Hour               0-23              * / , -
         `--|--|--|->  Day of Month       1-31              * / , - L W
            `--|--|->  Month              1-12 or JAN-DEC   * / , -
               `--|->  Day of Week        0-7 or SUN-SAT    * / , - L #
                  `->  Year (optional)    1970-2099         * / , -
                    </pre>
                    <ul>
                        <li>* = All values</li>
                        <li>, = List of expressions, e.g. 4,5,15.</li>
                        <li>/ = Increments of a range. e.g. */3 (every 3 min/hours/etc).</li>
                        <li>- = Range, e.g. 20019-2024.</li>
                        <li>L = Last day of month, or with day of week, last of those days of the month. e.g. WedL, last Wed of the month.</li>
                        <li>W = Nearest weekday (Mon-Fri) of given day of month.</li>
                        <li># = Nth of month. e.g. Tue#3 is the 3rd Tue of the month.</li>
                    </ul>
                    <b-table-lite :items="cronEgs" small hover striped caption-top head-variant="dark">
                        <template v-slot:table-caption>Example CRON Expressions</template>
                    </b-table-lite>
                </b-card>
            </b-collapse>
        </div>
    </script>

    <div id="app" v-cloak>
        <b-container id="app_container">

            <h1>
                UIbuilder - Cron-Plus front-end
            </h1>
            <p>
                This is a <a href="https://github.com/TotallyInformation/node-red-contrib-uibuilder">uibuilder</a> example of allowing end-users to build cron schedules with the help of node-red-contrib-cron-plus.
            </p>

            <cron-input></cron-input>
            <cron-schedules></cron-schedules>
            <cron-help></cron-help>

        </b-container>
    </div>

    <script src="../uibuilder/vendor/socket.io/socket.io.js"></script>
    <script src="../uibuilder/vendor/vue/dist/vue.min.js"></script> <!-- dev version with component compiler -->
    <script src="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.js"></script>
    <script src="./uibuilderfe.min.js"></script> <!--    //prod version -->
    <script src="./index.js"></script>

</body></html>

index.js

/* jshint browser: true, esversion: 5, asi: true */
/*globals Vue, uibuilder */
// @ts-nocheck
/*
  Copyright (c) 2019 Julian Knight (Totally Information)

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/
'use strict'

/** @see https://github.com/TotallyInformation/node-red-contrib-uibuilder/wiki/Front-End-Library---available-properties-and-methods */

// Global Template Components
Vue.component('cron-input', {
    props: [],
    template: '#cron-input-template',
    data: function() { return {
        inputName: '',
        inputPayload: '',
        inputExpression: '0 0 * * * *', // If you update this, change the description too
        expressionDescription: 'Every hour',
        nextDate: '',
        nextDescription: '',
        testOutput: '',
    }},
    computed: {
        // inputDescription: function() {
        //     console.log(this.inputName + '/' + this.inputExpression + '/' + this.inputPayload)

        //     return this.inputName + '/' + this.inputExpression + '/' + this.inputPayload
        // },
    },
    methods: {
    },
    watch: {
        /** Whenever inputExpression changes, ask cron-plus for the description */
        inputExpression: function() {
            //console.log(this.inputExpression)
            uibuilder.send({
                'topic': 'describe',
                'payload': this.inputExpression
            })
        },
    },
    mounted: function() {
        var that = this

        uibuilder.onChange('msg', function(msg){
            //console.info('[indexjs:uibuilder.onChange] msg received from Node-RED server:', msg)

            if ( msg.topic === 'describe' )  {
                that.expressionDescription = msg.payload.result.description
                that.nextDate = msg.payload.result.nextDate
                that.nextDescription = msg.payload.result.nextDescription
                console.log('DESCRIBE:', msg.payload)
            } else if ( msg.topic === 'test' )  {
                console.log('test', msg.payload)
                that.testOutput = msg.payload
            }
        })
    }
})
Vue.component('cron-schedules', {
    props: [],
    template: '#cron-schedules-template',
    data: function() { return {
    }},
    computed: {
    },
    methods: {
    },
})
Vue.component('cron-help', {
    props: [],
    template: '#cron-help-template',
    data: function() { return {
        cronEgs: [
            {'expression': '* * * * * *', 'description': 'Every Second',},
            {'expression': '0 * * * * *', 'description': 'Every minute',},
            {'expression': '0 */10 * * * *', 'description': 'Every 10 minutes',},
            {'expression': '0 */20 1 * * *', 'description': 'Every 20 minutes, between 01:00 AM and 01:59 AM',},
            {'expression': '0 15,30,45 * * * *', 'description': 'At 15, 30, and 45 minutes past the hour',},
            {'expression': '0 0 12 * * *', 'description': 'Every day at noon - 12pm',},
            {'expression': '0 0 2 29 FEB * 2020-2040', 'description': 'At 02:00 AM, on day 29 of the month, only in February, every 4 years, 2020 through 2040',},
            {'expression': '0 0 7 * * MON#1 *', 'description': 'At 07:00 AM, on the first Monday of the month',},
            {'expression': '0 0 12 * JAN,FEB,MAR,APR *', 'description': 'Every day at noon in January, February, March and April',},
            {'expression': '* * 1W * *', 'description': 'Every minute, on the first weekday of the month',},
            {'expression': '* * * * Tue#', 'description': 'Every minute, on the third Tuesday of the month',},
            {'expression': '0 12 * * ', 'description': 'At 12:00 PM, on the last Monday of the month',},
        ],
    }},
    computed: {
    },
    methods: {
    },
})


// eslint-disable-next-line no-unused-vars
var app1 = new Vue({
    el: '#app',
    data: {
    }, // --- End of data --- //
    computed: {
    }, // --- End of computed --- //
    methods: {
    }, // --- End of methods --- //

    // Available hooks: init,mounted,updated,destroyed
    mounted: function(){
        //console.debug('[indexjs:Vue.mounted] app mounted - setting up uibuilder watchers')

        /** **REQUIRED** Start uibuilder comms with Node-RED @since v2.0.0-dev3
         * Pass the namespace and ioPath variables if hosting page is not in the instance root folder
         * e.g. If you get continual `uibuilderfe:ioSetup: SOCKET CONNECT ERROR` error messages.
         * e.g. uibuilder.start('/nr/uib', '/nr/uibuilder/vendor/socket.io') // change to use your paths/names
         */
        uibuilder.start()

        var vueApp = this

        uibuilder.onChange('msg', function(msg){
            //console.info('[indexjs:uibuilder.onChange] msg received from Node-RED server:', msg)
            vueApp.msgRecvd = msg

        })

    } // --- End of mounted hook --- //

}) // --- End of app1 --- //

// EOF

index.css

/* Cloak elements on initial load to hide the possible display of {{ ... }} 
 * Add to the app tag or to specific tags
 * To display "loading...", change to the following:
 *    [v-cloak] > * { display:none }
 *    [v-cloak]::before { content: "loading…" }
 */
[v-cloak] { display: none; }

.pointer { cursor: pointer; }

Code is split so that the main app is kept minimal and there are 3 components. These could be moved to external .vue files and dynamically loaded if needed.

1 Like

Nice work.

I gotta invest some time in vue & get up to speed.

BTW, V0.4.0 is now in flow library - let me know if things are working properly now please.

That's looking much better Steve, thanks for that.

Updated version of my uibuilder flow:

[{"id":"db553021.e64ac","type":"uibuilder","z":"63281c77.40a064","name":"uib cron","topic":"","url":"cron","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":true,"showfolder":false,"x":170,"y":2920,"wires":[["478c4836.d0fa98","b28661ee.2f57b"],[]]},{"id":"755458a1.0e2c28","type":"function","z":"63281c77.40a064","name":"describe cmd","func":"msg.payload = {\n    \"command\": \"describe\",\n    \"expression\": msg.payload\n}\nreturn msg;","outputs":1,"noerr":0,"x":200,"y":3220,"wires":[["29d47dae.6be632"]]},{"id":"29d47dae.6be632","type":"cronplus","z":"63281c77.40a064","name":"","outputField":"payload","timeZone":"","options":[],"x":390,"y":3220,"wires":[["5487012f.84a5e"]]},{"id":"478c4836.d0fa98","type":"switch","z":"63281c77.40a064","name":"request type","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"describe","vt":"str"},{"t":"eq","v":"addJob","vt":"str"},{"t":"eq","v":"refresh","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":4,"x":350,"y":2920,"wires":[["a768cfd6.d5677","dcc724ce.033568"],["ce00103f.456d5","70525ed3.3878c"],["ce00103f.456d5","70525ed3.3878c"],[]],"outputLabels":["describe","add","refresh","other"]},{"id":"2dfd1241.3fee8e","type":"link in","z":"63281c77.40a064","name":"describe-cron","links":["a768cfd6.d5677"],"x":75,"y":3220,"wires":[["755458a1.0e2c28"]]},{"id":"a768cfd6.d5677","type":"link out","z":"63281c77.40a064","name":"uib-cron-describe-out","links":["2dfd1241.3fee8e"],"x":515,"y":2880,"wires":[]},{"id":"5487012f.84a5e","type":"link out","z":"63281c77.40a064","name":"cron-describe-out","links":["6f114bd6.d008d4"],"x":545,"y":3220,"wires":[]},{"id":"6f114bd6.d008d4","type":"link in","z":"63281c77.40a064","name":"uib-cron-in","links":["5487012f.84a5e","dee69c83.8a411","a9a1ff52.62be1"],"x":75,"y":2920,"wires":[["db553021.e64ac"]]},{"id":"e51df78.11b3408","type":"comment","z":"63281c77.40a064","name":"==== ---- ==== ---- uibuilder cron-plus example ---- ==== ---- ====","info":"","x":290,"y":2820,"wires":[]},{"id":"ce00103f.456d5","type":"link out","z":"63281c77.40a064","name":"uib-cron-add-out","links":["56cfdd0a.d0ee94"],"x":515,"y":2920,"wires":[]},{"id":"30c625f2.1c648a","type":"cronplus","z":"63281c77.40a064","name":"","outputField":"payload","timeZone":"","options":[],"x":180,"y":3060,"wires":[["591fda51.4b4e84","293616a5.b66aca"]]},{"id":"56cfdd0a.d0ee94","type":"link in","z":"63281c77.40a064","name":"cron-input","links":["ce00103f.456d5"],"x":75,"y":3060,"wires":[["30c625f2.1c648a"]]},{"id":"591fda51.4b4e84","type":"switch","z":"63281c77.40a064","name":"1=Cron Event   2=List Results ","property":"cronplus.triggerTimestamp","propertyType":"msg","rules":[{"t":"nnull"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":400,"y":3060,"wires":[["b2ed28f2.18e348"],["40f2cd30.5e41a4","b2a4c86.5f63e38"]]},{"id":"f6abacae.6147d","type":"debug","z":"63281c77.40a064","name":"DO SOMETHING USEFULL","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":900,"y":3020,"wires":[]},{"id":"40f2cd30.5e41a4","type":"function","z":"63281c77.40a064","name":"Make listJobs table","func":"msg.originalTopic = msg.topic\nmsg.topic = 'listJobs'\n\nif(!msg.payload || !msg.payload.result || !msg.payload.result.length){\n    msg.payload = [];\n    return msg;\n}\n\nvar result = [];\n\nmsg.payload.result.forEach(function(job){\n    node.warn(job)\n    result.push({\n        name: job.config.name,\n        expression: job.config.expression,\n        payload: job.config.payload,\n        description: job.status.description,\n        nextRun: job.status.nextDescription,\n        nextDate: job.status.nextDate,\n        state: job.status.isRunning,\n    })\n})\n\nmsg.payload = result\n\nreturn msg;","outputs":1,"noerr":0,"x":650,"y":3080,"wires":[["a9a1ff52.62be1"]]},{"id":"b2ed28f2.18e348","type":"change","z":"63281c77.40a064","name":"trigger","rules":[{"t":"set","p":"originalTopic","pt":"msg","to":"topic","tot":"msg"},{"t":"set","p":"topic","pt":"msg","to":"trigger","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":610,"y":3040,"wires":[["f6abacae.6147d","a9a1ff52.62be1"]]},{"id":"a9a1ff52.62be1","type":"link out","z":"63281c77.40a064","name":"cron-trigger-out","links":["6f114bd6.d008d4"],"x":795,"y":3060,"wires":[]},{"id":"81955350.e8a99","type":"catch","z":"63281c77.40a064","name":"catch uib-cron+","scope":["db553021.e64ac","755458a1.0e2c28","29d47dae.6be632","478c4836.d0fa98","2dfd1241.3fee8e","a768cfd6.d5677","2482e719.803068","58877c4f.ce50f4","bfda6f87.ffbb1","7c319937.5dc158","5487012f.84a5e","6f114bd6.d008d4","c2da6d69.e515","3f74672c.cf13c8","e51df78.11b3408","ce00103f.456d5","30c625f2.1c648a","56cfdd0a.d0ee94","591fda51.4b4e84","f6abacae.6147d","40f2cd30.5e41a4","b2ed28f2.18e348","a9a1ff52.62be1"],"uncaught":false,"x":620,"y":2820,"wires":[["e2580d45.3036c"]]},{"id":"e2580d45.3036c","type":"debug","z":"63281c77.40a064","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":790,"y":2820,"wires":[]},{"id":"b28661ee.2f57b","type":"debug","z":"63281c77.40a064","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":330,"y":2860,"wires":[]},{"id":"70525ed3.3878c","type":"debug","z":"63281c77.40a064","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":610,"y":2920,"wires":[]},{"id":"dcc724ce.033568","type":"debug","z":"63281c77.40a064","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":610,"y":2880,"wires":[]},{"id":"293616a5.b66aca","type":"debug","z":"63281c77.40a064","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":330,"y":3020,"wires":[]},{"id":"b2a4c86.5f63e38","type":"debug","z":"63281c77.40a064","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":610,"y":3100,"wires":[]}]

index.html

<!doctype html><html lang="en"><head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">

    <title>uibuilder cron-plus example</title>
    <meta name="description" content="Node-RED UI Builder - cron-plus example">
    <link rel="icon" href="./images/node-blue.ico">

    <link type="text/css" rel="stylesheet" href="../uibuilder/vendor/bootstrap/dist/css/bootstrap.min.css" />
    <link type="text/css" rel="stylesheet" href="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.css" />
    
    <link rel="stylesheet" href="./index.css" media="all">
</head><body>

    <script type="text/x-template" id="cron-input-template">
        <div>
                <b-form inline>
                    <b-form-input
                        id="input-name"
                        v-model="inputName"
                        type="text"
                        required autofocus
                        placeholder="Name for this job"
                    ></b-form-input>
                    <b-form-input
                        id="input-expression"
                        v-model="inputExpression"
                        type="text"
                        required
                        placeholder="CRON expression"
                    ></b-form-input>
                    <b-form-input
                        id="input-payload"
                        v-model="inputPayload"
                        type="text"
                        required
                        placeholder="Output payload"
                    ></b-form-input>
                    <b-button @click="addJob">Add</b-button>
                    <b-button @click="refresh">Refresh</b-button>
                </b-form>
                <b-card>
                    Expression Description ... <br>
                    <b>{{expressionDescription}}</b>
                    <h5>Next</h5>
                    <div>Date: {{nextDate}}</div>
                    <div>Description: {{nextDescription}}</div>
                </b-card>
                <!-- <b-card><pre>{{testOutput}}</pre></b-card> -->
        </div>
    </script>
    <script type="text/x-template" id="cron-schedules-template">
        <div>
            <b-table-lite :items="schedules" small hover striped caption-top head-variant="dark">
                <template v-slot:table-caption>Current Schedules</template>
            </b-table-lite>
            <div v-html="lastTrigger"></div>
        </div>
    </script>
    <script type="text/x-template" id="cron-help-template">
        <div>
            <h5 v-b-toggle.collapse-1 tabindex="0" role="button" class="pointer">
                &#x21f5;
                Help
            </h5>
            <b-collapse id="collapse-1" class="mt-2">
                <b-card>
                    <pre>{{cronExpression}}</pre>
                    <ul>
                        <li>* = All values</li>
                        <li>, = List of expressions, e.g. 4,5,15.</li>
                        <li>/ = Increments of a range. e.g. */3 (every 3 min/hours/etc).</li>
                        <li>- = Range, e.g. 20019-2024.</li>
                        <li>L = Last day of month, or with day of week, last of those days of the month. e.g. WedL, last Wed of the month.</li>
                        <li>W = Nearest weekday (Mon-Fri) of given day of month.</li>
                        <li># = Nth of month. e.g. Tue#3 is the 3rd Tue of the month.</li>
                    </ul>
                    <b-table-lite :items="cronEgs" small hover striped caption-top head-variant="dark">
                        <template v-slot:table-caption>Example CRON Expressions</template>
                    </b-table-lite>
                </b-card>
            </b-collapse>
        </div>
    </script>

    <div id="app" v-cloak>
        <b-container id="app_container">

            <h1>
                UIbuilder - Cron-Plus front-end
            </h1>
            <p>
                This is a <a href="https://github.com/TotallyInformation/node-red-contrib-uibuilder">uibuilder</a> example of allowing end-users to build cron schedules with the help of node-red-contrib-cron-plus.
            </p>

            <cron-input></cron-input>
            <cron-schedules></cron-schedules>
            <cron-help></cron-help>

        </b-container>
    </div>

    <script src="../uibuilder/vendor/socket.io/socket.io.js"></script>
    <script src="../uibuilder/vendor/vue/dist/vue.min.js"></script> <!-- dev version with component compiler -->
    <script src="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.js"></script>
    <script src="./uibuilderfe.min.js"></script> <!--    //prod version -->
    <script src="./index.js"></script>

</body></html>

index.js

/* jshint browser: true, esversion: 5, asi: true */
/*globals Vue, uibuilder */
// @ts-nocheck
/*
  Copyright (c) 2019 Julian Knight (Totally Information)

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/
'use strict'

/** @see https://github.com/TotallyInformation/node-red-contrib-uibuilder/wiki/Front-End-Library---available-properties-and-methods */

// Global Template Components
Vue.component('cron-input', {
    props: [],
    template: '#cron-input-template',
    data: function() { return {
        inputName: '',
        inputPayload: '',
        inputExpression: '* * * * *', // If you update this, change the description too
        expressionDescription: 'Every minute',
        nextDate: '',
        nextDescription: '',
        testOutput: '',
    }},
    computed: {
        // inputDescription: function() {
        //     console.log(this.inputName + '/' + this.inputExpression + '/' + this.inputPayload)

        //     return this.inputName + '/' + this.inputExpression + '/' + this.inputPayload
        // },
    },
    methods: {
        addJob: function() {
            console.log('addJob', this.inputName, this.inputExpression, this.inputPayload)
            uibuilder.send({
                'topic': 'addJob',
                'payload': {
                    'name': this.inputName, 
                    'expression': this.inputExpression, 
                    'payload': this.inputPayload,
                    'command': 'add',
                    'type': 'str',
                },
            })
        },
        refresh: function() {
            console.log('refresh', this.inputName, this.inputExpression, this.inputPayload)
            uibuilder.send({
                'topic': 'refresh',
                'payload': {
                    'command': 'list-all',
                },
            })
        },
    },
    watch: {
        /** Whenever inputExpression changes, ask cron-plus for the description */
        inputExpression: function() {
            //console.log(this.inputExpression)
            uibuilder.send({
                'topic': 'describe',
                'payload': this.inputExpression
            })
        },
    },
    mounted: function() {
        var that = this

        uibuilder.onChange('msg', function(msg){
            //console.info('[indexjs:uibuilder.onChange] msg received from Node-RED server:', msg)

            if ( msg.topic === 'describe' )  {
                that.expressionDescription = msg.payload.result.description
                that.nextDate = msg.payload.result.nextDate
                that.nextDescription = msg.payload.result.nextDescription
                console.log('DESCRIBE:', msg.payload)
            } else if ( msg.topic === 'test' )  {
                console.log('test', msg.payload)
                that.testOutput = msg.payload
            }
        })
    },
})
Vue.component('cron-schedules', {
    props: [],
    template: '#cron-schedules-template',
    data: function() { return {
        schedules: [],
        lastTrigger: '',
    }},
    computed: {
    },
    methods: {
    },
    mounted: function() {
        var that = this

        uibuilder.onChange('msg', function(msg){
            if ( msg.topic === 'trigger' )  {
                console.log('trigger', msg)
                var triggerTs = new Date(msg.cronplus.triggerTimestamp)
                that.lastTrigger = `Payload <b>${msg.payload}</b> triggered at <i>${triggerTs.toLocaleString()}</i> by job <i>${msg.cronplus.config.name}</i>`

            } else if ( msg.topic === 'listJobs' )  {
                console.log('listJobs', msg)

                that.schedules = msg.payload
            }
        })
    },
})
Vue.component('cron-help', {
    props: [],
    template: '#cron-help-template',
    data: function() { return {
        cronEgs: [
            {'expression': '* * * * * *', 'description': 'Every Second',},
            {'expression': '0 * * * * *', 'description': 'Every minute',},
            {'expression': '0 */10 * * * *', 'description': 'Every 10 minutes',},
            {'expression': '0 */20 1 * * *', 'description': 'Every 20 minutes, between 01:00 AM and 01:59 AM',},
            {'expression': '0 15,30,45 * * * *', 'description': 'At 15, 30, and 45 minutes past the hour',},
            {'expression': '0 0 12 * * *', 'description': 'Every day at noon - 12pm',},
            {'expression': '0 0 2 29 FEB * 2020-2040', 'description': 'At 02:00 AM, on day 29 of the month, only in February, every 4 years, 2020 through 2040',},
            {'expression': '0 0 7 * * MON#1 *', 'description': 'At 07:00 AM, on the first Monday of the month',},
            {'expression': '0 0 12 * JAN,FEB,MAR,APR *', 'description': 'Every day at noon in January, February, March and April',},
            {'expression': '* * 1W * *', 'description': 'Every minute, on the first weekday of the month',},
            {'expression': '* * * * Tue#', 'description': 'Every minute, on the third Tuesday of the month',},
            {'expression': '0 12 * * ', 'description': 'At 12:00 PM, on the last Monday of the month',},
        ],
        cronExpression: `
*  *  *  *  *  *  *    Field              Allowed values    Special symbols
|  |  |  |  |  |  |    -----------------  ---------------   ---------------
\--|--|--|--|--|--|->  Second (optional)  0-59              * / , -
   \--|--|--|--|--|->  Minute             0-59              * / , -
      \--|--|--|--|->  Hour               0-23              * / , -
         \--|--|--|->  Day of Month       1-31              * / , - L W
            \--|--|->  Month              1-12 or JAN-DEC   * / , -
               \--|->  Day of Week        0-7 or SUN-SAT    * / , - L #
                  \->  Year (optional)    1970-2099         * / , -
        `,
    }},
    computed: {
    },
    methods: {
    },
})


// eslint-disable-next-line no-unused-vars
var app1 = new Vue({
    el: '#app',
    data: {
    }, // --- End of data --- //
    computed: {
    }, // --- End of computed --- //
    methods: {
    }, // --- End of methods --- //

    // Available hooks: init,mounted,updated,destroyed
    mounted: function(){
        //console.debug('[indexjs:Vue.mounted] app mounted - setting up uibuilder watchers')

        /** **REQUIRED** Start uibuilder comms with Node-RED @since v2.0.0-dev3
         * Pass the namespace and ioPath variables if hosting page is not in the instance root folder
         * e.g. If you get continual `uibuilderfe:ioSetup: SOCKET CONNECT ERROR` error messages.
         * e.g. uibuilder.start('/nr/uib', '/nr/uibuilder/vendor/socket.io') // change to use your paths/names
         */
        uibuilder.start()

        var vueApp = this

        uibuilder.onChange('msg', function(msg){
            //console.info('[indexjs:uibuilder.onChange] msg received from Node-RED server:', msg)
            vueApp.msgRecvd = msg

        })

    } // --- End of mounted hook --- //

}) // --- End of app1 --- //

// EOF

Bedtime!

1 Like