Manuale · 11

Workflows

I Workflows collegano piccoli passi in un processo – in modo simile a Node-RED, solo in versione ridotta. Un flow inizia da un node di avvio (pulsante, pianificazione, endpoint HTTP o evento di Collection); poi recuperi dati, li trasformi con JavaScript, crei diramazioni, li salvi in una Collection o li restituisci come risposta HTTP. Casi tipici: un endpoint JSON per un tabellone, un modulo di contatto che finisce in una Collection, una sincronizzazione notturna con un altro sistema.

L’editor

In Workflows ogni Workflow compare come scheda in alto. Sotto:

  • Palette (a sinistra): trascina i node nell’area o fai clic su di essi.
  • Area (al centro): collega trascinando dall’uscita (a destra del node) all’ingresso (a sinistra). Un’uscita può portare a più node – il messaggio percorre allora tutti i rami. Canc elimina i node e i fili selezionati; la rotella del mouse ingrandisce, trascinare su un’area libera sposta la vista.
  • Barra laterale (a destra): Node mostra le impostazioni del node selezionato, Debug gli output dell’esecuzione, Esecuzioni le ultime 50 esecuzioni, Memoria la memoria del Workflow.
  • Copia e incolla: selezioni più node con Ctrl (Mac: ⌘) + clic oppure con ⇧ + trascinamento di un riquadro. Ctrl+C li copia insieme ai fili tra di essi, Ctrl+V li incolla – anche in un altro Workflow o in un’altra scheda del browser; Ctrl+X taglia. Le copie ricevono nuovi ID e restano selezionate, così puoi spostarle subito.

▶ sul node di avvio avvia il flow – con la versione presente nell’area, anche se non salvata. Salva rende operativi ingressi HTTP, pianificazioni ed eventi; l’interruttore Attivo li disattiva tutti insieme (▶ funziona comunque).

Dopo un’esecuzione ogni node mostra quante volte è stato eseguito (in rosso in caso di errore). Se ci fai clic sopra, in basso nella barra laterale vedi il suo ingresso e cosa è uscito da ciascuna uscita – così trovi rapidamente il passo che si inceppa.

Messaggi

Lungo i fili scorre un messaggio msg – un oggetto JSON. I dati veri e propri stanno in msg.payload; i node possono aggiungere altri campi (msg.req per l’ingresso HTTP, msg.statusCode dopo una richiesta HTTP). Ogni node riceve una propria copia, quindi i rami non si influenzano a vicenda.

Nei campi di testo dei node (URL, header, body, ID voce) {{ msg.pfad }} inserisce valori, ad es. https://api.example/artikel/{{ msg.payload.id }}. Le variabili d’ambiente del server non sono disponibili nei Workflow – inserisci credenziali e impostazioni direttamente nel node o nello script.

I node

Node Cosa fa
Avvio Parte con ▶ e/o secondo una pianificazione. msg.payload = il valore impostato (JSON o testo)
Ingresso HTTP Endpoint proprio sotto /api/v1/flows/<pfad>. msg.payload = body JSON o del modulo, senza body i parametri di query; msg.req = method, path, params, query, headers, body
Evento collection Parte quando in una Collection una voce viene creata, modificata o eliminata. msg.payload = la voce, msg.event, msg.collection
Funzione JavaScript con un numero qualsiasi di uscite (vedi sotto)
Deviatore Confronta un valore (msg.payload.preis, msg.req.query.format, …) con delle regole – un’uscita per regola. Regole: ==, !=, <, >, contiene, corrisponde a RegExp, è vero/falso/vuoto, altrimenti. Normalmente il messaggio va alla prima regola corrispondente, a richiesta a tutte
Richiesta HTTP Richiama un URL (metodo, header, body). La risposta finisce in msg.payload (il JSON viene letto), lo stato in msg.statusCode, gli header in msg.headers. Percorsi come /api/v1/… vanno a questo CMS. Facoltativo: errore con stato da 400 in su
Collection Legge voci (tutte o una), le crea, modifica, elimina. Per creare e modificare i campi arrivano da msg.payload, l’ID per impostazione predefinita da {{ msg.payload.id }}. Risultato in msg.payload
IllusionDB Legge record da IllusionDB (filtro, ordinamento, raggruppamento, pagine), ne legge uno tramite chiave primaria o colonna, li crea, modifica, elimina, interroga schema e tabelle. Risultato in msg.payload
Ritarda Attende il tempo impostato (secondi, anche frazioni) prima che il messaggio prosegua. Gli altri rami intanto continuano. Al massimo fino al limite di tempo dell’esecuzione
Unisci Raccoglie messaggi da più rami e ne inoltra uno non appena sono arrivati tutti – per impostazione predefinita tanti quanti i fili in ingresso. msg.payload diventa la lista dei payload (in ordine di arrivo) o un oggetto con msg.topic come chiave. Se non arriva tutto, non prosegue nulla (avviso nel debug)
E-mail Invia un’e-mail tramite l’accesso SMTP dell’istanza: A (più indirizzi separati da virgola), Oggetto, Testo (vuoto = msg.payload), Rispondi a, facoltativamente come HTML – ovunque con {{ msg.… }}. Il messaggio prosegue invariato
Risposta HTTP Risponde all’ingresso HTTP: msg.payload come body (oggetto → JSON, testo → text/plain), stato e header impostabili
Debug Mostra msg.payload, l’intero msg o un percorso nel pannello di debug
Personalizzati I tuoi node personalizzati – script riutilizzabili con parametri e uscite con nome

