74 lines
3.0 KiB
JavaScript
74 lines
3.0 KiB
JavaScript
|
|
// 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') } }];
|