b4fc144cd3
- Make email authentication optional in transporter config - Only add auth object if both username and password are provided - Update initialization to only require SMTP_HOST - Add logging to show whether auth is enabled - Update documentation with no-auth configuration examples - Fix "Unrecognized authentication type" error for open relays Exchange servers configured as internal relays often don't require authentication. This fix allows using them by leaving SMTP_USER and SMTP_PASS empty in the .env file. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
251 lines
7.2 KiB
JavaScript
251 lines
7.2 KiB
JavaScript
const cron = require('node-cron');
|
|
const nodemailer = require('nodemailer');
|
|
const MinIOService = require('./minio.service');
|
|
const config = require('../config');
|
|
const { logger } = require('../utils/logger');
|
|
|
|
class ReportService {
|
|
constructor() {
|
|
this.minioService = new MinIOService();
|
|
this.transporter = null;
|
|
this.scheduledTask = null;
|
|
|
|
if (config.email.host) {
|
|
this.initializeMailer();
|
|
this.setupSchedule();
|
|
} else {
|
|
logger.warn('Email host not configured. Report scheduling disabled.');
|
|
}
|
|
}
|
|
|
|
initializeMailer() {
|
|
const transportConfig = {
|
|
host: config.email.host,
|
|
port: config.email.port,
|
|
secure: config.email.secure,
|
|
// For Exchange Server on port 25 without TLS
|
|
tls: {
|
|
rejectUnauthorized: false, // Accept self-signed certificates
|
|
// If you want to disable TLS completely when secure is false:
|
|
...(config.email.port === 25 && !config.email.secure ? { enabled: false } : {})
|
|
},
|
|
// Disable STARTTLS for port 25 if not using secure
|
|
requireTLS: config.email.secure,
|
|
ignoreTLS: config.email.port === 25 && !config.email.secure,
|
|
};
|
|
|
|
// Only add auth if username and password are provided
|
|
if (config.email.auth.user && config.email.auth.pass) {
|
|
transportConfig.auth = {
|
|
user: config.email.auth.user,
|
|
pass: config.email.auth.pass,
|
|
};
|
|
logger.info('Email configured with authentication');
|
|
} else {
|
|
logger.info('Email configured without authentication (open relay)');
|
|
}
|
|
|
|
this.transporter = nodemailer.createTransport(transportConfig);
|
|
|
|
// Verify connection
|
|
this.transporter.verify((error) => {
|
|
if (error) {
|
|
logger.error('Email transporter verification failed:', error);
|
|
} else {
|
|
logger.info('Email transporter ready');
|
|
}
|
|
});
|
|
}
|
|
|
|
setupSchedule() {
|
|
if (!cron.validate(config.reports.schedule)) {
|
|
logger.error(`Invalid cron expression: ${config.reports.schedule}`);
|
|
return;
|
|
}
|
|
|
|
this.scheduledTask = cron.schedule(config.reports.schedule, async () => {
|
|
logger.info('Running scheduled storage report...');
|
|
try {
|
|
await this.generateAndSendReport();
|
|
} catch (error) {
|
|
logger.error('Scheduled report failed:', error);
|
|
}
|
|
});
|
|
|
|
logger.info(`Report scheduled with cron: ${config.reports.schedule}`);
|
|
}
|
|
|
|
async generateReport() {
|
|
const [bucketSizes, users] = await Promise.all([
|
|
this.minioService.getBucketSizes(),
|
|
this.minioService.listUsers(),
|
|
]);
|
|
|
|
const totalSize = bucketSizes.reduce((sum, b) => sum + b.size, 0);
|
|
const date = new Date().toISOString().split('T')[0];
|
|
|
|
const report = {
|
|
date,
|
|
summary: {
|
|
totalBuckets: bucketSizes.length,
|
|
totalUsers: users.length,
|
|
totalSize,
|
|
totalSizeFormatted: this.minioService.formatBytes(totalSize),
|
|
},
|
|
buckets: bucketSizes,
|
|
users: users.map(u => u.accessKey),
|
|
};
|
|
|
|
return report;
|
|
}
|
|
|
|
formatReportText(report) {
|
|
let text = `MinIO Speicherauswertung\n`;
|
|
text += `Datum: ${report.date}\n`;
|
|
text += `----------------------------------------\n\n`;
|
|
|
|
text += `Zusammenfassung:\n`;
|
|
text += `- Buckets: ${report.summary.totalBuckets}\n`;
|
|
text += `- Benutzer: ${report.summary.totalUsers}\n`;
|
|
text += `- Gesamtspeicher: ${report.summary.totalSizeFormatted}\n\n`;
|
|
|
|
text += `Alle MinIO-User:\n`;
|
|
report.users.forEach(user => {
|
|
text += `- ${user}\n`;
|
|
});
|
|
|
|
text += `\nAlle Buckets:\n`;
|
|
report.buckets.forEach(bucket => {
|
|
text += `\nBucket: ${bucket.name}\n`;
|
|
text += ` Größe: ${bucket.sizeFormatted}\n`;
|
|
text += ` Objekte: ${bucket.objects}\n`;
|
|
text += ` Letzte Änderung: ${bucket.lastModified}\n`;
|
|
});
|
|
|
|
text += `\n----------------------------------------\n`;
|
|
text += `Gesamtspeicher: ${report.summary.totalSizeFormatted}\n`;
|
|
|
|
return text;
|
|
}
|
|
|
|
formatReportHTML(report) {
|
|
let html = `
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<style>
|
|
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
|
|
h1 { color: #2c3e50; }
|
|
.summary { background: #f4f4f4; padding: 15px; border-radius: 5px; margin: 20px 0; }
|
|
table { border-collapse: collapse; width: 100%; margin: 20px 0; }
|
|
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
|
|
th { background-color: #2c3e50; color: white; }
|
|
tr:nth-child(even) { background-color: #f2f2f2; }
|
|
.footer { margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 0.9em; color: #666; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>MinIO Speicherauswertung</h1>
|
|
<p>Datum: ${report.date}</p>
|
|
|
|
<div class="summary">
|
|
<h2>Zusammenfassung</h2>
|
|
<ul>
|
|
<li>Anzahl Buckets: ${report.summary.totalBuckets}</li>
|
|
<li>Anzahl Benutzer: ${report.summary.totalUsers}</li>
|
|
<li>Gesamtspeicher: <strong>${report.summary.totalSizeFormatted}</strong></li>
|
|
</ul>
|
|
</div>
|
|
|
|
<h2>Bucket-Details</h2>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Bucket Name</th>
|
|
<th>Größe</th>
|
|
<th>Objekte</th>
|
|
<th>Letzte Änderung</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${report.buckets.map(bucket => `
|
|
<tr>
|
|
<td>${bucket.name}</td>
|
|
<td>${bucket.sizeFormatted}</td>
|
|
<td>${bucket.objects}</td>
|
|
<td>${bucket.lastModified}</td>
|
|
</tr>
|
|
`).join('')}
|
|
</tbody>
|
|
</table>
|
|
|
|
<h2>Benutzer</h2>
|
|
<ul>
|
|
${report.users.map(user => `<li>${user}</li>`).join('')}
|
|
</ul>
|
|
|
|
<div class="footer">
|
|
<p>Dieser Bericht wurde automatisch von MinIO WebUI generiert.</p>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
`;
|
|
|
|
return html;
|
|
}
|
|
|
|
async sendEmail(report, recipients = null) {
|
|
if (!this.transporter) {
|
|
throw new Error('Email service not configured');
|
|
}
|
|
|
|
const to = recipients || config.email.to;
|
|
if (!to) {
|
|
throw new Error('No recipients configured');
|
|
}
|
|
|
|
const mailOptions = {
|
|
from: config.email.from,
|
|
to: Array.isArray(to) ? to.join(', ') : to,
|
|
subject: `MinIO Speicherauswertung ${report.date}`,
|
|
text: this.formatReportText(report),
|
|
html: this.formatReportHTML(report),
|
|
};
|
|
|
|
const info = await this.transporter.sendMail(mailOptions);
|
|
logger.info(`Report email sent: ${info.messageId}`);
|
|
|
|
return info;
|
|
}
|
|
|
|
async generateAndSendReport(recipients = null) {
|
|
const report = await this.generateReport();
|
|
await this.sendEmail(report, recipients);
|
|
return report;
|
|
}
|
|
|
|
getScheduleInfo() {
|
|
return {
|
|
enabled: !!this.scheduledTask,
|
|
schedule: config.reports.schedule,
|
|
nextRun: this.scheduledTask ? cron.getTasks()[0]?.nextDates(1)[0] : null,
|
|
recipients: config.email.to,
|
|
};
|
|
}
|
|
|
|
stopSchedule() {
|
|
if (this.scheduledTask) {
|
|
this.scheduledTask.stop();
|
|
this.scheduledTask = null;
|
|
logger.info('Report schedule stopped');
|
|
}
|
|
}
|
|
|
|
startSchedule() {
|
|
if (!this.scheduledTask && this.transporter) {
|
|
this.setupSchedule();
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = new ReportService(); |