182 lines
5.8 KiB
JavaScript
182 lines
5.8 KiB
JavaScript
import fs from 'fs';
|
|
import path from 'path';
|
|
import nodemailer from 'nodemailer';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
const urls = [
|
|
'https://andreknie.de',
|
|
'https://api.andreknie.de',
|
|
'https://notes.andreknie.de',
|
|
'https://unikat.andreknie.de',
|
|
'https://projekt-kiq.d-hive.de',
|
|
'https://d-hive.de'
|
|
];
|
|
|
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
|
async function checkUrl(url) {
|
|
try {
|
|
const res = await fetch(url, { method: 'GET', redirect: 'follow' });
|
|
if (res.ok) {
|
|
return { url, status: 'UP', code: res.status };
|
|
} else {
|
|
return { url, status: 'DOWN', code: res.status };
|
|
}
|
|
} catch (err) {
|
|
return { url, status: 'DOWN', error: err.message };
|
|
}
|
|
}
|
|
|
|
async function sendAlert(failedUrls) {
|
|
const { SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, STAKEHOLDER_EMAIL } = process.env;
|
|
|
|
if (!SMTP_HOST || !SMTP_USER || !SMTP_PASS || !STAKEHOLDER_EMAIL) {
|
|
console.error('SMTP credentials missing. Cannot send alert email.');
|
|
return;
|
|
}
|
|
|
|
const transporter = nodemailer.createTransport({
|
|
host: SMTP_HOST,
|
|
port: parseInt(SMTP_PORT || '587', 10),
|
|
secure: parseInt(SMTP_PORT || '587', 10) === 465,
|
|
auth: {
|
|
user: SMTP_USER,
|
|
pass: SMTP_PASS,
|
|
},
|
|
});
|
|
|
|
const urlList = failedUrls.map(f => `- ${f.url} (Code: ${f.code || f.error})`).join('\n');
|
|
|
|
try {
|
|
await transporter.sendMail({
|
|
from: `"Smoketest Alert" <${SMTP_USER}>`,
|
|
to: STAKEHOLDER_EMAIL,
|
|
subject: '🚨 Smoketest Alert: Endpoints Down',
|
|
text: `The following endpoints failed after two attempts:\n\n${urlList}`,
|
|
});
|
|
console.log('Alert email sent.');
|
|
} catch (err) {
|
|
console.error('Failed to send alert email:', err);
|
|
}
|
|
}
|
|
|
|
async function generateDashboard(results) {
|
|
const html = `
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Smoketest Dashboard</title>
|
|
<style>
|
|
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background: #0f172a; color: #f8fafc; padding: 2rem; }
|
|
.container { max-width: 800px; margin: 0 auto; background: #1e293b; padding: 2.5rem; border-radius: 12px; box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.3); }
|
|
h1 { margin-top: 0; color: #e2e8f0; font-size: 2rem; border-bottom: 2px solid #334155; padding-bottom: 1rem; }
|
|
.status-up { color: #4ade80; font-weight: bold; background: rgba(74, 222, 128, 0.1); padding: 0.25rem 0.5rem; border-radius: 4px; }
|
|
.status-down { color: #f87171; font-weight: bold; background: rgba(248, 113, 113, 0.1); padding: 0.25rem 0.5rem; border-radius: 4px; }
|
|
table { width: 100%; border-collapse: collapse; margin-top: 2rem; }
|
|
th, td { padding: 1rem; text-align: left; border-bottom: 1px solid #334155; }
|
|
th { background: #0f172a; font-weight: 600; color: #cbd5e1; }
|
|
tr:last-child td { border-bottom: none; }
|
|
a { color: #38bdf8; text-decoration: none; }
|
|
a:hover { text-decoration: underline; }
|
|
.footer { margin-top: 3rem; font-size: 0.875rem; color: #64748b; text-align: center; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>Smoketest Dashboard</h1>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>URL</th>
|
|
<th>Status</th>
|
|
<th>Details</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${results.map(r => `
|
|
<tr>
|
|
<td><a href="${r.url}" target="_blank">${r.url}</a></td>
|
|
<td><span class="${r.status === 'UP' ? 'status-up' : 'status-down'}">${r.status}</span></td>
|
|
<td>${r.status === 'UP' ? `HTTP ${r.code}` : (r.code ? `HTTP ${r.code}` : r.error)}</td>
|
|
</tr>
|
|
`).join('')}
|
|
</tbody>
|
|
</table>
|
|
<div class="footer">
|
|
Last updated: ${new Date().toUTCString()}
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
`;
|
|
|
|
// Write to a separate directory to avoid interfering with Vite's public dir if we run locally
|
|
const outDir = path.join(__dirname, '..', 'public-dashboard');
|
|
if (!fs.existsSync(outDir)) {
|
|
fs.mkdirSync(outDir, { recursive: true });
|
|
}
|
|
|
|
fs.writeFileSync(path.join(outDir, 'index.html'), html);
|
|
console.log('Dashboard generated at public-dashboard/index.html');
|
|
}
|
|
|
|
async function main() {
|
|
console.log('Starting smoketest...');
|
|
|
|
const results = [];
|
|
const failedFirstTry = [];
|
|
|
|
for (const url of urls) {
|
|
const result = await checkUrl(url);
|
|
if (result.status === 'DOWN') {
|
|
console.log(`❌ ${url} is DOWN (First Try)`);
|
|
failedFirstTry.push(result);
|
|
} else {
|
|
console.log(`✅ ${url} is UP`);
|
|
results.push(result);
|
|
}
|
|
}
|
|
|
|
const finalFailures = [];
|
|
|
|
if (failedFirstTry.length > 0) {
|
|
console.log(`\nWaiting 5 minutes before retrying ${failedFirstTry.length} failed endpoints...`);
|
|
// Wait 5 minutes
|
|
await sleep(5 * 60 * 1000);
|
|
|
|
console.log('\nRetrying failed endpoints...');
|
|
for (const f of failedFirstTry) {
|
|
const result = await checkUrl(f.url);
|
|
if (result.status === 'DOWN') {
|
|
console.log(`❌ ${f.url} is DOWN (Second Try)`);
|
|
finalFailures.push(result);
|
|
results.push(result);
|
|
} else {
|
|
console.log(`✅ ${f.url} is UP (Second Try)`);
|
|
results.push(result);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Generate Dashboard
|
|
await generateDashboard(results);
|
|
|
|
// Alert if needed
|
|
if (finalFailures.length > 0) {
|
|
await sendAlert(finalFailures);
|
|
process.exit(1);
|
|
} else {
|
|
console.log('All endpoints are UP.');
|
|
}
|
|
}
|
|
|
|
main().catch(err => {
|
|
console.error('Unexpected error:', err);
|
|
process.exit(1);
|
|
});
|