Matrix Homeserver (Conduit): - E2EE mesh messaging using Conduit v0.10.12 in LXC container - matrixctl CLI: install/uninstall, user/room management, federation - luci-app-matrix: status cards, user form, emancipate, mesh publish - RPCD backend with 17 methods - Identity (DID) integration and P2P mesh publication SaaS Relay CDN Caching & Session Replay: - CDN cache profiles: minimal, gandalf (default), aggressive - Session replay modes: shared, per_user, master - saasctl cache/session commands for management - Enhanced mitmproxy addon (415 lines) with response caching Media Services Hub Dashboard: - Unified dashboard at /admin/services/media-hub - Category-organized cards (streaming, conferencing, apps, etc.) - Service status indicators with start/stop/restart controls - RPCD backend querying 8 media services Also includes: - HexoJS static upload workflow and multi-user auth - Jitsi config.js Promise handling fix - Feed package updates Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
285 lines
9.5 KiB
JavaScript
285 lines
9.5 KiB
JavaScript
'use strict';
|
|
'require view';
|
|
'require dom';
|
|
'require ui';
|
|
'require rpc';
|
|
|
|
var callStatus = rpc.declare({
|
|
object: 'luci.secubox-users',
|
|
method: 'status',
|
|
expect: { }
|
|
});
|
|
|
|
var callUsers = rpc.declare({
|
|
object: 'luci.secubox-users',
|
|
method: 'users',
|
|
expect: { }
|
|
});
|
|
|
|
var callAddUser = rpc.declare({
|
|
object: 'luci.secubox-users',
|
|
method: 'add',
|
|
params: ['username', 'password', 'services'],
|
|
expect: { }
|
|
});
|
|
|
|
var callDeleteUser = rpc.declare({
|
|
object: 'luci.secubox-users',
|
|
method: 'delete',
|
|
params: ['username'],
|
|
expect: { }
|
|
});
|
|
|
|
var callPasswd = rpc.declare({
|
|
object: 'luci.secubox-users',
|
|
method: 'passwd',
|
|
params: ['username', 'password'],
|
|
expect: { }
|
|
});
|
|
|
|
return view.extend({
|
|
load: function() {
|
|
return Promise.all([
|
|
callStatus(),
|
|
callUsers()
|
|
]);
|
|
},
|
|
|
|
renderServiceBadge: function(name, running) {
|
|
var color = running ? '#4CAF50' : '#9e9e9e';
|
|
return E('span', {
|
|
'style': 'display:inline-block;padding:2px 8px;margin:2px;border-radius:3px;color:#fff;background:' + color + ';font-size:0.85em;'
|
|
}, name);
|
|
},
|
|
|
|
renderUserRow: function(user) {
|
|
var self = this;
|
|
var services = (user.services || []).map(function(s) {
|
|
return E('span', {
|
|
'style': 'display:inline-block;padding:1px 6px;margin:1px;border-radius:3px;background:#e3f2fd;font-size:0.8em;'
|
|
}, s);
|
|
});
|
|
|
|
return E('tr', {}, [
|
|
E('td', {}, user.username),
|
|
E('td', {}, user.email),
|
|
E('td', {}, services),
|
|
E('td', {}, user.enabled === '1' ? 'Yes' : 'No'),
|
|
E('td', {}, [
|
|
E('button', {
|
|
'class': 'btn cbi-button',
|
|
'click': function() { self.handlePasswd(user.username); },
|
|
'style': 'margin-right:5px;'
|
|
}, _('Password')),
|
|
E('button', {
|
|
'class': 'btn cbi-button cbi-button-remove',
|
|
'click': function() { self.handleDelete(user.username); }
|
|
}, _('Delete'))
|
|
])
|
|
]);
|
|
},
|
|
|
|
handleAdd: function() {
|
|
var self = this;
|
|
|
|
ui.showModal(_('Add User'), [
|
|
E('div', { 'class': 'cbi-section' }, [
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('Username')),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
E('input', { 'type': 'text', 'id': 'new-username', 'style': 'width:200px;' })
|
|
])
|
|
]),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('Password')),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
E('input', { 'type': 'password', 'id': 'new-password', 'placeholder': _('Leave empty to generate'), 'style': 'width:200px;' })
|
|
])
|
|
]),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('Services')),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
E('label', {}, [E('input', { 'type': 'checkbox', 'id': 'svc-nextcloud', 'checked': true }), ' Nextcloud']),
|
|
E('label', { 'style': 'margin-left:10px;' }, [E('input', { 'type': 'checkbox', 'id': 'svc-peertube', 'checked': true }), ' PeerTube']),
|
|
E('label', { 'style': 'margin-left:10px;' }, [E('input', { 'type': 'checkbox', 'id': 'svc-jabber', 'checked': true }), ' Jabber']),
|
|
E('label', { 'style': 'margin-left:10px;' }, [E('input', { 'type': 'checkbox', 'id': 'svc-matrix', 'checked': true }), ' Matrix']),
|
|
E('label', { 'style': 'margin-left:10px;' }, [E('input', { 'type': 'checkbox', 'id': 'svc-email', 'checked': true }), ' Email'])
|
|
])
|
|
])
|
|
]),
|
|
E('div', { 'class': 'right' }, [
|
|
E('button', {
|
|
'class': 'btn',
|
|
'click': ui.hideModal
|
|
}, _('Cancel')),
|
|
E('button', {
|
|
'class': 'btn cbi-button-positive',
|
|
'click': function() {
|
|
var username = document.getElementById('new-username').value;
|
|
var password = document.getElementById('new-password').value;
|
|
var services = [];
|
|
if (document.getElementById('svc-nextcloud').checked) services.push('nextcloud');
|
|
if (document.getElementById('svc-peertube').checked) services.push('peertube');
|
|
if (document.getElementById('svc-jabber').checked) services.push('jabber');
|
|
if (document.getElementById('svc-matrix').checked) services.push('matrix');
|
|
if (document.getElementById('svc-email').checked) services.push('email');
|
|
|
|
if (!username) {
|
|
ui.addNotification(null, E('p', {}, _('Username required')), 'error');
|
|
return;
|
|
}
|
|
|
|
ui.hideModal();
|
|
ui.showModal(_('Creating User...'), [
|
|
E('p', { 'class': 'spinning' }, _('Please wait...'))
|
|
]);
|
|
|
|
callAddUser(username, password, services.join(',')).then(function(res) {
|
|
ui.hideModal();
|
|
if (res && res.success) {
|
|
ui.showModal(_('User Created'), [
|
|
E('div', { 'style': 'padding:20px;' }, [
|
|
E('p', {}, _('User created successfully!')),
|
|
E('table', { 'style': 'margin:15px 0;' }, [
|
|
E('tr', {}, [E('td', { 'style': 'padding:5px;font-weight:bold;' }, _('Username:')), E('td', { 'style': 'padding:5px;' }, res.username)]),
|
|
E('tr', {}, [E('td', { 'style': 'padding:5px;font-weight:bold;' }, _('Password:')), E('td', { 'style': 'padding:5px;font-family:monospace;background:#f5f5f5;' }, res.password)]),
|
|
E('tr', {}, [E('td', { 'style': 'padding:5px;font-weight:bold;' }, _('Email:')), E('td', { 'style': 'padding:5px;' }, res.email)])
|
|
])
|
|
]),
|
|
E('div', { 'class': 'right' }, [
|
|
E('button', { 'class': 'btn cbi-button-positive', 'click': function() { ui.hideModal(); location.reload(); } }, _('OK'))
|
|
])
|
|
]);
|
|
} else {
|
|
ui.addNotification(null, E('p', {}, _('Error: ') + (res.error || 'Unknown error')), 'error');
|
|
}
|
|
});
|
|
},
|
|
'style': 'margin-left:10px;'
|
|
}, _('Create User'))
|
|
])
|
|
]);
|
|
},
|
|
|
|
handleDelete: function(username) {
|
|
var self = this;
|
|
|
|
if (!confirm(_('Delete user "%s" from all services?').format(username))) {
|
|
return;
|
|
}
|
|
|
|
ui.showModal(_('Deleting...'), [
|
|
E('p', { 'class': 'spinning' }, _('Please wait...'))
|
|
]);
|
|
|
|
callDeleteUser(username).then(function(res) {
|
|
ui.hideModal();
|
|
if (res && res.success) {
|
|
ui.addNotification(null, E('p', {}, _('User deleted')), 'success');
|
|
location.reload();
|
|
} else {
|
|
ui.addNotification(null, E('p', {}, _('Error: ') + (res.error || 'Unknown error')), 'error');
|
|
}
|
|
});
|
|
},
|
|
|
|
handlePasswd: function(username) {
|
|
var self = this;
|
|
|
|
ui.showModal(_('Change Password'), [
|
|
E('div', { 'class': 'cbi-section' }, [
|
|
E('p', {}, _('Change password for: %s').format(username)),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('New Password')),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
E('input', { 'type': 'password', 'id': 'new-passwd', 'placeholder': _('Leave empty to generate'), 'style': 'width:200px;' })
|
|
])
|
|
])
|
|
]),
|
|
E('div', { 'class': 'right' }, [
|
|
E('button', { 'class': 'btn', 'click': ui.hideModal }, _('Cancel')),
|
|
E('button', {
|
|
'class': 'btn cbi-button-positive',
|
|
'click': function() {
|
|
var password = document.getElementById('new-passwd').value;
|
|
ui.hideModal();
|
|
ui.showModal(_('Updating...'), [
|
|
E('p', { 'class': 'spinning' }, _('Please wait...'))
|
|
]);
|
|
|
|
callPasswd(username, password).then(function(res) {
|
|
ui.hideModal();
|
|
if (res && res.success) {
|
|
ui.showModal(_('Password Updated'), [
|
|
E('div', { 'style': 'padding:20px;' }, [
|
|
E('p', {}, _('Password updated for all services!')),
|
|
E('p', { 'style': 'font-family:monospace;background:#f5f5f5;padding:10px;margin:10px 0;' }, res.password)
|
|
]),
|
|
E('div', { 'class': 'right' }, [
|
|
E('button', { 'class': 'btn cbi-button-positive', 'click': ui.hideModal }, _('OK'))
|
|
])
|
|
]);
|
|
} else {
|
|
ui.addNotification(null, E('p', {}, _('Error updating password')), 'error');
|
|
}
|
|
});
|
|
},
|
|
'style': 'margin-left:10px;'
|
|
}, _('Update'))
|
|
])
|
|
]);
|
|
},
|
|
|
|
render: function(data) {
|
|
var self = this;
|
|
var status = data[0] || {};
|
|
var usersData = data[1] || {};
|
|
var users = usersData.users || [];
|
|
var services = status.services || {};
|
|
|
|
var content = [];
|
|
|
|
// Header
|
|
content.push(E('h2', {}, _('SecuBox User Management')));
|
|
|
|
// Status Section
|
|
content.push(E('div', { 'class': 'cbi-section' }, [
|
|
E('h3', {}, _('Services')),
|
|
E('div', { 'style': 'margin:10px 0;' }, [
|
|
this.renderServiceBadge('Nextcloud', services.nextcloud),
|
|
this.renderServiceBadge('PeerTube', services.peertube),
|
|
this.renderServiceBadge('Matrix', services.matrix),
|
|
this.renderServiceBadge('Jabber', services.jabber),
|
|
this.renderServiceBadge('Email', services.email)
|
|
]),
|
|
E('p', { 'style': 'color:#666;' }, _('Domain: %s | Users: %d').format(status.domain, status.user_count))
|
|
]));
|
|
|
|
// Users Table
|
|
var userRows = users.map(function(u) { return self.renderUserRow(u); });
|
|
|
|
content.push(E('div', { 'class': 'cbi-section' }, [
|
|
E('h3', {}, _('Users')),
|
|
E('div', { 'class': 'cbi-page-actions', 'style': 'margin-bottom:15px;' }, [
|
|
E('button', {
|
|
'class': 'btn cbi-button-positive',
|
|
'click': function() { self.handleAdd(); }
|
|
}, _('Add User'))
|
|
]),
|
|
users.length > 0 ?
|
|
E('table', { 'class': 'table cbi-section-table' }, [
|
|
E('tr', { 'class': 'tr table-titles' }, [
|
|
E('th', { 'class': 'th' }, _('Username')),
|
|
E('th', { 'class': 'th' }, _('Email')),
|
|
E('th', { 'class': 'th' }, _('Services')),
|
|
E('th', { 'class': 'th' }, _('Enabled')),
|
|
E('th', { 'class': 'th' }, _('Actions'))
|
|
])
|
|
].concat(userRows)) :
|
|
E('p', { 'style': 'color:#666;' }, _('No users configured. Click "Add User" to create one.'))
|
|
]));
|
|
|
|
return E('div', { 'class': 'cbi-map' }, content);
|
|
}
|
|
});
|