secubox-openwrt/package/secubox/luci-app-mailserver/htdocs/luci-static/resources/view/mailserver/overview.js
CyberMind-FR ded107e408 feat(luci-app-mailserver): Add unified mail server dashboard
All-in-one LuCI interface for:
- Mail server status and control
- User/alias management with modals
- Port status monitoring
- DNS/SSL setup actions
- Webmail configuration
- Mesh backup integration

RPCD handler with 17 methods for full mail management.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 10:43:55 +01:00

481 lines
13 KiB
JavaScript

'use strict';
'require view';
'require rpc';
'require ui';
'require form';
'require uci';
var callStatus = rpc.declare({
object: 'luci.mailserver',
method: 'status',
expect: {}
});
var callUserList = rpc.declare({
object: 'luci.mailserver',
method: 'user_list',
expect: {}
});
var callAliasList = rpc.declare({
object: 'luci.mailserver',
method: 'alias_list',
expect: {}
});
var callWebmailStatus = rpc.declare({
object: 'luci.mailserver',
method: 'webmail_status',
expect: {}
});
var callInstall = rpc.declare({
object: 'luci.mailserver',
method: 'install',
expect: {}
});
var callStart = rpc.declare({
object: 'luci.mailserver',
method: 'start',
expect: {}
});
var callStop = rpc.declare({
object: 'luci.mailserver',
method: 'stop',
expect: {}
});
var callUserAdd = rpc.declare({
object: 'luci.mailserver',
method: 'user_add',
params: ['email', 'password'],
expect: {}
});
var callUserDel = rpc.declare({
object: 'luci.mailserver',
method: 'user_del',
params: ['email'],
expect: {}
});
var callAliasAdd = rpc.declare({
object: 'luci.mailserver',
method: 'alias_add',
params: ['alias', 'target'],
expect: {}
});
var callDnsSetup = rpc.declare({
object: 'luci.mailserver',
method: 'dns_setup',
expect: {}
});
var callSslSetup = rpc.declare({
object: 'luci.mailserver',
method: 'ssl_setup',
expect: {}
});
var callWebmailConfigure = rpc.declare({
object: 'luci.mailserver',
method: 'webmail_configure',
expect: {}
});
var callMeshBackup = rpc.declare({
object: 'luci.mailserver',
method: 'mesh_backup',
expect: {}
});
return view.extend({
load: function() {
return Promise.all([
callStatus(),
callUserList(),
callAliasList(),
callWebmailStatus()
]);
},
render: function(data) {
var status = data[0] || {};
var users = (data[1] || {}).users || [];
var aliases = (data[2] || {}).aliases || [];
var webmail = data[3] || {};
var view = E('div', { 'class': 'cbi-map' }, [
E('h2', {}, 'Mail Server'),
// Status Section
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, 'Server Status'),
E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td', 'style': 'width:150px' }, 'Status'),
E('td', { 'class': 'td' }, [
E('span', { 'style': status.state === 'running' ? 'color:green' : 'color:red' },
status.state === 'running' ? '● Running' : '○ Stopped')
])
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, 'Domain'),
E('td', { 'class': 'td' }, status.fqdn || 'Not configured')
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, 'Users'),
E('td', { 'class': 'td' }, String(status.users || 0))
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, 'Storage'),
E('td', { 'class': 'td' }, status.storage || '0')
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, 'SSL'),
E('td', { 'class': 'td' }, status.ssl_valid ? '✓ Valid' : '✗ Not configured')
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, 'Webmail'),
E('td', { 'class': 'td' }, webmail.running ? '● Running (port ' + webmail.port + ')' : '○ Stopped')
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, 'Mesh Backup'),
E('td', { 'class': 'td' }, status.mesh_enabled ? '● Enabled' : '○ Disabled')
])
]),
// Port Status
E('h4', { 'style': 'margin-top:15px' }, 'Ports'),
this.renderPortStatus(status.ports || {})
]),
// Quick Actions
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, 'Quick Actions'),
E('div', { 'style': 'display:flex;gap:10px;flex-wrap:wrap' }, [
status.state !== 'running' ?
E('button', {
'class': 'btn cbi-button-action',
'click': ui.createHandlerFn(this, this.doStart)
}, 'Start Server') :
E('button', {
'class': 'btn cbi-button-neutral',
'click': ui.createHandlerFn(this, this.doStop)
}, 'Stop Server'),
E('button', {
'class': 'btn cbi-button-neutral',
'click': ui.createHandlerFn(this, this.doDnsSetup)
}, 'Setup DNS'),
E('button', {
'class': 'btn cbi-button-neutral',
'click': ui.createHandlerFn(this, this.doSslSetup)
}, 'Setup SSL'),
E('button', {
'class': 'btn cbi-button-neutral',
'click': ui.createHandlerFn(this, this.doWebmailConfigure)
}, 'Configure Webmail'),
E('button', {
'class': 'btn cbi-button-neutral',
'click': ui.createHandlerFn(this, this.doMeshBackup)
}, 'Mesh Backup')
])
]),
// Users Section
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, 'Mail Users'),
E('div', { 'style': 'margin-bottom:10px' }, [
E('button', {
'class': 'btn cbi-button-add',
'click': ui.createHandlerFn(this, this.showAddUserModal)
}, 'Add User')
]),
this.renderUserTable(users)
]),
// Aliases Section
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, 'Email Aliases'),
E('div', { 'style': 'margin-bottom:10px' }, [
E('button', {
'class': 'btn cbi-button-add',
'click': ui.createHandlerFn(this, this.showAddAliasModal)
}, 'Add Alias')
]),
this.renderAliasTable(aliases)
])
]);
return view;
},
renderPortStatus: function(ports) {
var portList = [
{ port: '25', name: 'SMTP' },
{ port: '587', name: 'Submission' },
{ port: '465', name: 'SMTPS' },
{ port: '993', name: 'IMAPS' },
{ port: '995', name: 'POP3S' }
];
return E('div', { 'style': 'display:flex;gap:15px;flex-wrap:wrap' },
portList.map(function(p) {
var isOpen = ports[p.port];
return E('span', {
'style': 'padding:5px 10px;border-radius:4px;background:' + (isOpen ? '#d4edda' : '#f8d7da')
}, p.name + ' (' + p.port + '): ' + (isOpen ? '✓' : '✗'));
})
);
},
renderUserTable: function(users) {
if (!users || users.length === 0) {
return E('p', { 'class': 'cbi-value-description' }, 'No mail users configured.');
}
var rows = users.map(L.bind(function(u) {
return E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, u.email),
E('td', { 'class': 'td' }, u.size || '0'),
E('td', { 'class': 'td' }, String(u.messages || 0)),
E('td', { 'class': 'td' }, [
E('button', {
'class': 'btn cbi-button-remove',
'style': 'padding:2px 8px;font-size:12px',
'click': ui.createHandlerFn(this, this.doDeleteUser, u.email)
}, 'Delete')
])
]);
}, this));
return E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, 'Email'),
E('th', { 'class': 'th' }, 'Size'),
E('th', { 'class': 'th' }, 'Messages'),
E('th', { 'class': 'th' }, 'Actions')
])
].concat(rows));
},
renderAliasTable: function(aliases) {
if (!aliases || aliases.length === 0) {
return E('p', { 'class': 'cbi-value-description' }, 'No aliases configured.');
}
var rows = aliases.map(function(a) {
return E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, a.alias),
E('td', { 'class': 'td' }, '→'),
E('td', { 'class': 'td' }, a.target)
]);
});
return E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, 'Alias'),
E('th', { 'class': 'th' }, ''),
E('th', { 'class': 'th' }, 'Target')
])
].concat(rows));
},
showAddUserModal: function() {
var emailInput, passwordInput;
ui.showModal('Add Mail User', [
E('p', {}, 'Enter email address and password for the new user.'),
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title' }, 'Email'),
E('div', { 'class': 'cbi-value-field' }, [
emailInput = E('input', { 'type': 'email', 'class': 'cbi-input-text', 'placeholder': 'user@domain.com' })
])
]),
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title' }, 'Password'),
E('div', { 'class': 'cbi-value-field' }, [
passwordInput = E('input', { 'type': 'password', 'class': 'cbi-input-text' })
])
]),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn',
'click': ui.hideModal
}, 'Cancel'),
' ',
E('button', {
'class': 'btn cbi-button-action',
'click': ui.createHandlerFn(this, function() {
var email = emailInput.value;
var password = passwordInput.value;
if (!email || !password) {
ui.addNotification(null, E('p', 'Email and password required'), 'error');
return;
}
ui.hideModal();
return this.doAddUser(email, password);
})
}, 'Add User')
])
]);
},
showAddAliasModal: function() {
var aliasInput, targetInput;
ui.showModal('Add Email Alias', [
E('p', {}, 'Create an alias that forwards to another address.'),
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title' }, 'Alias'),
E('div', { 'class': 'cbi-value-field' }, [
aliasInput = E('input', { 'type': 'email', 'class': 'cbi-input-text', 'placeholder': 'info@domain.com' })
])
]),
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title' }, 'Forward To'),
E('div', { 'class': 'cbi-value-field' }, [
targetInput = E('input', { 'type': 'email', 'class': 'cbi-input-text', 'placeholder': 'user@domain.com' })
])
]),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn',
'click': ui.hideModal
}, 'Cancel'),
' ',
E('button', {
'class': 'btn cbi-button-action',
'click': ui.createHandlerFn(this, function() {
var alias = aliasInput.value;
var target = targetInput.value;
if (!alias || !target) {
ui.addNotification(null, E('p', 'Alias and target required'), 'error');
return;
}
ui.hideModal();
return this.doAddAlias(alias, target);
})
}, 'Add Alias')
])
]);
},
doStart: function() {
ui.showModal('Starting Server', [
E('p', { 'class': 'spinning' }, 'Starting mail server...')
]);
return callStart().then(function() {
ui.hideModal();
window.location.reload();
});
},
doStop: function() {
ui.showModal('Stopping Server', [
E('p', { 'class': 'spinning' }, 'Stopping mail server...')
]);
return callStop().then(function() {
ui.hideModal();
window.location.reload();
});
},
doAddUser: function(email, password) {
ui.showModal('Adding User', [
E('p', { 'class': 'spinning' }, 'Adding user: ' + email)
]);
return callUserAdd(email, password).then(function(res) {
ui.hideModal();
if (res.code === 0) {
ui.addNotification(null, E('p', 'User added: ' + email), 'success');
} else {
ui.addNotification(null, E('p', 'Failed: ' + res.output), 'error');
}
window.location.reload();
});
},
doDeleteUser: function(email) {
if (!confirm('Delete user ' + email + '?')) return;
ui.showModal('Deleting User', [
E('p', { 'class': 'spinning' }, 'Deleting user: ' + email)
]);
return callUserDel(email).then(function() {
ui.hideModal();
window.location.reload();
});
},
doAddAlias: function(alias, target) {
ui.showModal('Adding Alias', [
E('p', { 'class': 'spinning' }, 'Adding alias: ' + alias)
]);
return callAliasAdd(alias, target).then(function() {
ui.hideModal();
window.location.reload();
});
},
doDnsSetup: function() {
ui.showModal('DNS Setup', [
E('p', { 'class': 'spinning' }, 'Creating MX, SPF, DMARC records...')
]);
return callDnsSetup().then(function(res) {
ui.hideModal();
if (res.code === 0) {
ui.addNotification(null, E('p', 'DNS records created'), 'success');
} else {
ui.addNotification(null, E('p', res.output), 'warning');
}
});
},
doSslSetup: function() {
ui.showModal('SSL Setup', [
E('p', { 'class': 'spinning' }, 'Obtaining SSL certificate via DNS-01...')
]);
return callSslSetup().then(function(res) {
ui.hideModal();
if (res.code === 0) {
ui.addNotification(null, E('p', 'SSL certificate installed'), 'success');
} else {
ui.addNotification(null, E('p', res.output), 'error');
}
window.location.reload();
});
},
doWebmailConfigure: function() {
ui.showModal('Configuring Webmail', [
E('p', { 'class': 'spinning' }, 'Configuring Roundcube...')
]);
return callWebmailConfigure().then(function(res) {
ui.hideModal();
ui.addNotification(null, E('p', 'Webmail configured. Restart webmail container to apply.'), 'success');
});
},
doMeshBackup: function() {
ui.showModal('Mesh Backup', [
E('p', { 'class': 'spinning' }, 'Creating backup for mesh sync...')
]);
return callMeshBackup().then(function(res) {
ui.hideModal();
if (res.code === 0) {
ui.addNotification(null, E('p', 'Backup created'), 'success');
} else {
ui.addNotification(null, E('p', res.output), 'error');
}
});
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});