{
"id": "Um37boya1U0mnCjS",
"meta": {
"instanceId": "fb924c73af8f703905bc09c9ee8076f48c17b596ed05b18c0ff86915ef8a7c4a",
"templateCredsSetupCompleted": true
},
"name": "Workflow dashboard with mermaid.js",
"tags": [],
"nodes": [
{
"id": "c1f74b3a-2ae6-4491-ac02-e1e0fd188664",
"name": "When clicking \u2018Test workflow\u2019",
"type": "n8n-nodes-base.manualTrigger",
"position": [
1220,
560
],
"parameters": [],
"typeVersion": 1
},
{
"id": "2aef0899-91bb-4141-9ec1-def1c31806ae",
"name": "Respond with Mermaid",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
2640,
560
],
"parameters": {
"options": {
"responseHeaders": {
"entries": [
{
"name": "Content-Type",
"value": "text\/plain"
}
]
}
},
"respondWith": "text",
"responseBody": "={{ $json.mermaidChart }}"
},
"typeVersion": 1.100000000000000088817841970012523233890533447265625
},
{
"id": "2c60a2e2-9f35-45dc-94d1-daf75314e934",
"name": "List workflows",
"type": "n8n-nodes-base.n8n",
"position": [
1620,
360
],
"parameters": {
"filters": [],
"requestOptions": []
},
"credentials": {
"n8nApi": {
"id": "eW7IdTFt4ARJbEwR",
"name": "Ted n8n account"
}
},
"typeVersion": 1
},
{
"id": "ce4e49b9-e1ab-44d1-9490-5c685c9023d9",
"name": "Aggregate",
"type": "n8n-nodes-base.aggregate",
"position": [
1980,
360
],
"parameters": {
"options": [],
"fieldsToAggregate": {
"fieldToAggregate": [
{
"fieldToAggregate": "wf_data"
}
]
}
},
"typeVersion": 1
},
{
"id": "bc48416a-01ff-45f4-9bf2-9f4a39054b54",
"name": "Single workflow",
"type": "n8n-nodes-base.n8n",
"position": [
1620,
560
],
"parameters": {
"operation": "get",
"workflowId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.query.wfid }}"
},
"requestOptions": []
},
"credentials": {
"n8nApi": {
"id": "eW7IdTFt4ARJbEwR",
"name": "Ted n8n account"
}
},
"typeVersion": 1
},
{
"id": "85f28981-544b-4510-b1ee-d4d538455074",
"name": "Switch",
"type": "n8n-nodes-base.switch",
"position": [
1420,
460
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "load page",
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "array",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ Object.keys($json?.query)}}",
"rightValue": "wfid"
}
]
},
"renameOutput": true
},
{
"outputKey": "has wfid",
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "a4c4c624-2ff5-4fc0-9bdb-802412a5d92f",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ Object.keys($json.query).join(',') }}",
"rightValue": "wfid"
}
]
},
"renameOutput": true
}
]
},
"options": {
"looseTypeValidation": true
}
},
"typeVersion": 3
},
{
"id": "95e0b67b-5e5b-4433-9822-da86900c12ca",
"name": "Send Page",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
2640,
360
],
"parameters": {
"options": [],
"respondWith": "text",
"responseBody": "=<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>n8n Workflow Visualizer<\/title>\n <link href=\"https:\/\/cdn.jsdelivr.net\/npm\/bootstrap@5.3.0\/dist\/css\/bootstrap.min.css\" rel=\"stylesheet\">\n <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/mermaid\/dist\/mermaid.min.js\"><\/script>\n <style>\n .card-img-container {\n height: 250px;\n overflow: hidden;\n }\n .card-img-container img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n object-position: top;\n }\n <\/style>\n<\/head>\n<body>\n <div class=\"container mt-4\">\n <h2>n8n automation flowcharts with mermaid.js<\/h2>\n <div id=\"workflows-container\"><\/div>\n <\/div>\n \n<hr class=\"featurette-divider border-dark\" \/>\n\n<section id=\"about\" class=\"container mt-3\">\n <h2 class=\"text-center mb-5\">About<\/h2>\n <div class=\"row\">\n\n <div class=\"col-lg-3 text-center\">\n <img src=\"https:\/\/gravatar.com\/avatar\/a551e67c6fe7affd5f882a527dee154bb6c3ac90cf878326accb3fb3ec77c8a6?r=pg&d=retro&size=200\" alt=\"Eduard\" class=\"rounded-circle mb-3\" width=\"140\" height=\"140\" \/>\n <h3 class=\"fw-normal\">Eduard<\/h3>\n <p><a class=\"btn btn-warning\" href=\"https:\/\/n8n.io\/creators\/eduard\/\" target=\"_blank\">More templates<\/a><\/p>\n <p><a class=\"btn btn-outline-primary\" href=\"https:\/\/www.linkedin.com\/in\/parsadanyan\/\" target=\"_blank\">LinkedIn<\/a><\/p>\n <\/div>\n\n<div class=\"col-lg-9 text-center\">\n <div class=\"card shadow-sm mb-3\">\n <div class=\"card-img-container\">\n <img src=\"https:\/\/n8niostorageaccount.blob.core.windows.net\/n8nio-strapi-blobs-prod\/assets\/Untitled_design_6_18de4ce8f4.png\" class=\"card-img-top\" alt=\"How to work with XML and SQL using n8n\" \/>\n <\/div>\n <div class=\"card-body\">\n <h5 class=\"card-title\">\ud83e\udd85 Workflow Dashboard for n8n<\/h5>\n <p class=\"card-text\">Get an overview of your n8n instance. This dashboard displays all workflows, nodes, and tags on a single page.<\/p>\n <a href=\"https:\/\/n8n.io\/workflows\/2269-get-a-birds-eye-view-of-your-n8n-instance-with-the-workflow-dashboard\/\" class=\"btn btn-primary\" target=\"_blank\">Grab the template!<\/a>\n <\/div>\n <\/div>\n<\/div>\n\n <\/div>\n<\/section>\n\n <script>\n \/\/ JSON object containing workflow data with base webhook URL\n const workflowsData = {\n baseWorkflowUrl: \"{{ `${$json.instance_url}\/workflow\/`.replace(\/([^:]\\\/)\\\/+\/g, '$1') }}\", \n baseWebhookUrl : \"{{ `${$json.instance_url}\/${$json.webhook_path}\/${$json.webhook_name}?wfid=`.replace(\/([^:]\\\/)\\\/+\/g, '$1') }}\", \n workflows : {{ JSON.stringify($json.wf_data) }}\n };\n\n document.addEventListener('DOMContentLoaded', () => {\n const workflowsContainer = document.getElementById('workflows-container');\n\n \/\/ Render initial page layout\n renderWorkflows(workflowsData.workflows);\n\n function renderWorkflows(workflows) {\n workflows.forEach(workflow => {\n const card = createWorkflowCard(workflow);\n workflowsContainer.appendChild(card);\n });\n }\n\n\t\t\tfunction createWorkflowCard(workflow) {\n\t\t\t\tconst card = document.createElement('div');\n\t\t\t\tcard.className = 'card mb-3';\n\t\t\t\tcard.innerHTML = `\n <div class=\"card-body\">\n <h5 class=\"card-title d-flex align-items-center\">\n ${workflow.name}\n <span class=\"badge bg-light-subtle border border-light-subtle text-light-emphasis rounded-pill ms-2\">\n <a href=\"${workflowsData.baseWorkflowUrl}${workflow.id}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-primary-emphasis text-decoration-none\" title=\"Open workflow in a new window\"> \ud83d\udd17 <\/a>\n <\/span>\n <\/h5>\n <button class=\"btn btn-primary show-workflow-btn\" data-workflow-id=\"${workflow.id}\">Show Workflow<\/button>\n <div class=\"mermaid-container mt-3\" style=\"display: none;\"><\/div>\n <\/div>\n\t\t\t\t`;\n\n\t\t\t\tconst showWorkflowBtn = card.querySelector('.show-workflow-btn');\n\t\t\t\tconst mermaidContainer = card.querySelector('.mermaid-container');\n\t\t\t\tlet isLoaded = false;\n\n\t\t\t\tshowWorkflowBtn.addEventListener('click', () => {\n\t\t\t\t\tif (!isLoaded) {\n\t\t\t\t\t\tfetchWorkflowDiagram(workflow.id, mermaidContainer);\n\t\t\t\t\t\tisLoaded = true;\n\t\t\t\t\t\tshowWorkflowBtn.textContent = 'Hide Workflow';\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (mermaidContainer.style.display === 'none') {\n\t\t\t\t\t\t\tmermaidContainer.style.display = 'block';\n\t\t\t\t\t\t\tshowWorkflowBtn.textContent = 'Hide Workflow';\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tmermaidContainer.style.display = 'none';\n\t\t\t\t\t\t\tshowWorkflowBtn.textContent = 'Show Workflow';\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\treturn card;\n\t\t\t}\n\n\t\t\tfunction fetchWorkflowDiagram(workflowId, container) {\n\t\t\t\tconst webhookUrl = `${workflowsData.baseWebhookUrl}${workflowId}`;\n\t\t\t\tfetch(webhookUrl)\n\t\t\t\t\t.then(response => response.text())\n\t\t\t\t\t.then(mermaidCode => {\n\t\t\t\t\t\tcontainer.innerHTML = mermaidCode;\n\t\t\t\t\t\tcontainer.style.display = 'block';\n\t\t\t\t\t\tmermaid.init(undefined, container);\n\t\t\t\t\t})\n\t\t\t\t\t.catch(error => {\n\t\t\t\t\t\tconsole.error('Error fetching workflow diagram:', error);\n\t\t\t\t\t\tcontainer.innerHTML = '<p class=\"text-danger\">Error loading workflow diagram.<\/p>';\n\t\t\t\t\t\tcontainer.style.display = 'block';\n\t\t\t\t\t});\n\t\t\t}\n\n \/\/ Initialize mermaid\n mermaid.initialize({ startOnLoad: false });\n });\n <\/script>\n\n <script>\n \/\/ Blog posts fetching and rendering\n document.addEventListener('DOMContentLoaded', () => {\n const blogPostsContainer = document.getElementById('blog-posts-container');\n const authors = ['Yulia Dmitrievna', 'Eduard Parsadanyan'];\n const maxPosts = 3;\n \n fetch('https:\/\/blog.n8n.io\/rss\/')\n .then(response => response.text())\n .then(str => new window.DOMParser().parseFromString(str, \"text\/xml\"))\n .then(data => {\n const items = data.querySelectorAll(\"item\");\n let postCount = 0;\n \n items.forEach(el => {\n if (postCount >= maxPosts) return;\n \n const author = el.querySelector(\"dc\\\\:creator\").textContent.trim();\n if (authors.includes(author)) {\n const title = el.querySelector(\"title\").textContent;\n const link = el.querySelector(\"link\").textContent;\n const imageUrl = el.querySelector(\"media\\\\:content\").getAttribute(\"url\");\n \n const card = document.createElement('div');\n card.className = 'col-md-4 mb-4';\n card.innerHTML = `\n <div class=\"card h-100\">\n <img src=\"${imageUrl}\" class=\"card-img-top\" alt=\"${title}\">\n <div class=\"card-body\">\n <h5 class=\"card-title\">${title}<\/h5>\n <p class=\"card-text\">By ${author}<\/p>\n <a href=\"${link}\" class=\"btn btn-primary\" target=\"_blank\">Read More<\/a>\n <\/div>\n <\/div>\n `;\n \n blogPostsContainer.appendChild(card);\n postCount++;\n }\n });\n })\n .catch(error => {\n console.error('Error fetching blog posts:', error);\n blogPostsContainer.innerHTML = '<p class=\"text-danger\">Error loading blog posts.<\/p>';\n });\n });\n <\/script>\n\n <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/bootstrap@5.3.0\/dist\/js\/bootstrap.bundle.min.js\"><\/script>\n<\/body>\n<\/html>"
},
"typeVersion": 1.100000000000000088817841970012523233890533447265625
},
{
"id": "7f964438-a211-40bf-a991-a93848607513",
"name": "Prepare workflow list",
"type": "n8n-nodes-base.set",
"position": [
1800,
360
],
"parameters": {
"options": [],
"assignments": {
"assignments": [
{
"id": "1ce915da-7ee4-487c-9233-0b603d4a913b",
"name": "wf_data",
"type": "object",
"value": "={\n\"id\" :\"{{ $json.id }}\",\n\"name\":\"{{ $json.name }}\"\n}"
}
]
}
},
"typeVersion": 3.399999999999999911182158029987476766109466552734375
},
{
"id": "d379a0b6-aaee-4f4d-91be-74d79c160bb8",
"name": "CONFIG",
"type": "n8n-nodes-base.set",
"position": [
2300,
360
],
"parameters": {
"options": [],
"assignments": {
"assignments": [
{
"id": "07da029f-3de3-45cb-8d33-798fa1a3d529",
"name": "instance_url",
"type": "string",
"value": "={{$env[\"N8N_PROTOCOL\"]}}:\/\/{{$env[\"N8N_HOST\"]}}"
},
{
"id": "f7dae7f3-e51b-4da3-ac8b-d198747679d2",
"name": "webhook_name",
"type": "string",
"value": "={{ $('Webhook').params.path}}"
},
{
"id": "185e41a7-8b61-46e3-99ea-0b0a66982080",
"name": "webhook_path",
"type": "string",
"value": "={{$env[\"N8N_ENDPOINT_WEBHOOK\"] || \"webhook\"}}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.399999999999999911182158029987476766109466552734375
},
{
"id": "bfc42a15-130c-4e81-9f89-c07b3bb56928",
"name": "Code",
"type": "n8n-nodes-base.code",
"position": [
1800,
560
],
"parameters": {
"jsCode": "const workflow = $input.first().json;\n\n\/\/ Extract nodes from the workflow\nconst nodes = workflow.nodes || [];\n\n\/\/ Node types to exclude\nconst excludedNodeTypes = ['n8n-nodes-base.stickyNote'];\n\n\/\/ Define shapes and their corresponding brackets\n\/\/ https:\/\/mermaid.js.org\/syntax\/flowchart.html\nconst shapes = {\n 'rect': ['[', ']'],\n 'rhombus': ['{', '}'],\n 'circle': ['((', '))'],\n 'hexagon': ['{{', '}}'],\n 'subroutine': ['[[', ']]'],\n 'parallelogram': ['[\\\/', '\\\/]'],\n 'wait': ['(', ')']\n \/\/ Add more shapes here as needed\n};\n\n\/\/ Define special shapes for specific node types\nconst specialShapes = {\n 'n8n-nodes-base.if': 'rhombus',\n 'n8n-nodes-base.switch': 'rhombus',\n 'n8n-nodes-base.code': 'subroutine',\n 'n8n-nodes-base.executeWorkflow': 'subroutine',\n 'n8n-nodes-base.httpRequest':'parallelogram',\n 'n8n-nodes-base.wait':'wait'\n \/\/ List more special node types\n};\n\n\/\/ Function to get the shape for a node type\nfunction getNodeShape(nodeType) {\n return specialShapes[nodeType] || 'rect';\n}\n\n\/\/ Create a map of node names to their \"EL<N>\" identifiers, disabled status, and shape\nconst nodeMap = {};\nlet nodeCounter = 1;\nnodes.forEach((node) => {\n if (!excludedNodeTypes.includes(node.type)) {\n const shape = getNodeShape(node.type);\n nodeMap[node.name] = {\n id: `EL${nodeCounter}`,\n disabled: node.disabled || false,\n shape: shape,\n brackets: shapes[shape] || shapes['rect'] \/\/ Default to rect if shape not found\n };\n nodeCounter++;\n }\n});\n\n\/\/ Function to convert special characters to HTML entities\nfunction convertToHTMLEntities(str) {\n return str.replaceAll('\"',\"'\").replace(\/[^\\w\\s-]\/g, function(char) {\n return '&#' + char.charCodeAt(0) + ';';\n });\n}\n\n\/\/ Function to format node text (with strike-through if disabled)\nfunction formatNodeText(nodeName, isDisabled) {\n const escapedName = convertToHTMLEntities(nodeName);\n return isDisabled ? `<s>${escapedName}<\/s>` : escapedName;\n}\n\n\/\/ Generate connections and isolated nodes\nconst connections = [];\nconst isolatedNodes = new Set(Object.keys(nodeMap));\n\nif (workflow.connections) {\n Object.entries(workflow.connections).forEach(([sourceName, targetConnections]) => {\n Object.entries(targetConnections).forEach(([connectionType, targets]) => {\n targets.forEach(targetArray => {\n targetArray.forEach(target => {\n const sourceNode = nodeMap[sourceName];\n const targetNode = nodeMap[target.node];\n if (sourceNode && targetNode) {\n let connectionLine = ` ${sourceNode.id}${sourceNode.brackets[0]}${formatNodeText(sourceName, sourceNode.disabled)}${sourceNode.brackets[1]}`;\n if (connectionType === 'main') {\n connectionLine += ` -->`;\n } else {\n connectionLine += ` -.- |${connectionType}|`;\n }\n connectionLine += ` ${targetNode.id}${targetNode.brackets[0]}${formatNodeText(target.node, targetNode.disabled)}${targetNode.brackets[1]}`;\n connections.push(connectionLine);\n isolatedNodes.delete(sourceName);\n isolatedNodes.delete(target.node);\n }\n });\n });\n });\n });\n}\n\n\/\/ Add isolated nodes to the connections array\nisolatedNodes.forEach(nodeName => {\n const node = nodeMap[nodeName];\n connections.push(` ${node.id}${node.brackets[0]}${formatNodeText(nodeName, node.disabled)}${node.brackets[1]}`);\n});\n\n\/\/ Generate the Mermaid flowchart string\nconst mermaidChart = `\n---\nconfig:\n look: neo\n theme: default\n---\nflowchart LR\n${connections.join('\\n')}`;\n\n\/\/ Output the result\nreturn {\n json: {\n mermaidChart: mermaidChart\n }\n};"
},
"typeVersion": 2
},
{
"id": "28375139-c433-4c6c-a5ac-3d725c9b79ef",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
2120,
100
],
"parameters": {
"color": 3,
"width": 470.915516288838944092276506125926971435546875,
"height": 419.34820384538846838040626607835292816162109375,
"content": "## IMPORTANT NOTE FOR CLOUD USERS\n### Since the cloud version doesn't support environmental variables, please update the following fields:\n\n1. **instance_url**. Change the `{{$env[\"N8N_PROTOCOL\"]}}:\/\/{{$env[\"N8N_HOST\"]}}` expression to your cloud instance URL\n2. **webhook_path**. Change the `{{$env[\"N8N_ENDPOINT_WEBHOOK\"] || \"webhook\"}}` simply to the `webhook`. So that the production webhook is called correclty."
},
"typeVersion": 1
},
{
"id": "63245902-69d7-4d75-8cb3-58198208220a",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
1220,
360
],
"webhookId": "dd9e2c5d-6c48-428e-aa54-bef9e369d3b0",
"parameters": {
"path": "dd9e2c5d-6c48-428e-aa54-bef9e369d3b0",
"options": [],
"responseMode": "responseNode"
},
"typeVersion": 2
}
],
"active": true,
"pinData": [],
"settings": {
"callerPolicy": "workflowsFromSameOwner",
"executionOrder": "v1",
"saveManualExecutions": true,
"saveDataSuccessExecution": "all"
},
"versionId": "e73fe710-a873-4827-9a3f-2740b5479d62",
"connections": {
"Code": {
"main": [
[
{
"node": "Respond with Mermaid",
"type": "main",
"index": 0
}
]
]
},
"CONFIG": {
"main": [
[
{
"node": "Send Page",
"type": "main",
"index": 0
}
]
]
},
"Switch": {
"main": [
[
{
"node": "List workflows",
"type": "main",
"index": 0
}
],
[
{
"node": "Single workflow",
"type": "main",
"index": 0
}
]
]
},
"Webhook": {
"main": [
[
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
},
"Aggregate": {
"main": [
[
{
"node": "CONFIG",
"type": "main",
"index": 0
}
]
]
},
"List workflows": {
"main": [
[
{
"node": "Prepare workflow list",
"type": "main",
"index": 0
}
]
]
},
"Single workflow": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"Prepare workflow list": {
"main": [
[
{
"node": "Aggregate",
"type": "main",
"index": 0
}
]
]
},
"When clicking \u2018Test workflow\u2019": {
"main": [
[
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
}
}
}