Funzione con più uscite

Il codice della funzione riceve msg e decide con return dove proseguire:

// Impostazione: 3 uscite
if (msg.payload.preis > 5) return [msg, null, null]   // solo uscita 1
if (msg.payload.preis > 2) return [null, msg, null]   // solo uscita 2
return [null, null, msg]                               // solo uscita 3
  • return msg – prosegue dall’uscita 1
  • return null – qui termina questo ramo
  • return [a, null, b] – per ogni uscita un messaggio o null
  • return [[a, b, c]] – più messaggi dalla stessa uscita (ad es. uno per voce)

Così riproduci molta logica in un solo node, invece di concatenare molti deviatori. Nel codice sono inoltre disponibili:

Nome Significato
node.warn(…), console.log(…) Output nel pannello di debug
store.get(key, standard), store.set(key, wert), store.delete(key), store.keys() Memoria del Workflow – si conserva tra le esecuzioni
fetch(url, { method, headers, body }) Richiesta HTTP, sincrona (niente await): risposta con status, ok, headers, text(), json()
ic.items(slug), ic.item(slug, id) Voci di una Collection (anche bozze – campo published)
ic.createItem(slug, data, { published }) Crea una voce; i valori vengono verificati rispetto allo schema come nell’API
ic.updateItem(slug, id, data), ic.deleteItem(slug, id) Modifica (solo i campi passati) o elimina
ic.data(slug, params) Come data() nel template – Collections e sorgenti REST
ic.api(method, path, body) Qualsiasi endpoint dell’API REST
idb.records(…), idb.find(…), idb.create(…) … Leggere e scrivere record in IllusionDB

Il motore JavaScript (goja) conosce ES5.1 e le novità più comuni successive – const/let, funzioni freccia, template string, destrutturazione, metodi di Array –, ma niente Intl e niente await. Numeri e date li formatti a mano, ad esempio betrag.toFixed(2).replace('.', ',') o con un’espressione regolare per i separatori delle migliaia.

Se una chiamata ic.* o idb.* non riesce (ad es. manca un campo obbligatorio), lancia un errore che puoi intercettare con try { … } catch (e) { … }. fetch() lancia errori solo in caso di problemi di rete – lo stato lo controlli tramite ok.

Esempi

Carta delle bevande per lo schermo di un bar – Ingresso HTTP (GET, percorso karte, pubblico) → Collection (leggi, tutte, solo pubblicate) → Funzione → Risposta HTTP:

msg.payload = msg.payload.map((d) => ({ name: d.data.name, preis: d.data.preis.toFixed(2) + ' €' }))
return msg

https://deine-domain.de/api/v1/flows/karte restituisce allora JSON, anche tramite fetch() da un altro dominio. Con un Deviatore su msg.req.query.format puoi ad es. rispondere a ?format=text con un secondo ramo in solo testo.

