Files
Orchestrator/privat/CV/andreknie.de/scripts/smoketest.js
ankn b46de6dc7d
Deploy CV Site (andreknie.de) / deploy (push) Canceled after 0s
ci: migrate GitHub actions to Gitea actions
2026-07-22 15:42:06 +02:00

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);
});