node-red-contrib-postgres-variable
Version:
PostgreSQL module for Node-RED with dynamic configuration from contexts (flow, global, environment)
448 lines (401 loc) • 19.3 kB
HTML
<!--
Copyright 2018 Andrii Lototskyi.
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.
-->
<script type="text/x-red" data-template-name="postgresdb">
<div class="form-row">
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-config-input-name" placeholder="Name" style="width: 70%;">
</div>
<div class="form-row">
<label for="node-config-input-hostname"><i class="fa fa-server"></i> Host</label>
<input type="text" id="node-config-input-hostname" style="width: 70%;">
</div>
<div class="form-row">
<label for="node-config-input-port"><i class="fa fa-plug"></i> Port</label>
<input type="text" id="node-config-input-port" style="width: 70%;">
</div>
<div class="form-row">
<label for="node-config-input-db"><i class="fa fa-database"></i> Database</label>
<input type="text" id="node-config-input-db" style="width: 70%;">
</div>
<div class="form-row">
<label for="node-config-input-ssl"><i class="fa fa-lock"></i> SSL</label>
<input type="text" id="node-config-input-ssl" style="width: 70%;">
</div>
<div class="form-row" id="ssl-path-row">
<label for="node-config-input-ssl_path"><i class="fa fa-file"></i> SSL Certificate Path</label>
<input type="text" id="node-config-input-ssl_path" style="width: 70%;">
</div>
<div class="form-row">
<label for="node-config-input-ignore_check_ssl"><i class="fa fa-check-circle"></i> Ignore SSL Certificate</label>
<input type="text" id="node-config-input-ignore_check_ssl" style="width: 70%;">
</div>
<div class="form-row">
<label for="node-config-input-user"><i class="fa fa-user"></i> Username</label>
<input type="text" id="node-config-input-user" style="width: 70%;">
</div>
<div class="form-row">
<label for="node-config-input-password-type"><i class="fa fa-key"></i> Password Type</label>
<select id="node-config-input-password-type" style="width: 70%;">
<option value="str">Direct Password (secure)</option>
<option value="flow">Flow Context</option>
<option value="global">Global Context</option>
<option value="env">Environment Variable</option>
</select>
</div>
<div class="form-row" id="password-str-row">
<label for="node-config-input-password-visible"><i class="fa fa-lock"></i> Password</label>
<input type="password" id="node-config-input-password-visible" style="width: 70%;">
</div>
<div class="form-row" id="password-context-row" style="display: none;">
<label for="node-config-input-password-context"><i class="fa fa-code"></i> Variable Name</label>
<input type="text" id="node-config-input-password-context" style="width: 70%;">
</div>
<!-- Hidden field for Node-RED credentials -->
<input type="password" id="node-config-input-password" style="display: none;">
</script>
<script type="text/javascript">
RED.nodes.registerType("postgresdb", {
category: "config",
defaults: {
name: { value: "" },
hostname: { value: "localhost" },
hostnameType: { value: "str" },
port: { value: "5432" },
portType: { value: "str" },
db: { value: "postgres" },
dbType: { value: "str" },
ssl: { value: false },
sslType: { value: "bool" },
ignore_check_ssl: { value: false },
ignore_check_sslType: { value: "bool" },
ssl_path: { value: "" },
ssl_pathType: { value: "str" },
user: { value: "" },
userType: { value: "str" },
password: { value: "" },
passwordType: { value: "str" },
passwordContext: { value: "" }
},
label: function() {
return this.name || "PostgreSQL";
},
oneditprepare: function() {
// Host
$("#node-config-input-hostname").typedInput({
default: 'str',
types: ['str', 'flow', 'global', 'env']
});
$("#node-config-input-hostname").typedInput('type', this.hostnameType || 'str');
$("#node-config-input-hostname").typedInput('value', this.hostname || 'localhost');
// Port
$("#node-config-input-port").typedInput({
default: 'str',
types: ['str', 'flow', 'global', 'env']
});
$("#node-config-input-port").typedInput('type', this.portType || 'str');
$("#node-config-input-port").typedInput('value', this.port || '5432');
// DB
$("#node-config-input-db").typedInput({
default: 'str',
types: ['str', 'flow', 'global', 'env']
});
$("#node-config-input-db").typedInput('type', this.dbType || 'str');
$("#node-config-input-db").typedInput('value', this.db || 'postgres');
// SSL
$("#node-config-input-ssl").typedInput({
default: 'bool',
types: ['bool', 'flow', 'global', 'env']
});
$("#node-config-input-ssl").typedInput('type', this.sslType || 'bool');
$("#node-config-input-ssl").typedInput('value', this.ssl !== undefined ? this.ssl : false);
// SSL Path
$("#node-config-input-ssl_path").typedInput({
default: 'str',
types: ['str', 'flow', 'global', 'env']
});
$("#node-config-input-ssl_path").typedInput('type', this.ssl_pathType || 'str');
$("#node-config-input-ssl_path").typedInput('value', this.ssl_path || '');
// Ignore SSL
$("#node-config-input-ignore_check_ssl").typedInput({
default: 'bool',
types: ['bool', 'flow', 'global', 'env']
});
$("#node-config-input-ignore_check_ssl").typedInput('type', this.ignore_check_sslType || 'bool');
$("#node-config-input-ignore_check_ssl").typedInput('value', this.ignore_check_ssl !== undefined ? this.ignore_check_ssl : false);
// SSL Path row visibility logic
var checkSSLVisibility = function() {
var sslType = $("#node-config-input-ssl").typedInput('type');
var sslValue = $("#node-config-input-ssl").typedInput('value');
var isSSLEnabled = false;
if (sslType === 'bool') {
isSSLEnabled = sslValue === true || sslValue === "true";
} else {
// For flow/global/env, show if there's a value (assume it might be true)
isSSLEnabled = !!sslValue;
}
if (isSSLEnabled) {
$("#ssl-path-row").show();
} else {
$("#ssl-path-row").hide();
}
};
$("#node-config-input-ssl").on("change", checkSSLVisibility);
checkSSLVisibility();
// User
$("#node-config-input-user").typedInput({
default: 'str',
types: ['str', 'flow', 'global', 'env']
});
$("#node-config-input-user").typedInput('type', this.userType || 'str');
$("#node-config-input-user").typedInput('value', this.user || '');
// Password type selection
if (this.passwordType && this.passwordType !== 'str') {
// Context type
$("#node-config-input-password-type").val(this.passwordType);
$("#node-config-input-password-context").val(this.passwordContext || '');
} else {
// String type
$("#node-config-input-password-type").val('str');
}
// Password field visibility logic
var updatePasswordFieldVisibility = function() {
var passwordType = $("#node-config-input-password-type").val();
if (passwordType === 'str') {
$("#password-str-row").show();
$("#password-context-row").hide();
} else {
$("#password-str-row").hide();
$("#password-context-row").show();
}
};
$("#node-config-input-password-type").on("change", updatePasswordFieldVisibility);
updatePasswordFieldVisibility();
// Sync visible password field with hidden credentials field
$("#node-config-input-password-visible").on("input", function() {
$("#node-config-input-password").val($(this).val());
});
// Handle placeholder behavior
$("#node-config-input-password-visible").on("focus", function() {
if ($(this).attr('placeholder') === '••••••••••••••••') {
$(this).attr('placeholder', 'Enter new password or leave empty to keep existing');
}
});
$("#node-config-input-password-visible").on("blur", function() {
var node = this;
var inputVal = $(this).val();
if (!inputVal && node.credentials && node.credentials.has_password) {
$(this).attr('placeholder', '••••••••••••••••');
}
}.bind(this));
// Load existing password if available
if (this.credentials && this.credentials.password) {
$("#node-config-input-password-visible").val(this.credentials.password);
$("#node-config-input-password").val(this.credentials.password);
} else if (this.credentials && this.credentials.has_password) {
// Password exists but not shown for security - show placeholder
$("#node-config-input-password-visible").attr('placeholder', '••••••••••••••••');
$("#node-config-input-password-visible").val('');
} else {
// No password - set default placeholder
$("#node-config-input-password-visible").attr('placeholder', 'Enter password');
$("#node-config-input-password-visible").val('');
}
},
oneditsave: function() {
// Save values and types separately
this.hostname = $("#node-config-input-hostname").typedInput('value');
this.hostnameType = $("#node-config-input-hostname").typedInput('type');
this.port = $("#node-config-input-port").typedInput('value');
this.portType = $("#node-config-input-port").typedInput('type');
this.db = $("#node-config-input-db").typedInput('value');
this.dbType = $("#node-config-input-db").typedInput('type');
this.ssl = $("#node-config-input-ssl").typedInput('value');
this.sslType = $("#node-config-input-ssl").typedInput('type');
this.ignore_check_ssl = $("#node-config-input-ignore_check_ssl").typedInput('value');
this.ignore_check_sslType = $("#node-config-input-ignore_check_ssl").typedInput('type');
this.ssl_path = $("#node-config-input-ssl_path").typedInput('value');
this.ssl_pathType = $("#node-config-input-ssl_path").typedInput('type');
this.user = $("#node-config-input-user").typedInput('value');
this.userType = $("#node-config-input-user").typedInput('type');
// Handle password based on type
var passwordType = $("#node-config-input-password-type").val();
this.passwordType = passwordType;
if (passwordType === 'str') {
// String password - save to credentials, clear context
this.passwordContext = '';
this.password = ''; // Clear from defaults
// Sync visible field to hidden credentials field only if not empty
var visiblePassword = $("#node-config-input-password-visible").val();
if (visiblePassword) {
$("#node-config-input-password").val(visiblePassword);
}
// If empty, keep existing password (don't overwrite)
} else {
// Context password - save context reference
var passwordValue = $("#node-config-input-password-context").val();
this.passwordContext = passwordValue;
this.password = ''; // Clear from defaults
}
},
credentials: {
password: { type: 'password' }
},
});
</script>
<script type="text/x-red" data-template-name="postgres">
<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-postgresdb"><i class="fa fa-tag"></i> Server</label>
<input type="text" id="node-input-postgresdb">
</div>
<div class="form-row">
<label for="node-input-output"><i class="fa fa-sign-out"></i> Output</label>
<input type="checkbox" id="node-input-output" style="display: inline-block; width: auto; vertical-align: top;">
<span>Receive query output</span>
</div>
</script>
<script type="text/x-red" data-help-name="postgres">
<p>A PostgreSql I/O node. </p>
<p>Executes the query specified in msg.payload with optional query parameters in msg.queryParameters</p>
<p>The queryParameters in the query must be specified as $propertyname</p>
<p>In msg.connectName you can set name of connects from settings.js
<p>See the node-postgres-named package for more info</p>
<p>When receiving data from the query, the msg.payload on the output will be a json array of the returned records</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('postgres',{
category: 'storage-output',
color:"rgb(183, 252, 148)",
defaults: {
postgresdb: { type:"postgresdb",required:true},
name: {value:""},
output: {value:false},
outputs: {value:0}
},
inputs: 1,
outputs: 0,
icon: "postgres.png",
align: "right",
label: function() {
return this.name||(this.sqlquery?this.sqlquery:"postgres");
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
$( "#node-input-output" ).prop( "checked", this.output );
$("#node-input-name").focus();
},
oneditsave: function() {
var hasOutput = $( "#node-input-output" ).prop( "checked" );
this.outputs = hasOutput ? 1:0;
}
});
</script>
<script type="text/x-red" data-help-name="postgresdb">
<p>A PostgreSQL database configuration node with flexible context support:</p>
<h3>Configuration Fields</h3>
<p>All configuration fields support multiple input types:</p>
<ul>
<li><b>String</b>: Direct value</li>
<li><b>Flow</b>: Value from flow context</li>
<li><b>Global</b>: Value from global context</li>
<li><b>Environment</b>: Value from environment variable</li>
</ul>
<h3>Supported Fields</h3>
<ul>
<li><b>Host</b>: Database server hostname</li>
<li><b>Port</b>: Database server port</li>
<li><b>Database</b>: Database name</li>
<li><b>Username</b>: Database username</li>
<li><b>SSL</b>: Enable SSL connection (boolean)</li>
<li><b>SSL Certificate Path</b>: Path to SSL certificate file</li>
<li><b>Ignore SSL Certificate</b>: Skip SSL certificate validation (boolean)</li>
</ul>
<h3>Password Security</h3>
<p>Password field has special security handling:</p>
<ul>
<li><b>Direct Password</b>: Stored securely in Node-RED credentials (encrypted)</li>
<li><b>Context Types</b>: Only variable names stored, actual passwords retrieved from contexts</li>
</ul>
<h3>Examples</h3>
<p><b>Environment-based configuration:</b></p>
<ul>
<li>Host: Environment → <code>DB_HOST</code></li>
<li>Port: Environment → <code>DB_PORT</code></li>
<li>Password: Environment → <code>DB_PASSWORD</code></li>
</ul>
<p><b>Mixed configuration:</b></p>
<ul>
<li>Host: String → <code>localhost</code></li>
<li>Database: Flow → <code>current_db</code></li>
<li>Username: Global → <code>db_user</code></li>
</ul>
</script>
<style>
.form-row {
margin-bottom: 10px;
}
.form-row label {
display: inline-block;
width: 120px;
}
.form-row input[type="text"],
.form-row input[type="password"] {
width: 250px;
}
.form-row input[type="checkbox"] {
margin-left: 120px;
}
.help-text {
font-size: 0.8em;
color: #666;
margin-top: 4px;
}
/* Password field styling with CSS classes */
.password-str input {
-webkit-text-security: disc;
text-security: disc;
font-family: monospace ;
letter-spacing: 2px;
}
.password-context input {
-webkit-text-security: none;
text-security: none;
font-family: inherit ;
letter-spacing: normal;
}
/* Additional password hiding styles */
.red-ui-typedInput.password-hidden input {
-webkit-text-security: disc ;
text-security: disc ;
font-family: monospace ;
letter-spacing: 2px ;
}
.red-ui-typedInput.password-visible input {
-webkit-text-security: none ;
text-security: none ;
font-family: inherit ;
letter-spacing: normal ;
}
/* Direct input styling for Node-RED 4.0.3 */
input.password-hidden-input {
-webkit-text-security: disc ;
text-security: disc ;
font-family: monospace ;
letter-spacing: 2px ;
}
</style>