使用mermaid.js的工作流仪表板
中级
这是一个Other领域的自动化工作流,包含 12 个节点。主要使用 N8n, Set, Code, Switch, Webhook 等节点。 🔍 使用Mermaid.js可视化您的n8n工作流!
前置要求
- •HTTP Webhook 端点(n8n 会自动生成)
分类
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"id": "Um37boya1U0mnCjS",
"meta": {
"instanceId": "fb924c73af8f703905bc09c9ee8076f48c17b596ed05b18c0ff86915ef8a7c4a",
"templateCredsSetupCompleted": true
},
"name": "使用 mermaid.js 的工作流仪表板",
"tags": [],
"nodes": [
{
"id": "c1f74b3a-2ae6-4491-ac02-e1e0fd188664",
"name": "当点击\"测试工作流\"时",
"type": "n8n-nodes-base.manualTrigger",
"position": [
1220,
560
],
"parameters": {},
"typeVersion": 1
},
{
"id": "2aef0899-91bb-4141-9ec1-def1c31806ae",
"name": "使用 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.1
},
{
"id": "2c60a2e2-9f35-45dc-94d1-daf75314e934",
"name": "列出工作流",
"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": "聚合",
"type": "n8n-nodes-base.aggregate",
"position": [
1980,
360
],
"parameters": {
"options": {},
"fieldsToAggregate": {
"fieldToAggregate": [
{
"fieldToAggregate": "wf_data"
}
]
}
},
"typeVersion": 1
},
{
"id": "bc48416a-01ff-45f4-9bf2-9f4a39054b54",
"name": "单个工作流",
"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": "发送页面",
"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\">🦅 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\"> 🔗 </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.1
},
{
"id": "7f964438-a211-40bf-a991-a93848607513",
"name": "准备工作流列表",
"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.4
},
{
"id": "d379a0b6-aaee-4f4d-91be-74d79c160bb8",
"name": "配置",
"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.4
},
{
"id": "bfc42a15-130c-4e81-9f89-c07b3bb56928",
"name": "代码",
"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": "便签 3",
"type": "n8n-nodes-base.stickyNote",
"position": [
2120,
100
],
"parameters": {
"color": 3,
"width": 470.91551628883894,
"height": 419.34820384538847,
"content": "## 云用户重要提示"
},
"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 ‘Test workflow’": {
"main": [
[
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
中级 - 其他
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
Docsify示例
📚 使用GPT和Docsify自动生成n8n工作流文档
If
N8n
Set
+17
60 节点Eduard
其他
GitHub 同步仪表板 - V2
具有提交历史和回滚功能的 GitHub 工作流版本控制仪表板
If
N8n
Set
+20
94 节点Eduard
开发运维
工作流统计
🦅 通过工作流仪表板全面概览您的n8n实例!
N8n
Set
Xml
+12
31 节点Eduard
开发运维
AI 代理餐厅 [模板]
🤖 WhatsApp、Instagram 和 Messenger 的 AI 餐厅助手
If
N8n
Set
+37
239 节点Amanda Benks
其他
在可视化参考库中探索n8n节点
在可视化参考库中探索n8n节点
If
Ftp
Set
+93
113 节点I versus AI
其他
灵活新闻聚合器 - 多源集成、AI分析和可设置频道
多源新闻策展系统,集成Mistral AI分析、摘要和自定义频道
If
Set
Xml
+32
120 节点Hybroht
内容创作