secubox-openwrt/package/secubox/luci-app-smtp-relay/htdocs/luci-static/resources/view/smtp-relay/settings.js
CyberMind-FR c74ba2e474 feat(luci): KISS theme rework for SMTP Relay and SecuBox Users
- Rewrite smtp-relay/settings.js with proper KISS theme styling
- Rewrite secubox-users/overview.js with proper KISS theme styling
- Use KissTheme.wrap() for consistent dark theme rendering
- Add stat cards with colored values matching mailserver reference
- Add proper form styling with inline CSS variables
- Add NZB tools (SABnzbd, NZBHydra) to KISS menu Downloads
- Add webtorrent to portal tree Downloads category
- Fix portal tree webtorrent pattern

KISS = Keep It Simple Sexy

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

348 lines
12 KiB
JavaScript

'use strict';
'require view';
'require rpc';
'require ui';
'require poll';
'require uci';
'require secubox/kiss-theme';
var callGetStatus = rpc.declare({
object: 'luci.smtp-relay',
method: 'get_status',
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 {}; }),
uci.load('smtp-relay')
]);
},
render: function(data) {
var self = this;
var status = data[0] || {};
// Start polling
poll.add(function() {
return callGetStatus().then(function(s) {
self.updateStats(s);
});
}, 15);
var modeLabels = {
'external': 'External',
'local': 'Local',
'direct': 'Direct'
};
var content = [
// Header
E('div', { 'style': 'margin-bottom: 24px;' }, [
E('h2', { 'style': 'margin: 0 0 8px 0; display: flex; align-items: center; gap: 12px;' }, [
E('span', {}, '\u2709'),
'SMTP Relay'
]),
E('p', { 'style': 'color: var(--kiss-muted); margin: 0;' },
'Centralized outbound email for all SecuBox services')
]),
// Stats Grid
E('div', { 'class': 'kiss-grid kiss-grid-4', 'style': 'margin-bottom: 20px;' }, [
this.statCard('Status', status.enabled ? 'Active' : 'Disabled',
status.enabled ? 'var(--kiss-green)' : 'var(--kiss-red)', 'status'),
this.statCard('Mode', modeLabels[status.mode] || 'N/A',
status.mode === 'local' ? 'var(--kiss-green)' : 'var(--kiss-cyan)', 'mode'),
this.statCard('Server', status.server || 'None', 'var(--kiss-purple)', 'server'),
this.statCard('TLS', status.tls ? 'Enabled' : 'Off',
status.tls ? 'var(--kiss-green)' : 'var(--kiss-yellow)', 'tls')
]),
// Quick Test Card
E('div', { 'class': 'kiss-card' }, [
E('div', { 'class': 'kiss-card-title' }, ['\u26a1 Quick Actions']),
E('div', { 'style': 'display: flex; gap: 12px; flex-wrap: wrap; align-items: center;' }, [
E('button', {
'class': 'kiss-btn kiss-btn-blue',
'click': ui.createHandlerFn(this, this.doDetectLocal)
}, '\ud83d\udd0d Detect Local'),
E('input', {
'type': 'email',
'id': 'test_recipient',
'placeholder': 'test@example.com',
'value': status.admin_recipient || '',
'style': 'flex: 1; min-width: 200px; padding: 8px 12px; border: 1px solid var(--kiss-border); border-radius: 6px; background: var(--kiss-bg2); color: var(--kiss-text);'
}),
E('button', {
'id': 'btn-test',
'class': 'kiss-btn kiss-btn-green',
'click': ui.createHandlerFn(this, this.doTestEmail)
}, '\u2709 Send Test'),
E('span', { 'id': 'test_result' }, '')
])
]),
// Configuration Card
E('div', { 'class': 'kiss-card' }, [
E('div', { 'class': 'kiss-card-title' }, ['\u2699 Configuration']),
E('div', { 'style': 'display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;' }, [
// Mode
E('div', {}, [
E('div', { 'style': 'color: var(--kiss-muted); font-size: 11px; text-transform: uppercase; margin-bottom: 6px;' }, 'Relay Mode'),
E('select', {
'id': 'cfg_mode',
'style': 'width: 100%; padding: 8px 12px; border: 1px solid var(--kiss-border); border-radius: 6px; background: var(--kiss-bg2); color: var(--kiss-text);'
}, [
E('option', { 'value': 'external', 'selected': status.mode === 'external' }, 'External SMTP'),
E('option', { 'value': 'local', 'selected': status.mode === 'local' }, 'Local Mailserver'),
E('option', { 'value': 'direct', 'selected': status.mode === 'direct' }, 'Direct Delivery')
])
]),
// Server
E('div', {}, [
E('div', { 'style': 'color: var(--kiss-muted); font-size: 11px; text-transform: uppercase; margin-bottom: 6px;' }, 'SMTP Server'),
E('input', {
'type': 'text',
'id': 'cfg_server',
'placeholder': 'smtp.gmail.com',
'value': status.server || '',
'style': 'width: 100%; padding: 8px 12px; border: 1px solid var(--kiss-border); border-radius: 6px; background: var(--kiss-bg2); color: var(--kiss-text);'
})
]),
// Port
E('div', {}, [
E('div', { 'style': 'color: var(--kiss-muted); font-size: 11px; text-transform: uppercase; margin-bottom: 6px;' }, 'Port'),
E('input', {
'type': 'number',
'id': 'cfg_port',
'placeholder': '587',
'value': status.port || 587,
'style': 'width: 100%; padding: 8px 12px; border: 1px solid var(--kiss-border); border-radius: 6px; background: var(--kiss-bg2); color: var(--kiss-text);'
})
]),
// TLS
E('div', {}, [
E('div', { 'style': 'color: var(--kiss-muted); font-size: 11px; text-transform: uppercase; margin-bottom: 6px;' }, 'Use TLS'),
E('div', { 'style': 'display: flex; align-items: center; gap: 8px;' }, [
E('input', {
'type': 'checkbox',
'id': 'cfg_tls',
'checked': status.tls,
'style': 'width: 18px; height: 18px;'
}),
E('span', { 'style': 'color: var(--kiss-muted);' }, 'Enable STARTTLS')
])
]),
// Username
E('div', {}, [
E('div', { 'style': 'color: var(--kiss-muted); font-size: 11px; text-transform: uppercase; margin-bottom: 6px;' }, 'Username'),
E('input', {
'type': 'text',
'id': 'cfg_user',
'placeholder': 'user@gmail.com',
'value': uci.get('smtp-relay', 'main', 'username') || '',
'style': 'width: 100%; padding: 8px 12px; border: 1px solid var(--kiss-border); border-radius: 6px; background: var(--kiss-bg2); color: var(--kiss-text);'
})
]),
// Password
E('div', {}, [
E('div', { 'style': 'color: var(--kiss-muted); font-size: 11px; text-transform: uppercase; margin-bottom: 6px;' }, 'Password'),
E('input', {
'type': 'password',
'id': 'cfg_pass',
'placeholder': '\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022',
'value': '',
'style': 'width: 100%; padding: 8px 12px; border: 1px solid var(--kiss-border); border-radius: 6px; background: var(--kiss-bg2); color: var(--kiss-text);'
})
]),
// From Address
E('div', {}, [
E('div', { 'style': 'color: var(--kiss-muted); font-size: 11px; text-transform: uppercase; margin-bottom: 6px;' }, 'From Address'),
E('input', {
'type': 'email',
'id': 'cfg_from',
'placeholder': 'noreply@secubox.in',
'value': status.from || '',
'style': 'width: 100%; padding: 8px 12px; border: 1px solid var(--kiss-border); border-radius: 6px; background: var(--kiss-bg2); color: var(--kiss-text);'
})
]),
// Admin Recipient
E('div', {}, [
E('div', { 'style': 'color: var(--kiss-muted); font-size: 11px; text-transform: uppercase; margin-bottom: 6px;' }, 'Admin Email'),
E('input', {
'type': 'email',
'id': 'cfg_admin',
'placeholder': 'admin@example.com',
'value': status.admin_recipient || '',
'style': 'width: 100%; padding: 8px 12px; border: 1px solid var(--kiss-border); border-radius: 6px; background: var(--kiss-bg2); color: var(--kiss-text);'
})
])
]),
E('div', { 'style': 'margin-top: 20px;' }, [
E('button', {
'class': 'kiss-btn kiss-btn-green',
'click': ui.createHandlerFn(this, this.doSaveConfig)
}, '\u2714 Save Configuration')
])
]),
// Quick Presets Card
E('div', { 'class': 'kiss-card kiss-panel-blue' }, [
E('div', { 'class': 'kiss-card-title' }, ['\ud83d\udce7 Provider Presets']),
E('div', { 'style': 'display: flex; gap: 12px; flex-wrap: wrap;' }, [
this.presetButton('Gmail', 'smtp.gmail.com', 587, true),
this.presetButton('SendGrid', 'smtp.sendgrid.net', 587, true),
this.presetButton('Mailgun', 'smtp.mailgun.org', 587, true),
this.presetButton('AWS SES', 'email-smtp.us-east-1.amazonaws.com', 587, true),
this.presetButton('Local', '192.168.255.30', 25, false)
]),
E('p', { 'style': 'margin-top: 12px; color: var(--kiss-muted); font-size: 12px;' },
'Click a preset to auto-fill server settings.')
])
];
return KissTheme.wrap(content, 'admin/secubox/system/smtp-relay');
},
statCard: function(label, value, color, dataAttr) {
return E('div', { 'class': 'kiss-stat' }, [
E('div', {
'class': 'kiss-stat-value',
'style': 'color: ' + color,
'data-stat': dataAttr
}, String(value)),
E('div', { 'class': 'kiss-stat-label' }, label)
]);
},
updateStats: function(status) {
var statusEl = document.querySelector('[data-stat="status"]');
var modeEl = document.querySelector('[data-stat="mode"]');
var serverEl = document.querySelector('[data-stat="server"]');
var tlsEl = document.querySelector('[data-stat="tls"]');
if (statusEl) {
statusEl.textContent = status.enabled ? 'Active' : 'Disabled';
statusEl.style.color = status.enabled ? 'var(--kiss-green)' : 'var(--kiss-red)';
}
if (modeEl) {
var labels = { 'external': 'External', 'local': 'Local', 'direct': 'Direct' };
modeEl.textContent = labels[status.mode] || 'N/A';
}
if (serverEl) {
serverEl.textContent = status.server || 'None';
}
if (tlsEl) {
tlsEl.textContent = status.tls ? 'Enabled' : 'Off';
tlsEl.style.color = status.tls ? 'var(--kiss-green)' : 'var(--kiss-yellow)';
}
},
presetButton: function(name, server, port, tls) {
return E('button', {
'class': 'kiss-btn',
'click': function() {
document.getElementById('cfg_server').value = server;
document.getElementById('cfg_port').value = port;
document.getElementById('cfg_tls').checked = tls;
document.getElementById('cfg_mode').value = name === 'Local' ? 'local' : 'external';
}
}, name);
},
doDetectLocal: function() {
ui.showModal(_('Detecting Local Mailserver'), [
E('p', { 'class': 'spinning' }, _('Scanning...'))
]);
callDetectLocal().then(function(res) {
ui.hideModal();
if (res.detected) {
document.getElementById('cfg_server').value = res.server || '192.168.255.30';
document.getElementById('cfg_port').value = res.port || 25;
document.getElementById('cfg_mode').value = 'local';
ui.addNotification(null, E('p', _('Local mailserver detected')), 'info');
} else {
ui.addNotification(null, E('p', _('No local mailserver found')), 'warning');
}
});
},
doTestEmail: function() {
var recipient = document.getElementById('test_recipient').value;
var btn = document.getElementById('btn-test');
var result = document.getElementById('test_result');
if (!recipient) {
ui.addNotification(null, E('p', _('Enter a recipient email')), 'warning');
return;
}
btn.disabled = true;
btn.textContent = 'Sending...';
result.innerHTML = '';
callTestEmail(recipient).then(function(res) {
btn.disabled = false;
btn.textContent = '\u2709 Send Test';
if (res.success) {
result.innerHTML = '<span class="kiss-badge kiss-badge-green">Sent</span>';
} else {
result.innerHTML = '<span class="kiss-badge kiss-badge-red">Failed</span>';
ui.addNotification(null, E('p', res.error || 'Send failed'), 'error');
}
}).catch(function(err) {
btn.disabled = false;
btn.textContent = '\u2709 Send Test';
result.innerHTML = '<span class="kiss-badge kiss-badge-red">Error</span>';
});
},
doSaveConfig: function() {
var mode = document.getElementById('cfg_mode').value;
var server = document.getElementById('cfg_server').value;
var port = document.getElementById('cfg_port').value;
var user = document.getElementById('cfg_user').value;
var pass = document.getElementById('cfg_pass').value;
var tls = document.getElementById('cfg_tls').checked;
var from = document.getElementById('cfg_from').value;
var admin = document.getElementById('cfg_admin').value;
uci.set('smtp-relay', 'main', 'mode', mode);
uci.set('smtp-relay', 'main', 'server', server);
uci.set('smtp-relay', 'main', 'port', port);
if (user) uci.set('smtp-relay', 'main', 'username', user);
if (pass) uci.set('smtp-relay', 'main', 'password', pass);
uci.set('smtp-relay', 'main', 'tls', tls ? '1' : '0');
if (from) uci.set('smtp-relay', 'main', 'from', from);
uci.set('smtp-relay', 'recipients', 'admin', admin);
uci.save().then(function() {
return uci.apply();
}).then(function() {
ui.addNotification(null, E('p', _('Configuration saved')), 'info');
}).catch(function(err) {
ui.addNotification(null, E('p', err.message), 'error');
});
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});