UNPKG

node-red-contrib-monogoto-operator

Version:

Allows communicating with the Monogoto Operator API from node-red.

572 lines (481 loc) 25.7 kB
<script type="text/x-red" data-template-name="monogoto-operator" id="monogoto-operator-template"> <style scoped> .hiddenAttrs {display:none !important;} .visibleAttrs {display:flex !important;} .form-row > label { align-self: center; padding-left:10pt; padding-right:10pt; width:173pt; } div.main-container > .form-row { display: flex; white-space: nowrap; white-space: pre; flex-direction: row; } .form-row > label + input, .form-row > label + select { flex-grow: 1; width: 100%; } div.red-ui-typedInput-container { width: 100% !important; } </style> </script> <script type="text/x-red" data-help-name="monogoto-operator" id="monogoto-operator-help"></script> <script type="text/javascript"> (()=>{ const areSetsEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value)); const staticConfigParams = new Set( [] ); function getJsonPromise(url) { return new Promise( function(resolve, reject) { $.getJSON(url).done(resolve).error(reject); }); } function getExampleValueForSpecProps(schema) { if(schema.type === 'object') { const retVal = {}; for(const key in schema.properties) { const val = schema.properties[key]; retVal[key] = getExampleValueForSpecProps(val); } return retVal; } else if(schema.type === 'array') { return [getExampleValueForSpecProps(schema.items)]; } else { return schema.hasOwnProperty('example')? schema.example : schema.type === 'string'? '' : schema.type === 'number'? 0 : schema.type === 'boolean'? false : undefined; } } getJsonPromise('./monogoto-operator/ui-choices') .then(function ({choices, allParams}) { const fieldsHtml = ` <div class="main-container"> <div class="form-row"> <label for="node-input-server">monogoto-operator server</label> <input id="node-input-server" /> </div> <hr/> <div class="form-row"> <label for="node-input-api">API</label> <select id="node-input-api"> ${Object.keys(choices).map(key=>'<option value="'+key+'">'+key+'</option>').join('')} </select> </div> <div class="form-row"> <label for="node-input-operation">Operation</label> <select id="node-input-operation"></select> </div> ${Object.keys(allParams).map(key=>staticConfigParams.has(key)?'':` <div class="form-row param" id="param-${key}"> <label for="node-input-param-${key}">${key}</label> <input type="text" id="node-input-param-${key}" class="param-input"> <input type="hidden" id="node-input-param-${key}-type"> </div> `).join('')} <div class="form-row" id="requestBody"> <label for="node-input-requestBody">requestBody</label> <input type="text" id="node-input-requestBody" class="param-input"> <input type="hidden" id="node-input-requestBody-type"> </div> `; $('#monogoto-operator-template').append(document.createTextNode(fieldsHtml)); $('#monogoto-operator-template').append(document.createTextNode(` <hr/> <div class="form-row" title="Pass errors on 2nd output instead of catch node"> <label for="node-input-outErrors">Out Errors ➡️</label> <input type="checkbox" id="node-input-outErrors"> </div> <hr /> <div class="form-row"> <label for="node-input-name"><i class="fa fa-tag"></i> Node Label</label> <input type="text" id="node-input-node-label"> </div> </div> `)); const container = $('<div>'); container.append($('<p>API(s):</p>')); const $apis = $('<ul></ul>'); for(const choice of Object.keys(choices)) { const $api = $(`<li><h3>${choice}</h3></li>`); const apiMethods = choices[choice]; const $apiMethods = $(`<ul></ul>`); for(const op of Object.keys(apiMethods)) { const opInfo = apiMethods[op]; const opId = `${choice}-${op}`; const $opEl = $(`<li id="${opId}"></li>`); $opEl.append($(`<h3>${op}</h3>`)); $opEl.append($(`<p>${opInfo.description}</p>`)); const $paramsEl = $(`<dl class="message-properties">`); for(const paramName of Object.keys(opInfo.parameters)) { const paramInfo = opInfo.parameters[paramName]; $paramsEl.append($(` <dt ${paramInfo.required?'':'class="optional"'}>${paramName}</dt> <dd>${paramInfo.description}</dd> `)); } // Request body documentation: if(opInfo.requestBody && opInfo.requestBody.content && opInfo.requestBody.content["application/json"]) { const schema = opInfo.requestBody.content["application/json"].schema; if(!schema) return; const bodyDoc = JSON.stringify(schema, null, 1); $paramsEl.append($(` <dt ${opInfo.requestBody.required?'':'class="optional"'}>requestBody</dt> <dd> <pre>${bodyDoc}</pre> </dd> `)); } $opEl.append($paramsEl); $apiMethods.append($opEl); } $apiMethods.appendTo($api); $apis.append($api); } container.append($apis); $('#monogoto-operator-help').html(container.html()); const defaultApiName = Object.keys(choices)[0]; const defaultOp = Object.keys(choices[defaultApiName])[0]; const defaults = { "api": {value: defaultApiName}, "operation": {value: defaultOp}, "node-label": {value: undefined}, "requestBody-type": {}, "requestBody": {value:undefined, validate: RED.validators.typedInput('requestBody')}, server: {value:"", type:"monogoto-operator-server"}, outErrors: {value:false}, outputs: {value:1} }; Object.keys(allParams).forEach(paramName=>{ defaults['param-'+CSS.escape(paramName)+'-type'] = {}; defaults['param-'+CSS.escape(paramName)] = {value:undefined, validate: RED.validators.typedInput(paramName)}; }); RED.nodes.registerType('monogoto-operator', { category: "Monogoto" , color: "#8fcfb0" , icon: "monogoto-icon-op.png" , label: function () { return this['node-label'] || this.operation; }, defaults: defaults, inputs:1, outputs:1, outputLabels: ["success","error"], oneditsave: function() { const isOutErrors = $('#node-input-outErrors').prop('checked'); this.outputs = isOutErrors? 2:1; }, oneditprepare: function() { const firstOpen = !this.hasOwnProperty('node-label'); const timeOfEditorOpen = new Date(); function eventTriggeredByUserInteraction() { // After certain milli-seconds, we can be sure an event happened by user interaction. return new Date()-timeOfEditorOpen > 500; } function shouldPutExampleValue() { return eventTriggeredByUserInteraction() || firstOpen; } function focusOnChosenOp() { try { const chosenOp = $('#node-input-operation').val(); const chosenApi = $('#node-input-api').val(); const documentationId = `${chosenApi}-${chosenOp}`; $('#'+documentationId).get(0).scrollIntoView(); } catch(e) {} } setTimeout(focusOnChosenOp, 310); const typesForBodyParam = ['msg', 'global', 'flow', 'str', 'jsonata', 'json']; $('#node-input-operation').on('change', function (element) { const $chosenOp = $(element.target).val(); const $chosenApi = $('#node-input-api').val(); const opDef = choices[$chosenApi][$chosenOp]; const paramIds = Object.keys(opDef.parameters).map(item=>{ const selector = '#param-'+CSS.escape(item); // Setting placeholder for field const input = $(`${selector} > div > div > input`); if(opDef.parameters[item].schema && opDef.parameters[item].schema.format) { input.attr('placeholder', opDef.parameters[item].schema.format); } else { input.attr('placeholder', ''); } return selector; }).join(','); $('.form-row.param').addClass('hiddenAttrs').removeClass('visibleAttrs'); $(paramIds).addClass('visibleAttrs').removeClass('hiddenAttrs'); // requestBody const hasRequestBody = !!opDef.requestBody; if(hasRequestBody) { $('#requestBody.form-row').addClass('visibleAttrs').removeClass('hiddenAttrs'); if(shouldPutExampleValue()) { const jsonContent = opDef.requestBody.content["application/json"]; if(jsonContent && jsonContent.schema) { const props = jsonContent.schema; const exampleJson = getExampleValueForSpecProps(props); setTimeout(()=>$("#node-input-requestBody").typedInput('value', JSON.stringify(exampleJson))); } } } else { $('#requestBody.form-row').addClass('hiddenAttrs').removeClass('visibleAttrs'); } focusOnChosenOp(); // Set typed input const inputParamsForOp = $(paramIds).find('input.param-input'); inputParamsForOp.each((index, element)=>{ const paramName = element.id.split('node-input-param-')[1]; const paramInfo = opDef.parameters[paramName]; let types = paramInfo.in === 'body'? typesForBodyParam: ['msg', 'global', 'flow', 'jsonata', 'num', 'str']; let defaultType; if(paramInfo.schema && paramInfo.schema.enum) { const EnumType = { value:'enum', label:'enum', options: paramInfo.schema.enum }; types = types.concat([EnumType]); defaultType = 'enum'; } else { defaultType = types[types.length-1]; } const $el = $(element); if(!element.types) { // If first time declaring typedInput on this field $el.typedInput({ default: defaultType, types: types, typeField: $(`input#${CSS.escape(element.id)}-type`) }); } else { // if not first type declaring typed input, check if types changed, if so, update typedInput options. const oldTypesSet = new Set(element.types); const newTypesSet = new Set(types); if(!areSetsEqual(oldTypesSet, newTypesSet)) { $el.typedInput('types', types); $el.typedInput('type', defaultType); } } element.types = types; // remembering previous types // Setting initial example value if(shouldPutExampleValue() && paramInfo.schema) { const exampleValue = getExampleValueForSpecProps(paramInfo.schema); setTimeout(()=>{ if(exampleValue || exampleValue === 0) { $el.typedInput('value', exampleValue); } else if($el.typedInput('type') === 'enum' && paramInfo.schema.enum && paramInfo.schema.enum.length) { $el.typedInput('value', paramInfo.schema.enum[0]); } }); } }); }); $('#node-input-api').on('change', function (element) { const chosenApi = $(element.target).val(); const optionsHtml = ` ${Object.keys(choices[chosenApi]).map(key=>'<option value="'+key+'">'+key+'</option>').join('')} `; $('#node-input-operation').html(optionsHtml); $('#node-input-operation').trigger('change'); }); $('#node-input-api').trigger('change'); if(this.hasOwnProperty('operation')) { setTimeout(() => { $('#node-input-operation').val(this.operation); $('#node-input-operation').trigger('change'); }, 0); } $('input#node-input-requestBody').typedInput({ default: typesForBodyParam[typesForBodyParam.length-1], types: typesForBodyParam, typeField: $(`input#node-input-requestBody-type`) }); } }); }, function (reason) { console.error("failed to load monogoto-operator " + reason); }); })(); </script> <script type="text/javascript"> (()=>{ function findLogElements() { return { request: { checkboxEnable: $('#log-request'), optionsDiv: $('#log-request-options'), url: $('#log-request-url'), headers: $('#log-request-headers'), body: $('#log-request-body'), }, response: { checkboxEnable: $('#log-response'), optionsDiv: $('#log-response-options'), headers: $('#log-response-headers'), body: $('#log-response-body'), } }; } RED.nodes.registerType('monogoto-operator-server',{ category: 'config', defaults: { name: {value: undefined}, host: {value: "console.monogoto.io", required:true}, "host-type": {value:"str"}, username: {value:undefined}, 'username-type': {value:"str"}, password: {value:undefined}, 'password-type': {value:"str"}, logging: {value:undefined}, loggingCfgFromEnvVar: {value:''} }, label: function() { return this.name || this.host; }, oneditprepare: function() { $('input#node-config-input-host').typedInput({ default: ['str'], types: ['str', 'env'], typeField: $(`input#node-config-input-host-type`) }); $('input#node-config-input-username').typedInput({ default: ['str'], types: ['str', 'env'], typeField: $(`input#node-config-input-username-type`) }); $('input#node-config-input-password').typedInput({ default: ['str'], types: ['str', 'env'], typeField: $(`input#node-config-input-password-type`) }); const logElements = findLogElements(); logElements.request.checkboxEnable.change(function(){ logElements.request.optionsDiv[this.checked?'removeClass':'addClass']('hidden'); }) logElements.response.checkboxEnable.change(function(){ logElements.response.optionsDiv[this.checked?'removeClass':'addClass']('hidden'); }) logElements.request.checkboxEnable.prop('checked', this?.logging?.request != null); logElements.request.url.prop('checked', this?.logging?.request?.url === true); logElements.request.headers.prop('checked', this?.logging?.request?.headers === true); logElements.request.body.prop('checked', this?.logging?.request?.body === true); logElements.response.checkboxEnable.prop('checked', this?.logging?.response != null); logElements.response.headers.prop('checked', this?.logging?.response?.headers === true); logElements.response.body.prop('checked', this?.logging?.response?.body === true); $('#node-logging-format').on('click',()=>{ alert('ENV VAR contents: json string with the following format:\r\n'+ JSON.stringify({ request: { url:true, body:true, headers:true }, response: { body:true, headers:true } },null,2)); }); // Refresh UI State logElements.request.checkboxEnable.trigger('change') logElements.response.checkboxEnable.trigger('change') }, oneditsave: function() { const logElements = findLogElements(); const enableReqLogging = logElements.request.checkboxEnable.prop('checked'); const enableResLogging = logElements.response.checkboxEnable.prop('checked'); if(!this.logging) this.logging = {}; if(enableReqLogging) { this.logging.request = { url: logElements.request.url.prop('checked'), headers: logElements.request.headers.prop('checked'), body: logElements.request.body.prop('checked'), } } else { delete this.logging.request; } if(enableResLogging) { this.logging.response = { headers: logElements.response.headers.prop('checked'), body: logElements.response.body.prop('checked'), } } else { delete this.logging.response; } } }); })() </script> <script type="text/x-red" data-template-name="monogoto-operator-server"> <style> .hidden { display:none } </style> <div class="form-row"> <label for="node-config-input-host"><i class="fa fa-server"></i> Host</label> <input type="text" id="node-config-input-host"> <input type="hidden" id="node-config-input-host-type"> </div> <div class="form-row"> <label for="node-config-input-username"><i class="fa fa-user"></i> Username</label> <input type="text" id="node-config-input-username"> <input type="hidden" id="node-config-input-username-type"> </div> <div class="form-row"> <label for="node-config-input-password"><i class="fa fa-key"></i> Password</label> <input type="password" id="node-config-input-password"> <input type="hidden" id="node-config-input-password-type"> </div> <hr /> <h2>Logging</h4> <div class="form-row"> <label for="log-request">Request</label> <input type="checkbox" id="log-request"> </div> <div id="log-request-options"> <div class="form-row"> <label for="log-request-url"> - URL</label> <input type="checkbox" id="log-request-url"> </div> <div class="form-row"> <label for="log-request-headers"> - Headers</label> <input type="checkbox" id="log-request-headers"> </div> <div class="form-row"> <label for="log-request-body"> - Body</label> <input type="checkbox" id="log-request-body"> </div> </div> <div class="form-row"> <label for="log-response">Response</label> <input type="checkbox" id="log-response"> </div> <div id="log-response-options"> <div class="form-row"> <label for="log-response-headers"> - Headers</label> <input type="checkbox" id="log-response-headers"> </div> <div class="form-row"> <label for="log-response-body"> - Body</label> <input type="checkbox" id="log-response-body"> </div> </div> <div class="form-row" style="display:flex;align-items:center"> <button id="node-logging-format">show format</button> <label style="margin-left:5pt; width:90pt" for="node-config-input-loggingCfgFromEnvVar">Log config from env-var:</label> <input style="margin-left:5pt; flex-grow:1" type="text" id="node-config-input-loggingCfgFromEnvVar"> </div> <hr /> <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"> </div> </script>