Files
egorin/deploy/orbita-commands.workflow.json
T
GermanandClaude Opus 4.8 b1c52ab748 Железный Егорин — форк Орбиты: тёплый стиль (amber), логин с odyssey WebGL-молнией справа, энергомониторинг+панель
Ребренд Мониторинг Орбита -> Железный Егорин; accent green->amber #F59E0B; LoginScreen правая половина = Lightning (WebGL, hue 35 тёплый) под стеклянной формой; левая панель — тёплый градиент.
Доступ агентам: репо публичный, push german-токеном.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 23:57:16 +03:00

97 lines
8.9 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
[
{
"id": "orbitaCmds01",
"name": "orbita-commands",
"active": false,
"isArchived": false,
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "orbita-cmd",
"responseMode": "onReceived",
"responseData": "={{ { \"ok\": true } }}",
"options": {}
},
"id": "wh-orbita-0001",
"name": "WH Orbita",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [-360, 0],
"webhookId": "orbita-cmd-webhook"
},
{
"parameters": {
"jsCode": "// orbita-commands :: /status /alarms /gps -> sendPhoto (QuickChart) + caption\n// Вход: Telegram update (webhook body). Выход: { chatId, photoUrl, caption, skip }\n// Реальные данные Orbita monitor-api (публичный HTTPS, демо-логин).\n\nconst upd = ($input.first().json.body) || $input.first().json || {};\nconst msg = upd.message || upd.edited_message || upd.channel_post || {};\nconst chatId = msg.chat && msg.chat.id;\nconst textRaw = String(msg.text || '').trim();\n\n// Нет текста / нет chat — тихо выходим (напр. callback/иные апдейты)\nif (!chatId || !textRaw) {\n return [{ json: { skip: true, reason: 'no chat/text' } }];\n}\n\n// команда: первое слово, срезаем @botname\nlet cmd = textRaw.split(/\\s+/)[0].toLowerCase();\ncmd = cmd.replace(/@[a-z0-9_]+$/i, '');\n\nconst API = 'https://monitor.servaki.online';\nconst http = this.helpers.httpRequest;\n\n// ---- helpers ----\nconst pad = n => String(n).padStart(2, '0');\nfunction mskNow() {\n const d = new Date(Date.now() + 3 * 3600 * 1000);\n return pad(d.getUTCDate()) + '.' + pad(d.getUTCMonth() + 1) + ' ' + pad(d.getUTCHours()) + ':' + pad(d.getUTCMinutes()) + ' МСК';\n}\nfunction qc(config) {\n // QuickChart GET, короткий URL не нужен — Telegram сам скачает\n const c = encodeURIComponent(JSON.stringify(config));\n return 'https://quickchart.io/chart?w=500&h=300&bkg=white&c=' + c;\n}\n\n// ---- справка ----\nif (!['/status', '/alarms', '/gps'].includes(cmd)) {\n const help = '<b>Орбита · команды</b>\\n' +\n '/status — сводка системы\\n' +\n '/alarms — активные тревоги\\n' +\n '/gps — статус автопарка';\n const banner = 'https://quickchart.io/chart?w=500&h=200&bkg=white&c=' +\n encodeURIComponent(JSON.stringify({ type: 'bar', data: { labels: ['status', 'alarms', 'gps'], datasets: [{ label: 'Orbita', data: [1, 1, 1] }] }, options: { plugins: { title: { display: true, text: 'Servaki Orbita — /status /alarms /gps' } }, legend: { display: false } } }));\n return [{ json: { skip: false, chatId, photoUrl: banner, caption: help, parseMode: 'HTML' } }];\n}\n\n// ---- получаем реальные данные Orbita ----\nlet entities = [], events = [], dataOk = true;\ntry {\n const login = await http({ method: 'POST', url: API + '/api/login', body: { userId: 'demo' }, json: true, timeout: 8000 });\n const token = login && login.token;\n const auth = { Authorization: 'Bearer ' + token };\n entities = await http({ method: 'GET', url: API + '/api/entities', headers: auth, json: true, timeout: 8000 }) || [];\n events = await http({ method: 'GET', url: API + '/api/events', headers: auth, json: true, timeout: 8000 }) || [];\n} catch (e) {\n dataOk = false;\n}\nif (!Array.isArray(entities)) entities = [];\nif (!Array.isArray(events)) events = [];\n\nconst dataNote = dataOk ? '' : '\\n⚠️ данные monitor-api недоступны — показаны 0';\n\nlet photoUrl = '', caption = '';\n\nif (cmd === '/status') {\n const total = entities.length;\n const online = entities.filter(e => e.online).length;\n const offline = total - online;\n const net = entities.filter(e => e.type === 'network');\n const uplink = net.reduce((s, e) => s + ((e.telemetry && e.telemetry.uplink) || 0), 0);\n const clients = net.reduce((s, e) => s + ((e.telemetry && e.telemetry.clients) || 0), 0);\n photoUrl = qc({ type: 'doughnut', data: { labels: ['Онлайн', 'Офлайн'], datasets: [{ data: [online, offline], backgroundColor: ['#22c55e', '#ef4444'] }] }, options: { plugins: { title: { display: true, text: 'Orbita · статус объектов' } } } });\n caption = '<b>📊 Статус системы</b>\\n' +\n 'Объектов: <b>' + total + '</b> (онлайн ' + online + ' / офлайн ' + offline + ')\\n' +\n 'Сетевых узлов: <b>' + net.length + '</b>\\n' +\n 'Клиентов на узлах: <b>' + clients + '</b>\\n' +\n 'Суммарный аплинк: <b>' + uplink + '</b> Мбит/с\\n' +\n '🕒 ' + mskNow() + dataNote;\n} else if (cmd === '/alarms') {\n const crit = events.filter(e => e.severity === 'critical');\n const warn = events.filter(e => e.severity === 'warning');\n const info = events.filter(e => e.severity === 'info');\n const recent = events.slice(0, 3).map(e => {\n const s = e.severity === 'critical' ? '🔴' : (e.severity === 'warning' ? '🟡' : '🔵');\n return s + ' ' + (e.text || e.type || '') + ' · ' + (e.callsign || e.entityId || '');\n }).join('\\n') || 'нет событий';\n photoUrl = qc({ type: 'bar', data: { labels: ['Critical', 'Warning', 'Info'], datasets: [{ label: 'события', data: [crit.length, warn.length, info.length], backgroundColor: ['#ef4444', '#f59e0b', '#3b82f6'] }] }, options: { plugins: { title: { display: true, text: 'Orbita · тревоги' } }, legend: { display: false } } });\n caption = '<b>🚨 Тревоги</b>\\n' +\n '🔴 critical: <b>' + crit.length + '</b> 🟡 warning: <b>' + warn.length + '</b> 🔵 info: <b>' + info.length + '</b>\\n' +\n '<b>Последние:</b>\\n' + recent + '\\n' +\n '🕒 ' + mskNow() + dataNote;\n} else if (cmd === '/gps') {\n const fleet = entities.filter(e => e.type === 'gps' || e.type === 'vehicle' || e.type === 'transport');\n const pool = fleet.length ? fleet : entities; // если нет gps-типа — берём все объекты\n const moving = pool.filter(e => e.online && e.telemetry && (e.telemetry.speed || 0) > 3).length;\n const online = pool.filter(e => e.online).length;\n const parked = online - moving;\n const offline = pool.length - online;\n photoUrl = qc({ type: 'doughnut', data: { labels: ['В движении', 'Стоянка', 'Офлайн'], datasets: [{ data: [moving, parked, offline], backgroundColor: ['#22c55e', '#3b82f6', '#9ca3af'] }] }, options: { plugins: { title: { display: true, text: 'Orbita · автопарк' } } } });\n caption = '<b>🚗 Автопарк / GPS</b>\\n' +\n 'Всего: <b>' + pool.length + '</b>\\n' +\n '🟢 в движении: <b>' + moving + '</b>\\n' +\n '🔵 на стоянке: <b>' + parked + '</b>\\n' +\n '⚪ офлайн: <b>' + offline + '</b>\\n' +\n '🕒 ' + mskNow() + dataNote;\n}\n\nreturn [{ json: { skip: false, chatId, photoUrl, caption, parseMode: 'HTML' } }];"
},
"id": "route-orbita-0003",
"name": "Route+Fetch",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [-140, -60]
},
{
"parameters": {
"conditions": {
"options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" },
"conditions": [
{
"id": "cond-skip",
"leftValue": "={{ $json.skip }}",
"rightValue": false,
"operator": { "type": "boolean", "operation": "false", "singleValue": true }
}
],
"combinator": "and"
},
"options": {}
},
"id": "if-send-0004",
"name": "IF Send?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [100, -60]
},
{
"parameters": {
"resource": "message",
"operation": "sendPhoto",
"chatId": "={{ $json.chatId }}",
"binaryData": false,
"file": "={{ $json.photoUrl }}",
"additionalFields": {
"caption": "={{ $json.caption }}",
"parse_mode": "={{ $json.parseMode }}"
}
},
"id": "tg-photo-0005",
"name": "TG SendPhoto",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [340, -140],
"credentials": {
"telegramApi": { "id": "alertsTgCred01", "name": "servaki_online_bot" }
},
"continueOnFail": true
}
],
"connections": {
"WH Orbita": {
"main": [
[
{ "node": "Route+Fetch", "type": "main", "index": 0 }
]
]
},
"Route+Fetch": {
"main": [[{ "node": "IF Send?", "type": "main", "index": 0 }]]
},
"IF Send?": {
"main": [[{ "node": "TG SendPhoto", "type": "main", "index": 0 }], []]
}
},
"settings": { "executionOrder": "v1" },
"pinData": {}
}
]