@oriolrius/node-red-contrib-kafka
Version:
Node-RED Kafka nodes: Send, Receive, and Schema validation with modern KafkaJS
316 lines (280 loc) • 13.5 kB
HTML
<script type="text/html" data-help-name="hm-kafka-producer">
<p>Send messages to a Kafka topic.</p>
<p>This node publishes the incoming message payload to the specified Kafka topic.</p>
<p>Optionally validates message payloads against registered Avro schemas using Confluent Schema Registry.</p>
<p>Supports message compression, acknowledgment settings, and IoT cloud data formatting.</p>
<p>On successful send, the node status shows "Sending". On failure, shows "Error".</p>
</script>
<script type="text/html" data-template-name="hm-kafka-producer">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name </label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-broker"><i class="fa fa-list-ul"></i> Broker </label>
<input type="text" id="node-input-broker">
</div>
<div class="form-row">
<label for="node-input-topic"><i class="fa fa-tag"></i> Topic </label>
<input type="text" id="node-input-topic" placeholder="Topic">
</div>
<div class="form-row">
<label for="node-input-requireAcks"><i class="fa fa-tag"></i> Require Ack </label>
<input type="number" min="0" max="1" id="node-input-requireAcks" >
</div>
<div class="form-row">
<label for="node-input-ackTimeoutMs"><i class="fa fa-tag"></i> Ack Timeout (Ms) </label>
<input type="number" min="100" max="100000" step="100" id="node-input-ackTimeoutMs" >
</div>
<div class="form-row">
<label for="node-input-attributes"><i class="fa fa-tag"></i> Attributes </label>
<select id="node-input-attributes">
<option value="0">No compression</option>
<option value="1">Compress using GZip</option>
<option value="2">Compress using snappy</option>
</select>
</div>
<!-- Schema Validation Section -->
<hr/>
<div class="form-row">
<input type="checkbox" id="node-input-useSchemaValidation" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-useSchemaValidation" style="width: auto">Enable Schema Validation</label>
</div>
<!-- Schema Registry Configuration (shown only when schema validation is enabled) -->
<div id="node-config-schema" style="display: none;">
<div class="form-row">
<h4><i class="fa fa-cog"></i> Schema Registry Configuration</h4>
</div>
<div class="form-row">
<label for="node-input-registryUrl"><i class="fa fa-globe"></i> Registry URL </label>
<input type="text" id="node-input-registryUrl" placeholder="http://localhost:8081">
</div>
<div class="form-row">
<label for="node-input-schemaSubject"><i class="fa fa-file-code-o"></i> Schema Subject </label>
<input type="text" id="node-input-schemaSubject" placeholder="my-topic-value">
</div>
<div class="form-row">
<label for="node-input-schemaVersion"><i class="fa fa-code-fork"></i> Schema Version </label>
<input type="text" id="node-input-schemaVersion" placeholder="latest (or specific version like 1, 2, 3...)">
</div>
<div class="form-row">
<input type="checkbox" id="node-input-useRegistryAuth" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-useRegistryAuth" style="width: auto">Use Registry Authentication</label>
</div>
<div id="node-config-registry-auth" class="form-row" style="display: none;">
<div class="form-row">
<label for="node-input-registryUsername"><i class="fa fa-user"></i> Username </label>
<input type="text" id="node-input-registryUsername" placeholder="Username">
</div>
<div class="form-row">
<label for="node-input-registryPassword"><i class="fa fa-lock"></i> Password </label>
<input type="password" id="node-input-registryPassword" placeholder="Password">
</div>
</div>
<div class="form-row">
<input type="checkbox" id="node-input-autoRegister" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-autoRegister" style="width: auto">Auto-register schema if not found</label>
</div>
<div id="node-config-auto-schema" class="form-row" style="display: none;">
<div class="form-row">
<label for="node-input-autoSchema"><i class="fa fa-code"></i> Default Schema (JSON) </label>
<textarea id="node-input-autoSchema" rows="8" placeholder='{"type": "record", "name": "Message", "fields": [{"name": "id", "type": "string"}, {"name": "message", "type": "string"}]}'></textarea>
</div>
</div>
<div class="form-row">
<input type="checkbox" id="node-input-validateOnly" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-validateOnly" style="width: auto">Validate schema only (don't publish)</label>
</div>
</div>
<!-- IoT Configuration Section -->
<hr/>
<div class="form-row">
<input type="checkbox" id="node-input-useiot" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-useiot" style="width: auto">Cloud Configuration</label>
</div>
<!-- iot Cloud Configuration -->
<div id="node-config-iot" class="form-row" style="display: none;">
<hr/>
<!-- Object Model -->
<div class="form-row">
<label for="node-input-model"><i class="fa fa-cogs"></i> Model </label>
<select id="node-input-model">
<option value="use">Obtain the model from the broker</option>
<option value="iot">Create your own model</option>
</select>
</div>
<!-- Device Code -->
<div class="form-row">
<label for="node-input-device"><i class="fa fa-microchip"></i> Device </label>
<select id="node-input-device">
<option value="use">Obtain the device from the broker</option>
<option value="iot">Create your own device</option>
</select>
</div>
<!-- Type -->
<div class="form-row">
<label for="node-input-iotType"><i class="fa fa-list"></i> Type </label>
<select id="node-input-iotType">
<option value="props">Props</option>
<option value="events">Events</option>
</select>
</div>
<!-- Fields -->
<div class="form-row">
<label for="node-input-fields"><i class="fa fa-list"></i> Fields </label>
<ol id="node-input-fields-container"></ol>
</div>
</div>
</script>
<script type="text/javascript">
RED.nodes.registerType('hm-kafka-producer', {
category: 'IOT',
paletteLabel: "Kafka Send",
color: '#CE93D8',
defaults: {
name: { required: false },
broker: { type: "hm-kafka-broker" },
topic: { required: true },
requireAcks: { value: 1, required: true },
ackTimeoutMs: { value: 100, required: true },
attributes: { value: 0 },
// Schema Validation
useSchemaValidation: { value: false },
// Schema Registry Configuration
registryUrl: { required: false, value: "http://localhost:8081" },
schemaSubject: { required: false },
schemaVersion: { required: false, value: "latest" },
useRegistryAuth: { value: false },
registryUsername: { required: false },
registryPassword: { required: false },
autoRegister: { value: false },
autoSchema: { required: false, value: '{"type": "record", "name": "Message", "fields": [{"name": "id", "type": "string"}, {"name": "message", "type": "string"}, {"name": "timestamp", "type": "long"}]}' },
validateOnly: { value: false },
// IoT Configuration
useiot: { value: false },
model: { value: "use" },
device: { value: "use" },
iotType: { value: "props" },
fields: { value: [] }
},
inputs: 1,
outputs: 1,
icon: "file.png",
label: function () {
return this.name || "Kafka Send";
},
oneditprepare: function() {
var node = this;
// Show/hide schema validation fields
$("#node-input-useSchemaValidation").change(function() {
if ($(this).is(":checked")) {
$("#node-config-schema").show();
} else {
$("#node-config-schema").hide();
}
});
// Show/hide registry auth fields
$("#node-input-useRegistryAuth").change(function() {
if ($(this).is(":checked")) {
$("#node-config-registry-auth").show();
} else {
$("#node-config-registry-auth").hide();
}
});
// Show/hide auto schema registration fields
$("#node-input-autoRegister").change(function() {
if ($(this).is(":checked")) {
$("#node-config-auto-schema").show();
} else {
$("#node-config-auto-schema").hide();
}
});
// Show/hide IoT configuration
$("#node-input-useiot").change(function() {
if ($(this).is(":checked")) {
$("#node-config-iot").show();
} else {
$("#node-config-iot").hide();
}
});
// Initialize visibility based on current values
if (this.useSchemaValidation) {
$("#node-config-schema").show();
}
if (this.useRegistryAuth) {
$("#node-config-registry-auth").show();
}
if (this.autoRegister) {
$("#node-config-auto-schema").show();
}
if (this.useiot) {
$("#node-config-iot").show();
}
// IoT Fields configuration
function generateField(i, field) {
var container = $('<li/>', { style: "background: #fff; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;" });
var row = $('<div/>').appendTo(container);
var row2 = $('<div/>', { style: "padding-top: 5px;" }).appendTo(container);
$('<i style="color: #eee; cursor: move; margin-right: 3px;" class="fa fa-bars"></i>').appendTo(row);
var fieldName = $('<input/>', {
class: "node-input-field-name",
type: "text",
style: "margin-left: 5px; width: 200px;",
placeholder: "Field Name"
}).appendTo(row).val(field.fieldName);
var dataType = $('<select/>', {
class: "node-input-field-type",
style: "margin-left: 10px; width: 100px;"
}).appendTo(row);
dataType.append('<option value="TEXT">TEXT</option>');
dataType.append('<option value="INT32">INT32</option>');
dataType.append('<option value="FLOAT">FLOAT</option>');
dataType.append('<option value="BOOLEAN">BOOLEAN</option>');
dataType.val(field.dataType);
var finalspan = $('<span/>', { style: "float: right; margin-right: 8px;" }).appendTo(row);
var deleteButton = $('<a/>', {
href: "#",
class: "editor-button editor-button-small",
style: "margin-top: 7px; margin-left: 5px;"
}).appendTo(finalspan);
$('<i/>', { class: "fa fa-remove" }).appendTo(deleteButton);
deleteButton.click(function() {
container.fadeOut(300, function() {
$(this).remove();
});
});
$("#node-input-fields-container").append(container);
}
// Add existing fields
for (var i = 0; i < this.fields.length; i++) {
generateField(i + 1, this.fields[i]);
}
// Add field button
$('<a href="#" class="editor-button" id="node-input-field-add" style="margin-top: 4px;"><i class="fa fa-plus"></i> Add Field</a>')
.appendTo($("#node-input-fields-container").parent())
.click(function() {
generateField($("#node-input-fields-container").children().length + 1, { fieldName: "", dataType: "TEXT" });
});
// Make fields sortable
$("#node-input-fields-container").sortable({
axis: "y",
handle: ".fa-bars",
cursor: "move"
});
},
oneditsave: function() {
var fields = [];
$("#node-input-fields-container").children().each(function(i) {
var fieldName = $(this).find(".node-input-field-name").val();
var dataType = $(this).find(".node-input-field-type").val();
if (fieldName !== "") {
fields.push({
fieldName: fieldName,
dataType: dataType
});
}
});
this.fields = fields;
}
});
</script>