// Builds subject + body from Evaluate Sources output. // Uses an array of lines joined at the end to avoid \n escape issues in JSON storage. const { results, overallStatus } = $input.first().json; const icon = { green: '✓', yellow: '⚠', red: '✗' }; const label = { green: 'Backups healthy', yellow: 'Backup warning', red: 'Backup failure' }; const weekOf = new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); const subject = icon[overallStatus] + ' ' + label[overallStatus] + ' — week of ' + weekOf; const fmt = ts => ts ? new Date(ts).toLocaleString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : 'unknown'; const L = []; if (overallStatus === 'green') { L.push('All backup sources ran successfully this week.'); L.push(''); results.forEach(r => L.push('✓ ' + r.machine + ' / ' + r.destination + ' (last success: ' + fmt(r.lastSuccess && r.lastSuccess.timestamp) + ')') ); } else { const problems = results.filter(r => r.status !== 'green'); const healthy = results.filter(r => r.status === 'green'); L.push(problems.length + ' source(s) need attention:'); L.push(''); problems.forEach(r => { L.push(icon[r.status] + ' ' + r.machine + ' / ' + r.destination); if (r.status === 'yellow') { L.push(' Ran this week but never succeeded.'); r.runs.slice(0, 3).forEach(run => { var line = ' ' + fmt(run.timestamp) + ' [' + run.status + ']'; if (run.log_path) line += ' log: ' + run.log_path; L.push(line); if (run.notes) L.push(' notes: ' + (run.notes || '').slice(0, 300)); }); L.push(''); L.push(' Next steps:'); L.push(' 1. Check the log at the path shown above'); L.push(' 2. For Synology failures: verify LAN reachability (ssh synology) — disks need ~30s to spin up'); L.push(' 3. Re-run manually: bash ~/.local/bin/backup.sh'); L.push(' 4. One-off skip (power outage, travel) is expected — yellow is a heads-up, not an alarm'); L.push(''); } if (r.status === 'red') { L.push(' No runs reported all week — silent failure.'); if (r.lastSuccess) { L.push(' Last known good run: ' + fmt(r.lastSuccess.timestamp)); if (r.lastSuccess.log_path) L.push(' Last log: ' + r.lastSuccess.log_path); } else { L.push(' No history found in the datatable.'); } L.push(''); L.push(' Next steps:'); L.push(' 1. Check systemd timer: systemctl --user status restic-backup.timer'); L.push(' 2. Verify backup.sh calls notify_backup.rb at the end'); L.push(' 3. Check n8n Workflow 1 execution log for webhook errors'); L.push(' 4. Run manually: bash ~/.local/bin/backup.sh'); L.push(''); } }); if (healthy.length > 0) { L.push('Healthy sources:'); healthy.forEach(r => L.push('✓ ' + r.machine + ' / ' + r.destination)); } } return [{ json: { subject, body: L.join('\n') } }];