生成GLPI支持性能报告(含SLA跟踪与邮件发送)
高级
这是一个Content Creation, Multimodal AI领域的自动化工作流,包含 20 个节点。主要使用 Set, Code, Html, Gmail, SplitOut 等节点。 生成GLPI支持性能报告,包含SLA跟踪与邮件发送功能
前置要求
- •Google 账号和 Gmail API 凭证
- •可能需要目标 API 的认证凭证
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"meta": {
"instanceId": "6fe840c9f744c9e44a1c50b6467124995157353b1fc80f3a8dd173276e4fc270",
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "d17beade-5e8d-482f-ad6a-d3f692b3ddc0",
"name": "变量",
"type": "n8n-nodes-base.set",
"position": [
-448,
32
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "ea290016-88f0-4287-be92-7a3f8c7046d6",
"name": "Server URL",
"type": "string",
"value": "https://server_glpi.com/"
},
{
"id": "5cd7f69c-f079-4155-b887-c4b5e6e1cb64",
"name": "App Token",
"type": "string",
"value": "\"You app token\""
},
{
"id": "4c2a3325-1547-4333-805a-c74b711e3bc5",
"name": "Entity name",
"type": "string",
"value": "\"name_entity\""
},
{
"id": "96765e53-2275-41e9-a297-b64207fa4a79",
"name": "WORK_START",
"type": "string",
"value": "8"
},
{
"id": "4c04dfd8-64ef-4ae0-8375-e3108222a4b8",
"name": "LUNCH_START",
"type": "string",
"value": "12"
},
{
"id": "b76f06aa-405f-421d-b64f-7468222ddfb8",
"name": "LUNCH_END",
"type": "string",
"value": "13"
},
{
"id": "1a1a5672-226e-439e-8121-bb48fbe46f67",
"name": "WORK_END",
"type": "string",
"value": "18"
},
{
"id": "b8ee7859-6533-4642-861b-d0e0136c3b39",
"name": "SLA_LIMIT_HOURS",
"type": "string",
"value": "24"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "3042ce97-b7b8-47c0-b702-905a31114297",
"name": "编辑字段",
"type": "n8n-nodes-base.set",
"position": [
416,
32
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "5a116716-658c-425a-9817-b84cfa462819",
"name": "Ticket_id",
"type": "string",
"value": "={{ $json[\"2\"] }}"
},
{
"id": "6deaea47-3c4a-4a2f-80de-a790f4dcd417",
"name": "Title",
"type": "string",
"value": "={{ $json[\"1\"] }}"
},
{
"id": "a710ff8c-c898-435d-91eb-c67e71ddc5f7",
"name": "technical_id",
"type": "string",
"value": "={{ $json[\"5\"] }}"
},
{
"id": "0ee2215d-760e-433e-9f1f-3237a8d0860c",
"name": "Entity",
"type": "string",
"value": "={{ $json[\"80\"] }}"
},
{
"id": "7b02dcbd-49df-43f9-b4e4-01b7d93a9a8e",
"name": "Opening date",
"type": "string",
"value": "={{ $json[\"15\"] }}"
},
{
"id": "62e29ecb-0dc2-4e94-ada5-c9e076dc596d",
"name": "Solution date",
"type": "string",
"value": "={{ $json[\"17\"] }}"
},
{
"id": "9741e21c-b42a-42e7-a58b-68947b24f29e",
"name": "Closing date",
"type": "string",
"value": "={{ $json[\"16\"] }}"
},
{
"id": "06abb1e0-7d26-4ac1-bd5e-a1c3b1823e3e",
"name": "Status",
"type": "string",
"value": "={{ $json[\"12\"] }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "7cf4932f-9afd-4fb4-94e7-22f2f7c634a9",
"name": "分离输出",
"type": "n8n-nodes-base.splitOut",
"position": [
192,
32
],
"parameters": {
"options": {},
"fieldToSplitOut": "data"
},
"typeVersion": 1
},
{
"id": "e583f78f-0288-4166-b930-ebb39a88f7ef",
"name": "发送消息",
"type": "n8n-nodes-base.gmail",
"position": [
1056,
-176
],
"webhookId": "4bee6d0c-8e87-46ff-90f0-a3539439b8cf",
"parameters": {
"sendTo": "info@example.com",
"message": "={{ $json.html }}",
"options": {},
"subject": "=Report - {{ $('Date range').first().json.month }}"
},
"credentials": {
"gmailOAuth2": {
"id": "1vGUy7BPigbNRllM",
"name": "Gmail account"
}
},
"typeVersion": 2.1
},
{
"id": "0cd4a9c1-5e1e-47b8-ba4c-92ffbdfef6d2",
"name": "获取工单",
"type": "n8n-nodes-base.httpRequest",
"position": [
-16,
-176
],
"parameters": {
"url": "={{ $('Variables').item.json[\"Server URL\"] }}apirest.php/search/Ticket",
"options": {},
"sendQuery": true,
"sendHeaders": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth",
"queryParameters": {
"parameters": [
{
"name": "criteria[0][field]",
"value": "15"
},
{
"name": "criteria[0][searchtype]",
"value": "morethan"
},
{
"name": "criteria[0][value]",
"value": "={{ $('Date range').item.json.startDate }}"
},
{
"name": "criteria[1][link]",
"value": "AND"
},
{
"name": "criteria[1][field]",
"value": "15"
},
{
"name": "criteria[1][searchtype]",
"value": "lessthan"
},
{
"name": "criteria[1][value]",
"value": "={{ $('Date range').item.json.endDate }}"
},
{
"name": "criteria[2][link]",
"value": "AND"
},
{
"name": "criteria[2][field]",
"value": "80"
},
{
"name": "criteria[2][searchtype]",
"value": "contains"
},
{
"name": "criteria[2][value]",
"value": "={{ $('Variables').item.json[\"Entity name\"] }}"
},
{
"name": "order",
"value": "DESC"
},
{
"name": "range",
"value": "0-999"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "Session-Token",
"value": "={{ $json.session_token }}"
},
{
"name": "App-Token",
"value": "={{ $('Variables').item.json[\"App Token\"] }}"
}
]
}
},
"credentials": {
"httpBasicAuth": {
"id": "1ri8MpwPvPOW9M8n",
"name": "Api GLPI"
}
},
"typeVersion": 4.2
},
{
"id": "0dc82824-be50-485d-b49a-398fff0dd4e1",
"name": "计划触发器",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-464,
-544
],
"parameters": {
"rule": {
"interval": [
{
"field": "months",
"triggerAtDayOfMonth": 6
}
]
}
},
"typeVersion": 1.2
},
{
"id": "3b5061f5-6fb0-4620-8d1d-d699f973e755",
"name": "获取会话令牌",
"type": "n8n-nodes-base.httpRequest",
"position": [
-208,
32
],
"parameters": {
"url": "={{ $json[\"Server URL\"] }}apirest.php/initSession",
"options": {},
"sendHeaders": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "App-Token",
"value": "={{ $json[\"App Token\"] }}"
}
]
}
},
"credentials": {
"httpBasicAuth": {
"id": "1ri8MpwPvPOW9M8n",
"name": "Api GLPI"
}
},
"typeVersion": 4.2
},
{
"id": "06ea7bb1-1347-4e14-8499-8df9c4b0619b",
"name": "结束会话",
"type": "n8n-nodes-base.httpRequest",
"position": [
1232,
-176
],
"parameters": {
"url": "={{ $('Variables').first().json[\"Server URL\"] }}apirest.php/killSession",
"options": {},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Session-Token",
"value": "={{ $('Get session token').first().json.session_token }}"
},
{
"name": "App-Token",
"value": "={{ $('Variables').first().json[\"App Token\"] }}"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "5af4ce0e-c957-49a0-a081-7e5c9b84a714",
"name": "无操作,不执行任何操作",
"type": "n8n-nodes-base.noOp",
"position": [
1408,
-176
],
"parameters": {},
"typeVersion": 1
},
{
"id": "51e2a4d5-d133-4ba7-9013-e24e10529a15",
"name": "日期范围",
"type": "n8n-nodes-base.code",
"position": [
-464,
-272
],
"parameters": {
"jsCode": "// Get the date from the Schedule Trigger\nconst today = new Date($input.item.json.timestamp);\n\n// Calculate the previous month\nconst previousMonth = new Date(today.getFullYear(), today.getMonth() - 1, 1);\n\n// Start date: last day of the month before the previous month\nconst startDate = new Date(previousMonth.getFullYear(), previousMonth.getMonth(), 0);\n\n// End date: first day of the month after the previous month\nconst endDate = new Date(previousMonth.getFullYear(), previousMonth.getMonth() + 1, 1);\n\n// Format as YYYY-MM-DD\nconst startDateStr = startDate.toISOString().split('T')[0];\nconst endDateStr = endDate.toISOString().split('T')[0];\n\n// Get the report month and year\nconst monthNames = [\n 'January', 'February', 'March', 'April', 'May', 'June',\n 'July', 'August', 'September', 'October', 'November', 'December'\n];\nconst reportMonth = monthNames[previousMonth.getMonth()];\nconst reportYear = previousMonth.getFullYear();\n\nreturn {\n json: {\n month: `${reportMonth} ${reportYear}`,\n startDate: startDateStr,\n endDate: endDateStr\n }\n};"
},
"typeVersion": 2
},
{
"id": "1946b065-da80-413f-aaee-0d23ce11eb6f",
"name": "便签",
"type": "n8n-nodes-base.stickyNote",
"position": [
-864,
-720
],
"parameters": {
"color": 4,
"width": 544,
"height": 304,
"content": "## 计划安排"
},
"typeVersion": 1
},
{
"id": "1d5b834b-be04-4c55-9282-33586fd9a6a6",
"name": "便签1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-864,
-400
],
"parameters": {
"width": 544,
"height": 288,
"content": "## 数据范围"
},
"typeVersion": 1
},
{
"id": "1284666f-52b4-49c0-b398-5e028eb80823",
"name": "便签2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-864,
-96
],
"parameters": {
"color": 3,
"width": 544,
"height": 400,
"content": "## 变量"
},
"typeVersion": 1
},
{
"id": "9ec74986-a8f0-43ec-935b-e26f16e45376",
"name": "便签4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-256,
-368
],
"parameters": {
"color": 5,
"width": 480,
"height": 320,
"content": "## 获取工单"
},
"typeVersion": 1
},
{
"id": "f38ad03a-8f49-4169-94da-ae3765796acc",
"name": "便签3",
"type": "n8n-nodes-base.stickyNote",
"position": [
256,
-576
],
"parameters": {
"width": 544,
"height": 528,
"content": "## 获取报告"
},
"typeVersion": 1
},
{
"id": "15df2869-8142-46b3-98f5-ef58bb750794",
"name": "指标",
"type": "n8n-nodes-base.code",
"position": [
544,
-176
],
"parameters": {
"jsCode": "// n8n Function Node - GLPI Report with Case Totals\n// =================================================\n\n// Business hours configuration\nconst WORK_START = $('Variables').first().json.WORK_START; // 8 AM\nconst LUNCH_START = $('Variables').first().json.LUNCH_START; // 12 PM\nconst LUNCH_END = $('Variables').first().json.WORK_END; // 1 PM\nconst WORK_END = $('Variables').first().json.WORK_END; // 6 PM\n\n// SLA configuration\nconst SLA_LIMIT_HOURS = $('Variables').first().json.SLA_LIMIT_HOURS; // cambiar aquí según SLA\n\nfunction toDate(str) {\n return str ? new Date(str.replace(\" \", \"T\")) : null;\n}\n\nfunction isWeekend(date) {\n const day = date.getDay();\n return day === 0 || day === 6;\n}\n\n// Calculate effective working hours within business schedule\nfunction businessHoursDiff(start, end) {\n if (!start || !end) return 0;\n if (end < start) return 0;\n\n let totalHours = 0;\n let current = new Date(start);\n\n while (current < end) {\n if (isWeekend(current)) {\n current.setDate(current.getDate() + 1);\n current.setHours(WORK_START, 0, 0, 0);\n continue;\n }\n\n const workMorningStart = new Date(current.setHours(WORK_START, 0, 0, 0));\n const workMorningEnd = new Date(current.setHours(LUNCH_START, 0, 0, 0));\n const workAfternoonStart = new Date(current.setHours(LUNCH_END, 0, 0, 0));\n const workAfternoonEnd = new Date(current.setHours(WORK_END, 0, 0, 0));\n\n const dayStart = start > workMorningStart ? start : workMorningStart;\n const dayEnd = end < workAfternoonEnd ? end : workAfternoonEnd;\n\n if (dayStart < workMorningEnd) {\n totalHours += Math.max(0, (Math.min(dayEnd, workMorningEnd) - dayStart) / 3600000);\n }\n if (dayEnd > workAfternoonStart) {\n totalHours += Math.max(0, (dayEnd - Math.max(dayStart, workAfternoonStart)) / 3600000);\n }\n\n current.setDate(current.getDate() + 1);\n current.setHours(WORK_START, 0, 0, 0);\n }\n\n return totalHours;\n}\n\n// Convert decimal → \"Xh Ym\"\nfunction decimalToHM(decimal) {\n const hours = Math.floor(decimal);\n const minutes = Math.round((decimal - hours) * 60);\n return `${hours}h ${minutes}m`;\n}\n\n// Initialize metrics\nlet general = { open: 0, in_progress: 0, solved: 0, closed: 0, hours: 0, sla_ok: 0, total: 0 };\nlet byTechnician = {};\n\n// Process tickets\nfor (const item of items) {\n const t = item.json;\n\n const opening = toDate(t[\"Opening date\"]);\n const solution = toDate(t[\"Solution date\"]);\n const closing = toDate(t[\"Closing date\"]);\n const technician = t.technical_id || \"Unassigned\";\n const status = parseInt(t.Status, 10);\n\n const end = solution || closing;\n const hours = end ? businessHoursDiff(opening, end) : 0;\n const sla_ok = solution ? (hours <= SLA_LIMIT_HOURS ? 1 : 0) : 0;\n\n // General report\n if (status === 1) general.open++;\n if (status === 2) general.in_progress++;\n if (status === 5) general.solved++;\n if (status === 6) general.closed++;\n general.hours += hours;\n general.sla_ok += sla_ok;\n general.total++;\n\n // Report by technician\n if (!byTechnician[technician]) {\n byTechnician[technician] = { open: 0, in_progress: 0, solved: 0, closed: 0, hours: 0, sla_ok: 0, total: 0 };\n }\n if (status === 1) byTechnician[technician].open++;\n if (status === 2) byTechnician[technician].in_progress++;\n if (status === 5) byTechnician[technician].solved++;\n if (status === 6) byTechnician[technician].closed++;\n byTechnician[technician].hours += hours;\n byTechnician[technician].sla_ok += sla_ok;\n byTechnician[technician].total++;\n}\n\n// Function to build summary\nfunction summary(data) {\n return {\n \"Total cases\": data.total,\n \"Open cases\": data.open,\n \"Cases in progress\": data.in_progress,\n \"Solved cases\": data.solved,\n \"Closed cases\": data.closed,\n \"Total hours (decimal)\": data.hours.toFixed(2),\n \"Total hours (formatted)\": decimalToHM(data.hours),\n \"Average hours (decimal)\": data.total > 0 ? (data.hours / data.total).toFixed(2) : 0,\n \"Average hours (formatted)\": data.total > 0 ? decimalToHM(data.hours / data.total) : \"0h 0m\",\n \"SLA compliance\": data.total > 0 ? ((data.sla_ok / data.total) * 100).toFixed(2) + \"%\" : \"0%\"\n };\n}\n\n// Final output\nreturn [\n {\n json: {\n \"General Report\": summary(general),\n \"Report by Technician\": Object.fromEntries(\n Object.entries(byTechnician).map(([tech, data]) => [tech, summary(data)])\n )\n }\n }\n];"
},
"typeVersion": 2
},
{
"id": "4dc2fc78-aedc-44ac-9c12-8343dee0ef20",
"name": "生成报告",
"type": "n8n-nodes-base.html",
"position": [
880,
-176
],
"parameters": {
"html": "<!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 <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n <title>Case Report - Technical Support</title>\n <style>\n /* Reset styles */\n body { margin: 0; padding: 0; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }\n table { border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; }\n img { border: 0; height: auto; line-height: 100%; outline: none; text-decoration: none; -ms-interpolation-mode: bicubic; }\n \n /* Responsive styles */\n @media only screen and (max-width: 620px) {\n .wrapper { width: 100% !important; }\n .container { width: 100% !important; min-width: 100% !important; }\n .mobile-padding { padding-left: 15px !important; padding-right: 15px !important; }\n .metric-card { display: block !important; width: 100% !important; margin-bottom: 10px !important; }\n .metric-row td { display: block !important; width: 100% !important; padding-bottom: 10px !important; }\n .hide-mobile { display: none !important; }\n .table-responsive { font-size: 12px !important; }\n .table-responsive td { padding: 8px 4px !important; }\n }\n </style>\n</head>\n<body style=\"margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; background-color: #f3f4f6;\">\n <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" role=\"presentation\" style=\"background-color: #f3f4f6; padding: 20px;\" class=\"wrapper\">\n <tr>\n <td align=\"center\">\n <table width=\"600\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" role=\"presentation\" style=\"background-color: #ffffff; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); max-width: 600px;\" class=\"container\">\n \n <!-- Header -->\n <tr>\n <td style=\"background: linear-gradient(135deg, #2F3F64 0%, #1a2744 100%); background-color: #2F3F64; padding: 40px 30px; text-align: center;\" class=\"mobile-padding\">\n <h1 style=\"margin: 0; color: #ffffff; font-size: 26px; font-weight: bold; line-height: 1.3;\">\n 📊 Technical Support Case Report - {{ $('Date range').first().json.month }}\n </h1>\n <p style=\"margin: 8px 0 0 0; color: #e2e8f0; font-size: 14px;\">\n Complete analysis of performance and case status\n </p>\n </td>\n </tr>\n \n <!-- Content -->\n <tr>\n <td style=\"padding: 30px;\" class=\"mobile-padding\">\n \n <!-- Section Title -->\n <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" role=\"presentation\" style=\"margin-bottom: 20px;\">\n <tr>\n <td style=\"border-left: 4px solid #2F3F64; padding-left: 12px;\">\n <h2 style=\"margin: 0; font-size: 18px; color: #0f172a; font-weight: bold;\">\n 📈 General Summary\n </h2>\n </td>\n </tr>\n </table>\n \n <!-- Metrics Row 1 -->\n <table width=\"100%\" cellpadding=\"8\" cellspacing=\"0\" border=\"0\" role=\"presentation\" style=\"margin-bottom: 12px;\" class=\"metric-row\">\n <tr>\n <!-- Total Cases -->\n <td width=\"25%\" style=\"background-color: #f8fafc; border-left: 4px solid #3b82f6; border-radius: 8px; padding: 16px; vertical-align: top;\" class=\"metric-card\">\n <div style=\"font-size: 10px; color: #64748b; font-weight: bold; text-transform: uppercase; margin-bottom: 8px; letter-spacing: 0.5px;\">\n TOTAL CASES\n </div>\n <div style=\"font-size: 26px; color: #0f172a; font-weight: bold; line-height: 1;\">\n {{$json[\"General Report\"][\"Total cases\"]}}\n </div>\n </td>\n \n <td width=\"2%\" class=\"hide-mobile\"></td>\n \n <!-- Open Cases -->\n <td width=\"25%\" style=\"background-color: #f8fafc; border-left: 4px solid #f59e0b; border-radius: 8px; padding: 16px; vertical-align: top;\" class=\"metric-card\">\n <div style=\"font-size: 10px; color: #64748b; font-weight: bold; text-transform: uppercase; margin-bottom: 8px; letter-spacing: 0.5px;\">\n OPEN CASES\n </div>\n <div style=\"font-size: 26px; color: #0f172a; font-weight: bold; line-height: 1;\">\n {{$json[\"General Report\"][\"Open cases\"]}}\n </div>\n </td>\n \n <td width=\"2%\" class=\"hide-mobile\"></td>\n \n <!-- In Progress with Alert -->\n <td width=\"23%\" style=\"background-color: #f8fafc; border-left: 4px solid #8b5cf6; border-radius: 8px; padding: 16px; vertical-align: top; position: relative;\" class=\"metric-card\">\n <div style=\"font-size: 10px; color: #64748b; font-weight: bold; text-transform: uppercase; margin-bottom: 8px; letter-spacing: 0.5px;\">\n IN PROGRESS\n </div>\n <div style=\"font-size: 26px; color: #0f172a; font-weight: bold; line-height: 1;\">\n {{$json[\"General Report\"][\"Cases in progress\"]}}\n </div>\n {{parseInt($json[\"General Report\"][\"Cases in progress\"]) > 100 ? `\n <div style=\"margin-top: 8px; padding: 4px 8px; background-color: #fef3c7; border-radius: 4px; font-size: 10px; color: #92400e; font-weight: bold;\">\n ⚠️ HIGH VOLUME\n </div>\n ` : ''}}\n </td>\n \n <td width=\"2%\" class=\"hide-mobile\"></td>\n \n <!-- Resolved -->\n <td width=\"23%\" style=\"background-color: #f8fafc; border-left: 4px solid #10b981; border-radius: 8px; padding: 16px; vertical-align: top;\" class=\"metric-card\">\n <div style=\"font-size: 10px; color: #64748b; font-weight: bold; text-transform: uppercase; margin-bottom: 8px; letter-spacing: 0.5px;\">\n RESOLVED\n </div>\n <div style=\"font-size: 26px; color: #0f172a; font-weight: bold; line-height: 1;\">\n {{$json[\"General Report\"][\"Solved cases\"]}}\n </div>\n </td>\n </tr>\n </table>\n \n <!-- Metrics Row 2 -->\n <table width=\"100%\" cellpadding=\"8\" cellspacing=\"0\" border=\"0\" role=\"presentation\" style=\"margin-bottom: 30px;\" class=\"metric-row\">\n <tr>\n <!-- Closed -->\n <td width=\"25%\" style=\"background-color: #f8fafc; border-left: 4px solid #6b7280; border-radius: 8px; padding: 16px; vertical-align: top;\" class=\"metric-card\">\n <div style=\"font-size: 10px; color: #64748b; font-weight: bold; text-transform: uppercase; margin-bottom: 8px; letter-spacing: 0.5px;\">\n CLOSED\n </div>\n <div style=\"font-size: 26px; color: #0f172a; font-weight: bold; line-height: 1;\">\n {{$json[\"General Report\"][\"Closed cases\"]}}\n </div>\n </td>\n \n <td width=\"2%\" class=\"hide-mobile\"></td>\n \n <!-- Total Hours -->\n <td width=\"25%\" style=\"background-color: #f8fafc; border-left: 4px solid #3b82f6; border-radius: 8px; padding: 16px; vertical-align: top;\" class=\"metric-card\">\n <div style=\"font-size: 10px; color: #64748b; font-weight: bold; text-transform: uppercase; margin-bottom: 8px; letter-spacing: 0.5px;\">\n TOTAL HOURS\n </div>\n <div style=\"font-size: 20px; color: #0f172a; font-weight: bold; line-height: 1;\">\n {{$json[\"General Report\"][\"Total hours (formatted)\"]}}\n </div>\n </td>\n \n <td width=\"2%\" class=\"hide-mobile\"></td>\n \n <!-- Average -->\n <td width=\"23%\" style=\"background-color: #f8fafc; border-left: 4px solid #f59e0b; border-radius: 8px; padding: 16px; vertical-align: top;\" class=\"metric-card\">\n <div style=\"font-size: 10px; color: #64748b; font-weight: bold; text-transform: uppercase; margin-bottom: 8px; letter-spacing: 0.5px;\">\n AVERAGE\n </div>\n <div style=\"font-size: 20px; color: #0f172a; font-weight: bold; line-height: 1;\">\n {{$json[\"General Report\"][\"Average hours (formatted)\"]}}\n </div>\n </td>\n \n <td width=\"2%\" class=\"hide-mobile\"></td>\n \n <!-- SLA Compliance with Dynamic Color -->\n <td width=\"23%\" style=\"background-color: #f8fafc; border-radius: 8px; padding: 16px; vertical-align: top;\" class=\"metric-card\">\n {{(() => {\n const sla = parseFloat($json[\"General Report\"][\"SLA compliance\"]);\n const borderColor = sla >= 80 ? '#10b981' : sla >= 50 ? '#f59e0b' : '#ef4444';\n return `<div style=\"border-left: 4px solid ${borderColor}; padding-left: 12px;\">\n <div style=\"font-size: 10px; color: #64748b; font-weight: bold; text-transform: uppercase; margin-bottom: 8px; letter-spacing: 0.5px;\">\n SLA COMPLIANCE\n </div>\n <div style=\"font-size: 22px; color: #0f172a; font-weight: bold; line-height: 1;\">\n ${$json[\"General Report\"][\"SLA compliance\"]}\n </div>\n </div>`;\n })()}}\n </td>\n </tr>\n </table>\n \n <!-- Alert Section for Critical SLA -->\n {{parseFloat($json[\"General Report\"][\"SLA compliance\"]) < 50 ? `\n <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" role=\"presentation\" style=\"margin-bottom: 20px;\">\n <tr>\n <td style=\"background-color: #fef2f2; border-left: 4px solid #dc2626; border-radius: 8px; padding: 16px;\">\n <div style=\"font-size: 14px; color: #991b1b; font-weight: bold; margin-bottom: 4px;\">\n ⚠️ CRITICAL ALERT\n </div>\n <div style=\"font-size: 13px; color: #7f1d1d; line-height: 1.5;\">\n SLA compliance is below 50%. Immediate action required to improve service levels.\n </div>\n </td>\n </tr>\n </table>\n ` : ''}}\n \n <!-- Divider -->\n <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" role=\"presentation\" style=\"margin: 30px 0;\">\n <tr>\n <td style=\"border-bottom: 1px solid #e2e8f0;\"></td>\n </tr>\n </table>\n \n <!-- Section Title 2 -->\n <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" role=\"presentation\" style=\"margin-bottom: 20px;\">\n <tr>\n <td style=\"border-left: 4px solid #2F3F64; padding-left: 12px;\">\n <h2 style=\"margin: 0; font-size: 18px; color: #0f172a; font-weight: bold;\">\n 👥 Technician Details\n </h2>\n </td>\n </tr>\n </table>\n \n <!-- Table -->\n <table width=\"100%\" cellpadding=\"12\" cellspacing=\"0\" border=\"0\" role=\"presentation\" style=\"border: 1px solid #e2e8f0; border-radius: 8px; overflow: hidden;\" class=\"table-responsive\">\n <thead>\n <tr style=\"background-color: #f8fafc;\">\n <th style=\"text-align: left; font-size: 11px; color: #475569; font-weight: bold; text-transform: uppercase; padding: 12px; border-bottom: 1px solid #e2e8f0;\">Technician</th>\n <th style=\"text-align: center; font-size: 11px; color: #475569; font-weight: bold; text-transform: uppercase; padding: 12px; border-bottom: 1px solid #e2e8f0;\">Total</th>\n <th style=\"text-align: center; font-size: 11px; color: #475569; font-weight: bold; text-transform: uppercase; padding: 12px; border-bottom: 1px solid #e2e8f0;\" class=\"hide-mobile\">Open</th>\n <th style=\"text-align: center; font-size: 11px; color: #475569; font-weight: bold; text-transform: uppercase; padding: 12px; border-bottom: 1px solid #e2e8f0;\">Progress</th>\n <th style=\"text-align: center; font-size: 11px; color: #475569; font-weight: bold; text-transform: uppercase; padding: 12px; border-bottom: 1px solid #e2e8f0;\" class=\"hide-mobile\">Solved</th>\n <th style=\"text-align: center; font-size: 11px; color: #475569; font-weight: bold; text-transform: uppercase; padding: 12px; border-bottom: 1px solid #e2e8f0;\">Closed</th>\n <th style=\"text-align: center; font-size: 11px; color: #475569; font-weight: bold; text-transform: uppercase; padding: 12px; border-bottom: 1px solid #e2e8f0;\" class=\"hide-mobile\">Total Hrs</th>\n <th style=\"text-align: center; font-size: 11px; color: #475569; font-weight: bold; text-transform: uppercase; padding: 12px; border-bottom: 1px solid #e2e8f0;\" class=\"hide-mobile\">Average</th>\n <th style=\"text-align: center; font-size: 11px; color: #475569; font-weight: bold; text-transform: uppercase; padding: 12px; border-bottom: 1px solid #e2e8f0;\">SLA</th>\n </tr>\n </thead>\n <tbody>\n {{($json[\"Report by Technician\"] && Object.keys($json[\"Report by Technician\"]).length > 0) \n ? Object.keys($json[\"Report by Technician\"]).map(techId => {\n const tech = $json[\"Report by Technician\"][techId];\n const sla = parseFloat(tech[\"SLA compliance\"]);\n const bgColor = sla >= 80 ? '#d1fae5' : sla >= 50 ? '#fef3c7' : '#fee2e2';\n const textColor = sla >= 80 ? '#065f46' : sla >= 50 ? '#92400e' : '#991b1b';\n const inProgress = parseInt(tech[\"Cases in progress\"]);\n const progressAlert = inProgress > 100 ? 'background-color: #fef3c7;' : '';\n \n return `\n <tr style=\"${progressAlert}\">\n <td style=\"padding: 12px; font-size: 14px; color: #0f172a; font-weight: bold; border-bottom: 1px solid #f1f5f9;\">\n Technician #${techId}\n </td>\n <td style=\"padding: 12px; font-size: 14px; color: #334155; border-bottom: 1px solid #f1f5f9; text-align: center;\">\n ${tech[\"Total cases\"]}\n </td>\n <td style=\"padding: 12px; font-size: 14px; color: #334155; border-bottom: 1px solid #f1f5f9; text-align: center;\" class=\"hide-mobile\">\n ${tech[\"Open cases\"]}\n </td>\n <td style=\"padding: 12px; font-size: 14px; border-bottom: 1px solid #f1f5f9; text-align: center;\">\n <span style=\"${inProgress > 100 ? 'font-weight: bold; color: #92400e;' : 'color: #334155;'}\">\n ${tech[\"Cases in progress\"]}${inProgress > 100 ? ' ⚠️' : ''}\n </span>\n </td>\n <td style=\"padding: 12px; font-size: 14px; color: #334155; border-bottom: 1px solid #f1f5f9; text-align: center;\" class=\"hide-mobile\">\n ${tech[\"Solved cases\"]}\n </td>\n <td style=\"padding: 12px; font-size: 14px; color: #334155; border-bottom: 1px solid #f1f5f9; text-align: center;\">\n ${tech[\"Closed cases\"]}\n </td>\n <td style=\"padding: 12px; font-size: 14px; color: #334155; border-bottom: 1px solid #f1f5f9; text-align: center;\" class=\"hide-mobile\">\n ${tech[\"Total hours (formatted)\"]}\n </td>\n <td style=\"padding: 12px; font-size: 14px; color: #334155; border-bottom: 1px solid #f1f5f9; text-align: center;\" class=\"hide-mobile\">\n ${tech[\"Average hours (formatted)\"]}\n </td>\n <td style=\"padding: 12px; font-size: 14px; border-bottom: 1px solid #f1f5f9; text-align: center;\">\n <span style=\"display: inline-block; padding: 6px 12px; background-color: ${bgColor}; color: ${textColor}; font-size: 12px; font-weight: bold; border-radius: 12px; white-space: nowrap;\">\n ${tech[\"SLA compliance\"]}\n </span>\n </td>\n </tr>\n `;\n }).join('')\n : '<tr><td colspan=\"9\" style=\"padding: 30px; text-align: center; color: #64748b; font-style: italic; border-bottom: 1px solid #f1f5f9;\">No technician data available</td></tr>'\n }}\n </tbody>\n </table>\n \n </td>\n </tr>\n \n <!-- Footer -->\n <tr>\n <td style=\"background-color: #f8fafc; padding: 20px; text-align: center; border-top: 1px solid #e2e8f0;\">\n <p style=\"margin: 0 0 8px 0; color: #64748b; font-size: 12px;\">\n Report automatically generated by the technical support system\n </p>\n <p style=\"margin: 0; color: #94a3b8; font-size: 11px;\">\n Generated on: {{new Date().toLocaleString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', timeZone: 'America/Bogota' })}}\n </p>\n </td>\n </tr>\n \n </table>\n </td>\n </tr>\n </table>\n</body>\n</html>"
},
"typeVersion": 1.2
},
{
"id": "b8578b10-7ce4-4bcc-945b-b938cfbd2fb7",
"name": "便利贴5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-368,
-1280
],
"parameters": {
"color": 7,
"width": 480,
"height": 304,
"content": "## 在 n8n 中创建 GLPI API 凭据"
},
"typeVersion": 1
},
{
"id": "dcfed78e-cb52-4a20-87b9-41e1699743b8",
"name": "便签 6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-864,
-1280
],
"parameters": {
"color": 7,
"width": 480,
"height": 304,
"content": "## GLPI 工单面板"
},
"typeVersion": 1
},
{
"id": "5623a271-7c94-40cb-9043-b9e6c2f35c71",
"name": "便签 7",
"type": "n8n-nodes-base.stickyNote",
"position": [
1008,
-320
],
"parameters": {
"width": 192,
"height": 128,
"content": "## 邮箱"
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Metrics": {
"main": [
[
{
"node": "Generate report",
"type": "main",
"index": 0
}
]
]
},
"Split Out": {
"main": [
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
]
]
},
"Variables": {
"main": [
[
{
"node": "Get session token",
"type": "main",
"index": 0
}
]
]
},
"Date range": {
"main": [
[
{
"node": "Variables",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields": {
"main": [
[
{
"node": "Metrics",
"type": "main",
"index": 0
}
]
]
},
"End session": {
"main": [
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
]
]
},
"Get tickets": {
"main": [
[
{
"node": "Split Out",
"type": "main",
"index": 0
}
]
]
},
"Send a message": {
"main": [
[
{
"node": "End session",
"type": "main",
"index": 0
}
]
]
},
"Generate report": {
"main": [
[
{
"node": "Send a message",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Date range",
"type": "main",
"index": 0
}
]
]
},
"Get session token": {
"main": [
[
{
"node": "Get tickets",
"type": "main",
"index": 0
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
高级 - 内容创作, 多模态 AI
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
使用Groq、Gemini和Slack审批系统自动化RSS到Medium发布
通过Groq、Gemini和Slack审批系统实现RSS到Medium发布的自动化流程
If
Set
Code
+16
41 节点ObisDev
内容创作
使用GPT-5和fal.ai图像从关键词到WordPress自动化SEO博客流程
使用GPT-5和fal.ai图像从关键词到WordPress自动化SEO博客流程
Set
Code
Wait
+20
96 节点Paul
内容创作
内容聚合
使用Gemini AI从网站文章自动化社交媒体帖子发布到LinkedIn和X/Twitter
If
Set
Xml
+16
34 节点Vadim
内容创作
自动化GLPI工单截止日期提醒
通过Microsoft Teams发送的自动化GLPI工单截止日期提醒
If
Set
Split Out
+7
16 节点Luis Hernandez
工单管理
防欺诈潜在客户捕获与培育系统
通过AI评分、表格跟踪和多渠道提醒捕获和培育防欺诈潜在客户
If
Set
Code
+11
28 节点Jitesh Dugar
内容创作
我的工作流5
基于 Gemini AI、网络搜索和 PDF 交付的全面研究报告生成器
Set
Code
Html
+14
102 节点Hichul
内容创作
工作流信息
难度等级
高级
节点数量20
分类2
节点类型9
作者
Luis Hernandez
@integropenI like solving problems and have a true passion for no-code automation. I'm thoughtful by nature and enjoy finding ways to optimize processes, always guided by my principles and values.
外部链接
在 n8n.io 查看 →
分享此工作流