UNPKG

@cameo69/node-red-ratelimit

Version:
340 lines (301 loc) 15.2 kB
<!-- ISC License Copyright (c) 2023 cameo69 Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. --> <!-- Define setting UI for weather-data node --> <script type="text/html" data-template-name="rate-limiter"> <div class="form-row"> <label for="node-input-delay-action"><i class="fa fa-tasks"></i> <span data-i18n="rate-limiter.action"></span></label> <select id="node-input-delay_action" style="width:270px !important"> <option value="ratelimit" data-i18n="rate-limiter.ratelimit"></option> </select> </div> <div id="rate-details"> <div class="form-row"> <label for="node-input-rate"><i class="fa fa-clock-o"></i> <span data-i18n="rate-limiter.rate"></span></label> <input type="text" id="node-input-rate" placeholder="1" style="text-align:end; width:60px !important"> <label for="node-input-rateUnits"> <span data-i18n="rate-limiter.msgper"></span></label> <input type="text" id="node-input-nbRateUnits" placeholder="1" style="text-align:end; width:60px !important"> <select id="node-input-rateUnits" style="width:120px !important"> <option value="millisecond" data-i18n="rate-limiter.label.units.millisecond.singular"></option> <option value="second" data-i18n="rate-limiter.label.units.second.singular"></option> <option value="minute" data-i18n="rate-limiter.label.units.minute.singular"></option> <option value="hour" data-i18n="rate-limiter.label.units.hour.singular"></option> <option value="day" data-i18n="rate-limiter.label.units.day.singular"></option> </select> </div> <div class="form-row"> <input type="hidden" id="node-input-outputs"> <label></label> <select id="node-input-drop_select" style="width: 70%"> <option value="queue" data-i18n="rate-limiter.queuemsg"></option> <option value="drop" data-i18n="rate-limiter.dropmsg"></option> <!-- <option value="emit" data-i18n="rate-limiter.sendmsg"></option> --> </select> </div> <!-- max queue size & keep newest --> <div id="queue_max_size"> <div class="form-row"> <label for="node-input-buffer_size"><span data-i18n="rate-limiter.buffer_config"></span></label> <input type="text" id="node-input-buffer_size" placeholder="1000" style="text-align:end; width:120px !important"> <label for="node-input-buffer_size"> <span data-i18n="rate-limiter.buffer_size"></span></label> </div> <div class="form-row"> <label></label> <select id="node-input-buffer_drop" style="width: 70%"> <option value="buffer_drop_old" data-i18n="rate-limiter.buffer_drop_old"></option> <option value="buffer_drop_new" data-i18n="rate-limiter.buffer_drop_new"></option> </select> </div> </div> <div class="form-row" style="display: flex; align-items: center"> <label></label> <input style="width:30px; margin:0" type="checkbox" id="node-input-emit_msg_2nd"> <label style="margin:0;width: auto;" for="node-input-emit_msg_2nd"> <span data-i18n="rate-limiter.emit_msg_2nd"></span></label> </div> <div class="form-row" style="display: flex; align-items: center"> <label></label> <input style="width:30px; margin:0" type="checkbox" id="node-input-addcurrentcount"> <label style="margin:0;width: auto;" for="node-input-addcurrentcount"> <span data-i18n="rate-limiter.addcurrentcount"></span></label> </div> </div> <div class="form-row" id="control-details"> <label for="node-input-control_topic"><i class="fa fa-tasks"></i> <span data-i18n="rate-limiter.control_topic_head"></span></label> <input type="text" id="node-input-control_topic" data-i18n="[placeholder]rate-limiter.control_topic" style="width:120px !important"> <label style="margin:5;width: auto;" for="node-input-control_topic"> <span data-i18n="rate-limiter.control_topic_text"></span></label> </div> <div class="form-row"> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="rate-limiter.name"></span></label> <input type="text" id="node-input-name" data-i18n="[placeholder]rate-limiter.name"> </div> <div class="form-row"> <label></label> <label id="node-versionlabel">Version</label> </div> </script> <!-- register rate-limiter node --> <script type="text/javascript"> RED.nodes.registerType('rate-limiter',{ category: 'function', color: '#E6E0F8', defaults: { delay_action: {value: "ratelimit"}, rate: { value:"1", required:true, label:"Rate", validate:function(v,opt) { function isPosInt(str) { return /^\+?\d+$/.test(str); } if (RED.validators.number(v) && isPosInt(v) && (v > 0)) { return true; } return RED._("rate-limiter.errors.invalid-rate"); } }, nbRateUnits: { value:"1", required:true, validate:function(v,opt) { function isPosInt(str) { return /^\+?\d+$/.test(str); } function isPositiveInteger(n) { return 0 === n % (!isNaN(parseFloat(n)) && 0 <= ~~n); } if (RED.validators.number(v) && (v > 0)) { let rateunitvalue = $("#node-input-rateUnits").val() || this.rateUnits; let validated = rateunitvalue === "millisecond" && isPosInt(v); validated = validated || ( rateunitvalue === "second" && isPositiveInteger(v*1000) ); validated = validated || ( rateunitvalue === "minute" && isPositiveInteger(v*1000*60) ); validated = validated || ( rateunitvalue === "hour" && isPositiveInteger(v*1000*60*60) ); validated = validated || ( rateunitvalue === "day" && isPositiveInteger(v*1000*60*60*24) ); if (validated) return true; } return RED._("rate-limiter.errors.invalid-rate-unit"); } }, rateUnits: {value: "second"}, drop_select: {value: "queue"}, addcurrentcount: {value: false}, name: {value: ""}, outputs: {value: 1}, buffer_size: { value: "0", validate:function(v,opt) { function isPosInt(str) { return /^\+?\d+$/.test(str); } if (typeof v == "undefined") { return true; } if (RED.validators.number(v) && isPosInt(v)) { return true; } return RED._("rate-limiter.errors.invalid-rate"); }}, buffer_drop: {value: "buffer_drop_new"}, emit_msg_2nd: {value: false}, control_topic: {value: ""}, version: {value: 0.0018} }, inputs:1, outputs:1, icon: "font-awesome/fa-clock-o", label: function() { // using this to set value for all //if ((this.version || 0) == 0) this.version = 0.0009; //if (typeof this.buffer_size == "undefined") this.buffer_size = 0; //if (!this.buffer_size) this.buffer_size = 100; //if ('buffer_size' in this) delete this.buffer_size; // determine label if (this.name) { return this.name; } const rateUnitTxt = this.rateUnits ? (this.rateUnits === "millisecond" ? "ms" : this.rateUnits.charAt(0)) : "s"; const rateTxt = this.rate+" msg/" + (this.nbRateUnits == 1 ? '' : this.nbRateUnits) + rateUnitTxt; let drpSelectTxt; if (this.drop_select === "queue") { if ((typeof this.buffer_size == "undefined") || (this.buffer_size === "0")) { drpSelectTxt = " (queued)"; } else { //drpSelectTxt = " (queued " + this.buffer_size + (this.buffer_drop === "buffer_drop_old" ? ", drop oldest)" : ", drop new)"); drpSelectTxt = " (queued " + this.buffer_size + ")"; } } else if (this.drop_select === "drop" && !this.emit_msg_2nd) { drpSelectTxt = " (drop)"; } else { drpSelectTxt = " (2nd out)"; } return "rate limit " + rateTxt + drpSelectTxt; }, oneditprepare: function() { var node = this; /* node-input-delay_action node-input-rate node-input-nbRateUnits node-input-rateUnits node-input-outputs node-input-drop_select node-input-emit_msg_2nd node-input-addcurrentcount node-input-name */ //fill empty from previous version //added with v0.0.10 if (typeof this.delay_action == "undefined") { //this.delay_action = "ratelimit"; $("#node-input-delay_action").val('ratelimit'); } /*if (!this.drop_select) { //this.drop_select = "emit"; $("#node-input-drop_select").val("emit"); }*/ //added post v0.0.12 if (typeof this.buffer_size == "undefined") { //this.delay_action = "ratelimit"; $("#node-input-buffer_size").val(0); } if (typeof this.buffer_drop == "undefined") { //$("#node-input-buffer_drop").val('buffer_drop_new'); $("#node-input-buffer_drop option[value='buffer_drop_new']").prop('selected', true); } if (typeof this.emit_msg_2nd == "undefined") { $("#node-input-emit_msg_2nd").prop('checked', (this.outputs === 2) ); } if (typeof this.drop_select == "undefined" || this.drop_select === "emit") { $("#node-input-outputs").val(2); $("#node-input-emit_msg_2nd").prop('checked', true); //setTimeout(() => { //$("#node-input-drop_select option[value='drop']").prop('selected', true); $("#node-input-drop_select").val("drop").trigger("change"); //}, 50); $("#queue_max_size").hide(); } //added post v0.0.13 if (typeof this.control_topic == "undefined") { //this.delay_action = "ratelimit"; $("#node-input-control_topic").val(''); } //normal definitions $( "#node-input-rate" ).spinner({min:1}); $( "#node-input-nbRateUnits" ).spinner({min:1}); $("#node-input-buffer_size").spinner({min:0}); $('.ui-spinner-button').on("click", function() { $(this).siblings('input').trigger("change"); }); $( "#node-input-nbRateUnits" ).on('change keyup', function() { var $this = $(this); var val = parseFloat($this.val()); var type = "singular"; if (val > 1) { type = "plural"; } if ($this.attr("data-type") == type) { return; } $this.attr("data-type", type); $("#node-input-rateUnits option").each(function () { var $option = $(this); var key = "rate-limiter.label.units." + $option.val() + "." + type; $option.attr('data-i18n', key); $option.html(node._(key)); }); }); $("#node-input-emit_msg_2nd").on("change", function() { //$("#node-input-outputs").val( ( $("#node-input-emit_msg_2nd").val() ? 2 : 1) ); const outp = $("#node-input-emit_msg_2nd").is(":checked") ? 2 : 1; $("#node-input-outputs").val(outp); //$("#node-input-outputs").val( (this.prop('checked') ? 2 : 1) ); }).trigger("change"); $("#node-input-buffer_size").on("change keyup", function() { debugger; if (this.value === "0" && $("#node-input-drop_select").val() === "queue") { //$("#node-input-buffer_drop").show(); $("#node-input-buffer_drop").prop('disabled', true); $("#node-input-emit_msg_2nd").prop('disabled', true); $("#node-input-emit_msg_2nd").prop('checked', false).change(); } else { //$("#node-input-buffer_drop").hide(); $("#node-input-buffer_drop").prop('disabled', false); $("#node-input-emit_msg_2nd").prop('disabled', false); } }).trigger("change"); $("#node-input-drop_select").on("change", function() { if (this.value === "queue") { $("#queue_max_size").show(); $("#node-input-buffer_size").change(); } else { $("#queue_max_size").hide(); $("#node-input-emit_msg_2nd").prop('disabled', false); //$("#node-input-buffer_size").change(); } }).trigger("change"); // configure version number const version = RED.nodes.registry.getModule("@cameo69/node-red-ratelimit").version; $('#node-versionlabel').append(" " + version); }, oneditsave: function() { //this.outputs = $("#node-input-emit_msg_2nd").is(":checked") ? 2 : 1; this.outputs = $("#node-input-outputs").val(); } }); </script> <script type="text/html" data-help-name="rate-limiter"> <p>A simple node that offers rate limiting based on a sliding window.</p> </script>