Backend package (secubox-app-repo): - repoctl CLI for managing local package repository - repo-sync script to download packages from GitHub releases - uhttpd-based server on port 8888 - UCI configuration at /etc/config/repo - RPCD handler for LuCI integration - Auto-sync cron support (configurable interval) Frontend package (luci-app-repo): - Dashboard showing repository status and package counts - Sync button to trigger package downloads - Log viewer for sync operations - Usage instructions for opkg configuration Supported architectures: - x86_64, aarch64_cortex-a72, aarch64_generic - mips_24kc, mipsel_24kc Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
204 lines
7.1 KiB
JavaScript
204 lines
7.1 KiB
JavaScript
'use strict';
|
|
'require view';
|
|
'require dom';
|
|
'require poll';
|
|
'require uci';
|
|
'require rpc';
|
|
'require ui';
|
|
|
|
var callStatus = rpc.declare({
|
|
object: 'luci.repo',
|
|
method: 'status',
|
|
expect: {}
|
|
});
|
|
|
|
var callPackages = rpc.declare({
|
|
object: 'luci.repo',
|
|
method: 'packages',
|
|
params: ['arch'],
|
|
expect: {}
|
|
});
|
|
|
|
var callSync = rpc.declare({
|
|
object: 'luci.repo',
|
|
method: 'sync',
|
|
params: ['version'],
|
|
expect: {}
|
|
});
|
|
|
|
var callLogs = rpc.declare({
|
|
object: 'luci.repo',
|
|
method: 'logs',
|
|
params: ['lines'],
|
|
expect: {}
|
|
});
|
|
|
|
return view.extend({
|
|
load: function() {
|
|
return Promise.all([
|
|
callStatus(),
|
|
uci.load('repo')
|
|
]);
|
|
},
|
|
|
|
formatBytes: function(bytes) {
|
|
if (bytes === 0) return '0 B';
|
|
var k = 1024;
|
|
var sizes = ['B', 'KB', 'MB', 'GB'];
|
|
var i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
},
|
|
|
|
renderStatus: function(status) {
|
|
var statusClass = status.running ? 'success' : 'danger';
|
|
var statusText = status.running ? _('Running') : _('Stopped');
|
|
|
|
return E('div', { 'class': 'cbi-section' }, [
|
|
E('h3', {}, _('Repository Status')),
|
|
E('div', { 'class': 'table' }, [
|
|
E('div', { 'class': 'tr' }, [
|
|
E('div', { 'class': 'td left', 'style': 'width:200px' }, _('Server Status')),
|
|
E('div', { 'class': 'td' }, [
|
|
E('span', { 'class': 'label ' + statusClass }, statusText),
|
|
status.running ? E('span', {}, ' (port ' + status.port + ')') : ''
|
|
])
|
|
]),
|
|
E('div', { 'class': 'tr' }, [
|
|
E('div', { 'class': 'td left' }, _('Version')),
|
|
E('div', { 'class': 'td' }, status.version || '-')
|
|
]),
|
|
E('div', { 'class': 'tr' }, [
|
|
E('div', { 'class': 'td left' }, _('GitHub Repository')),
|
|
E('div', { 'class': 'td' }, status.github_repo || '-')
|
|
]),
|
|
E('div', { 'class': 'tr' }, [
|
|
E('div', { 'class': 'td left' }, _('Last Sync')),
|
|
E('div', { 'class': 'td' }, status.last_sync || _('Never'))
|
|
]),
|
|
E('div', { 'class': 'tr' }, [
|
|
E('div', { 'class': 'td left' }, _('Auto Sync')),
|
|
E('div', { 'class': 'td' }, status.auto_sync ?
|
|
_('Every %d hours').format(status.sync_interval) : _('Disabled'))
|
|
])
|
|
])
|
|
]);
|
|
},
|
|
|
|
renderArchitectures: function(status) {
|
|
var archs = status.architectures || {};
|
|
var rows = [];
|
|
|
|
Object.keys(archs).sort().forEach(function(arch) {
|
|
rows.push(E('div', { 'class': 'tr' }, [
|
|
E('div', { 'class': 'td left' }, arch),
|
|
E('div', { 'class': 'td' }, archs[arch] + ' ' + _('packages'))
|
|
]));
|
|
});
|
|
|
|
if (rows.length === 0) {
|
|
rows.push(E('div', { 'class': 'tr' }, [
|
|
E('div', { 'class': 'td', 'colspan': 2 }, _('No packages synced yet'))
|
|
]));
|
|
}
|
|
|
|
return E('div', { 'class': 'cbi-section' }, [
|
|
E('h3', {}, _('Available Architectures')),
|
|
E('div', { 'class': 'table' }, rows)
|
|
]);
|
|
},
|
|
|
|
renderActions: function(status) {
|
|
var self = this;
|
|
|
|
var syncBtn = E('button', {
|
|
'class': 'cbi-button cbi-button-action',
|
|
'click': function() {
|
|
ui.showModal(_('Sync Packages'), [
|
|
E('p', {}, _('Sync packages from GitHub release?')),
|
|
E('div', { 'class': 'right' }, [
|
|
E('button', {
|
|
'class': 'cbi-button',
|
|
'click': ui.hideModal
|
|
}, _('Cancel')),
|
|
' ',
|
|
E('button', {
|
|
'class': 'cbi-button cbi-button-positive',
|
|
'click': function() {
|
|
ui.hideModal();
|
|
ui.showModal(_('Syncing...'), [
|
|
E('p', { 'class': 'spinning' }, _('Downloading packages from GitHub...'))
|
|
]);
|
|
callSync(status.version).then(function() {
|
|
setTimeout(function() {
|
|
ui.hideModal();
|
|
window.location.reload();
|
|
}, 3000);
|
|
});
|
|
}
|
|
}, _('Sync Now'))
|
|
])
|
|
]);
|
|
}
|
|
}, _('Sync Packages'));
|
|
|
|
var logsBtn = E('button', {
|
|
'class': 'cbi-button',
|
|
'click': function() {
|
|
callLogs(100).then(function(result) {
|
|
var logs = (result.logs || '').split('|').join('\n');
|
|
ui.showModal(_('Sync Logs'), [
|
|
E('pre', { 'style': 'max-height:400px;overflow:auto;font-size:12px;' }, logs || _('No logs')),
|
|
E('div', { 'class': 'right' }, [
|
|
E('button', {
|
|
'class': 'cbi-button',
|
|
'click': ui.hideModal
|
|
}, _('Close'))
|
|
])
|
|
]);
|
|
});
|
|
}
|
|
}, _('View Logs'));
|
|
|
|
return E('div', { 'class': 'cbi-section' }, [
|
|
E('h3', {}, _('Actions')),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
syncBtn, ' ', logsBtn
|
|
])
|
|
]);
|
|
},
|
|
|
|
renderUsage: function(status) {
|
|
var port = status.port || 8888;
|
|
|
|
return E('div', { 'class': 'cbi-section' }, [
|
|
E('h3', {}, _('Usage')),
|
|
E('p', {}, _('Add to /etc/opkg/customfeeds.conf:')),
|
|
E('pre', { 'style': 'background:#f5f5f5;padding:10px;border-radius:4px;' }, [
|
|
'# Local repository\n',
|
|
'src/gz secubox_luci http://127.0.0.1:' + port + '/luci/{ARCH}\n\n',
|
|
'# Or via HTTPS (external)\n',
|
|
'src/gz secubox_luci https://repo.secubox.in/luci/{ARCH}'
|
|
].join('')),
|
|
E('p', {}, _('Replace {ARCH} with your architecture: x86_64, aarch64_cortex-a72, aarch64_generic, etc.'))
|
|
]);
|
|
},
|
|
|
|
render: function(data) {
|
|
var status = data[0] || {};
|
|
|
|
return E('div', { 'class': 'cbi-map' }, [
|
|
E('h2', {}, _('Package Repository')),
|
|
E('div', { 'class': 'cbi-map-descr' },
|
|
_('SecuBox package repository - serves OpenWrt packages locally for opkg installation.')),
|
|
this.renderStatus(status),
|
|
this.renderArchitectures(status),
|
|
this.renderActions(status),
|
|
this.renderUsage(status)
|
|
]);
|
|
},
|
|
|
|
handleSaveApply: null,
|
|
handleSave: null,
|
|
handleReset: null
|
|
});
|