secubox-openwrt/package/secubox/luci-app-smtp-relay/htdocs/luci-static/resources/view/smtp-relay/settings.js
CyberMind-FR a345c16425 feat(smtp-relay): Add unified SMTP relay configuration
- New secubox-app-smtp-relay package with centralized SMTP config
- Shared library with send_mail(), send_html_mail(), send_text_mail()
- CLI: smtp-relayctl with status/test/send/configure/admin commands
- RPCD: 5 methods for LuCI integration
- LuCI settings page with mode selection and test button
- Modes: external (SMTP server), local (auto-detect mailserver), direct
- Migrated reporter and bandwidth-manager to use shared library
- Backwards-compatible fallback to legacy per-app config

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-15 16:01:12 +01:00

282 lines
8.7 KiB
JavaScript

'use strict';
'require view';
'require form';
'require ui';
'require uci';
'require rpc';
var callGetStatus = rpc.declare({
object: 'luci.smtp-relay',
method: 'get_status',
expect: {}
});
var callGetConfig = rpc.declare({
object: 'luci.smtp-relay',
method: 'get_config',
expect: {}
});
var callTestEmail = rpc.declare({
object: 'luci.smtp-relay',
method: 'test_email',
params: ['recipient'],
expect: {}
});
var callDetectLocal = rpc.declare({
object: 'luci.smtp-relay',
method: 'detect_local',
expect: {}
});
return view.extend({
load: function() {
return Promise.all([
callGetStatus().catch(function() { return {}; }),
callGetConfig().catch(function() { return {}; }),
callDetectLocal().catch(function() { return {}; }),
uci.load('smtp-relay')
]);
},
handleTestEmail: function(ev) {
var recipient = document.getElementById('test_recipient').value;
var btn = ev.target;
var resultDiv = document.getElementById('test_result');
if (!recipient) {
recipient = uci.get('smtp-relay', 'recipients', 'admin') || '';
}
if (!recipient) {
ui.addNotification(null, E('p', _('Please enter a recipient email address')), 'warning');
return;
}
btn.disabled = true;
btn.textContent = _('Sending...');
resultDiv.innerHTML = '';
callTestEmail(recipient).then(function(res) {
btn.disabled = false;
btn.textContent = _('Send Test Email');
if (res.success) {
resultDiv.innerHTML = '<span style="color: #4caf50;">✓ ' + _('Test email sent successfully!') + '</span>';
ui.addNotification(null, E('p', _('Test email sent to %s').format(recipient)), 'info');
} else {
resultDiv.innerHTML = '<span style="color: #f44336;">✗ ' + (res.error || _('Failed to send')) + '</span>';
ui.addNotification(null, E('p', res.error || _('Failed to send test email')), 'error');
}
}).catch(function(err) {
btn.disabled = false;
btn.textContent = _('Send Test Email');
resultDiv.innerHTML = '<span style="color: #f44336;">✗ ' + err.message + '</span>';
});
},
render: function(data) {
var status = data[0] || {};
var config = data[1] || {};
var localDetect = data[2] || {};
var m, s, o;
m = new form.Map('smtp-relay', _('SMTP Relay'),
_('Centralized outbound email configuration for all SecuBox services. Configure once, use everywhere.'));
// Status section
s = m.section(form.NamedSection, 'main', 'smtp_relay', _('Status'));
s.anonymous = true;
o = s.option(form.DummyValue, '_status', _('Current Status'));
o.rawhtml = true;
o.cfgvalue = function() {
var modeText = {
'external': _('External SMTP Server'),
'local': _('Local Mailserver'),
'direct': _('Direct Delivery')
};
var statusHtml = '<div style="display: flex; gap: 20px; flex-wrap: wrap;">';
// Enabled status
statusHtml += '<div><strong>' + _('Relay:') + '</strong> ';
if (status.enabled) {
statusHtml += '<span style="color: #4caf50;">●</span> ' + _('Enabled');
} else {
statusHtml += '<span style="color: #f44336;">●</span> ' + _('Disabled');
}
statusHtml += '</div>';
// Mode
statusHtml += '<div><strong>' + _('Mode:') + '</strong> ' + (modeText[status.mode] || status.mode || '-') + '</div>';
// Server
if (status.server) {
statusHtml += '<div><strong>' + _('Server:') + '</strong> ' + status.server + ':' + status.port + '</div>';
}
// Transport
statusHtml += '<div><strong>' + _('Transport:') + '</strong> ';
if (status.msmtp_available) {
statusHtml += '<span style="color: #4caf50;">msmtp</span>';
} else if (status.sendmail_available) {
statusHtml += '<span style="color: #ff9800;">sendmail</span>';
} else {
statusHtml += '<span style="color: #f44336;">' + _('None') + '</span>';
}
statusHtml += '</div>';
// Local mailserver
if (localDetect.detected) {
statusHtml += '<div><strong>' + _('Local Mail:') + '</strong> ';
if (localDetect.responding) {
statusHtml += '<span style="color: #4caf50;">● ' + _('Available') + '</span>';
} else {
statusHtml += '<span style="color: #ff9800;">● ' + _('Not responding') + '</span>';
}
statusHtml += '</div>';
}
statusHtml += '</div>';
return statusHtml;
};
// Main settings
s = m.section(form.NamedSection, 'main', 'smtp_relay', _('General Settings'));
s.anonymous = true;
o = s.option(form.Flag, 'enabled', _('Enable SMTP Relay'));
o.default = '0';
o.rmempty = false;
o = s.option(form.ListValue, 'mode', _('Relay Mode'));
o.value('external', _('External SMTP Server'));
o.value('local', _('Local Mailserver'));
o.value('direct', _('Direct Delivery (MTA)'));
o.default = 'external';
o.description = _('External: Use Gmail, SendGrid, etc. Local: Use secubox-app-mailserver. Direct: Send directly (requires port 25).');
o = s.option(form.Flag, 'auto_detect', _('Auto-detect Local Mailserver'));
o.description = _('Automatically use local mailserver if available and responding');
o.default = '1';
o.depends('mode', 'external');
// External SMTP section
s = m.section(form.NamedSection, 'external', 'external', _('External SMTP Settings'));
s.anonymous = true;
o = s.option(form.ListValue, '_preset', _('Provider Preset'));
o.value('', _('-- Custom --'));
o.value('gmail', 'Gmail / Google Workspace');
o.value('sendgrid', 'SendGrid');
o.value('mailgun', 'Mailgun');
o.value('ses', 'Amazon SES (us-east-1)');
o.value('mailjet', 'Mailjet');
o.rmempty = true;
o.write = function() {}; // Don't save, just triggers onchange
o = s.option(form.Value, 'server', _('SMTP Server'));
o.placeholder = 'smtp.example.com';
o.rmempty = true;
o = s.option(form.Value, 'port', _('Port'));
o.datatype = 'port';
o.default = '587';
o.placeholder = '587';
o = s.option(form.Flag, 'tls', _('Use STARTTLS'));
o.default = '1';
o.description = _('Use STARTTLS encryption (recommended for port 587)');
o = s.option(form.Flag, 'ssl', _('Use SSL/TLS'));
o.default = '0';
o.description = _('Use implicit SSL/TLS (for port 465)');
o = s.option(form.Flag, 'auth', _('Authentication Required'));
o.default = '1';
o = s.option(form.Value, 'user', _('Username'));
o.depends('auth', '1');
o.rmempty = true;
o = s.option(form.Value, 'password', _('Password'));
o.password = true;
o.depends('auth', '1');
o.rmempty = true;
o = s.option(form.Value, 'from', _('From Address'));
o.placeholder = 'secubox@example.com';
o.datatype = 'email';
o.rmempty = true;
o = s.option(form.Value, 'from_name', _('From Name'));
o.placeholder = 'SecuBox';
o.default = 'SecuBox';
o.rmempty = true;
// Recipients section
s = m.section(form.NamedSection, 'recipients', 'recipients', _('Default Recipients'));
s.anonymous = true;
o = s.option(form.Value, 'admin', _('Admin Email'));
o.datatype = 'email';
o.description = _('Default recipient for system notifications and test emails');
// Test section
s = m.section(form.NamedSection, 'main', 'smtp_relay', _('Connection Test'));
s.anonymous = true;
o = s.option(form.DummyValue, '_test', ' ');
o.rawhtml = true;
o.cfgvalue = function() {
var adminEmail = uci.get('smtp-relay', 'recipients', 'admin') || '';
return E('div', { 'style': 'display: flex; gap: 10px; align-items: center; flex-wrap: wrap;' }, [
E('input', {
'type': 'email',
'id': 'test_recipient',
'placeholder': adminEmail || _('recipient@example.com'),
'value': adminEmail,
'style': 'flex: 1; min-width: 200px; padding: 8px; border: 1px solid #ccc; border-radius: 4px;'
}),
E('button', {
'class': 'cbi-button cbi-button-action',
'click': ui.createHandlerFn(this, function(ev) {
var view = document.querySelector('[data-page="smtp-relay/settings"]');
if (view && view.handleTestEmail) {
view.handleTestEmail(ev);
} else {
// Fallback
var recipient = document.getElementById('test_recipient').value;
var btn = ev.target;
btn.disabled = true;
btn.textContent = _('Sending...');
callTestEmail(recipient).then(function(res) {
btn.disabled = false;
btn.textContent = _('Send Test Email');
var resultDiv = document.getElementById('test_result');
if (res.success) {
resultDiv.innerHTML = '<span style="color: #4caf50;">✓ ' + _('Test email sent!') + '</span>';
} else {
resultDiv.innerHTML = '<span style="color: #f44336;">✗ ' + (res.error || _('Failed')) + '</span>';
}
}).catch(function(err) {
btn.disabled = false;
btn.textContent = _('Send Test Email');
});
}
})
}, _('Send Test Email')),
E('div', { 'id': 'test_result', 'style': 'margin-left: 10px;' })
]);
};
return m.render();
},
handleSave: null,
handleSaveApply: null,
handleReset: null
});