- Add enhanced instant ban for critical threats (SQL injection, CVE exploits, RCE) - CrowdSec trigger scenario for single-hit bans on severity=critical - Instant ban daemon (10s polling) for rapid response - UCI options: instant_ban_enabled, instant_ban_duration (48h default) - WAF addon updated to route critical threats to instant-ban.log - Add centralized user management (secubox-core-users, luci-app-secubox-users) - CLI tool: secubox-users add/del/passwd/list/sync/status - LuCI dashboard under System > SecuBox Users - Unified user provisioning across Nextcloud, PeerTube, Matrix, Jabber, Email - Add Matrix/Conduit integration (secubox-app-matrix, luci-app-matrix) - LXC-based Conduit homeserver deployment - Full RPCD handler with user/room management - HAProxy integration for federation - Add provision-users.sh script for bulk user creation - Update secubox-feed with new IPKs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
377 lines
12 KiB
JavaScript
377 lines
12 KiB
JavaScript
'use strict';
|
|
'require view';
|
|
'require dom';
|
|
'require poll';
|
|
'require ui';
|
|
'require uci';
|
|
'require matrix.api as api';
|
|
|
|
return view.extend({
|
|
handleAction: function(action, args) {
|
|
var self = this;
|
|
|
|
ui.showModal(_('Processing...'), [
|
|
E('p', { 'class': 'spinning' }, _('Please wait...'))
|
|
]);
|
|
|
|
var promise;
|
|
switch(action) {
|
|
case 'start':
|
|
promise = api.start();
|
|
break;
|
|
case 'stop':
|
|
promise = api.stop();
|
|
break;
|
|
case 'install':
|
|
promise = api.install();
|
|
break;
|
|
case 'uninstall':
|
|
promise = api.uninstall();
|
|
break;
|
|
case 'update':
|
|
promise = api.update();
|
|
break;
|
|
case 'emancipate':
|
|
promise = api.emancipate(args.domain);
|
|
break;
|
|
case 'configure_haproxy':
|
|
promise = api.configureHaproxy();
|
|
break;
|
|
case 'user_add':
|
|
promise = api.userAdd(args.mxid, args.password);
|
|
break;
|
|
case 'identity_link':
|
|
promise = api.identityLink(args.mxid);
|
|
break;
|
|
case 'identity_unlink':
|
|
promise = api.identityUnlink();
|
|
break;
|
|
case 'mesh_publish':
|
|
promise = api.meshPublish();
|
|
break;
|
|
case 'mesh_unpublish':
|
|
promise = api.meshUnpublish();
|
|
break;
|
|
default:
|
|
promise = Promise.reject(new Error('Unknown action'));
|
|
}
|
|
|
|
promise.then(function(res) {
|
|
ui.hideModal();
|
|
|
|
if (res && res.success) {
|
|
var msg = res.message || 'Operation completed successfully';
|
|
if (res.password) {
|
|
msg += '\n\nGenerated password: ' + res.password;
|
|
}
|
|
ui.addNotification(null, E('p', {}, msg), 'success');
|
|
} else {
|
|
ui.addNotification(null, E('p', {}, _('Error: ') + (res.error || 'Unknown error')), 'error');
|
|
}
|
|
|
|
// Refresh status
|
|
self.load().then(function(data) {
|
|
dom.content(document.querySelector('#matrix-content'), self.renderContent(data));
|
|
});
|
|
}).catch(function(e) {
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', {}, _('Error: ') + e.message), 'error');
|
|
});
|
|
},
|
|
|
|
load: function() {
|
|
return Promise.all([
|
|
api.getStatus(),
|
|
uci.load('matrix'),
|
|
api.getFederationStatus(),
|
|
api.getIdentityStatus(),
|
|
api.getMeshStatus()
|
|
]);
|
|
},
|
|
|
|
renderStatusBadge: function(running) {
|
|
var color = running ? '#4CAF50' : '#9e9e9e';
|
|
var text = running ? _('Running') : _('Stopped');
|
|
return E('span', {
|
|
'style': 'display:inline-block;padding:3px 10px;border-radius:3px;color:#fff;background:' + color
|
|
}, text);
|
|
},
|
|
|
|
renderFeatureBadge: function(enabled, label) {
|
|
var color = enabled ? '#2196F3' : '#9e9e9e';
|
|
return E('span', {
|
|
'style': 'display:inline-block;padding:2px 8px;border-radius:3px;color:#fff;background:' + color + ';margin-right:5px;font-size:0.85em;'
|
|
}, label);
|
|
},
|
|
|
|
renderInstallWizard: function() {
|
|
var self = this;
|
|
|
|
return E('div', { 'class': 'cbi-section' }, [
|
|
E('h3', {}, _('Matrix Homeserver')),
|
|
E('p', {}, _('Matrix is a decentralized communication protocol with end-to-end encryption.')),
|
|
E('div', { 'style': 'margin:20px 0;padding:15px;background:#f5f5f5;border-radius:5px;' }, [
|
|
E('h4', {}, _('Features:')),
|
|
E('ul', {}, [
|
|
E('li', {}, _('End-to-end encrypted messaging')),
|
|
E('li', {}, _('Federated chat (connect with other Matrix servers)')),
|
|
E('li', {}, _('Group chats and spaces')),
|
|
E('li', {}, _('Voice and video calls')),
|
|
E('li', {}, _('File sharing')),
|
|
E('li', {}, _('Bridge to other platforms'))
|
|
]),
|
|
E('h4', { 'style': 'margin-top:15px;' }, _('Compatible clients:')),
|
|
E('p', {}, _('Element (Web/Desktop/Mobile), FluffyChat, Nheko, SchildiChat'))
|
|
]),
|
|
E('div', { 'class': 'cbi-page-actions' }, [
|
|
E('button', {
|
|
'class': 'btn cbi-button cbi-button-positive',
|
|
'click': function() { self.handleAction('install'); }
|
|
}, _('Install Matrix Homeserver'))
|
|
])
|
|
]);
|
|
},
|
|
|
|
renderContent: function(data) {
|
|
var self = this;
|
|
var status = data[0] || {};
|
|
var federation = data[2] || {};
|
|
var identity = data[3] || {};
|
|
var mesh = data[4] || {};
|
|
|
|
// Show install wizard if not installed
|
|
if (status.container_state === 'not_installed') {
|
|
return this.renderInstallWizard();
|
|
}
|
|
|
|
var running = status.running === 'true' || status.running === true;
|
|
var hostname = status.hostname || 'matrix.local';
|
|
var httpPort = status.http_port || '8008';
|
|
var domain = status.domain || '';
|
|
var lanIp = '192.168.255.1';
|
|
|
|
var content = [];
|
|
|
|
// Status Section
|
|
content.push(E('div', { 'class': 'cbi-section' }, [
|
|
E('h3', {}, _('Matrix Homeserver')),
|
|
E('div', { 'style': 'display:flex;flex-wrap:wrap;gap:20px;margin:15px 0;' }, [
|
|
// Status Card
|
|
E('div', { 'style': 'flex:1;min-width:200px;padding:15px;border:1px solid #ddd;border-radius:5px;' }, [
|
|
E('h4', { 'style': 'margin:0 0 10px 0;' }, _('Server Status')),
|
|
E('div', {}, this.renderStatusBadge(running)),
|
|
E('div', { 'style': 'margin-top:10px;color:#666;' }, [
|
|
E('div', {}, _('Hostname: ') + hostname),
|
|
E('div', {}, _('Client Port: ') + httpPort),
|
|
status.version ? E('div', {}, _('Version: ') + status.version) : ''
|
|
])
|
|
]),
|
|
// Features Card
|
|
E('div', { 'style': 'flex:1;min-width:200px;padding:15px;border:1px solid #ddd;border-radius:5px;' }, [
|
|
E('h4', { 'style': 'margin:0 0 10px 0;' }, _('Features')),
|
|
E('div', {}, [
|
|
this.renderFeatureBadge(federation.enabled === '1', _('Federation')),
|
|
this.renderFeatureBadge(status.haproxy === '1', _('HAProxy')),
|
|
this.renderFeatureBadge(identity.linked === '1', _('DID Linked')),
|
|
this.renderFeatureBadge(mesh.published === '1', _('Mesh'))
|
|
])
|
|
]),
|
|
// Connection Card
|
|
E('div', { 'style': 'flex:1;min-width:200px;padding:15px;border:1px solid #ddd;border-radius:5px;' }, [
|
|
E('h4', { 'style': 'margin:0 0 10px 0;' }, _('Connection Info')),
|
|
E('div', { 'style': 'font-family:monospace;font-size:0.9em;' }, [
|
|
E('div', {}, _('Homeserver URL:')),
|
|
domain ? E('div', { 'style': 'color:#2196F3;' }, 'https://' + domain) :
|
|
E('div', { 'style': 'color:#666;' }, 'http://' + lanIp + ':' + httpPort),
|
|
E('div', { 'style': 'margin-top:5px;' }, _('Admin Room:')),
|
|
E('div', { 'style': 'color:#666;' }, '#admins:' + hostname)
|
|
])
|
|
])
|
|
])
|
|
]));
|
|
|
|
// Service Controls
|
|
content.push(E('div', { 'class': 'cbi-section' }, [
|
|
E('h4', {}, _('Service Controls')),
|
|
E('div', { 'class': 'cbi-page-actions', 'style': 'margin:0;' }, [
|
|
E('button', {
|
|
'class': 'btn cbi-button cbi-button-positive',
|
|
'click': function() { self.handleAction('start'); },
|
|
'disabled': running
|
|
}, _('Start')),
|
|
E('button', {
|
|
'class': 'btn cbi-button cbi-button-negative',
|
|
'click': function() { self.handleAction('stop'); },
|
|
'disabled': !running,
|
|
'style': 'margin-left:5px;'
|
|
}, _('Stop')),
|
|
E('button', {
|
|
'class': 'btn cbi-button',
|
|
'click': function() { self.handleAction('update'); },
|
|
'style': 'margin-left:5px;'
|
|
}, _('Update')),
|
|
E('button', {
|
|
'class': 'btn cbi-button cbi-button-remove',
|
|
'click': function() {
|
|
if (confirm(_('Are you sure you want to uninstall? Data will be preserved.'))) {
|
|
self.handleAction('uninstall');
|
|
}
|
|
},
|
|
'style': 'margin-left:5px;'
|
|
}, _('Uninstall'))
|
|
])
|
|
]));
|
|
|
|
// User Management
|
|
content.push(E('div', { 'class': 'cbi-section' }, [
|
|
E('h4', {}, _('User Management')),
|
|
E('p', { 'style': 'color:#666;' }, _('Create Matrix users for this homeserver.')),
|
|
E('div', { 'style': 'display:flex;gap:10px;align-items:center;flex-wrap:wrap;' }, [
|
|
E('input', {
|
|
'type': 'text',
|
|
'id': 'new-user-mxid',
|
|
'placeholder': '@username:' + hostname,
|
|
'style': 'width:200px;padding:5px;'
|
|
}),
|
|
E('input', {
|
|
'type': 'password',
|
|
'id': 'new-user-password',
|
|
'placeholder': _('Password (optional)'),
|
|
'style': 'width:150px;padding:5px;'
|
|
}),
|
|
E('button', {
|
|
'class': 'btn cbi-button cbi-button-positive',
|
|
'click': function() {
|
|
var mxid = document.getElementById('new-user-mxid').value;
|
|
var password = document.getElementById('new-user-password').value;
|
|
if (mxid) {
|
|
self.handleAction('user_add', { mxid: mxid, password: password });
|
|
}
|
|
}
|
|
}, _('Add User'))
|
|
]),
|
|
E('p', { 'style': 'margin-top:10px;color:#888;font-size:0.9em;' },
|
|
_('For user listing and deletion, use the admin room: #admins:') + hostname)
|
|
]));
|
|
|
|
// Emancipate (Public Exposure)
|
|
content.push(E('div', { 'class': 'cbi-section' }, [
|
|
E('h4', {}, _('Public Exposure')),
|
|
E('p', { 'style': 'color:#666;' }, _('Expose your Matrix server to the internet with SSL.')),
|
|
E('div', { 'style': 'display:flex;gap:10px;align-items:center;flex-wrap:wrap;' }, [
|
|
E('input', {
|
|
'type': 'text',
|
|
'id': 'emancipate-domain',
|
|
'placeholder': 'matrix.example.com',
|
|
'value': domain,
|
|
'style': 'width:250px;padding:5px;'
|
|
}),
|
|
E('button', {
|
|
'class': 'btn cbi-button cbi-button-action',
|
|
'click': function() {
|
|
var domain = document.getElementById('emancipate-domain').value;
|
|
if (domain) {
|
|
self.handleAction('emancipate', { domain: domain });
|
|
}
|
|
}
|
|
}, _('Emancipate'))
|
|
]),
|
|
domain ? E('div', { 'style': 'margin-top:10px;padding:10px;background:#e8f5e9;border-radius:5px;' }, [
|
|
E('strong', {}, _('Currently exposed at: ')),
|
|
E('a', { 'href': 'https://' + domain, 'target': '_blank' }, 'https://' + domain)
|
|
]) : ''
|
|
]));
|
|
|
|
// Identity Integration
|
|
content.push(E('div', { 'class': 'cbi-section' }, [
|
|
E('h4', {}, _('Identity Integration')),
|
|
E('p', { 'style': 'color:#666;' }, _('Link your Matrix identity to the node DID.')),
|
|
identity.linked === '1' ? E('div', { 'style': 'padding:10px;background:#e3f2fd;border-radius:5px;' }, [
|
|
E('div', {}, _('Matrix ID: ') + identity.mxid),
|
|
E('div', {}, _('Node DID: ') + (identity.did || 'N/A')),
|
|
E('button', {
|
|
'class': 'btn cbi-button',
|
|
'click': function() { self.handleAction('identity_unlink'); },
|
|
'style': 'margin-top:10px;'
|
|
}, _('Unlink'))
|
|
]) : E('div', { 'style': 'display:flex;gap:10px;align-items:center;' }, [
|
|
E('input', {
|
|
'type': 'text',
|
|
'id': 'identity-mxid',
|
|
'placeholder': '@user:' + hostname,
|
|
'style': 'width:200px;padding:5px;'
|
|
}),
|
|
E('button', {
|
|
'class': 'btn cbi-button cbi-button-action',
|
|
'click': function() {
|
|
var mxid = document.getElementById('identity-mxid').value;
|
|
if (mxid) {
|
|
self.handleAction('identity_link', { mxid: mxid });
|
|
}
|
|
}
|
|
}, _('Link Identity'))
|
|
])
|
|
]));
|
|
|
|
// Mesh Publication
|
|
content.push(E('div', { 'class': 'cbi-section' }, [
|
|
E('h4', {}, _('P2P Mesh Publication')),
|
|
E('p', { 'style': 'color:#666;' }, _('Publish this Matrix server to the SecuBox P2P mesh.')),
|
|
mesh.published === '1' ?
|
|
E('div', {}, [
|
|
E('span', { 'style': 'color:#4CAF50;' }, _('Published to mesh as: ') + mesh.service_name),
|
|
E('button', {
|
|
'class': 'btn cbi-button',
|
|
'click': function() { self.handleAction('mesh_unpublish'); },
|
|
'style': 'margin-left:15px;'
|
|
}, _('Unpublish'))
|
|
]) :
|
|
E('button', {
|
|
'class': 'btn cbi-button cbi-button-action',
|
|
'click': function() { self.handleAction('mesh_publish'); }
|
|
}, _('Publish to Mesh'))
|
|
]));
|
|
|
|
// Logs Section
|
|
content.push(E('div', { 'class': 'cbi-section' }, [
|
|
E('h4', {}, _('Server Logs')),
|
|
E('div', { 'style': 'display:flex;gap:10px;margin-bottom:10px;' }, [
|
|
E('button', {
|
|
'class': 'btn cbi-button',
|
|
'click': function() {
|
|
api.getLogs(100).then(function(res) {
|
|
var pre = document.getElementById('matrix-logs');
|
|
if (pre) pre.textContent = res.logs || 'No logs available';
|
|
});
|
|
}
|
|
}, _('Refresh Logs'))
|
|
]),
|
|
E('pre', {
|
|
'id': 'matrix-logs',
|
|
'style': 'background:#1e1e1e;color:#d4d4d4;padding:15px;border-radius:5px;max-height:300px;overflow:auto;font-size:0.85em;'
|
|
}, _('Click "Refresh Logs" to load...'))
|
|
]));
|
|
|
|
return E('div', {}, content);
|
|
},
|
|
|
|
render: function(data) {
|
|
var content = E('div', { 'id': 'matrix-content' }, this.renderContent(data));
|
|
|
|
// Poll for status updates
|
|
poll.add(L.bind(function() {
|
|
return api.getStatus().then(L.bind(function(status) {
|
|
// Update status badges without full reload
|
|
var statusBadge = document.querySelector('#matrix-content .cbi-section span');
|
|
if (statusBadge && status) {
|
|
var running = status.running === 'true' || status.running === true;
|
|
statusBadge.style.background = running ? '#4CAF50' : '#9e9e9e';
|
|
statusBadge.textContent = running ? _('Running') : _('Stopped');
|
|
}
|
|
}, this));
|
|
}, this), 10);
|
|
|
|
return content;
|
|
}
|
|
});
|