Dynamic cron schedules / timers via dashboard control
About
This flow demonstrates how you can add, remove, pause, resume CRON schedules / timers via dashboard control at runtime with node-red-contrib-cron-plus.
Requirements...
Install node-red-contrib-cron-plus
from the pallet manager
Features...
- Add a schedule
- CRON expression or a datetime can be entered
- View existing schedules
- Delete a schedule
- Pause a schedule
- Resume a schedule
Screenshot...
[{"id":"952ffcf0.f84ac","type":"ui_text","z":"67b3dea3.cc212","group":"c3f194d9.93ecd8","order":1,"width":"16","height":"2","name":"CRON Description","label":"Expression Description (updates as you type)...","format":"{{msg.payload.result.description}}","layout":"col-center","x":770,"y":380,"wires":[]},{"id":"d657bcf5.0f961","type":"cronplus","z":"67b3dea3.cc212","name":"","outputField":"payload","timeZone":"","options":[],"x":480,"y":380,"wires":[["952ffcf0.f84ac"]]},{"id":"8a3b6d25.2cc45","type":"function","z":"67b3dea3.cc212","name":"describe cmd","func":"msg.payload = {\n \"command\": \"describe\",\n \"expression\": msg.payload\n}\nreturn msg;","outputs":1,"noerr":0,"x":290,"y":380,"wires":[["d657bcf5.0f961"]]},{"id":"7f182a47.6a92d4","type":"change","z":"67b3dea3.cc212","name":"","rules":[{"t":"set","p":"ui_add_schedule_name","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":350,"y":260,"wires":[[]]},{"id":"178c29c6.469c26","type":"change","z":"67b3dea3.cc212","name":"","rules":[{"t":"set","p":"ui_add_schedule_payload","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":360,"y":300,"wires":[[]]},{"id":"4e82804c.deec8","type":"change","z":"67b3dea3.cc212","name":"","rules":[{"t":"set","p":"ui_add_schedule_expression","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":370,"y":340,"wires":[[]]},{"id":"b5b431d9.bfb55","type":"change","z":"67b3dea3.cc212","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"{}","tot":"json"},{"t":"set","p":"payload.payload","pt":"msg","to":"ui_add_schedule_payload","tot":"flow"},{"t":"set","p":"payload.name","pt":"msg","to":"ui_add_schedule_name","tot":"flow"},{"t":"set","p":"payload.expression","pt":"msg","to":"ui_add_schedule_expression","tot":"flow"},{"t":"set","p":"payload.command","pt":"msg","to":"add","tot":"str"},{"t":"set","p":"payload.type","pt":"msg","to":"str","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":300,"y":460,"wires":[["3f284188.fdf30e","f78bacd7.fd897"]]},{"id":"3f284188.fdf30e","type":"cronplus","z":"67b3dea3.cc212","name":"","outputField":"payload","timeZone":"","options":[],"x":640,"y":460,"wires":[["abf40655.039a88"]]},{"id":"be8bc068.715f3","type":"ui_toast","z":"67b3dea3.cc212","position":"top right","displayTime":"3","highlight":"","sendall":true,"outputs":0,"ok":"OK","cancel":"","topic":"","name":"","x":590,"y":540,"wires":[]},{"id":"abf40655.039a88","type":"switch","z":"67b3dea3.cc212","name":"1=Cron Event 2=List Results ","property":"cronplus.triggerTimestamp","propertyType":"msg","rules":[{"t":"nnull"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":320,"y":560,"wires":[["be8bc068.715f3"],["7ae8d780.88fc08"]]},{"id":"eba9438.7bf69c","type":"catch","z":"67b3dea3.cc212","name":"","scope":null,"uncaught":false,"x":100,"y":680,"wires":[["92cbfc6f.77ad5","d860d2d.5afc53"]]},{"id":"92cbfc6f.77ad5","type":"ui_toast","z":"67b3dea3.cc212","position":"top right","displayTime":"3","highlight":"red","outputs":0,"ok":"OK","cancel":"","topic":"Error...","name":"","x":310,"y":680,"wires":[]},{"id":"f78bacd7.fd897","type":"ui_button","z":"67b3dea3.cc212","name":"Refresh","group":"c3f194d9.93ecd8","order":6,"width":"1","height":"1","passthru":true,"label":"","tooltip":"Get status of jobs","color":"","bgcolor":"","icon":"fa-refresh","payload":"{\"command\":\"list-all\"}","payloadType":"json","topic":"","x":480,"y":480,"wires":[["3f284188.fdf30e"]]},{"id":"2e128d0.3fb4a74","type":"ui_text_input","z":"67b3dea3.cc212","name":"","label":"Expression","tooltip":"CRON expression or date/csv list of dates","group":"c3f194d9.93ecd8","order":3,"width":"4","height":"1","passthru":true,"mode":"text","delay":"100","topic":"","x":110,"y":340,"wires":[["8a3b6d25.2cc45","4e82804c.deec8"]]},{"id":"bda83091.c7f44","type":"ui_text_input","z":"67b3dea3.cc212","name":"","label":"Name","tooltip":"Schedule Name","group":"c3f194d9.93ecd8","order":2,"width":"4","height":"1","passthru":true,"mode":"text","delay":"100","topic":"","x":90,"y":260,"wires":[["7f182a47.6a92d4"]]},{"id":"96463052.88a8d","type":"ui_button","z":"67b3dea3.cc212","name":"","group":"c3f194d9.93ecd8","order":5,"width":"3","height":"1","passthru":false,"label":"Add","tooltip":"Add or update named schedule","color":"","bgcolor":"","icon":"send","payload":"","payloadType":"str","topic":"","x":90,"y":460,"wires":[["b5b431d9.bfb55"]]},{"id":"5bb3aee1.5fc43","type":"ui_text_input","z":"67b3dea3.cc212","name":"","label":"Payload","tooltip":"Payload to send when schedule fires","group":"c3f194d9.93ecd8","order":4,"width":"4","height":"1","passthru":true,"mode":"text","delay":"100","topic":"","x":100,"y":300,"wires":[["178c29c6.469c26"]]},{"id":"e15ad659.7d0128","type":"ui_template","z":"67b3dea3.cc212","group":"c3f194d9.93ecd8","name":"","order":7,"width":"16","height":"5","format":"<style type=\"text/css\">\n.t1 {border-collapse:collapse;border-spacing:0;border-color:#ccc;}\n.t1 td{font-family:Arial, sans-serif;font-size:14px;padding:8px 3px;border-style:solid;border-width:0px;overflow:hidden;word-break:normal;border-top-width:1px;border-bottom-width:1px;border-color:#ccc;color:#333;background-color:#fff;}\n.t1 th{font-family:Arial, sans-serif;font-size:14px;font-weight:normal;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:#ccc;color:#333;background-color:#f0f0f0;}\n.t1 .t1-36r9{background-color:#000000;color:#ffffff;text-align:left;vertical-align:top}\n.t1 .t1-x2zo{background-color:#c0c0c0;font-size:small;text-align:left;vertical-align:top}\n.t1 .t1-o73e{background-color:#c0c0c0;font-size:small;text-align:center;vertical-align:top}\n.t1 .t1-5qt9{font-size:small;text-align:left;vertical-align:top}\n.t1 .t1-5qta{font-size:small;text-align:center;vertical-align:middle}\n</style>\n<table class=\"t1\">\n <tr>\n <th class=\"t1-36r9\" colspan=\"9\">CRON Schedules</th>\n </tr>\n <tr>\n <td class=\"t1-x2zo\">Name</td>\n <td class=\"t1-x2zo\">Expression</td>\n <td class=\"t1-x2zo\">Payload</td>\n <td class=\"t1-x2zo\">Description</td>\n <td class=\"t1-x2zo\">Next run</td>\n <td class=\"t1-x2zo\">State</td>\n <td class=\"t1-o73e\">Delete</td>\n <td class=\"t1-o73e\">Pause</td>\n <td class=\"t1-o73e\">Resume</td>\n </tr>\n <tr ng-repeat=\"item in msg.payload\">\n <td class=\"t1-5qt9\">{{item.config.name}}</td>\n <td class=\"t1-5qt9\" style=\"white-space: nowrap\" nowrap>{{item.config.expression}}</td>\n <td class=\"t1-5qt9\">{{item.config.payload}}</td>\n <td class=\"t1-5qt9\">{{item.status.description}}</td>\n <td class=\"t1-5qt9\">{{item.status.nextDescription}}</td>\n <td class=\"t1-5qta\"><i class=\"fa fa-{{item.status.isRunning ? 'play' : 'pause'}}\"> </i></td>\n\n <td class=\"t1-5qta\">\n <md-button class=\"md-raised\"\n ng-click=\"send([[{payload:{command:'remove', name: item.config.name}}, {payload:{command:'list-all'}}]]);\"> <i class=\"fa fa-trash\"> </i></md-button>\n </td>\n <td class=\"t1-5qta\">\n <md-button class=\"button\" \n ng-click=\"send([[{payload:{command:'pause', name: item.config.name}}, {payload:{command:'list-all'}}]]);\"> <i class=\"fa fa-pause\"> </i></md-button>\n </td>\n <td class=\"t1-5qta\">\n <md-button class=\"button\" \n ng-click=\"send([[{payload:{command:'start', name: item.config.name}}, {payload:{command:'list-all'}}]]);\"> <i class=\"fa fa-play\"> </i></md-button>\n </td>\n\n </tr>\n\n </tbody>\n</table>\n\n<!--<table id=\"table\" class=\"table table-striped table-responsive-md btn-table\" >-->\n<!-- <tr>-->\n<!-- <th>Name</th> -->\n<!-- <th>Expression</th> -->\n<!-- <th>Payload</th> -->\n<!-- <th>Description</th>-->\n<!-- <th>Next run</th>-->\n<!-- <th>State</th>-->\n<!-- <th>Delete</th>-->\n<!-- <th>Pause</th>-->\n<!-- <th>Resume</th>-->\n<!-- </tr>-->\n<!-- <tbody>-->\n<!-- <tr ng-repeat=\"item in msg.payload\">-->\n<!-- <td >{{item.config.name}}</td>-->\n<!-- <td style=\"white-space: nowrap\" nowrap>{{item.config.expression}}</td>-->\n<!-- <td >{{item.config.payload}}</td>-->\n<!-- <td >{{item.status.description}}</td>-->\n<!-- <td >{{item.status.nextDescription}}</td>-->\n<!-- <td >{{item.status.isRunning ? 'Running' : 'Paused'}}</td>-->\n<!-- <td>-->\n<!-- <md-button class=\"md-raised\"-->\n<!-- ng-click=\"send([[{payload:{command:'remove', name: item.config.name}}, {payload:{command:'list-all'}}]]);\"> <i class=\"fa fa-trash\"> </i></md-button>-->\n<!-- </td>-->\n<!-- <td>-->\n<!-- <md-button class=\"button\" -->\n<!-- ng-click=\"send([[{payload:{command:'pause', name: item.config.name}}, {payload:{command:'list-all'}}]]);\"> <i class=\"fa fa-pause\"> </i></md-button>-->\n<!-- </td>-->\n<!-- <td>-->\n<!-- <md-button class=\"button\" -->\n<!-- ng-click=\"send([[{payload:{command:'start', name: item.config.name}}, {payload:{command:'list-all'}}]]);\"> <i class=\"fa fa-play\"> </i></md-button>-->\n<!-- </td>-->\n<!-- </tr>-->\n<!-- </tbody>-->\n<!--</table>-->\n\n","storeOutMessages":false,"fwdInMessages":false,"templateScope":"local","x":740,"y":580,"wires":[["851d95e5.61cc58"]]},{"id":"7ae8d780.88fc08","type":"function","z":"67b3dea3.cc212","name":"Make table data","func":"if(!msg.payload || !msg.payload.result || !msg.payload.result.length){\n msg.payload = [];\n return msg;\n}\n\nmsg.payload = msg.payload.result;\nreturn msg;","outputs":1,"noerr":0,"x":580,"y":580,"wires":[["e15ad659.7d0128"]]},{"id":"d860d2d.5afc53","type":"debug","z":"67b3dea3.cc212","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":270,"y":720,"wires":[]},{"id":"851d95e5.61cc58","type":"link out","z":"67b3dea3.cc212","name":"","links":["b9b9e3e3.d4933","e48ee70d.63d348"],"x":875,"y":580,"wires":[]},{"id":"e73772df.72258","type":"ui_template","z":"67b3dea3.cc212","group":"c3f194d9.93ecd8","name":"CRON Info","order":8,"width":"16","height":"12","format":"<div>\n<pre>CRON Format...\n\n s m h md m wd y\n | | | | | | |\n * * * * * * * Field Allowed values Special symbols\n | | | | | | | ----------------- --------------- ---------------\n `--|--|--|--|--|--|-> Second (optional) 0-59 * / , -\n `--|--|--|--|--|-> Minute 0-59 * / , -\n `--|--|--|--|-> Hour 0-23 * / , -\n `--|--|--|-> Day of Month 1-31 * / , - L W\n `--|--|-> Month 1-12 or JAN-DEC * / , -\n `--|-> Day of Week 0-7 or SUN-SAT * / , - L #\n `-> Year (optional) 1970-2099 * / , -\n \n</pre> \n</div>\n\n<style type=\"text/css\">\n.t2 {border-collapse:collapse;border-spacing:0;border-color:#ccc;}\n.t2 td{font-family:Arial, sans-serif;font-size:14px;padding:2px 2px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:#ccc;color:#333;background-color:#fff;}\n.t2 th{font-family:Arial, sans-serif;font-size:14px;font-weight:normal;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:#ccc;color:#333;background-color:#f0f0f0;}\n.t2 .t2-dg6o{background-color:#000000;color:#ffffff;text-align:left;vertical-align:top}\n.t2 .t2-6e8n{font-weight:bold;background-color:#c0c0c0;border-color:inherit;text-align:left;vertical-align:top}\n.t2 .t2-0pky{border-color:inherit;text-align:left;vertical-align:top}\n</style>\n<table class=\"t2\">\n <tr>\n <th class=\"t2-dg6o\" colspan=\"2\">Example CRON Expressions</th>\n </tr>\n <tr>\n <td class=\"t2-6e8n\">Expression</td>\n <td class=\"t2-6e8n\">Description</td>\n </tr>\n <tr>\n <td class=\"t2-0pky\">* * * * * *</td>\n <td class=\"t2-0pky\">Every Second</td>\n </tr>\n <tr>\n <td class=\"t2-0pky\">0 * * * * *</td>\n <td class=\"t2-0pky\">Every minute</td>\n </tr>\n <tr>\n <td class=\"t2-0pky\">0 */10 * * * *</td>\n <td class=\"t2-0pky\">Every 10 minutes</td>\n </tr>\n <tr>\n <td class=\"t2-0pky\">0 */20 1 * * *</td>\n <td class=\"t2-0pky\">Every 20 minutes, between 01:00 AM and 01:59 AM</td>\n </tr>\n <tr>\n <td class=\"t2-0pky\">0 15,30,45 * * * *</td>\n <td class=\"t2-0pky\">At 15, 30, and 45 minutes past the hour</td>\n </tr>\n <tr>\n <td class=\"t2-0pky\">0 0 12 * * *</td>\n <td class=\"t2-0pky\">Every day at noon - 12pm</td>\n </tr>\n <tr>\n <td class=\"t2-0pky\">0 0 2 29 FEB * 2020-2040</td>\n <td class=\"t2-0pky\">At 02:00 AM, on day 29 of the month, only in February, every 4 years, 2020 through 2040</td>\n </tr>\n <tr>\n <td class=\"t2-0pky\">0 0 7 * *</td>\n <td class=\"t2-0pky\">MON#1 * At 07:00 AM, on the first Monday of the month</td>\n </tr>\n <tr>\n <td class=\"t2-0pky\">0 0 12 * JAN,FEB,MAR,APR *</td>\n <td class=\"t2-0pky\">Every day at noon in January, February, March and April</td>\n </tr>\n <tr>\n <td class=\"t2-0pky\">* * 1W * *</td>\n <td class=\"t2-0pky\">Every minute, on the first weekday of the month</td>\n </tr>\n <tr>\n <td class=\"t2-0pky\">* * * * Tue#3</td>\n <td class=\"t2-0pky\">Every minute, on the third Tuesday of the month</td>\n </tr>\n <tr>\n <td class=\"t2-0pky\">0 12 * * MONL</td>\n <td class=\"t2-0pky\">At 12:00 PM, on the last Monday of the month</td>\n </tr>\n</table>","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":750,"y":640,"wires":[[]]},{"id":"e48ee70d.63d348","type":"link in","z":"67b3dea3.cc212","name":"","links":["851d95e5.61cc58","dde0b2cf.76d48"],"x":435,"y":440,"wires":[["3f284188.fdf30e"]]},{"id":"c3f194d9.93ecd8","type":"ui_group","z":"","name":"Default","tab":"327ea06c.0a258","disp":false,"width":"16","collapse":false},{"id":"327ea06c.0a258","type":"ui_tab","z":"","name":"CRON","icon":"dashboard","order":2,"disabled":false,"hidden":false}]