- Добавлена полная интеграция с Telegram Mini App (динамическая загрузка SDK) - Отдельный компактный дизайн для Telegram Mini App - Добавлен loader при инициализации (предотвращает мелькание SMS-авторизации) - Улучшена навигация: кнопки "Назад" и "К списку заявок" теперь сохраняют авторизацию - Telegram Mini App: кнопка "Выход" просто закрывает приложение - Telegram Mini App: заявки "В работе" скрыты из списка - Веб-версия: для заявок "В работе" добавлена кнопка "Просмотреть в Telegram" (ссылка на @klientprav_bot) - Telegram Mini App: кнопки действий в черновиках расположены вертикально - Веб-версия: убрано отображение номера телефона в приветствии - Исправлена проблема с возвратом к списку черновиков (не требует повторной SMS-авторизации) - Заблокировано удаление и редактирование заявок со статусом "В работе" - Добавлена документация по Telegram Mini App интеграции
699 lines
20 KiB
JavaScript
699 lines
20 KiB
JavaScript
// ============================================================================
|
||
// n8n Code Node: Обработка данных о рейсах из FlightAware и FlightRadar24
|
||
// ============================================================================
|
||
// Объединяет данные из двух источников и формирует красивый HTML для PDF
|
||
// ============================================================================
|
||
|
||
// ==== ПОЛУЧЕНИЕ ВХОДНЫХ ДАННЫХ ====
|
||
// Ожидаемая структура: массив с двумя элементами
|
||
// [0] - данные из FlightAware (body.flights[])
|
||
// [1] - данные из FlightRadar24 (body.data[])
|
||
const inputItems = $input.all();
|
||
|
||
if (!inputItems || inputItems.length === 0) {
|
||
return [{
|
||
json: {
|
||
error: 'Нет входных данных',
|
||
html: '<html><body><h1>Ошибка: данные не получены</h1></body></html>',
|
||
flights: [],
|
||
sources: { flightaware: false, flightradar24: false }
|
||
}
|
||
}];
|
||
}
|
||
|
||
// ==== ИЗВЛЕЧЕНИЕ ДАННЫХ ИЗ ИСТОЧНИКОВ ====
|
||
let flightAwareData = [];
|
||
let flightRadar24Data = [];
|
||
|
||
try {
|
||
// Первый элемент - FlightAware
|
||
const faItem = inputItems[0];
|
||
if (faItem && faItem.json && faItem.json.body && faItem.json.body.flights) {
|
||
flightAwareData = Array.isArray(faItem.json.body.flights)
|
||
? faItem.json.body.flights
|
||
: [];
|
||
}
|
||
} catch (e) {
|
||
console.log('⚠️ Ошибка извлечения FlightAware:', e.message);
|
||
}
|
||
|
||
try {
|
||
// Второй элемент - FlightRadar24
|
||
const fr24Item = inputItems[1];
|
||
if (fr24Item && fr24Item.json && fr24Item.json.body && fr24Item.json.body.data) {
|
||
flightRadar24Data = Array.isArray(fr24Item.json.body.data)
|
||
? fr24Item.json.body.data
|
||
: [];
|
||
}
|
||
} catch (e) {
|
||
console.log('⚠️ Ошибка извлечения FlightRadar24:', e.message);
|
||
}
|
||
|
||
// ==== УТИЛИТЫ ====
|
||
const safeStr = (v) => (v == null ? '' : String(v));
|
||
const safeDate = (v) => {
|
||
if (!v) return '—';
|
||
try {
|
||
const d = new Date(v);
|
||
return d.toLocaleString('ru-RU', {
|
||
timeZone: 'UTC',
|
||
year: 'numeric',
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit'
|
||
});
|
||
} catch {
|
||
return v;
|
||
}
|
||
};
|
||
|
||
const formatDuration = (seconds) => {
|
||
if (!seconds) return '—';
|
||
const hours = Math.floor(seconds / 3600);
|
||
const minutes = Math.floor((seconds % 3600) / 60);
|
||
return `${hours}ч ${minutes}м`;
|
||
};
|
||
|
||
const formatDistance = (km) => {
|
||
if (!km) return '—';
|
||
return `${Number(km).toFixed(2)} км`;
|
||
};
|
||
|
||
// ==== ОБЪЕДИНЕНИЕ ДАННЫХ ПО REGISTRATION ====
|
||
// Создаём карту для быстрого поиска
|
||
const flightsMap = new Map();
|
||
|
||
// Добавляем данные из FlightAware
|
||
flightAwareData.forEach(flight => {
|
||
const reg = safeStr(flight.registration).trim();
|
||
if (!reg) return;
|
||
|
||
if (!flightsMap.has(reg)) {
|
||
flightsMap.set(reg, {
|
||
registration: reg,
|
||
flightNumber: safeStr(flight.flight_number),
|
||
ident: safeStr(flight.ident),
|
||
identIata: safeStr(flight.ident_iata),
|
||
aircraftType: safeStr(flight.aircraft_type),
|
||
flightAware: flight,
|
||
flightRadar24: null
|
||
});
|
||
} else {
|
||
flightsMap.get(reg).flightAware = flight;
|
||
}
|
||
});
|
||
|
||
// Добавляем данные из FlightRadar24
|
||
flightRadar24Data.forEach(flight => {
|
||
const reg = safeStr(flight.reg).trim();
|
||
if (!reg) return;
|
||
|
||
if (!flightsMap.has(reg)) {
|
||
flightsMap.set(reg, {
|
||
registration: reg,
|
||
flightNumber: safeStr(flight.flight),
|
||
ident: safeStr(flight.callsign),
|
||
identIata: safeStr(flight.flight),
|
||
aircraftType: safeStr(flight.type),
|
||
flightAware: null,
|
||
flightRadar24: flight
|
||
});
|
||
} else {
|
||
flightsMap.get(reg).flightRadar24 = flight;
|
||
}
|
||
});
|
||
|
||
// Преобразуем Map в массив
|
||
const mergedFlights = Array.from(flightsMap.values());
|
||
|
||
// ==== ГЕНЕРАЦИЯ HTML ====
|
||
const generateFlightCard = (flight) => {
|
||
const fa = flight.flightAware;
|
||
const fr24 = flight.flightRadar24;
|
||
|
||
let html = `
|
||
<div class="flight-card">
|
||
<div class="flight-header">
|
||
<h2>Рейс ${flight.flightNumber || flight.ident || 'N/A'}</h2>
|
||
<span class="registration">${flight.registration}</span>
|
||
</div>
|
||
|
||
<div class="flight-info">
|
||
<div class="info-row">
|
||
<span class="label">Тип самолёта:</span>
|
||
<span class="value">${flight.aircraftType || '—'}</span>
|
||
</div>
|
||
<div class="info-row">
|
||
<span class="label">Идентификатор:</span>
|
||
<span class="value">${flight.ident || '—'} (${flight.identIata || '—'})</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// Данные из FlightAware
|
||
if (fa) {
|
||
html += `
|
||
<div class="source-section">
|
||
<div class="source-header">
|
||
<span class="source-badge source-flightaware">FlightAware</span>
|
||
</div>
|
||
<div class="source-content">
|
||
<div class="route-info">
|
||
<div class="route-item">
|
||
<span class="route-label">Откуда:</span>
|
||
<span class="route-value">${safeStr(fa.origin?.name || fa.origin?.code_iata || '—')} (${safeStr(fa.origin?.code_iata || '—')})</span>
|
||
</div>
|
||
<div class="route-item">
|
||
<span class="route-label">Куда:</span>
|
||
<span class="route-value">${safeStr(fa.destination?.name || fa.destination?.code_iata || '—')} (${safeStr(fa.destination?.code_iata || '—')})</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="timeline">
|
||
<div class="timeline-item">
|
||
<span class="timeline-label">Плановый вылет:</span>
|
||
<span class="timeline-value">${safeDate(fa.scheduled_out)}</span>
|
||
</div>
|
||
<div class="timeline-item">
|
||
<span class="timeline-label">Фактический вылет:</span>
|
||
<span class="timeline-value">${safeDate(fa.actual_out)}</span>
|
||
</div>
|
||
<div class="timeline-item">
|
||
<span class="timeline-label">Взлёт:</span>
|
||
<span class="timeline-value">${safeDate(fa.actual_off)} ${fa.actual_runway_off ? `(ВПП ${fa.actual_runway_off})` : ''}</span>
|
||
</div>
|
||
<div class="timeline-item">
|
||
<span class="timeline-label">Посадка:</span>
|
||
<span class="timeline-value">${safeDate(fa.actual_on)} ${fa.actual_runway_on ? `(ВПП ${fa.actual_runway_on})` : ''}</span>
|
||
</div>
|
||
<div class="timeline-item">
|
||
<span class="timeline-label">Фактический прилёт:</span>
|
||
<span class="timeline-value">${safeDate(fa.actual_in)}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="status-info">
|
||
<div class="status-item">
|
||
<span class="status-label">Статус:</span>
|
||
<span class="status-value">${safeStr(fa.status || '—')}</span>
|
||
</div>
|
||
${fa.departure_delay !== null && fa.departure_delay !== undefined ? `
|
||
<div class="status-item">
|
||
<span class="status-label">Задержка вылета:</span>
|
||
<span class="status-value ${fa.departure_delay < 0 ? 'delay-negative' : 'delay-positive'}">${fa.departure_delay > 0 ? '+' : ''}${Math.floor(fa.departure_delay / 60)} мин</span>
|
||
</div>
|
||
` : ''}
|
||
${fa.arrival_delay !== null && fa.arrival_delay !== undefined ? `
|
||
<div class="status-item">
|
||
<span class="status-label">Задержка прилёта:</span>
|
||
<span class="status-value ${fa.arrival_delay < 0 ? 'delay-negative' : 'delay-positive'}">${fa.arrival_delay > 0 ? '+' : ''}${Math.floor(fa.arrival_delay / 60)} мин</span>
|
||
</div>
|
||
` : ''}
|
||
${fa.gate_origin ? `
|
||
<div class="status-item">
|
||
<span class="status-label">Гейт вылета:</span>
|
||
<span class="status-value">${fa.gate_origin}</span>
|
||
</div>
|
||
` : ''}
|
||
${fa.gate_destination ? `
|
||
<div class="status-item">
|
||
<span class="status-label">Гейт прилёта:</span>
|
||
<span class="status-value">${fa.gate_destination}</span>
|
||
</div>
|
||
` : ''}
|
||
${fa.baggage_claim ? `
|
||
<div class="status-item">
|
||
<span class="status-label">Выдача багажа:</span>
|
||
<span class="status-value">${fa.baggage_claim}</span>
|
||
</div>
|
||
` : ''}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
} else {
|
||
html += `
|
||
<div class="source-section">
|
||
<div class="source-header">
|
||
<span class="source-badge source-flightaware">FlightAware</span>
|
||
<span class="source-missing">Данные не получены</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// Данные из FlightRadar24
|
||
if (fr24) {
|
||
html += `
|
||
<div class="source-section">
|
||
<div class="source-header">
|
||
<span class="source-badge source-flightradar24">FlightRadar24</span>
|
||
</div>
|
||
<div class="source-content">
|
||
<div class="route-info">
|
||
<div class="route-item">
|
||
<span class="route-label">Откуда:</span>
|
||
<span class="route-value">${safeStr(fr24.orig_iata || '—')} (${safeStr(fr24.orig_icao || '—')})</span>
|
||
</div>
|
||
<div class="route-item">
|
||
<span class="route-label">Куда:</span>
|
||
<span class="route-value">${safeStr(fr24.dest_iata || '—')} (${safeStr(fr24.dest_icao || '—')})</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="timeline">
|
||
<div class="timeline-item">
|
||
<span class="timeline-label">Взлёт:</span>
|
||
<span class="timeline-value">${safeDate(fr24.datetime_takeoff)} ${fr24.runway_takeoff ? `(ВПП ${fr24.runway_takeoff})` : ''}</span>
|
||
</div>
|
||
<div class="timeline-item">
|
||
<span class="timeline-label">Посадка:</span>
|
||
<span class="timeline-value">${safeDate(fr24.datetime_landed)} ${fr24.runway_landed ? `(ВПП ${fr24.runway_landed})` : ''}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="status-info">
|
||
<div class="status-item">
|
||
<span class="status-label">Время полёта:</span>
|
||
<span class="status-value">${formatDuration(fr24.flight_time)}</span>
|
||
</div>
|
||
<div class="status-item">
|
||
<span class="status-label">Фактическое расстояние:</span>
|
||
<span class="status-value">${formatDistance(fr24.actual_distance)}</span>
|
||
</div>
|
||
<div class="status-item">
|
||
<span class="status-label">Кратчайшее расстояние:</span>
|
||
<span class="status-value">${formatDistance(fr24.circle_distance)}</span>
|
||
</div>
|
||
<div class="status-item">
|
||
<span class="status-label">Статус полёта:</span>
|
||
<span class="status-value">${fr24.flight_ended ? 'Завершён' : 'В процессе'}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
} else {
|
||
html += `
|
||
<div class="source-section">
|
||
<div class="source-header">
|
||
<span class="source-badge source-flightradar24">FlightRadar24</span>
|
||
<span class="source-missing">Данные не получены</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
html += `</div>`;
|
||
return html;
|
||
};
|
||
|
||
// ==== ГЕНЕРАЦИЯ ПОЛНОГО HTML ДОКУМЕНТА ====
|
||
const generateFullHTML = (flights) => {
|
||
const now = new Date();
|
||
const reportDate = now.toLocaleString('ru-RU', {
|
||
year: 'numeric',
|
||
month: 'long',
|
||
day: 'numeric',
|
||
hour: '2-digit',
|
||
minute: '2-digit'
|
||
});
|
||
|
||
let flightsHTML = '';
|
||
if (flights.length === 0) {
|
||
flightsHTML = '<div class="no-data">Данные о рейсах не найдены</div>';
|
||
} else {
|
||
flightsHTML = flights.map(flight => generateFlightCard(flight)).join('');
|
||
}
|
||
|
||
return `<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Отчёт о рейсах</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||
line-height: 1.6;
|
||
color: #333;
|
||
background: #f5f5f5;
|
||
padding: 20px;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
background: white;
|
||
padding: 30px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.header {
|
||
border-bottom: 3px solid #2563eb;
|
||
padding-bottom: 20px;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.header h1 {
|
||
color: #1e40af;
|
||
font-size: 28px;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.header-meta {
|
||
color: #666;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.sources-info {
|
||
display: flex;
|
||
gap: 15px;
|
||
margin-top: 10px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.source-tag {
|
||
display: inline-block;
|
||
padding: 4px 12px;
|
||
border-radius: 12px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.source-tag.available {
|
||
background: #d1fae5;
|
||
color: #065f46;
|
||
}
|
||
|
||
.source-tag.unavailable {
|
||
background: #fee2e2;
|
||
color: #991b1b;
|
||
}
|
||
|
||
.flight-card {
|
||
border: 1px solid #e5e7eb;
|
||
border-radius: 8px;
|
||
margin-bottom: 25px;
|
||
overflow: hidden;
|
||
background: white;
|
||
}
|
||
|
||
.flight-header {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
padding: 20px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.flight-header h2 {
|
||
font-size: 24px;
|
||
margin: 0;
|
||
}
|
||
|
||
.registration {
|
||
background: rgba(255,255,255,0.2);
|
||
padding: 6px 12px;
|
||
border-radius: 4px;
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.flight-info {
|
||
padding: 15px 20px;
|
||
background: #f9fafb;
|
||
border-bottom: 1px solid #e5e7eb;
|
||
}
|
||
|
||
.info-row {
|
||
display: flex;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.info-row .label {
|
||
font-weight: 600;
|
||
color: #4b5563;
|
||
width: 150px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.info-row .value {
|
||
color: #111827;
|
||
}
|
||
|
||
.source-section {
|
||
border-top: 1px solid #e5e7eb;
|
||
padding: 20px;
|
||
}
|
||
|
||
.source-section:first-of-type {
|
||
border-top: none;
|
||
}
|
||
|
||
.source-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.source-badge {
|
||
display: inline-block;
|
||
padding: 6px 14px;
|
||
border-radius: 6px;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: white;
|
||
}
|
||
|
||
.source-badge.source-flightaware {
|
||
background: #3b82f6;
|
||
}
|
||
|
||
.source-badge.source-flightradar24 {
|
||
background: #10b981;
|
||
}
|
||
|
||
.source-missing {
|
||
color: #ef4444;
|
||
font-size: 13px;
|
||
font-style: italic;
|
||
}
|
||
|
||
.source-content {
|
||
margin-left: 0;
|
||
}
|
||
|
||
.route-info {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 15px;
|
||
margin-bottom: 20px;
|
||
padding: 15px;
|
||
background: #f9fafb;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.route-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.route-label {
|
||
font-size: 12px;
|
||
color: #6b7280;
|
||
margin-bottom: 4px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
|
||
.route-value {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #111827;
|
||
}
|
||
|
||
.timeline {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.timeline-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 10px 0;
|
||
border-bottom: 1px solid #e5e7eb;
|
||
}
|
||
|
||
.timeline-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.timeline-label {
|
||
font-weight: 500;
|
||
color: #4b5563;
|
||
width: 180px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.timeline-value {
|
||
color: #111827;
|
||
text-align: right;
|
||
}
|
||
|
||
.status-info {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 15px;
|
||
padding: 15px;
|
||
background: #f9fafb;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.status-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.status-label {
|
||
font-size: 12px;
|
||
color: #6b7280;
|
||
margin-bottom: 4px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
|
||
.status-value {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #111827;
|
||
}
|
||
|
||
.delay-negative {
|
||
color: #10b981;
|
||
}
|
||
|
||
.delay-positive {
|
||
color: #ef4444;
|
||
}
|
||
|
||
.no-data {
|
||
text-align: center;
|
||
padding: 60px 20px;
|
||
color: #6b7280;
|
||
font-size: 18px;
|
||
}
|
||
|
||
@media print {
|
||
body {
|
||
background: white;
|
||
padding: 0;
|
||
}
|
||
|
||
.container {
|
||
box-shadow: none;
|
||
padding: 20px;
|
||
}
|
||
|
||
.flight-card {
|
||
page-break-inside: avoid;
|
||
margin-bottom: 20px;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="header">
|
||
<h1>Отчёт о рейсах</h1>
|
||
<div class="header-meta">
|
||
<div>Дата формирования: ${reportDate}</div>
|
||
<div class="sources-info">
|
||
<span class="source-tag ${flightAwareData.length > 0 ? 'available' : 'unavailable'}">
|
||
FlightAware: ${flightAwareData.length > 0 ? '✓ Данные получены' : '✗ Данные отсутствуют'}
|
||
</span>
|
||
<span class="source-tag ${flightRadar24Data.length > 0 ? 'available' : 'unavailable'}">
|
||
FlightRadar24: ${flightRadar24Data.length > 0 ? '✓ Данные получены' : '✗ Данные отсутствуют'}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flights-container">
|
||
${flightsHTML}
|
||
</div>
|
||
</div>
|
||
</body>
|
||
</html>`;
|
||
};
|
||
|
||
// ==== ФОРМИРОВАНИЕ РЕЗУЛЬТАТА ====
|
||
const html = generateFullHTML(mergedFlights);
|
||
|
||
// ==== ПОДГОТОВКА ДАННЫХ ДЛЯ КОНВЕРТАЦИИ В BASE64 PDF ====
|
||
// Эти данные будут использованы в следующей HTTP Request ноде
|
||
// для конвертации HTML в PDF и получения base64
|
||
|
||
// Настройки сервиса конвертации (замените на ваши)
|
||
const PDF_SERVICE_URL = 'https://api.htmlpdfapi.com/v1/pdf'; // Или другой сервис
|
||
const PDF_API_KEY = 'YOUR_API_KEY'; // ⚠️ ЗАМЕНИТЕ на ваш API ключ
|
||
|
||
// Подготовка запроса для HTTP Request ноды
|
||
const pdfRequestData = {
|
||
method: 'POST',
|
||
url: PDF_SERVICE_URL,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${PDF_API_KEY}`
|
||
},
|
||
body: JSON.stringify({
|
||
html: html,
|
||
options: {
|
||
format: 'A4',
|
||
printBackground: true,
|
||
margin: {
|
||
top: '20mm',
|
||
right: '15mm',
|
||
bottom: '20mm',
|
||
left: '15mm'
|
||
}
|
||
},
|
||
base64: true // Запрашиваем base64 напрямую
|
||
})
|
||
};
|
||
|
||
return [{
|
||
json: {
|
||
html: html,
|
||
flights: mergedFlights,
|
||
flights_count: mergedFlights.length,
|
||
sources: {
|
||
flightaware: {
|
||
available: flightAwareData.length > 0,
|
||
count: flightAwareData.length
|
||
},
|
||
flightradar24: {
|
||
available: flightRadar24Data.length > 0,
|
||
count: flightRadar24Data.length
|
||
}
|
||
},
|
||
generated_at: new Date().toISOString(),
|
||
|
||
// Данные для конвертации в PDF (используйте в следующей HTTP Request ноде)
|
||
pdf_request: pdfRequestData,
|
||
pdf_request_method: pdfRequestData.method,
|
||
pdf_request_url: pdfRequestData.url,
|
||
pdf_request_headers: pdfRequestData.headers,
|
||
pdf_request_body: pdfRequestData.body
|
||
}
|
||
}];
|