Modulo di contatto – Ingresso HTTP (POST, percorso kontakt, pubblico) → Funzione con 2 uscite → uscita 1: Collection (crea in “anfragen”, bozza) → Funzione “Reindirizza” → Risposta HTTP; uscita 2 direttamente a “Reindirizza”:

<form method="post" action="/api/v1/flows/kontakt">
  <input name="name" required> <textarea name="nachricht" required></textarea>
  <input name="website" style="display:none" tabindex="-1" autocomplete="off"> <!-- Trappola per i bot -->
  <button>Senden</button>
</form>
// Verifica: i bot compilano il campo nascosto
if (msg.payload.website) return [null, msg]
msg.payload = { name: msg.payload.name, nachricht: msg.payload.nachricht }
return [msg, null]
// “Reindirizza” (prima della risposta HTTP, lì imposta lo stato 303)
msg.payload = ''
return msg

Nella Risposta HTTP imposta inoltre l’header Location: /danke. Per la Collection “anfragen” spunta nello schema Dati live – altrimenti il prossimo bundle dal locale sovrascrive le richieste ricevute.

Sincronizzazione notturna – Avvio (pianificazione 0 3 * * *) → Richiesta HTTP (https://kasse.example/api/artikel, header Authorization: Bearer k123, errore da 400) → Funzione:

const vorhanden = ic.items('drinks')
let neu = 0, geaendert = 0
for (const a of msg.payload) {
  const d = vorhanden.find((x) => x.data.name === a.name)
  if (!d) { ic.createItem('drinks', { name: a.name, preis: a.preis }); neu++ }
  else if (d.data.preis !== a.preis) { ic.updateItem('drinks', d.id, { preis: a.preis }); geaendert++ }
}
node.warn(neu + ' neu, ' + geaendert + ' geändert')
return null

Richiamare nel template

Un flow con ingresso HTTP (GET o ANY) si può richiamare direttamente da una pagina o da uno snippet – internamente, senza rete e senza token (anche gli ingressi protetti):

<ul>
{% for d in workflow("karte", "format", "kurz") %}
  <li>{{ d.name }} – {{ d.preis }}</li>
{% endfor %}
</ul>
  • Il primo valore è il percorso dell’ingresso HTTP (karte per /api/v1/flows/karte; funzionano anche segnaposto come text/{wer}: workflow("text/Ada")).
  • Gli altri valori arrivano a coppie come parametri in msg.req.query e msg.payload – qui { "format": "kurz" }.
  • Torna indietro ciò che il node Risposta HTTP invierebbe: JSON come dati (liste, oggetti), altrimenti testo. Senza risposta HTTP non c’è nulla.
  • Gli errori nel flow appaiono come gli altri errori del template. Limite di tempo qui: 10 secondi – i servizi esterni lenti rendono lenta la pagina.
  • Le pagine con workflow() la cache delle pagine le conserva al massimo un minuto, così i dati esterni restano freschi. Nell’anteprima dell’editor il risultato viene memorizzato per 30 secondi, così il flow non parte a ogni tasto premuto.
  • Nel registro del Workflow queste esecuzioni compaiono con l’attivazione Pagina.

Più parametri, ad es. dall’URL: aggiungi semplicemente altre coppie – workflow("pfad", "a", wert1, "b", wert2, …). I parametri della pagina richiamata si trovano nel template in route.query. Esempio: una carta delle bevande con ricerca e prezzo massimo sotto /karte?suche=mate&max=4.

Pagina /karte:

<form>
  <input name="suche" value="{{ route.query.suche }}" placeholder="Name">
  <input name="max" type="number" step="0.5" value="{{ route.query.max }}" placeholder="max. Preis">
  <button>Filtern</button>
</form>
<ul>
{% for d in workflow("karte-filter", "suche", route.query.suche, "max", route.query.max) %}
  <li>{{ d.name }} – {{ d.preis }} €</li>
{% empty %}
  <li>Nichts gefunden.</li>
{% endfor %}
</ul>

Workflow: Ingresso HTTP (GET, percorso karte-filter; pubblico non è necessario per la chiamata dal template) → Collection (leggi, tutte, solo pubblicate) → Funzione → Risposta HTTP. Il node Collection sovrascrive msg.payload con le voci – perciò la funzione legge i parametri da msg.req.query:

// I valori dall’URL sono testo (o mancano) – verificarli prima dell’uso
const suche = String(msg.req.query.suche || '').trim().toLowerCase()
const max = parseFloat(String(msg.req.query.max || '').replace(',', '.'))
msg.payload = msg.payload
  .filter((d) => !suche || d.data.name.toLowerCase().includes(suche))
  .filter((d) => isNaN(max) || d.data.preis <= max)
  .map((d) => ({ name: d.data.name, preis: d.data.preis }))
return msg
  • Se un parametro manca nell’URL, arriva vuoto – qui la carta mostra allora tutto.
  • I valori dall’URL sono input dei visitatori: verificali nel Workflow prima che finiscano ad es. in una richiesta HTTP verso un altro servizio. Nel template gli output vengono sottoposti automaticamente a escape.
  • La cache delle pagine distingue in base all’indirizzo completo con i parametri – ogni combinazione passa dal Workflow al massimo una volta al minuto.
  • Con parametri ripetuti (?a=1&a=2) route.query contiene solo il primo valore.

Pianificazione

La pianificazione sul node di avvio è un’espressione cron con cinque campi – Minuto Ora Giorno Mese Giorno della settimana (0 = domenica). Ci sono dei modelli; in alto accanto ad Attivo compare la prossima esecuzione.

Espressione Significato
*/15 * * * * ogni 15 minuti
0 * * * * ogni ora allo scoccare dell’ora
0 3 * * * ogni giorno alle 3
30 7 * * 1-5 nei giorni feriali alle 7:30
0 6 1 * * il primo di ogni mese alle 6

Vale il fuso orario del server (nell’immagine Docker Europe/Berlin); un altro tramite prefisso: CRON_TZ=Europe/Berlin 0 3 * * *. Se un node di avvio è ancora in esecuzione alla scadenza successiva, questa viene saltata; le scadenze in cui il server era spento non vengono recuperate.

Ingressi HTTP

  • Percorso dopo /api/v1/flows/, con segnaposto: drinks/{id} corrisponde a /api/v1/flows/drinks/5 (msg.req.params.id è "5"), dateien/{rest...} alla fine raccoglie il resto. Un percorso con lo stesso metodo appartiene esattamente a un Workflow attivo.
  • Protetto (predefinito): solo con token API (Authorization: Bearer … o con GET ?token=…) o come amministratore connesso. Pubblico: senza accesso, anche tramite JavaScript da altri domini (CORS).
  • La risposta arriva dal primo node Risposta HTTP raggiunto. Se il flow termina senza uno, il server risponde con 204; in caso di errore con 500 e il messaggio.
  • Da pagine e snippet richiami un ingresso HTTP con workflow().
  • Nell’editor provi un ingresso HTTP tramite Esecuzione di prova nelle sue impostazioni – con un payload scelto da te, senza una richiesta reale.

Eventi delle Collections

Gli eventi vengono eseguiti in background dopo il salvataggio – il salvataggio nell’admin o tramite API non li attende. Scattano a ogni modifica tramite admin, API e Workflows. Se un Workflow modifica a sua volta voci che scatenano di nuovo eventi, la catena si interrompe dopo tre livelli.

Memoria ed e-mail

Memoria: ogni Workflow ha una piccola memoria chiave-valore che si conserva tra le esecuzioni – per contatori, “ultimo elaborato” o un risultato intermedio. I valori sono valori JSON qualsiasi (fino a 64 KB per valore, fino a 1.000 chiavi). La scheda Memoria nella barra laterale ne mostra il contenuto; lì elimini singole chiavi o tutto. La memoria appartiene all’istanza: non viaggia con i bundle, quindi lo stato sul server live si conserva durante il caricamento. Insieme al Workflow viene eliminata anche la sua memoria.

Esempio – una pianificazione segnala solo le richieste arrivate dall’ultima esecuzione (Avvio con pianificazione → Collection “leggi (tutte)” → Funzione → E-mail):

const zuletzt = store.get("zuletzt", 0)
const neu = msg.payload.filter((e) => e.id > zuletzt)
if (!neu.length) return null
store.set("zuletzt", Math.max(...neu.map((e) => e.id)))
msg.payload = neu.length + " neue Anfrage(n): " + neu.map((e) => e.data.name).join(", ")
return msg

Invio e-mail: l’accesso SMTP lo inserisci in Impostazioni → Sistema → Invio e-mail (server, porta, utente, password, mittente come Website <web@firma.de>) e lo verifichi con Invia e-mail di prova. La porta 465 si connette tramite TLS, le altre porte tramite STARTTLS; senza cifratura non viene inviata alcuna password. L’accesso vale solo per questa istanza – in locale e sul server live lo configuri separatamente (in modalità remota direttamente per il server).

IllusionDB

IllusionDB gestisce database esistenti (PostgreSQL, MySQL/MariaDB, SQLite) con interfaccia grafica e API REST. Il node IllusionDB recupera i record direttamente da lì – prodotti, team, appuntamenti, pagine di dettaglio –, senza copiarli in una Collection.

Configurare la connessione: Impostazioni → IllusionDB: nome, indirizzo (ad es. https://db.firma.de) e token API (in IllusionDB in Impostazioni → Il mio account; per leggere basta il permesso di lettura sulle tabelle). Verifica connessione mostra la versione e quante tabelle sono leggibili. Senza token sono raggiungibili solo le tabelle leggibili pubblicamente in IllusionDB.

  • I Workflows fanno riferimento al nome della connessione, le tabelle al loro nome (per tabelle omonime in più origini dati Datenquelle/tabelle). Crea in locale e sul server live una connessione ciascuno con lo stesso nome – lo stesso Workflow usa allora il database di volta in volta corretto.
  • Le connessioni appartengono all’istanza, non al bundle; il token non viene mai restituito. In modalità live modifichi quelle del server.

Il node: compare nella palette solo quando è configurata almeno una connessione. Connessione, tabella, colonne e viste salvate arrivano come opzioni di selezione in diretta dal server IllusionDB (↻ ricarica lo schema, ad es. dopo l’aggiunta di colonne).

Azione Risultato in msg.payload
leggi record Lista – con filtro, ordinamento, raggruppamento, riepilogo, quantità e pagina, facoltativamente a partire da una vista salvata
leggine uno un record tramite chiave primaria o tramite una colonna (ad es. slug); se non trovato: null o, a richiesta, un errore
crea / modifica / elimina colonne da msg.payload, chiave primaria per impostazione predefinita {{ msg.payload.id }}; risultato: il record salvato
Schema la tabella con tutte le colonne (nome, tipo, chiave primaria, …)
elenca tabelle tutte le tabelle leggibili di tutte le origini dati
  • Filtro: regole Colonna – Confronto – Valore (=, ≠, >, ≥, <, ≤, contiene, uno di a, b, c, nessuno di, è vuoto, non è vuoto), combinate con tutte le regole o almeno una. I valori possono contenere {{ msg.… }}; numeri e sì/no vengono convertiti in base al tipo di colonna. Le regole il cui valore resta vuoto vengono omesse – un campo di ricerca vuoto quindi non filtra (disattivabile).
  • Filtro come JSON sostituisce le regole, per condizioni e/o annidate nel formato dell’API di IllusionDB: {"or": [{"col": "name", "op": "like", "val": "{{ msg.payload.q }}"}, {"col": "preis", "op": "lt", "val": {{ msg.payload.max }}}]}. Un segnaposto tra virgolette viene inserito come testo, senza virgolette come valore JSON (numero, true, lista).
  • Solo queste colonne (nella lettura di liste e singoli record): ad es. slug, name, preis – IllusionDB invia solo queste colonne e salta il calcolo dei campi formula, lookup e collegamento non necessari; con tabelle larghe o grandi si risparmiano così volume di dati e tempo. Un nome di colonna sconosciuto è un errore. Vuoto = tutte (le versioni più vecchie di IllusionDB restituiscono sempre tutte).
  • Pagine: Quantità (al massimo 1000) e Pagina (ad es. {{ msg.payload.seite }}) – oppure carica tutte le pagine (fino a 10.000 record).
  • Output: la lista dei record; tutto = records, total, page, pages, has_more, summary, groups; solo i gruppi; oppure il primo record.
  • Gruppi: per ogni gruppo value, count (su tutti i risultati, non solo sulla pagina caricata) e records – con più colonne di raggruppamento invece groups un livello più in basso.
  • Riepilogo: SUM, AVG, COUNT, MIN, MAX su tutti i risultati, in summary.<spalte>. SUM e AVG IllusionDB li calcola solo per colonne di tipo Numero o Numero decimale.
  • Memorizza risultato: per quanti secondi un risultato di lettura viene memorizzato – alleggerisce il database con molto traffico (0 = chiedi ogni volta). Tabelle e colonne il CMS le memorizza per un minuto.
  • I nomi delle colonne sono quelli che IllusionDB usa nella sua API (nome visualizzato, se impostato).

Elenco e pagine di dettaglio direttamente dal database – come il blog nel capitolo 10, solo con IllusionDB invece di una Collection. Due Workflows:

  • Ingresso HTTP (GET, percorso produkte) → IllusionDB (leggi record, tabella produkte, regola aktiv = true, ordinamento name, raggruppamento kategorie, quantità 20, pagina {{ msg.payload.seite }}, output tutto) → Risposta HTTP
  • Ingresso HTTP (GET, percorso produkt) → IllusionDB (leggine uno, tabella produkte, cerca per colonna slug, valore {{ msg.payload.slug }}) → Risposta HTTP

In più una pagina /produkte con Catch-All e Disattiva cache:

{% if route.slug %}
  {% set p = workflow("produkt", "slug", route.slug) %}
  {% if not p %}{{ not_found() }}{% endif %}
  <h1>{{ p.name }}</h1>
  <p>{{ p.preis }} € · {{ p.kategorie }}</p>
  <a href="/produkte">← Alle Produkte</a>
{% else %}
  {% set r = workflow("produkte", "seite", route.query.seite|default:"1") %}
  {% for g in r.groups %}
    <h2>{{ g.value }} ({{ g.count }})</h2>
    {% for p in g.records %}<a href="/produkte/{{ p.slug }}">{{ p.name }}</a>{% endfor %}
  {% endfor %}
  <p>Seite {{ r.page }} von {{ r.pages }} · {{ r.total }} Produkte</p>
  {% if r.has_more %}<a href="?seite={{ r.page + 1 }}">weiter</a>{% endif %}
{% endif %}
  • /produkte/mate mostra il record con slug = mate, uno slug sconosciuto la pagina 404 (leggine uno restituisce allora null).
  • Con Disattiva cache ogni modifica in IllusionDB è subito visibile; senza, la cache delle pagine conserva la pagina al massimo un minuto. Con molto traffico: lascia attiva la cache e imposta Memorizza risultato nel node.
  • Sitemap: nelle impostazioni della pagina, in Pagine di dettaglio per sitemap ed esportazione statica, scegli la sorgente Tabella IllusionDB (connessione shop, tabella produkte, colonna slug, regola aktiv = true) – vedi capitolo 10. Con Verifica vedi subito quante pagine risultano.

Migliaia di landing page: lo schema regge anche grandi quantità – ad esempio una tabella landingpages con colonne slug (berlin/zahnarzt), stadt, leistung, text, aktiv, geaendert, gestita in IllusionDB (o importata lì via CSV). Una pagina Catch-All /leistungen con un template, un Workflow leggine uno tramite slug, la sorgente della sitemap sulla stessa tabella – e tutte le pagine sono pronte, sitemap inclusa. Le nuove righe in IllusionDB sono subito online (con Disattiva cache) e al più tardi dopo 10 minuti nella sitemap.

Nel node Function è disponibile idb – per tutto ciò che non si può comporre con il node:

const r = idb.records('shop', 'produkte', {
  where: { and: [{ col: 'aktiv', op: 'eq', val: true }, { col: 'preis', op: 'lt', val: 5 }] },
  sort: '-preis', // più campi: 'kategorie,-preis'
  limit: 50,
})
msg.payload = r.records.map((p) => `${p.name}: ${p.preis} €`)
return msg

idb.records(verbindung, tabelle, { where, sort, group_by, summary, fields, view, limit, page, all }) restituisce lo stesso dell’output tutto; inoltre idb.record(v, t, pk) (o null), idb.find(v, t, spalte, wert) (primo risultato o null), idb.create(v, t, daten), idb.update(v, t, pk, daten), idb.delete(v, t, pk), idb.schema(v, t), idb.tables(v) e idb.api(v, methode, pfad, body) per qualsiasi altro endpoint dell’API di IllusionDB (percorso senza /api/v1). Gli errori di IllusionDB interrompono l’esecuzione con il relativo messaggio.

Node personalizzati

Se scrivi sempre lo stesso script (formattare prezzi, accorciare testi, una notifica verso un servizio), puoi trasformarlo in un node personalizzato: scritto una volta, trascinabile dalla palette in ogni Workflow (gruppo Personalizzati) – con impostazioni per ogni utilizzo e uscite con nome. Se lo script cambia, l’effetto vale in tutti i Workflows.

Si creano e modificano in Workflows → Node personalizzati (in alto a destra):

  • Nome, descrizione, colore – così il node compare nella palette e nell’area; la descrizione è il suo testo di aiuto.
  • Ingresso: con ingresso elabora messaggi come un node Function. Senza ingresso è un node di avvio – con ▶ e pianificazione facoltativa come Avvio, ad es. “Recupera feed”.
  • Uscite: da 0 a 10, ognuna con un’etichetta facoltativa (“ok”, “errore”) che compare sul node.
  • Parametri: campi che nel Workflow vengono compilati per ogni utilizzo – Testo, Testo, più righe, Numero, Sì/No o Selezione (con opzioni), ciascuno con valore predefinito e testo di aiuto. Nel codice si trovano in config.<name>; nei testi {{ msg.… }} viene sostituito prima.
  • Codice: JavaScript come nel node Function – con msg e config, restituzione di msg, [a, null, b] per uscita o null. Tutto il resto (fetch, store, ic.*, idb.*, node.warn) è ugualmente disponibile.
  • Test: inserisci parametri e un messaggio di esempio, Esegui mostra cosa esce da ciascuna uscita – anche senza salvare, senza voce nelle esecuzioni.

Esempio Formatta prezzo – parametri waehrung (selezione €, $, predefinito €) e stellen (numero, predefinito 2), uscite “ok” e “senza prezzo”:

const preis = Number(msg.payload.preis)
if (msg.payload.preis == null || isNaN(preis)) return [null, msg]
msg.payload.preisText = preis.toFixed(config.stellen).replace('.', ',') + ' ' + config.waehrung
return [msg, null]
  • I Workflows fanno riferimento alla chiave (derivata dal nome al momento della creazione, ad es. preis-formatieren) – rinominare è quindi sicuro.
  • Un node si può eliminare solo quando nessun Workflow lo usa più; Cronologia mostra le versioni precedenti del codice.
  • I node personalizzati fanno parte del contenuto: finiscono nel bundle e quindi, al caricamento, sul server live.

Limiti e sicurezza

  • Limiti di tempo: 30 secondi per i flow con ingresso HTTP, 5 minuti negli altri casi; al massimo 1.000 esecuzioni di node per esecuzione (protezione dai cicli nel grafo). Se un flow richiama se stesso tramite il proprio hook, ci si ferma dopo tre livelli.
  • I Workflows girano con pieni permessi: possono modificare tutti i contenuti e, tramite richiesta HTTP, raggiungere anche indirizzi della rete interna. Chi modifica i Workflows richiede la stessa fiducia di chi modifica HTML e template.
  • La password SMTP è salvata nel database dell’istanza (non nei bundle, nell’admin non viene mai più mostrata).
  • Le credenziali stanno nel node o nello script – e quindi anche nei bundle e nella cronologia delle versioni. Non cedere quindi bundle e backup. Le variabili d’ambiente del server non sono volutamente disponibili nei Workflow.
  • Gli ingressi pubblici sono raggiungibili da chiunque – verifica gli input, non restituire dati interni.
  • Le voci delle Collections non hanno cronologia delle versioni. Chi modifica molte voci tramite Workflow scarica prima un bundle come backup.