secubox-openwrt/package/secubox/luci-app-secubox-users/htdocs/luci-static/resources/view/secubox-users/overview.js
CyberMind-FR 2bb40d9419 fix(users,routing): Add gitea/jellyfin support and fix mitmproxy routes
secubox-users:
- Add gitea and jellyfin to supported services list
- Add create/update/delete handlers for gitea (via API) and jellyfin
- Update CLI help and status display to include new services

luci-app-secubox-users:
- Add jellyfin service checkbox and badge in frontend
- Update RPCD handler to check jellyfin service status

mitmproxy routing fix:
- nextcloudctl: Use host LAN IP instead of 127.0.0.1 for WAF routes
  (mitmproxy runs in container, can't reach host's localhost)
- metablogizerctl: Same fix for mitmproxy route registration
- mitmproxyctl: Fix sync_metablogizer_routes to use host IP

This fixes 502/403 errors when accessing services through HAProxy->mitmproxy
because the mitmproxy container couldn't route to 127.0.0.1 on the host.

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

305 lines
11 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);
});
// Login stats
var lastLogin = user.last_login || 'never';
var loginSuccess = user.login_success || 0;
var loginFailure = user.login_failure || 0;
// Color code failures
var failColor = loginFailure > 10 ? '#f44336' : (loginFailure > 0 ? '#ff9800' : '#4caf50');
return E('tr', {}, [
E('td', {}, user.username),
E('td', {}, user.email),
E('td', {}, lastLogin),
E('td', { 'style': 'text-align:center;' }, [
E('span', { 'style': 'color:#4caf50;font-weight:bold;' }, String(loginSuccess)),
E('span', {}, ' / '),
E('span', { 'style': 'color:' + failColor + ';font-weight:bold;' }, String(loginFailure))
]),
E('td', {}, services),
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('label', { 'style': 'margin-left:10px;' }, [E('input', { 'type': 'checkbox', 'id': 'svc-gitea', 'checked': true }), ' Gitea']),
E('label', { 'style': 'margin-left:10px;' }, [E('input', { 'type': 'checkbox', 'id': 'svc-jellyfin', 'checked': true }), ' Jellyfin'])
])
])
]),
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 (document.getElementById('svc-gitea').checked) services.push('gitea');
if (document.getElementById('svc-jellyfin').checked) services.push('jellyfin');
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),
this.renderServiceBadge('Gitea', services.gitea),
this.renderServiceBadge('Jellyfin', services.jellyfin)
]),
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' }, _('Last Login')),
E('th', { 'class': 'th', 'style': 'text-align:center;' }, _('OK / Fail')),
E('th', { 'class': 'th' }, _('Services')),
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);
}
});