secubox-openwrt/package/secubox/luci-app-gotosocial/htdocs/luci-static/resources/view/gotosocial/overview.js
CyberMind-FR f20bb1df6b feat(gotosocial): Add GoToSocial Fediverse server packages
Add secubox-app-gotosocial and luci-app-gotosocial for running a lightweight
ActivityPub social network server in LXC container.

Features:
- gotosocialctl CLI with install, start, stop, user management
- LXC container deployment (ARM64)
- HAProxy integration via emancipate command
- UCI configuration for instance, container, proxy, federation settings
- LuCI web interface with overview, users, and settings tabs
- Mesh integration support for auto-federation between SecuBox nodes
- Backup/restore functionality

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-13 13:52:58 +01:00

400 lines
12 KiB
JavaScript

'use strict';
'require view';
'require rpc';
'require ui';
'require poll';
var callStatus = rpc.declare({
object: 'luci.gotosocial',
method: 'status',
expect: {}
});
var callInstall = rpc.declare({
object: 'luci.gotosocial',
method: 'install',
expect: {}
});
var callStart = rpc.declare({
object: 'luci.gotosocial',
method: 'start',
expect: {}
});
var callStop = rpc.declare({
object: 'luci.gotosocial',
method: 'stop',
expect: {}
});
var callRestart = rpc.declare({
object: 'luci.gotosocial',
method: 'restart',
expect: {}
});
var callEmancipate = rpc.declare({
object: 'luci.gotosocial',
method: 'emancipate',
params: ['domain', 'tor', 'dns', 'mesh'],
expect: {}
});
var callRevoke = rpc.declare({
object: 'luci.gotosocial',
method: 'revoke',
expect: {}
});
var callBackup = rpc.declare({
object: 'luci.gotosocial',
method: 'backup',
expect: {}
});
var callLogs = rpc.declare({
object: 'luci.gotosocial',
method: 'logs',
params: ['lines'],
expect: {}
});
return view.extend({
status: null,
load: function() {
return callStatus();
},
pollStatus: function() {
return callStatus().then(L.bind(function(status) {
this.status = status;
this.updateStatusDisplay(status);
}, this));
},
updateStatusDisplay: function(status) {
var containerEl = document.getElementById('container-status');
var serviceEl = document.getElementById('service-status');
var versionEl = document.getElementById('gts-version');
var hostEl = document.getElementById('gts-host');
var exposureEl = document.getElementById('exposure-status');
if (containerEl) {
if (status.container_running) {
containerEl.textContent = 'Running';
containerEl.className = 'badge success';
} else if (status.installed) {
containerEl.textContent = 'Stopped';
containerEl.className = 'badge warning';
} else {
containerEl.textContent = 'Not Installed';
containerEl.className = 'badge danger';
}
}
if (serviceEl) {
if (status.service_running) {
serviceEl.textContent = 'Running';
serviceEl.className = 'badge success';
} else {
serviceEl.textContent = 'Stopped';
serviceEl.className = 'badge warning';
}
}
if (versionEl) {
versionEl.textContent = status.version || '-';
}
if (hostEl) {
hostEl.textContent = status.host || '-';
}
if (exposureEl) {
var channels = [];
if (status.tor_enabled) channels.push('Tor');
if (status.dns_enabled) channels.push('DNS/SSL');
if (status.mesh_enabled) channels.push('Mesh');
exposureEl.textContent = channels.length > 0 ? channels.join(', ') : 'None';
}
// Update button states
var installBtn = document.getElementById('btn-install');
var startBtn = document.getElementById('btn-start');
var stopBtn = document.getElementById('btn-stop');
var restartBtn = document.getElementById('btn-restart');
if (installBtn) installBtn.disabled = status.installed;
if (startBtn) startBtn.disabled = !status.installed || status.container_running;
if (stopBtn) stopBtn.disabled = !status.container_running;
if (restartBtn) restartBtn.disabled = !status.container_running;
},
handleInstall: function() {
return ui.showModal(_('Install GoToSocial'), [
E('p', _('This will download and install GoToSocial in an LXC container. This may take several minutes.')),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn',
'click': ui.hideModal
}, _('Cancel')),
' ',
E('button', {
'class': 'btn cbi-button-action important',
'click': ui.createHandlerFn(this, function() {
ui.hideModal();
ui.showModal(_('Installing...'), [
E('p', { 'class': 'spinning' }, _('Installing GoToSocial, please wait...'))
]);
return callInstall().then(function(res) {
ui.hideModal();
if (res.success) {
ui.addNotification(null, E('p', _('GoToSocial installed successfully')), 'success');
} else {
ui.addNotification(null, E('p', res.error || _('Installation failed')), 'error');
}
return callStatus();
}).then(L.bind(function(status) {
this.updateStatusDisplay(status);
}, this));
})
}, _('Install'))
])
]);
},
handleStart: function() {
ui.showModal(_('Starting...'), [
E('p', { 'class': 'spinning' }, _('Starting GoToSocial...'))
]);
return callStart().then(L.bind(function(res) {
ui.hideModal();
ui.addNotification(null, E('p', res.message || _('GoToSocial started')), 'success');
return this.pollStatus();
}, this));
},
handleStop: function() {
return callStop().then(L.bind(function(res) {
ui.addNotification(null, E('p', res.message || _('GoToSocial stopped')), 'info');
return this.pollStatus();
}, this));
},
handleRestart: function() {
ui.showModal(_('Restarting...'), [
E('p', { 'class': 'spinning' }, _('Restarting GoToSocial...'))
]);
return callRestart().then(L.bind(function(res) {
ui.hideModal();
ui.addNotification(null, E('p', res.message || _('GoToSocial restarted')), 'success');
return this.pollStatus();
}, this));
},
handleEmancipate: function() {
var domain = this.status && this.status.host ? this.status.host : '';
return ui.showModal(_('Expose Service'), [
E('p', _('Configure exposure channels for your Fediverse instance.')),
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title' }, _('Domain')),
E('div', { 'class': 'cbi-value-field' }, [
E('input', { 'type': 'text', 'id': 'emancipate-domain', 'class': 'cbi-input-text', 'value': domain })
])
]),
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title' }, _('Channels')),
E('div', { 'class': 'cbi-value-field' }, [
E('label', { 'style': 'display:block' }, [
E('input', { 'type': 'checkbox', 'id': 'emancipate-tor' }), ' ', _('Tor (.onion)')
]),
E('label', { 'style': 'display:block' }, [
E('input', { 'type': 'checkbox', 'id': 'emancipate-dns', 'checked': true }), ' ', _('DNS/SSL (HTTPS)')
]),
E('label', { 'style': 'display:block' }, [
E('input', { 'type': 'checkbox', 'id': 'emancipate-mesh' }), ' ', _('Mesh Network')
])
])
]),
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 domain = document.getElementById('emancipate-domain').value;
var tor = document.getElementById('emancipate-tor').checked;
var dns = document.getElementById('emancipate-dns').checked;
var mesh = document.getElementById('emancipate-mesh').checked;
ui.hideModal();
ui.showModal(_('Exposing...'), [
E('p', { 'class': 'spinning' }, _('Setting up exposure channels...'))
]);
return callEmancipate(domain, tor, dns, mesh).then(function(res) {
ui.hideModal();
if (res.success) {
ui.addNotification(null, E('p', res.message || _('Service exposed successfully')), 'success');
} else {
ui.addNotification(null, E('p', res.error || _('Exposure failed')), 'error');
}
});
})
}, _('Expose'))
])
]);
},
handleRevoke: function() {
return ui.showModal(_('Revoke Exposure'), [
E('p', _('This will remove all exposure channels for GoToSocial. The service will no longer be accessible externally.')),
E('div', { 'class': 'right' }, [
E('button', { 'class': 'btn', 'click': ui.hideModal }, _('Cancel')),
' ',
E('button', {
'class': 'btn cbi-button-negative',
'click': ui.createHandlerFn(this, function() {
ui.hideModal();
return callRevoke().then(function(res) {
ui.addNotification(null, E('p', res.message || _('Exposure revoked')), 'info');
});
})
}, _('Revoke'))
])
]);
},
handleBackup: function() {
ui.showModal(_('Creating Backup...'), [
E('p', { 'class': 'spinning' }, _('Creating backup...'))
]);
return callBackup().then(function(res) {
ui.hideModal();
if (res.success) {
ui.addNotification(null, E('p', res.message || _('Backup created')), 'success');
} else {
ui.addNotification(null, E('p', res.error || _('Backup failed')), 'error');
}
});
},
handleViewLogs: function() {
return callLogs(100).then(function(res) {
var logs = res.logs || [];
ui.showModal(_('GoToSocial Logs'), [
E('div', { 'style': 'max-height:400px; overflow-y:auto; font-family:monospace; font-size:12px; background:#111; color:#0f0; padding:10px; white-space:pre-wrap;' },
logs.join('\n') || _('No logs available')
),
E('div', { 'class': 'right', 'style': 'margin-top:10px' }, [
E('button', { 'class': 'btn', 'click': ui.hideModal }, _('Close'))
])
]);
});
},
render: function(status) {
this.status = status;
var view = E('div', { 'class': 'cbi-map' }, [
E('h2', _('GoToSocial Fediverse Server')),
E('div', { 'class': 'cbi-map-descr' }, _('Lightweight ActivityPub social network server for the Fediverse.')),
E('div', { 'class': 'cbi-section' }, [
E('h3', _('Status')),
E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td', 'width': '200px' }, _('Container')),
E('td', { 'class': 'td' }, E('span', { 'id': 'container-status', 'class': 'badge' }, '-'))
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, _('Service')),
E('td', { 'class': 'td' }, E('span', { 'id': 'service-status', 'class': 'badge' }, '-'))
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, _('Version')),
E('td', { 'class': 'td' }, E('span', { 'id': 'gts-version' }, '-'))
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, _('Domain')),
E('td', { 'class': 'td' }, E('span', { 'id': 'gts-host' }, '-'))
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, _('Exposure')),
E('td', { 'class': 'td' }, E('span', { 'id': 'exposure-status' }, '-'))
])
])
]),
E('div', { 'class': 'cbi-section' }, [
E('h3', _('Actions')),
E('div', { 'class': 'cbi-value' }, [
E('button', {
'id': 'btn-install',
'class': 'btn cbi-button-action',
'click': ui.createHandlerFn(this, this.handleInstall)
}, _('Install')),
' ',
E('button', {
'id': 'btn-start',
'class': 'btn cbi-button-action',
'click': ui.createHandlerFn(this, this.handleStart)
}, _('Start')),
' ',
E('button', {
'id': 'btn-stop',
'class': 'btn cbi-button-neutral',
'click': ui.createHandlerFn(this, this.handleStop)
}, _('Stop')),
' ',
E('button', {
'id': 'btn-restart',
'class': 'btn cbi-button-neutral',
'click': ui.createHandlerFn(this, this.handleRestart)
}, _('Restart'))
]),
E('div', { 'class': 'cbi-value', 'style': 'margin-top:10px' }, [
E('button', {
'class': 'btn cbi-button-action',
'click': ui.createHandlerFn(this, this.handleEmancipate)
}, _('Expose Service')),
' ',
E('button', {
'class': 'btn cbi-button-neutral',
'click': ui.createHandlerFn(this, this.handleRevoke)
}, _('Revoke Exposure')),
' ',
E('button', {
'class': 'btn cbi-button-neutral',
'click': ui.createHandlerFn(this, this.handleBackup)
}, _('Backup')),
' ',
E('button', {
'class': 'btn cbi-button-neutral',
'click': ui.createHandlerFn(this, this.handleViewLogs)
}, _('View Logs'))
])
]),
E('style', {}, `
.badge { padding: 2px 8px; border-radius: 3px; font-weight: bold; }
.badge.success { background: #4CAF50; color: white; }
.badge.warning { background: #FF9800; color: white; }
.badge.danger { background: #f44336; color: white; }
`)
]);
this.updateStatusDisplay(status);
poll.add(L.bind(this.pollStatus, this), 5);
return view;
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});