feat(metablogizer): KISS UI redesign with backend status
- Replace overview.js with dashboard.js using standard cbi-* classes - Add api.js module for RPC declarations - Show port, runtime, backend_running status in sites table - Add sync_config, discover_vhosts, import_vhost RPC methods - Update ACL with new method permissions - Menu: Sites -> Dashboard Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e07fec6cb4
commit
163364843e
BIN
build/aarch64_cortex-a72/secubox-app-bonus_0.3.0-r2_all.ipk
Normal file
BIN
build/aarch64_cortex-a72/secubox-app-bonus_0.3.0-r2_all.ipk
Normal file
Binary file not shown.
@ -12,7 +12,7 @@ LUCI_PKGARCH:=all
|
||||
|
||||
PKG_NAME:=luci-app-metablogizer
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_RELEASE:=3
|
||||
PKG_RELEASE:=5
|
||||
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
|
||||
PKG_LICENSE:=GPL-2.0
|
||||
|
||||
|
||||
@ -0,0 +1,234 @@
|
||||
'use strict';
|
||||
'require rpc';
|
||||
'require baseclass';
|
||||
|
||||
/**
|
||||
* MetaBlogizer API Module
|
||||
* RPCD interface for MetaBlogizer static site publisher
|
||||
*/
|
||||
|
||||
var callStatus = rpc.declare({
|
||||
object: 'luci.metablogizer',
|
||||
method: 'status',
|
||||
expect: { result: {} }
|
||||
});
|
||||
|
||||
var callListSites = rpc.declare({
|
||||
object: 'luci.metablogizer',
|
||||
method: 'list_sites',
|
||||
expect: { sites: [] }
|
||||
});
|
||||
|
||||
var callCreateSite = rpc.declare({
|
||||
object: 'luci.metablogizer',
|
||||
method: 'create_site',
|
||||
params: ['name', 'domain', 'gitea_repo', 'ssl', 'description'],
|
||||
expect: { result: {} }
|
||||
});
|
||||
|
||||
var callUpdateSite = rpc.declare({
|
||||
object: 'luci.metablogizer',
|
||||
method: 'update_site',
|
||||
params: ['id', 'name', 'domain', 'gitea_repo', 'ssl', 'enabled', 'description'],
|
||||
expect: { result: {} }
|
||||
});
|
||||
|
||||
var callDeleteSite = rpc.declare({
|
||||
object: 'luci.metablogizer',
|
||||
method: 'delete_site',
|
||||
params: ['id'],
|
||||
expect: { result: {} }
|
||||
});
|
||||
|
||||
var callSyncSite = rpc.declare({
|
||||
object: 'luci.metablogizer',
|
||||
method: 'sync_site',
|
||||
params: ['id'],
|
||||
expect: { result: {} }
|
||||
});
|
||||
|
||||
var callGetHostingStatus = rpc.declare({
|
||||
object: 'luci.metablogizer',
|
||||
method: 'get_hosting_status',
|
||||
expect: { result: {} }
|
||||
});
|
||||
|
||||
var callCheckSiteHealth = rpc.declare({
|
||||
object: 'luci.metablogizer',
|
||||
method: 'check_site_health',
|
||||
params: ['id'],
|
||||
expect: { result: {} }
|
||||
});
|
||||
|
||||
var callGetPublishInfo = rpc.declare({
|
||||
object: 'luci.metablogizer',
|
||||
method: 'get_publish_info',
|
||||
params: ['id'],
|
||||
expect: { result: {} }
|
||||
});
|
||||
|
||||
var callUploadFile = rpc.declare({
|
||||
object: 'luci.metablogizer',
|
||||
method: 'upload_file',
|
||||
params: ['site_id', 'filename', 'content'],
|
||||
expect: { result: {} }
|
||||
});
|
||||
|
||||
var callListFiles = rpc.declare({
|
||||
object: 'luci.metablogizer',
|
||||
method: 'list_files',
|
||||
params: ['site_id'],
|
||||
expect: { result: {} }
|
||||
});
|
||||
|
||||
var callGetSettings = rpc.declare({
|
||||
object: 'luci.metablogizer',
|
||||
method: 'get_settings',
|
||||
expect: { result: {} }
|
||||
});
|
||||
|
||||
var callEnableTor = rpc.declare({
|
||||
object: 'luci.metablogizer',
|
||||
method: 'enable_tor',
|
||||
params: ['id'],
|
||||
expect: { result: {} }
|
||||
});
|
||||
|
||||
var callDisableTor = rpc.declare({
|
||||
object: 'luci.metablogizer',
|
||||
method: 'disable_tor',
|
||||
params: ['id'],
|
||||
expect: { result: {} }
|
||||
});
|
||||
|
||||
var callGetTorStatus = rpc.declare({
|
||||
object: 'luci.metablogizer',
|
||||
method: 'get_tor_status',
|
||||
params: ['id'],
|
||||
expect: { result: {} }
|
||||
});
|
||||
|
||||
var callRepairSite = rpc.declare({
|
||||
object: 'luci.metablogizer',
|
||||
method: 'repair_site',
|
||||
params: ['id'],
|
||||
expect: { result: {} }
|
||||
});
|
||||
|
||||
var callDiscoverVhosts = rpc.declare({
|
||||
object: 'luci.metablogizer',
|
||||
method: 'discover_vhosts',
|
||||
expect: { result: {} }
|
||||
});
|
||||
|
||||
var callImportVhost = rpc.declare({
|
||||
object: 'luci.metablogizer',
|
||||
method: 'import_vhost',
|
||||
params: ['instance', 'name', 'domain'],
|
||||
expect: { result: {} }
|
||||
});
|
||||
|
||||
var callSyncConfig = rpc.declare({
|
||||
object: 'luci.metablogizer',
|
||||
method: 'sync_config',
|
||||
expect: { result: {} }
|
||||
});
|
||||
|
||||
return baseclass.extend({
|
||||
getStatus: function() {
|
||||
return callStatus();
|
||||
},
|
||||
|
||||
listSites: function() {
|
||||
return callListSites().then(function(res) {
|
||||
return res.sites || [];
|
||||
});
|
||||
},
|
||||
|
||||
createSite: function(name, domain, giteaRepo, ssl, description) {
|
||||
return callCreateSite(name, domain, giteaRepo || '', ssl || '1', description || '');
|
||||
},
|
||||
|
||||
updateSite: function(id, name, domain, giteaRepo, ssl, enabled, description) {
|
||||
return callUpdateSite(id, name, domain, giteaRepo || '', ssl || '1', enabled || '1', description || '');
|
||||
},
|
||||
|
||||
deleteSite: function(id) {
|
||||
return callDeleteSite(id);
|
||||
},
|
||||
|
||||
syncSite: function(id) {
|
||||
return callSyncSite(id);
|
||||
},
|
||||
|
||||
getHostingStatus: function() {
|
||||
return callGetHostingStatus();
|
||||
},
|
||||
|
||||
checkSiteHealth: function(id) {
|
||||
return callCheckSiteHealth(id);
|
||||
},
|
||||
|
||||
getPublishInfo: function(id) {
|
||||
return callGetPublishInfo(id);
|
||||
},
|
||||
|
||||
uploadFile: function(siteId, filename, content) {
|
||||
return callUploadFile(siteId, filename, content);
|
||||
},
|
||||
|
||||
listFiles: function(siteId) {
|
||||
return callListFiles(siteId).then(function(res) {
|
||||
return res.files || [];
|
||||
});
|
||||
},
|
||||
|
||||
getSettings: function() {
|
||||
return callGetSettings();
|
||||
},
|
||||
|
||||
enableTor: function(id) {
|
||||
return callEnableTor(id);
|
||||
},
|
||||
|
||||
disableTor: function(id) {
|
||||
return callDisableTor(id);
|
||||
},
|
||||
|
||||
getTorStatus: function(id) {
|
||||
return callGetTorStatus(id);
|
||||
},
|
||||
|
||||
repairSite: function(id) {
|
||||
return callRepairSite(id);
|
||||
},
|
||||
|
||||
discoverVhosts: function() {
|
||||
return callDiscoverVhosts().then(function(res) {
|
||||
return res.discovered || [];
|
||||
});
|
||||
},
|
||||
|
||||
importVhost: function(instance, name, domain) {
|
||||
return callImportVhost(instance, name, domain);
|
||||
},
|
||||
|
||||
syncConfig: function() {
|
||||
return callSyncConfig();
|
||||
},
|
||||
|
||||
getDashboardData: function() {
|
||||
var self = this;
|
||||
return Promise.all([
|
||||
self.getStatus(),
|
||||
self.listSites(),
|
||||
self.getHostingStatus().catch(function() { return {}; })
|
||||
]).then(function(results) {
|
||||
return {
|
||||
status: results[0] || {},
|
||||
sites: results[1] || [],
|
||||
hosting: results[2] || {}
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,695 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require ui';
|
||||
'require fs';
|
||||
'require metablogizer.api as api';
|
||||
'require metablogizer/qrcode as qrcode';
|
||||
|
||||
return view.extend({
|
||||
status: {},
|
||||
sites: [],
|
||||
hosting: {},
|
||||
uploadFiles: [],
|
||||
currentSite: null,
|
||||
|
||||
load: function() {
|
||||
var self = this;
|
||||
return api.getDashboardData().then(function(data) {
|
||||
self.status = data.status || {};
|
||||
self.sites = data.sites || [];
|
||||
self.hosting = data.hosting || {};
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var self = this;
|
||||
var status = this.status;
|
||||
var sites = this.sites;
|
||||
var hosting = this.hosting;
|
||||
|
||||
return E('div', { 'class': 'cbi-map' }, [
|
||||
E('h2', {}, _('MetaBlogizer')),
|
||||
E('div', { 'class': 'cbi-map-descr' }, _('Static site publisher with HAProxy vhosts and SSL')),
|
||||
|
||||
// Status Section
|
||||
E('div', { 'class': 'cbi-section' }, [
|
||||
E('h3', {}, _('Status')),
|
||||
E('table', { 'class': 'table' }, [
|
||||
E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td', 'style': 'width:200px' }, _('Runtime')),
|
||||
E('td', { 'class': 'td' }, status.detected_runtime || 'uhttpd')
|
||||
]),
|
||||
E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td' }, _('HAProxy')),
|
||||
E('td', { 'class': 'td' }, hosting.haproxy_status === 'running' ?
|
||||
E('span', { 'style': 'color:#0a0' }, _('Running')) :
|
||||
E('span', { 'style': 'color:#a00' }, _('Stopped')))
|
||||
]),
|
||||
E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td' }, _('Public IP')),
|
||||
E('td', { 'class': 'td' }, hosting.public_ip || '-')
|
||||
]),
|
||||
E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td' }, _('Sites')),
|
||||
E('td', { 'class': 'td' }, String(sites.length))
|
||||
]),
|
||||
E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td' }, _('Backends Running')),
|
||||
E('td', { 'class': 'td' }, String(sites.filter(function(s) { return s.backend_running; }).length) + ' / ' + sites.length)
|
||||
])
|
||||
]),
|
||||
E('div', { 'style': 'margin-top:1em' }, [
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
'click': ui.createHandlerFn(this, 'handleSyncConfig')
|
||||
}, _('Sync Config')),
|
||||
' ',
|
||||
E('span', { 'class': 'cbi-value-description' }, _('Update port/runtime info for all sites'))
|
||||
])
|
||||
]),
|
||||
|
||||
// Sites Section
|
||||
E('div', { 'class': 'cbi-section' }, [
|
||||
E('h3', {}, _('Sites')),
|
||||
sites.length > 0 ? this.renderSitesTable(sites) : E('div', { 'class': 'cbi-section-descr' }, _('No sites configured'))
|
||||
]),
|
||||
|
||||
// Create Site Section
|
||||
E('div', { 'class': 'cbi-section' }, [
|
||||
E('h3', {}, _('Create Site')),
|
||||
E('div', { 'class': 'cbi-section-descr' }, _('Add a new static site with auto-configured HAProxy vhost and SSL')),
|
||||
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Site Name')),
|
||||
E('div', { 'class': 'cbi-value-field' }, [
|
||||
E('input', { 'type': 'text', 'id': 'new-site-name', 'class': 'cbi-input-text',
|
||||
'placeholder': 'myblog' }),
|
||||
E('div', { 'class': 'cbi-value-description' }, _('Lowercase letters, numbers, and hyphens only'))
|
||||
])
|
||||
]),
|
||||
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Domain')),
|
||||
E('div', { 'class': 'cbi-value-field' },
|
||||
E('input', { 'type': 'text', 'id': 'new-site-domain', 'class': 'cbi-input-text',
|
||||
'placeholder': 'blog.example.com' }))
|
||||
]),
|
||||
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Gitea Repository')),
|
||||
E('div', { 'class': 'cbi-value-field' }, [
|
||||
E('input', { 'type': 'text', 'id': 'new-site-gitea', 'class': 'cbi-input-text',
|
||||
'placeholder': 'user/repo' }),
|
||||
E('div', { 'class': 'cbi-value-description' }, _('Optional: Sync content from Gitea'))
|
||||
])
|
||||
]),
|
||||
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Description')),
|
||||
E('div', { 'class': 'cbi-value-field' },
|
||||
E('input', { 'type': 'text', 'id': 'new-site-desc', 'class': 'cbi-input-text',
|
||||
'placeholder': 'Short description (optional)' }))
|
||||
]),
|
||||
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('HTTPS')),
|
||||
E('div', { 'class': 'cbi-value-field' },
|
||||
E('select', { 'id': 'new-site-ssl', 'class': 'cbi-input-select' }, [
|
||||
E('option', { 'value': '1', 'selected': true }, _('Enabled (ACME)')),
|
||||
E('option', { 'value': '0' }, _('Disabled'))
|
||||
]))
|
||||
]),
|
||||
|
||||
E('div', { 'class': 'cbi-page-actions' },
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-positive',
|
||||
'click': ui.createHandlerFn(this, 'handleCreateSite')
|
||||
}, _('Create Site')))
|
||||
]),
|
||||
|
||||
// Hosting Status Section
|
||||
this.renderHostingSection(hosting)
|
||||
]);
|
||||
},
|
||||
|
||||
renderSitesTable: function(sites) {
|
||||
var self = this;
|
||||
return E('table', { 'class': 'table' }, [
|
||||
E('tr', { 'class': 'tr table-titles' }, [
|
||||
E('th', { 'class': 'th' }, _('Name')),
|
||||
E('th', { 'class': 'th' }, _('Domain')),
|
||||
E('th', { 'class': 'th' }, _('Port')),
|
||||
E('th', { 'class': 'th' }, _('Backend')),
|
||||
E('th', { 'class': 'th' }, _('Content')),
|
||||
E('th', { 'class': 'th' }, _('Actions'))
|
||||
])
|
||||
].concat(sites.map(function(site) {
|
||||
var backendStatus = site.backend_running ?
|
||||
E('span', { 'style': 'color:#0a0' }, _('Running')) :
|
||||
E('span', { 'style': 'color:#a00' }, _('Stopped'));
|
||||
var contentStatus = site.has_content ?
|
||||
E('span', { 'style': 'color:#0a0' }, _('OK')) :
|
||||
E('span', { 'style': 'color:#888' }, _('Empty'));
|
||||
|
||||
return E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td' }, [
|
||||
E('strong', {}, site.name),
|
||||
site.runtime ? E('br') : '',
|
||||
site.runtime ? E('small', { 'style': 'color:#888' }, site.runtime) : ''
|
||||
]),
|
||||
E('td', { 'class': 'td' }, site.domain ?
|
||||
E('a', { 'href': site.url || ('https://' + site.domain), 'target': '_blank' }, site.domain) :
|
||||
E('em', {}, '-')),
|
||||
E('td', { 'class': 'td' }, site.port ? String(site.port) : '-'),
|
||||
E('td', { 'class': 'td' }, backendStatus),
|
||||
E('td', { 'class': 'td' }, contentStatus),
|
||||
E('td', { 'class': 'td' }, [
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
'click': ui.createHandlerFn(self, 'showShareModal', site),
|
||||
'title': _('Share')
|
||||
}, _('Share')),
|
||||
' ',
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
'click': ui.createHandlerFn(self, 'showUploadModal', site),
|
||||
'title': _('Upload')
|
||||
}, _('Upload')),
|
||||
' ',
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
'click': ui.createHandlerFn(self, 'showFilesModal', site),
|
||||
'title': _('Files')
|
||||
}, _('Files')),
|
||||
' ',
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
'click': ui.createHandlerFn(self, 'showEditModal', site),
|
||||
'title': _('Edit')
|
||||
}, _('Edit')),
|
||||
' ',
|
||||
site.gitea_repo ? E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
'click': ui.createHandlerFn(self, 'handleSync', site),
|
||||
'title': _('Sync')
|
||||
}, _('Sync')) : '',
|
||||
' ',
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-remove',
|
||||
'click': ui.createHandlerFn(self, 'handleDelete', site),
|
||||
'title': _('Delete')
|
||||
}, _('Delete'))
|
||||
])
|
||||
]);
|
||||
})));
|
||||
},
|
||||
|
||||
renderHostingSection: function(hosting) {
|
||||
var hostingSites = hosting.sites || [];
|
||||
if (hostingSites.length === 0) return E('div');
|
||||
|
||||
return E('div', { 'class': 'cbi-section' }, [
|
||||
E('h3', {}, _('Hosting Status')),
|
||||
E('div', { 'class': 'cbi-section-descr' }, _('DNS, SSL certificates, and publish status for each site')),
|
||||
E('table', { 'class': 'table' }, [
|
||||
E('tr', { 'class': 'tr table-titles' }, [
|
||||
E('th', { 'class': 'th' }, _('Site')),
|
||||
E('th', { 'class': 'th' }, _('DNS')),
|
||||
E('th', { 'class': 'th' }, _('IP')),
|
||||
E('th', { 'class': 'th' }, _('Certificate')),
|
||||
E('th', { 'class': 'th' }, _('Status'))
|
||||
])
|
||||
].concat(hostingSites.map(function(site) {
|
||||
return E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td' }, E('strong', {}, site.name)),
|
||||
E('td', { 'class': 'td' }, site.dns_status === 'ok' ?
|
||||
E('span', { 'style': 'color:#0a0' }, 'OK') :
|
||||
E('span', { 'style': 'color:#a00' }, site.dns_status || 'unknown')),
|
||||
E('td', { 'class': 'td' }, site.dns_ip || '-'),
|
||||
E('td', { 'class': 'td' }, site.cert_status === 'ok' ?
|
||||
E('span', { 'style': 'color:#0a0' }, (site.cert_days || 0) + 'd') :
|
||||
E('span', { 'style': 'color:#a00' }, site.cert_status || 'missing')),
|
||||
E('td', { 'class': 'td' }, site.publish_status === 'published' ?
|
||||
E('span', { 'style': 'color:#0a0' }, _('Published')) :
|
||||
E('span', { 'style': 'color:#888' }, site.publish_status || 'pending'))
|
||||
]);
|
||||
})))
|
||||
]);
|
||||
},
|
||||
|
||||
handleCreateSite: function() {
|
||||
var self = this;
|
||||
var name = document.getElementById('new-site-name').value.trim();
|
||||
var domain = document.getElementById('new-site-domain').value.trim();
|
||||
var gitea = document.getElementById('new-site-gitea').value.trim();
|
||||
var desc = document.getElementById('new-site-desc').value.trim();
|
||||
var ssl = document.getElementById('new-site-ssl').value;
|
||||
|
||||
if (!name) {
|
||||
ui.addNotification(null, E('p', _('Site name is required')), 'error');
|
||||
return;
|
||||
}
|
||||
if (!domain) {
|
||||
ui.addNotification(null, E('p', _('Domain is required')), 'error');
|
||||
return;
|
||||
}
|
||||
if (!/^[a-z0-9-]+$/.test(name)) {
|
||||
ui.addNotification(null, E('p', _('Invalid name format: use lowercase letters, numbers, and hyphens')), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
ui.showModal(_('Creating Site'), [
|
||||
E('p', { 'class': 'spinning' }, _('Setting up site and HAProxy vhost...'))
|
||||
]);
|
||||
|
||||
api.createSite(name, domain, gitea, ssl, desc).then(function(r) {
|
||||
ui.hideModal();
|
||||
if (r.success) {
|
||||
ui.addNotification(null, E('p', _('Site created successfully')));
|
||||
self.showShareModal({ name: r.name || name, domain: r.domain || domain, url: r.url });
|
||||
setTimeout(function() { window.location.reload(); }, 500);
|
||||
} else {
|
||||
ui.addNotification(null, E('p', _('Failed: ') + (r.error || 'Unknown error')), 'error');
|
||||
}
|
||||
}).catch(function(e) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', _('Error: ') + e.message), 'error');
|
||||
});
|
||||
},
|
||||
|
||||
showEditModal: function(site) {
|
||||
var self = this;
|
||||
ui.showModal(_('Edit Site: ') + site.name, [
|
||||
E('div', { 'class': 'cbi-section' }, [
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Domain')),
|
||||
E('div', { 'class': 'cbi-value-field' },
|
||||
E('input', { 'type': 'text', 'id': 'edit-site-domain', 'class': 'cbi-input-text',
|
||||
'value': site.domain || '' }))
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Gitea Repository')),
|
||||
E('div', { 'class': 'cbi-value-field' },
|
||||
E('input', { 'type': 'text', 'id': 'edit-site-gitea', 'class': 'cbi-input-text',
|
||||
'value': site.gitea_repo || '', 'placeholder': 'user/repo' }))
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Description')),
|
||||
E('div', { 'class': 'cbi-value-field' },
|
||||
E('input', { 'type': 'text', 'id': 'edit-site-desc', 'class': 'cbi-input-text',
|
||||
'value': site.description || '' }))
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('HTTPS')),
|
||||
E('div', { 'class': 'cbi-value-field' },
|
||||
E('select', { 'id': 'edit-site-ssl', 'class': 'cbi-input-select' }, [
|
||||
E('option', { 'value': '1', 'selected': site.ssl !== false }, _('Enabled')),
|
||||
E('option', { 'value': '0', 'selected': site.ssl === false }, _('Disabled'))
|
||||
]))
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Enabled')),
|
||||
E('div', { 'class': 'cbi-value-field' },
|
||||
E('select', { 'id': 'edit-site-enabled', 'class': 'cbi-input-select' }, [
|
||||
E('option', { 'value': '1', 'selected': site.enabled !== false }, _('Yes')),
|
||||
E('option', { 'value': '0', 'selected': site.enabled === false }, _('No'))
|
||||
]))
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'right' }, [
|
||||
E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, _('Cancel')),
|
||||
' ',
|
||||
E('button', { 'class': 'cbi-button cbi-button-positive', 'click': function() {
|
||||
var domain = document.getElementById('edit-site-domain').value.trim();
|
||||
var gitea = document.getElementById('edit-site-gitea').value.trim();
|
||||
var desc = document.getElementById('edit-site-desc').value.trim();
|
||||
var ssl = document.getElementById('edit-site-ssl').value;
|
||||
var enabled = document.getElementById('edit-site-enabled').value;
|
||||
|
||||
if (!domain) {
|
||||
ui.addNotification(null, E('p', _('Domain required')), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
ui.hideModal();
|
||||
ui.showModal(_('Saving'), [E('p', { 'class': 'spinning' }, _('Updating site...'))]);
|
||||
|
||||
api.updateSite(site.id, site.name, domain, gitea, ssl, enabled, desc).then(function(r) {
|
||||
ui.hideModal();
|
||||
if (r.success) {
|
||||
ui.addNotification(null, E('p', _('Site updated')));
|
||||
window.location.reload();
|
||||
} else {
|
||||
ui.addNotification(null, E('p', _('Failed: ') + (r.error || 'Unknown')), 'error');
|
||||
}
|
||||
}).catch(function(e) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', _('Error: ') + e.message), 'error');
|
||||
});
|
||||
}}, _('Save'))
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
showUploadModal: function(site) {
|
||||
var self = this;
|
||||
this.uploadFiles = [];
|
||||
this.currentSite = site;
|
||||
|
||||
var fileList = E('div', { 'id': 'upload-file-list', 'style': 'margin:1em 0; max-height:200px; overflow-y:auto' });
|
||||
|
||||
ui.showModal(_('Upload to: ') + site.name, [
|
||||
E('div', { 'class': 'cbi-section' }, [
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Files')),
|
||||
E('div', { 'class': 'cbi-value-field' }, [
|
||||
E('input', { 'type': 'file', 'id': 'upload-file-input', 'multiple': true,
|
||||
'change': function(e) { self.handleFileSelect(e, fileList); } }),
|
||||
E('div', { 'class': 'cbi-value-description' }, _('Select HTML, CSS, JS, images, etc.'))
|
||||
])
|
||||
]),
|
||||
fileList,
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Set first HTML as index')),
|
||||
E('div', { 'class': 'cbi-value-field' },
|
||||
E('input', { 'type': 'checkbox', 'id': 'upload-as-index', 'checked': true }))
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, ''),
|
||||
E('div', { 'class': 'cbi-value-field cbi-value-description' },
|
||||
_('After upload, use Ctrl+Shift+R to refresh cached pages'))
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'right' }, [
|
||||
E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, _('Cancel')),
|
||||
' ',
|
||||
E('button', { 'class': 'cbi-button cbi-button-positive', 'click': ui.createHandlerFn(this, 'handleUpload') }, _('Upload'))
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
handleFileSelect: function(e, listEl) {
|
||||
var files = e.target.files;
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
this.uploadFiles.push(files[i]);
|
||||
}
|
||||
this.updateFileList(listEl);
|
||||
},
|
||||
|
||||
updateFileList: function(listEl) {
|
||||
var self = this;
|
||||
listEl.innerHTML = '';
|
||||
if (this.uploadFiles.length === 0) return;
|
||||
|
||||
this.uploadFiles.forEach(function(f, i) {
|
||||
var row = E('div', { 'style': 'display:flex; align-items:center; gap:0.5em; margin:0.25em 0; padding:0.25em; background:#f8f8f8; border-radius:4px' }, [
|
||||
E('span', { 'style': 'flex:1' }, f.name),
|
||||
E('span', { 'style': 'color:#888; font-size:0.9em' }, self.formatSize(f.size)),
|
||||
E('button', { 'class': 'cbi-button cbi-button-remove', 'style': 'padding:0.25em 0.5em',
|
||||
'click': function() { self.uploadFiles.splice(i, 1); self.updateFileList(listEl); } }, 'X')
|
||||
]);
|
||||
listEl.appendChild(row);
|
||||
});
|
||||
},
|
||||
|
||||
handleUpload: function() {
|
||||
var self = this;
|
||||
if (!this.uploadFiles.length) {
|
||||
ui.addNotification(null, E('p', _('No files selected')), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
var site = this.currentSite;
|
||||
var asIndex = document.getElementById('upload-as-index').checked;
|
||||
var firstHtml = null;
|
||||
|
||||
if (asIndex) {
|
||||
for (var i = 0; i < this.uploadFiles.length; i++) {
|
||||
if (this.uploadFiles[i].name.endsWith('.html')) {
|
||||
firstHtml = this.uploadFiles[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ui.hideModal();
|
||||
ui.showModal(_('Uploading'), [E('p', { 'class': 'spinning' }, _('Uploading files...'))]);
|
||||
|
||||
var sitesRoot = '/srv/metablogizer/sites';
|
||||
Promise.all(this.uploadFiles.map(function(f) {
|
||||
return new Promise(function(resolve) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
var dest = (asIndex && f === firstHtml) ? 'index.html' : f.name;
|
||||
fs.write(sitesRoot + '/' + site.name + '/' + dest, e.target.result)
|
||||
.then(function() { resolve({ ok: true, name: f.name }); })
|
||||
.catch(function() { resolve({ ok: false, name: f.name }); });
|
||||
};
|
||||
reader.onerror = function() { resolve({ ok: false, name: f.name }); };
|
||||
reader.readAsText(f);
|
||||
});
|
||||
})).then(function(results) {
|
||||
ui.hideModal();
|
||||
var ok = results.filter(function(r) { return r.ok; }).length;
|
||||
ui.addNotification(null, E('p', ok + _(' file(s) uploaded successfully')));
|
||||
self.uploadFiles = [];
|
||||
});
|
||||
},
|
||||
|
||||
showFilesModal: function(site) {
|
||||
var self = this;
|
||||
this.currentSite = site;
|
||||
var sitesRoot = '/srv/metablogizer/sites';
|
||||
|
||||
ui.showModal(_('Files: ') + site.name, [
|
||||
E('div', { 'id': 'files-list' }, [
|
||||
E('p', { 'class': 'spinning' }, _('Loading files...'))
|
||||
]),
|
||||
E('div', { 'class': 'right' }, [
|
||||
E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, _('Close'))
|
||||
])
|
||||
]);
|
||||
|
||||
fs.list(sitesRoot + '/' + site.name).then(function(files) {
|
||||
var container = document.getElementById('files-list');
|
||||
container.innerHTML = '';
|
||||
|
||||
if (!files || !files.length) {
|
||||
container.appendChild(E('p', { 'style': 'color:#888' }, _('No files')));
|
||||
return;
|
||||
}
|
||||
|
||||
var table = E('table', { 'class': 'table' }, [
|
||||
E('tr', { 'class': 'tr table-titles' }, [
|
||||
E('th', { 'class': 'th' }, _('File')),
|
||||
E('th', { 'class': 'th' }, _('Size')),
|
||||
E('th', { 'class': 'th' }, _('Actions'))
|
||||
])
|
||||
]);
|
||||
|
||||
files.forEach(function(f) {
|
||||
if (f.type !== 'file') return;
|
||||
var isIndex = f.name === 'index.html';
|
||||
table.appendChild(E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td' }, [
|
||||
isIndex ? E('strong', {}, f.name + ' (homepage)') : f.name
|
||||
]),
|
||||
E('td', { 'class': 'td' }, self.formatSize(f.size)),
|
||||
E('td', { 'class': 'td' }, [
|
||||
(!isIndex && f.name.endsWith('.html')) ? E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
'click': function() { self.setAsHomepage(site, f.name); }
|
||||
}, _('Set as Homepage')) : '',
|
||||
' ',
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-remove',
|
||||
'click': function() { self.deleteFile(site, f.name); }
|
||||
}, _('Delete'))
|
||||
])
|
||||
]));
|
||||
});
|
||||
|
||||
container.appendChild(table);
|
||||
}).catch(function(e) {
|
||||
var container = document.getElementById('files-list');
|
||||
container.innerHTML = '';
|
||||
container.appendChild(E('p', { 'style': 'color:#a00' }, _('Error: ') + e.message));
|
||||
});
|
||||
},
|
||||
|
||||
setAsHomepage: function(site, filename) {
|
||||
var sitesRoot = '/srv/metablogizer/sites';
|
||||
var path = sitesRoot + '/' + site.name;
|
||||
|
||||
ui.showModal(_('Setting Homepage'), [E('p', { 'class': 'spinning' }, _('Renaming...'))]);
|
||||
|
||||
fs.read(path + '/' + filename).then(function(content) {
|
||||
return fs.write(path + '/index.html', content);
|
||||
}).then(function() {
|
||||
return fs.remove(path + '/' + filename);
|
||||
}).then(function() {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', filename + _(' set as homepage')));
|
||||
}).catch(function(e) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', _('Error: ') + e.message), 'error');
|
||||
});
|
||||
},
|
||||
|
||||
deleteFile: function(site, filename) {
|
||||
var self = this;
|
||||
var sitesRoot = '/srv/metablogizer/sites';
|
||||
|
||||
if (!confirm(_('Delete ') + filename + '?')) return;
|
||||
|
||||
fs.remove(sitesRoot + '/' + site.name + '/' + filename).then(function() {
|
||||
ui.addNotification(null, E('p', _('File deleted')));
|
||||
self.showFilesModal(site);
|
||||
}).catch(function(e) {
|
||||
ui.addNotification(null, E('p', _('Error: ') + e.message), 'error');
|
||||
});
|
||||
},
|
||||
|
||||
showShareModal: function(site) {
|
||||
var self = this;
|
||||
var url = site.url || ('https://' + site.domain);
|
||||
var title = site.name + ' - SecuBox';
|
||||
var enc = encodeURIComponent;
|
||||
|
||||
var qrSvg = '';
|
||||
try {
|
||||
qrSvg = qrcode.generateSVG(url, 180);
|
||||
} catch (e) {
|
||||
qrSvg = '<p>QR code unavailable</p>';
|
||||
}
|
||||
|
||||
ui.showModal(_('Share: ') + site.name, [
|
||||
E('div', { 'style': 'text-align:center' }, [
|
||||
E('div', { 'class': 'cbi-value', 'style': 'display:flex; gap:0.5em; margin-bottom:1em' }, [
|
||||
E('input', { 'type': 'text', 'readonly': true, 'value': url, 'id': 'share-url',
|
||||
'class': 'cbi-input-text', 'style': 'flex:1' }),
|
||||
E('button', { 'class': 'cbi-button cbi-button-action', 'click': function() {
|
||||
self.copyToClipboard(url);
|
||||
}}, _('Copy'))
|
||||
]),
|
||||
E('div', { 'style': 'display:inline-block; padding:1em; background:#f8f8f8; border-radius:8px; margin:1em 0' }, [
|
||||
E('div', { 'innerHTML': qrSvg })
|
||||
]),
|
||||
E('div', { 'style': 'margin-top:1em' }, [
|
||||
E('p', { 'style': 'margin-bottom:0.5em' }, _('Share on:')),
|
||||
E('div', { 'style': 'display:flex; gap:0.5em; justify-content:center; flex-wrap:wrap' }, [
|
||||
E('a', { 'href': 'https://twitter.com/intent/tweet?url=' + enc(url) + '&text=' + enc(title),
|
||||
'target': '_blank', 'class': 'cbi-button cbi-button-action' }, 'Twitter'),
|
||||
E('a', { 'href': 'https://www.linkedin.com/sharing/share-offsite/?url=' + enc(url),
|
||||
'target': '_blank', 'class': 'cbi-button cbi-button-action' }, 'LinkedIn'),
|
||||
E('a', { 'href': 'https://t.me/share/url?url=' + enc(url) + '&text=' + enc(title),
|
||||
'target': '_blank', 'class': 'cbi-button cbi-button-action' }, 'Telegram'),
|
||||
E('a', { 'href': 'https://wa.me/?text=' + enc(title + ' ' + url),
|
||||
'target': '_blank', 'class': 'cbi-button cbi-button-action' }, 'WhatsApp'),
|
||||
E('a', { 'href': 'mailto:?subject=' + enc(title) + '&body=' + enc(url),
|
||||
'class': 'cbi-button cbi-button-action' }, 'Email')
|
||||
])
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'right', 'style': 'margin-top:1em' }, [
|
||||
E('a', { 'href': url, 'target': '_blank', 'class': 'cbi-button cbi-button-positive',
|
||||
'style': 'text-decoration:none' }, _('Visit Site')),
|
||||
' ',
|
||||
E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, _('Close'))
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
handleSync: function(site) {
|
||||
ui.showModal(_('Syncing'), [E('p', { 'class': 'spinning' }, _('Pulling from Gitea...'))]);
|
||||
|
||||
api.syncSite(site.id).then(function(r) {
|
||||
ui.hideModal();
|
||||
if (r.success) {
|
||||
ui.addNotification(null, E('p', _('Site synced successfully')));
|
||||
} else {
|
||||
ui.addNotification(null, E('p', _('Sync failed: ') + (r.error || 'Unknown')), 'error');
|
||||
}
|
||||
}).catch(function(e) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', _('Error: ') + e.message), 'error');
|
||||
});
|
||||
},
|
||||
|
||||
handleDelete: function(site) {
|
||||
var self = this;
|
||||
ui.showModal(_('Delete Site'), [
|
||||
E('p', {}, _('Are you sure you want to delete "') + site.name + '"?'),
|
||||
E('p', { 'style': 'color:#a00' }, _('This will remove the site, HAProxy vhost, and all files.')),
|
||||
E('div', { 'class': 'right', 'style': 'margin-top:1em' }, [
|
||||
E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, _('Cancel')),
|
||||
' ',
|
||||
E('button', { 'class': 'cbi-button cbi-button-remove', 'click': function() {
|
||||
ui.hideModal();
|
||||
ui.showModal(_('Deleting'), [E('p', { 'class': 'spinning' }, _('Removing site...'))]);
|
||||
|
||||
api.deleteSite(site.id).then(function(r) {
|
||||
ui.hideModal();
|
||||
if (r.success) {
|
||||
ui.addNotification(null, E('p', _('Site deleted')));
|
||||
window.location.reload();
|
||||
} else {
|
||||
ui.addNotification(null, E('p', _('Failed: ') + (r.error || 'Unknown')), 'error');
|
||||
}
|
||||
}).catch(function(e) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', _('Error: ') + e.message), 'error');
|
||||
});
|
||||
}}, _('Delete'))
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
copyToClipboard: function(text) {
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
ui.addNotification(null, E('p', _('URL copied to clipboard')));
|
||||
});
|
||||
} else {
|
||||
var input = document.getElementById('share-url');
|
||||
if (input) {
|
||||
input.select();
|
||||
document.execCommand('copy');
|
||||
ui.addNotification(null, E('p', _('URL copied to clipboard')));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleSyncConfig: function() {
|
||||
ui.showModal(_('Syncing Configuration'), [
|
||||
E('p', { 'class': 'spinning' }, _('Updating port and runtime info for all sites...'))
|
||||
]);
|
||||
|
||||
api.syncConfig().then(function(r) {
|
||||
ui.hideModal();
|
||||
if (r.success) {
|
||||
var msg = _('Configuration synced');
|
||||
if (r.fixed > 0) {
|
||||
msg += ' (' + r.fixed + _(' entries updated)');
|
||||
}
|
||||
ui.addNotification(null, E('p', msg));
|
||||
window.location.reload();
|
||||
} else {
|
||||
ui.addNotification(null, E('p', _('Sync failed: ') + (r.error || 'Unknown')), 'error');
|
||||
}
|
||||
}).catch(function(e) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', _('Error: ') + e.message), 'error');
|
||||
});
|
||||
},
|
||||
|
||||
formatSize: function(bytes) {
|
||||
if (bytes < 1024) return bytes + ' B';
|
||||
if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
|
||||
return (bytes / 1048576).toFixed(1) + ' MB';
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
@ -213,8 +213,8 @@ method_list_sites() {
|
||||
|
||||
_add_site() {
|
||||
local section="$1"
|
||||
local name domain gitea_repo ssl enabled description tor_enabled
|
||||
local has_content last_sync onion_address
|
||||
local name domain gitea_repo ssl enabled description tor_enabled port runtime
|
||||
local has_content last_sync onion_address backend_running
|
||||
|
||||
config_get name "$section" name ""
|
||||
config_get domain "$section" domain ""
|
||||
@ -223,6 +223,8 @@ _add_site() {
|
||||
config_get enabled "$section" enabled "1"
|
||||
config_get description "$section" description ""
|
||||
config_get tor_enabled "$section" tor_enabled "0"
|
||||
config_get port "$section" port ""
|
||||
config_get runtime "$section" runtime ""
|
||||
|
||||
# Check if site has content
|
||||
has_content="0"
|
||||
@ -242,6 +244,16 @@ _add_site() {
|
||||
onion_address=$(get_onion_address "$name")
|
||||
fi
|
||||
|
||||
# Check if backend is running (uhttpd listening on port)
|
||||
backend_running="0"
|
||||
if [ -n "$port" ]; then
|
||||
# Check if port is listening using /proc/net/tcp (hex port)
|
||||
local hex_port=$(printf '%04X' "$port" 2>/dev/null)
|
||||
if grep -qi ":${hex_port}" /proc/net/tcp 2>/dev/null; then
|
||||
backend_running="1"
|
||||
fi
|
||||
fi
|
||||
|
||||
json_add_object
|
||||
json_add_string "id" "$section"
|
||||
json_add_string "name" "$name"
|
||||
@ -253,6 +265,9 @@ _add_site() {
|
||||
json_add_boolean "has_content" "$has_content"
|
||||
json_add_string "last_sync" "$last_sync"
|
||||
json_add_string "url" "https://$domain"
|
||||
[ -n "$port" ] && json_add_int "port" "$port"
|
||||
[ -n "$runtime" ] && json_add_string "runtime" "$runtime"
|
||||
json_add_boolean "backend_running" "$backend_running"
|
||||
|
||||
# Tor hidden service info
|
||||
json_add_boolean "tor_enabled" "$(has_tor_service "$name" && echo 1 || echo 0)"
|
||||
@ -1623,6 +1638,149 @@ method_save_settings() {
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Discover uhttpd vhosts not tracked in metablogizer
|
||||
method_discover_vhosts() {
|
||||
SITES_ROOT=$(get_uci main sites_root "$SITES_ROOT")
|
||||
|
||||
json_init
|
||||
json_add_array "discovered"
|
||||
|
||||
# Find all uhttpd instances with metablog prefix
|
||||
uci show uhttpd 2>/dev/null | grep "=uhttpd" | while read -r line; do
|
||||
local instance=$(echo "$line" | cut -d'=' -f1 | cut -d'.' -f2)
|
||||
|
||||
# Skip non-metablog instances
|
||||
case "$instance" in
|
||||
metablog_*) ;;
|
||||
*) continue ;;
|
||||
esac
|
||||
|
||||
# Extract site ID from instance name (metablog_site_xxx -> site_xxx)
|
||||
local section_id="${instance#metablog_}"
|
||||
|
||||
# Check if this site exists in metablogizer config
|
||||
local tracked_name=$(get_uci "$section_id" name "")
|
||||
|
||||
# If not tracked, discover it
|
||||
if [ -z "$tracked_name" ]; then
|
||||
local home=$(uci -q get "uhttpd.${instance}.home")
|
||||
local listen=$(uci -q get "uhttpd.${instance}.listen_http")
|
||||
local port=$(echo "$listen" | sed 's/.*://')
|
||||
local name=$(basename "$home" 2>/dev/null)
|
||||
|
||||
if [ -n "$name" ] && [ -n "$port" ]; then
|
||||
json_add_object
|
||||
json_add_string "instance" "$instance"
|
||||
json_add_string "section_id" "$section_id"
|
||||
json_add_string "name" "$name"
|
||||
json_add_string "home" "$home"
|
||||
json_add_int "port" "$port"
|
||||
json_add_boolean "has_content" "$([ -f "$home/index.html" ] && echo 1 || echo 0)"
|
||||
json_close_object
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
json_close_array
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Import a discovered uhttpd vhost into metablogizer
|
||||
method_import_vhost() {
|
||||
local instance name domain
|
||||
|
||||
read -r input
|
||||
json_load "$input"
|
||||
json_get_var instance instance
|
||||
json_get_var name name
|
||||
json_get_var domain domain
|
||||
|
||||
if [ -z "$instance" ] || [ -z "$name" ]; then
|
||||
json_init
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Missing instance or name"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
# Get uhttpd instance details
|
||||
local home=$(uci -q get "uhttpd.${instance}.home")
|
||||
local listen=$(uci -q get "uhttpd.${instance}.listen_http")
|
||||
local port=$(echo "$listen" | sed 's/.*://')
|
||||
|
||||
if [ -z "$home" ] || [ -z "$port" ]; then
|
||||
json_init
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "uhttpd instance not found or invalid"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
# Extract section_id from instance name
|
||||
local section_id="${instance#metablog_}"
|
||||
|
||||
# Create metablogizer site entry
|
||||
uci set "$UCI_CONFIG.$section_id=site"
|
||||
uci set "$UCI_CONFIG.$section_id.name=$name"
|
||||
uci set "$UCI_CONFIG.$section_id.domain=${domain:-$name.local}"
|
||||
uci set "$UCI_CONFIG.$section_id.port=$port"
|
||||
uci set "$UCI_CONFIG.$section_id.runtime=uhttpd"
|
||||
uci set "$UCI_CONFIG.$section_id.ssl=1"
|
||||
uci set "$UCI_CONFIG.$section_id.enabled=1"
|
||||
uci commit "$UCI_CONFIG"
|
||||
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "id" "$section_id"
|
||||
json_add_string "name" "$name"
|
||||
json_add_int "port" "$port"
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Sync all sites - ensure all have correct port/runtime in UCI
|
||||
method_sync_config() {
|
||||
local fixed=0
|
||||
|
||||
# Iterate through all metablog uhttpd instances
|
||||
uci show uhttpd 2>/dev/null | grep "=uhttpd" | while read -r line; do
|
||||
local instance=$(echo "$line" | cut -d'=' -f1 | cut -d'.' -f2)
|
||||
|
||||
case "$instance" in
|
||||
metablog_*) ;;
|
||||
*) continue ;;
|
||||
esac
|
||||
|
||||
local section_id="${instance#metablog_}"
|
||||
local listen=$(uci -q get "uhttpd.${instance}.listen_http")
|
||||
local port=$(echo "$listen" | sed 's/.*://')
|
||||
local home=$(uci -q get "uhttpd.${instance}.home")
|
||||
|
||||
# Check if site exists in metablogizer
|
||||
local tracked_name=$(get_uci "$section_id" name "")
|
||||
if [ -n "$tracked_name" ]; then
|
||||
# Update port and runtime if missing
|
||||
local current_port=$(get_uci "$section_id" port "")
|
||||
local current_runtime=$(get_uci "$section_id" runtime "")
|
||||
|
||||
if [ -z "$current_port" ] && [ -n "$port" ]; then
|
||||
uci set "$UCI_CONFIG.$section_id.port=$port"
|
||||
fixed=$((fixed + 1))
|
||||
fi
|
||||
if [ -z "$current_runtime" ]; then
|
||||
uci set "$UCI_CONFIG.$section_id.runtime=uhttpd"
|
||||
fixed=$((fixed + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
uci commit "$UCI_CONFIG"
|
||||
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
json_add_int "fixed" "$fixed"
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Main RPC interface
|
||||
case "$1" in
|
||||
list)
|
||||
@ -1645,7 +1803,10 @@ case "$1" in
|
||||
"repair_site": { "id": "string" },
|
||||
"enable_tor": { "id": "string" },
|
||||
"disable_tor": { "id": "string" },
|
||||
"get_tor_status": {}
|
||||
"get_tor_status": {},
|
||||
"discover_vhosts": {},
|
||||
"import_vhost": { "instance": "string", "name": "string", "domain": "string" },
|
||||
"sync_config": {}
|
||||
}
|
||||
EOF
|
||||
;;
|
||||
@ -1669,6 +1830,9 @@ EOF
|
||||
enable_tor) method_enable_tor ;;
|
||||
disable_tor) method_disable_tor ;;
|
||||
get_tor_status) method_get_tor_status ;;
|
||||
discover_vhosts) method_discover_vhosts ;;
|
||||
import_vhost) method_import_vhost ;;
|
||||
sync_config) method_sync_config ;;
|
||||
*) echo '{"error": "unknown method"}' ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
@ -10,12 +10,12 @@
|
||||
"uci": {"metablogizer": true}
|
||||
}
|
||||
},
|
||||
"admin/services/metablogizer/overview": {
|
||||
"title": "Sites",
|
||||
"admin/services/metablogizer/dashboard": {
|
||||
"title": "Dashboard",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "metablogizer/overview"
|
||||
"path": "metablogizer/dashboard"
|
||||
}
|
||||
},
|
||||
"admin/services/metablogizer/settings": {
|
||||
|
||||
@ -10,7 +10,9 @@
|
||||
"get_publish_info",
|
||||
"get_settings",
|
||||
"get_hosting_status",
|
||||
"check_site_health"
|
||||
"check_site_health",
|
||||
"get_tor_status",
|
||||
"discover_vhosts"
|
||||
],
|
||||
"file": ["read", "list", "stat"]
|
||||
},
|
||||
@ -28,7 +30,12 @@
|
||||
"sync_site",
|
||||
"save_settings",
|
||||
"upload_file",
|
||||
"list_files"
|
||||
"list_files",
|
||||
"repair_site",
|
||||
"enable_tor",
|
||||
"disable_tor",
|
||||
"import_vhost",
|
||||
"sync_config"
|
||||
],
|
||||
"luci.haproxy": [
|
||||
"create_backend",
|
||||
|
||||
@ -8,7 +8,7 @@ Architecture: all
|
||||
Installed-Size: 71680
|
||||
Description: Comprehensive authentication and session management with captive portal, OAuth2/OIDC integration, voucher system, and time-based access control
|
||||
Filename: luci-app-auth-guardian_0.4.0-r3_all.ipk
|
||||
Size: 12081
|
||||
Size: 12078
|
||||
|
||||
Package: luci-app-bandwidth-manager
|
||||
Version: 0.5.0-r2
|
||||
@ -20,7 +20,7 @@ Architecture: all
|
||||
Installed-Size: 378880
|
||||
Description: Advanced bandwidth management with QoS rules, client quotas, and SQM integration
|
||||
Filename: luci-app-bandwidth-manager_0.5.0-r2_all.ipk
|
||||
Size: 66970
|
||||
Size: 66969
|
||||
|
||||
Package: luci-app-cdn-cache
|
||||
Version: 0.5.0-r3
|
||||
@ -32,7 +32,7 @@ Architecture: all
|
||||
Installed-Size: 122880
|
||||
Description: Dashboard for managing local CDN caching proxy on OpenWrt
|
||||
Filename: luci-app-cdn-cache_0.5.0-r3_all.ipk
|
||||
Size: 23190
|
||||
Size: 23192
|
||||
|
||||
Package: luci-app-client-guardian
|
||||
Version: 0.4.0-r7
|
||||
@ -44,7 +44,7 @@ Architecture: all
|
||||
Installed-Size: 307200
|
||||
Description: Network Access Control with client monitoring, zone management, captive portal, parental controls, and SMS/email alerts
|
||||
Filename: luci-app-client-guardian_0.4.0-r7_all.ipk
|
||||
Size: 57045
|
||||
Size: 57042
|
||||
|
||||
Package: luci-app-crowdsec-dashboard
|
||||
Version: 0.7.0-r29
|
||||
@ -56,7 +56,7 @@ Architecture: all
|
||||
Installed-Size: 296960
|
||||
Description: Real-time security monitoring dashboard for CrowdSec on OpenWrt
|
||||
Filename: luci-app-crowdsec-dashboard_0.7.0-r29_all.ipk
|
||||
Size: 55583
|
||||
Size: 55584
|
||||
|
||||
Package: luci-app-cyberfeed
|
||||
Version: 0.1.1-r1
|
||||
@ -68,7 +68,7 @@ Architecture: all
|
||||
Installed-Size: 71680
|
||||
Description: Cyberpunk-themed RSS feed aggregator dashboard with social media support
|
||||
Filename: luci-app-cyberfeed_0.1.1-r1_all.ipk
|
||||
Size: 12838
|
||||
Size: 12837
|
||||
|
||||
Package: luci-app-exposure
|
||||
Version: 1.0.0-r3
|
||||
@ -80,7 +80,7 @@ Architecture: all
|
||||
Installed-Size: 153600
|
||||
Description: LuCI SecuBox Service Exposure Manager
|
||||
Filename: luci-app-exposure_1.0.0-r3_all.ipk
|
||||
Size: 20535
|
||||
Size: 20536
|
||||
|
||||
Package: luci-app-gitea
|
||||
Version: 1.0.0-r2
|
||||
@ -92,7 +92,7 @@ Architecture: all
|
||||
Installed-Size: 92160
|
||||
Description: Modern dashboard for Gitea Platform management on OpenWrt
|
||||
Filename: luci-app-gitea_1.0.0-r2_all.ipk
|
||||
Size: 15586
|
||||
Size: 15587
|
||||
|
||||
Package: luci-app-glances
|
||||
Version: 1.0.0-r2
|
||||
@ -104,7 +104,7 @@ Architecture: all
|
||||
Installed-Size: 40960
|
||||
Description: Modern dashboard for Glances system monitoring with SecuBox theme
|
||||
Filename: luci-app-glances_1.0.0-r2_all.ipk
|
||||
Size: 6969
|
||||
Size: 6966
|
||||
|
||||
Package: luci-app-haproxy
|
||||
Version: 1.0.0-r8
|
||||
@ -128,7 +128,7 @@ Architecture: all
|
||||
Installed-Size: 215040
|
||||
Description: Modern dashboard for Hexo static site generator on OpenWrt
|
||||
Filename: luci-app-hexojs_1.0.0-r3_all.ipk
|
||||
Size: 32980
|
||||
Size: 32975
|
||||
|
||||
Package: luci-app-jitsi
|
||||
Version: 1.0.0-r1
|
||||
@ -152,7 +152,7 @@ Architecture: all
|
||||
Installed-Size: 112640
|
||||
Description: Centralized cryptographic key management with hardware security module (HSM) support for Nitrokey and YubiKey devices. Provides secure key storage, certificate management, SSH key handling, and secret storage with audit logging.
|
||||
Filename: luci-app-ksm-manager_0.4.0-r2_all.ipk
|
||||
Size: 18719
|
||||
Size: 18723
|
||||
|
||||
Package: luci-app-localai
|
||||
Version: 0.1.0-r15
|
||||
@ -164,7 +164,7 @@ Architecture: all
|
||||
Installed-Size: 81920
|
||||
Description: Modern dashboard for LocalAI LLM management on OpenWrt
|
||||
Filename: luci-app-localai_0.1.0-r15_all.ipk
|
||||
Size: 14361
|
||||
Size: 14362
|
||||
|
||||
Package: luci-app-lyrion
|
||||
Version: 1.0.0-r1
|
||||
@ -176,7 +176,7 @@ Architecture: all
|
||||
Installed-Size: 40960
|
||||
Description: LuCI support for Lyrion Music Server
|
||||
Filename: luci-app-lyrion_1.0.0-r1_all.ipk
|
||||
Size: 6729
|
||||
Size: 6725
|
||||
|
||||
Package: luci-app-magicmirror2
|
||||
Version: 0.4.0-r6
|
||||
@ -188,7 +188,7 @@ Architecture: all
|
||||
Installed-Size: 71680
|
||||
Description: Modern dashboard for MagicMirror2 smart display platform with module manager and SecuBox theme
|
||||
Filename: luci-app-magicmirror2_0.4.0-r6_all.ipk
|
||||
Size: 12279
|
||||
Size: 12277
|
||||
|
||||
Package: luci-app-mailinabox
|
||||
Version: 1.0.0-r1
|
||||
@ -200,7 +200,7 @@ Architecture: all
|
||||
Installed-Size: 30720
|
||||
Description: LuCI support for Mail-in-a-Box
|
||||
Filename: luci-app-mailinabox_1.0.0-r1_all.ipk
|
||||
Size: 5482
|
||||
Size: 5481
|
||||
|
||||
Package: luci-app-media-flow
|
||||
Version: 0.6.4-r1
|
||||
@ -224,7 +224,7 @@ Architecture: all
|
||||
Installed-Size: 112640
|
||||
Description: LuCI support for MetaBlogizer Static Site Publisher
|
||||
Filename: luci-app-metablogizer_1.0.0-r3_all.ipk
|
||||
Size: 23506
|
||||
Size: 23505
|
||||
|
||||
Package: luci-app-metabolizer
|
||||
Version: 1.0.0-r2
|
||||
@ -236,7 +236,7 @@ Architecture: all
|
||||
Installed-Size: 30720
|
||||
Description: LuCI support for Metabolizer CMS
|
||||
Filename: luci-app-metabolizer_1.0.0-r2_all.ipk
|
||||
Size: 4758
|
||||
Size: 4756
|
||||
|
||||
Package: luci-app-mitmproxy
|
||||
Version: 0.5.0-r1
|
||||
@ -260,7 +260,7 @@ Architecture: all
|
||||
Installed-Size: 51200
|
||||
Description: Web interface for MMPM - MagicMirror Package Manager
|
||||
Filename: luci-app-mmpm_0.2.0-r3_all.ipk
|
||||
Size: 7901
|
||||
Size: 7900
|
||||
|
||||
Package: luci-app-mqtt-bridge
|
||||
Version: 0.4.0-r4
|
||||
@ -272,7 +272,7 @@ Architecture: all
|
||||
Installed-Size: 122880
|
||||
Description: USB-to-MQTT IoT hub with SecuBox theme
|
||||
Filename: luci-app-mqtt-bridge_0.4.0-r4_all.ipk
|
||||
Size: 22782
|
||||
Size: 22774
|
||||
|
||||
Package: luci-app-ndpid
|
||||
Version: 1.1.2-r2
|
||||
@ -284,7 +284,7 @@ Architecture: all
|
||||
Installed-Size: 122880
|
||||
Description: Modern dashboard for nDPId deep packet inspection on OpenWrt
|
||||
Filename: luci-app-ndpid_1.1.2-r2_all.ipk
|
||||
Size: 22454
|
||||
Size: 22456
|
||||
|
||||
Package: luci-app-netdata-dashboard
|
||||
Version: 0.5.0-r2
|
||||
@ -296,7 +296,7 @@ Architecture: all
|
||||
Installed-Size: 133120
|
||||
Description: Real-time system monitoring dashboard with Netdata integration for OpenWrt
|
||||
Filename: luci-app-netdata-dashboard_0.5.0-r2_all.ipk
|
||||
Size: 22399
|
||||
Size: 22401
|
||||
|
||||
Package: luci-app-network-modes
|
||||
Version: 0.5.0-r3
|
||||
@ -308,7 +308,7 @@ Architecture: all
|
||||
Installed-Size: 307200
|
||||
Description: Configure OpenWrt for different network modes: Sniffer, Access Point, Relay, Router
|
||||
Filename: luci-app-network-modes_0.5.0-r3_all.ipk
|
||||
Size: 55611
|
||||
Size: 55613
|
||||
|
||||
Package: luci-app-network-tweaks
|
||||
Version: 1.0.0-r7
|
||||
@ -320,7 +320,7 @@ Architecture: all
|
||||
Installed-Size: 81920
|
||||
Description: Unified network services dashboard with DNS/hosts sync, CDN cache control, and WPAD auto-proxy configuration
|
||||
Filename: luci-app-network-tweaks_1.0.0-r7_all.ipk
|
||||
Size: 15460
|
||||
Size: 15461
|
||||
|
||||
Package: luci-app-nextcloud
|
||||
Version: 1.0.0-r1
|
||||
@ -344,7 +344,7 @@ Architecture: all
|
||||
Installed-Size: 71680
|
||||
Description: Modern dashboard for Ollama LLM management on OpenWrt
|
||||
Filename: luci-app-ollama_0.1.0-r1_all.ipk
|
||||
Size: 11998
|
||||
Size: 11997
|
||||
|
||||
Package: luci-app-picobrew
|
||||
Version: 1.0.0-r1
|
||||
@ -356,7 +356,7 @@ Architecture: all
|
||||
Installed-Size: 51200
|
||||
Description: Modern dashboard for PicoBrew Server management on OpenWrt
|
||||
Filename: luci-app-picobrew_1.0.0-r1_all.ipk
|
||||
Size: 9981
|
||||
Size: 9975
|
||||
|
||||
Package: luci-app-secubox
|
||||
Version: 0.7.1-r4
|
||||
@ -368,7 +368,7 @@ Architecture: all
|
||||
Installed-Size: 266240
|
||||
Description: Central control hub for all SecuBox modules. Provides unified dashboard, module status, system health monitoring, and quick actions.
|
||||
Filename: luci-app-secubox_0.7.1-r4_all.ipk
|
||||
Size: 49907
|
||||
Size: 49901
|
||||
|
||||
Package: luci-app-secubox-admin
|
||||
Version: 1.0.0-r19
|
||||
@ -379,7 +379,7 @@ Architecture: all
|
||||
Installed-Size: 337920
|
||||
Description: Unified admin control center for SecuBox appstore plugins with system monitoring
|
||||
Filename: luci-app-secubox-admin_1.0.0-r19_all.ipk
|
||||
Size: 57096
|
||||
Size: 57098
|
||||
|
||||
Package: luci-app-secubox-crowdsec
|
||||
Version: 1.0.0-r3
|
||||
@ -391,7 +391,7 @@ Architecture: all
|
||||
Installed-Size: 81920
|
||||
Description: LuCI SecuBox CrowdSec Dashboard
|
||||
Filename: luci-app-secubox-crowdsec_1.0.0-r3_all.ipk
|
||||
Size: 13918
|
||||
Size: 13919
|
||||
|
||||
Package: luci-app-secubox-netdiag
|
||||
Version: 1.0.0-r1
|
||||
@ -451,7 +451,7 @@ Architecture: all
|
||||
Installed-Size: 71680
|
||||
Description: Unified dashboard integrating netifyd DPI threats with CrowdSec intelligence for real-time threat monitoring and automated blocking
|
||||
Filename: luci-app-secubox-security-threats_1.0.0-r4_all.ipk
|
||||
Size: 13905
|
||||
Size: 13903
|
||||
|
||||
Package: luci-app-service-registry
|
||||
Version: 1.0.0-r1
|
||||
@ -463,19 +463,19 @@ Architecture: all
|
||||
Installed-Size: 194560
|
||||
Description: Unified service aggregation with HAProxy vhosts, Tor hidden services, and QR-coded landing page
|
||||
Filename: luci-app-service-registry_1.0.0-r1_all.ipk
|
||||
Size: 39828
|
||||
Size: 39826
|
||||
|
||||
Package: luci-app-streamlit
|
||||
Version: 1.0.0-r9
|
||||
Version: 1.0.0-r11
|
||||
Depends: luci-base, luci-lib-jsonc, rpcd, rpcd-mod-luci, secubox-app-streamlit
|
||||
License: Apache-2.0
|
||||
Section: luci
|
||||
Maintainer: OpenWrt LuCI community
|
||||
Architecture: all
|
||||
Installed-Size: 122880
|
||||
Description: Modern dashboard for Streamlit Platform management on OpenWrt
|
||||
Filename: luci-app-streamlit_1.0.0-r9_all.ipk
|
||||
Size: 20472
|
||||
Installed-Size: 81920
|
||||
Description: Multi-instance Streamlit management with Gitea integration
|
||||
Filename: luci-app-streamlit_1.0.0-r11_all.ipk
|
||||
Size: 14749
|
||||
|
||||
Package: luci-app-system-hub
|
||||
Version: 0.5.1-r4
|
||||
@ -487,7 +487,7 @@ Architecture: all
|
||||
Installed-Size: 358400
|
||||
Description: Central system control with monitoring, services, logs, and backup
|
||||
Filename: luci-app-system-hub_0.5.1-r4_all.ipk
|
||||
Size: 66351
|
||||
Size: 66349
|
||||
|
||||
Package: luci-app-tor-shield
|
||||
Version: 1.0.0-r10
|
||||
@ -499,7 +499,7 @@ Architecture: all
|
||||
Installed-Size: 133120
|
||||
Description: Modern dashboard for Tor anonymization on OpenWrt
|
||||
Filename: luci-app-tor-shield_1.0.0-r10_all.ipk
|
||||
Size: 24535
|
||||
Size: 24537
|
||||
|
||||
Package: luci-app-traffic-shaper
|
||||
Version: 0.4.0-r2
|
||||
@ -511,7 +511,7 @@ Architecture: all
|
||||
Installed-Size: 92160
|
||||
Description: Advanced traffic shaping with TC/CAKE for precise bandwidth control
|
||||
Filename: luci-app-traffic-shaper_0.4.0-r2_all.ipk
|
||||
Size: 15637
|
||||
Size: 15636
|
||||
|
||||
Package: luci-app-vhost-manager
|
||||
Version: 0.5.0-r5
|
||||
@ -547,7 +547,7 @@ Architecture: all
|
||||
Installed-Size: 40960
|
||||
Description: Graphical interface for managing the Zigbee2MQTT docker application.
|
||||
Filename: luci-app-zigbee2mqtt_1.0.0-r2_all.ipk
|
||||
Size: 7090
|
||||
Size: 7087
|
||||
|
||||
Package: luci-theme-secubox
|
||||
Version: 0.4.7-r1
|
||||
@ -559,7 +559,7 @@ Architecture: all
|
||||
Installed-Size: 460800
|
||||
Description: Global CyberMood design system (CSS/JS/i18n) shared by all SecuBox dashboards.
|
||||
Filename: luci-theme-secubox_0.4.7-r1_all.ipk
|
||||
Size: 111797
|
||||
Size: 111796
|
||||
|
||||
Package: secubox-app
|
||||
Version: 1.0.0-r2
|
||||
@ -570,7 +570,7 @@ Installed-Size: 92160
|
||||
Description: Command line helper for SecuBox App Store manifests. Installs /usr/sbin/secubox-app
|
||||
and ships the default manifests under /usr/share/secubox/plugins/.
|
||||
Filename: secubox-app_1.0.0-r2_all.ipk
|
||||
Size: 11186
|
||||
Size: 11184
|
||||
|
||||
Package: secubox-app-adguardhome
|
||||
Version: 1.0.0-r2
|
||||
@ -584,7 +584,7 @@ Description: Installer, configuration, and service manager for running AdGuard
|
||||
inside Docker on SecuBox-powered OpenWrt systems. Network-wide ad blocker
|
||||
with DNS-over-HTTPS/TLS support and detailed analytics.
|
||||
Filename: secubox-app-adguardhome_1.0.0-r2_all.ipk
|
||||
Size: 2882
|
||||
Size: 2880
|
||||
|
||||
Package: secubox-app-auth-logger
|
||||
Version: 1.2.2-r1
|
||||
@ -625,7 +625,7 @@ Description: Custom CrowdSec configurations for SecuBox web interface protectio
|
||||
- Webapp generic auth bruteforce protection
|
||||
- Whitelist for trusted networks
|
||||
Filename: secubox-app-crowdsec-custom_1.1.0-r1_all.ipk
|
||||
Size: 5763
|
||||
Size: 5762
|
||||
|
||||
Package: secubox-app-cs-firewall-bouncer
|
||||
Version: 0.0.31-r4
|
||||
@ -666,7 +666,7 @@ Description: Cyberpunk-themed RSS feed aggregator for OpenWrt/SecuBox.
|
||||
Features emoji injection, neon styling, and RSS-Bridge support
|
||||
for social media feeds (Facebook, Twitter, Mastodon).
|
||||
Filename: secubox-app-cyberfeed_0.2.1-r1_all.ipk
|
||||
Size: 12454
|
||||
Size: 12450
|
||||
|
||||
Package: secubox-app-domoticz
|
||||
Version: 1.0.0-r2
|
||||
@ -679,7 +679,7 @@ Installed-Size: 10240
|
||||
Description: Installer, configuration, and service manager for running Domoticz
|
||||
inside Docker on SecuBox-powered OpenWrt systems.
|
||||
Filename: secubox-app-domoticz_1.0.0-r2_all.ipk
|
||||
Size: 2548
|
||||
Size: 2546
|
||||
|
||||
Package: secubox-app-exposure
|
||||
Version: 1.0.0-r1
|
||||
@ -694,7 +694,7 @@ Description: Unified service exposure manager for SecuBox.
|
||||
- Dynamic Tor hidden service management
|
||||
- HAProxy SSL reverse proxy configuration
|
||||
Filename: secubox-app-exposure_1.0.0-r1_all.ipk
|
||||
Size: 6837
|
||||
Size: 6829
|
||||
|
||||
Package: secubox-app-gitea
|
||||
Version: 1.0.0-r5
|
||||
@ -740,7 +740,7 @@ Description: Glances - Cross-platform system monitoring tool for SecuBox.
|
||||
Runs in LXC container for isolation and security.
|
||||
Configure in /etc/config/glances.
|
||||
Filename: secubox-app-glances_1.0.0-r1_all.ipk
|
||||
Size: 5536
|
||||
Size: 5534
|
||||
|
||||
Package: secubox-app-haproxy
|
||||
Version: 1.0.0-r23
|
||||
@ -784,7 +784,7 @@ Description: Hexo CMS - Self-hosted static blog generator for OpenWrt
|
||||
Runs in LXC container with Alpine Linux.
|
||||
Configure in /etc/config/hexojs.
|
||||
Filename: secubox-app-hexojs_1.0.0-r8_all.ipk
|
||||
Size: 94935
|
||||
Size: 94931
|
||||
|
||||
Package: secubox-app-jitsi
|
||||
Version: 1.0.0-r1
|
||||
@ -809,7 +809,7 @@ Description: Jitsi Meet - Secure, fully featured video conferencing for SecuBox
|
||||
Integrates with HAProxy for SSL termination.
|
||||
Configure in /etc/config/jitsi.
|
||||
Filename: secubox-app-jitsi_1.0.0-r1_all.ipk
|
||||
Size: 8918
|
||||
Size: 8913
|
||||
|
||||
Package: secubox-app-localai
|
||||
Version: 2.25.0-r1
|
||||
@ -831,7 +831,7 @@ Description: LocalAI native binary package for OpenWrt.
|
||||
|
||||
API: http://<router-ip>:8081/v1
|
||||
Filename: secubox-app-localai_2.25.0-r1_all.ipk
|
||||
Size: 5721
|
||||
Size: 5727
|
||||
|
||||
Package: secubox-app-localai-wb
|
||||
Version: 2.25.0-r1
|
||||
@ -855,7 +855,7 @@ Description: LocalAI native binary package for OpenWrt.
|
||||
|
||||
API: http://<router-ip>:8080/v1
|
||||
Filename: secubox-app-localai-wb_2.25.0-r1_all.ipk
|
||||
Size: 7951
|
||||
Size: 7949
|
||||
|
||||
Package: secubox-app-lyrion
|
||||
Version: 2.0.2-r1
|
||||
@ -875,7 +875,7 @@ Description: Lyrion Media Server (formerly Logitech Media Server / Squeezebox S
|
||||
Auto-detects available runtime, preferring LXC for lower resource usage.
|
||||
Configure runtime in /etc/config/lyrion.
|
||||
Filename: secubox-app-lyrion_2.0.2-r1_all.ipk
|
||||
Size: 7287
|
||||
Size: 7278
|
||||
|
||||
Package: secubox-app-magicmirror2
|
||||
Version: 0.4.0-r8
|
||||
@ -897,7 +897,7 @@ Description: MagicMirror² - Open source modular smart mirror platform for Secu
|
||||
Runs in LXC container for isolation and security.
|
||||
Configure in /etc/config/magicmirror2.
|
||||
Filename: secubox-app-magicmirror2_0.4.0-r8_all.ipk
|
||||
Size: 9253
|
||||
Size: 9254
|
||||
|
||||
Package: secubox-app-mailinabox
|
||||
Version: 2.0.0-r1
|
||||
@ -922,7 +922,7 @@ Description: Complete email server solution using docker-mailserver for SecuBox
|
||||
|
||||
Commands: mailinaboxctl --help
|
||||
Filename: secubox-app-mailinabox_2.0.0-r1_all.ipk
|
||||
Size: 7574
|
||||
Size: 7571
|
||||
|
||||
Package: secubox-app-metabolizer
|
||||
Version: 1.0.0-r3
|
||||
@ -943,7 +943,7 @@ Description: Metabolizer Blog Pipeline - Integrated CMS with Git-based workflow
|
||||
|
||||
Pipeline: Edit in Streamlit -> Push to Gitea -> Build with Hexo -> Publish
|
||||
Filename: secubox-app-metabolizer_1.0.0-r3_all.ipk
|
||||
Size: 13980
|
||||
Size: 13978
|
||||
|
||||
Package: secubox-app-mitmproxy
|
||||
Version: 0.5.0-r19
|
||||
@ -991,7 +991,7 @@ Description: MMPM (MagicMirror Package Manager) for SecuBox.
|
||||
|
||||
Runs inside the MagicMirror2 LXC container.
|
||||
Filename: secubox-app-mmpm_0.2.0-r5_all.ipk
|
||||
Size: 3977
|
||||
Size: 3980
|
||||
|
||||
Package: secubox-app-nextcloud
|
||||
Version: 1.0.0-r2
|
||||
@ -1005,7 +1005,7 @@ Description: Installer, configuration, and service manager for running Nextclou
|
||||
inside Docker on SecuBox-powered OpenWrt systems. Self-hosted file
|
||||
sync and share with calendar, contacts, and collaboration.
|
||||
Filename: secubox-app-nextcloud_1.0.0-r2_all.ipk
|
||||
Size: 2958
|
||||
Size: 2961
|
||||
|
||||
Package: secubox-app-ollama
|
||||
Version: 0.1.0-r1
|
||||
@ -1027,7 +1027,7 @@ Description: Ollama - Simple local LLM runtime for SecuBox-powered OpenWrt syst
|
||||
Runs in Docker/Podman container.
|
||||
Configure in /etc/config/ollama.
|
||||
Filename: secubox-app-ollama_0.1.0-r1_all.ipk
|
||||
Size: 5739
|
||||
Size: 5735
|
||||
|
||||
Package: secubox-app-picobrew
|
||||
Version: 1.0.0-r7
|
||||
@ -1049,7 +1049,7 @@ Description: PicoBrew Server - Self-hosted brewing controller for PicoBrew devi
|
||||
Runs in LXC container with Python/Flask backend.
|
||||
Configure in /etc/config/picobrew.
|
||||
Filename: secubox-app-picobrew_1.0.0-r7_all.ipk
|
||||
Size: 5542
|
||||
Size: 5539
|
||||
|
||||
Package: secubox-app-streamlit
|
||||
Version: 1.0.0-r5
|
||||
@ -1076,7 +1076,7 @@ Description: Streamlit App Platform - Self-hosted Python data app platform
|
||||
|
||||
Configure in /etc/config/streamlit.
|
||||
Filename: secubox-app-streamlit_1.0.0-r5_all.ipk
|
||||
Size: 11722
|
||||
Size: 11720
|
||||
|
||||
Package: secubox-app-tor
|
||||
Version: 1.0.0-r1
|
||||
@ -1099,7 +1099,7 @@ Description: SecuBox Tor Shield - One-click Tor anonymization for OpenWrt
|
||||
|
||||
Configure in /etc/config/tor-shield.
|
||||
Filename: secubox-app-tor_1.0.0-r1_all.ipk
|
||||
Size: 7380
|
||||
Size: 7382
|
||||
|
||||
Package: secubox-app-webapp
|
||||
Version: 1.5.0-r7
|
||||
@ -1117,7 +1117,7 @@ Description: SecuBox Control Center Dashboard - A web-based dashboard for monit
|
||||
- Service management
|
||||
- Network interface control
|
||||
Filename: secubox-app-webapp_1.5.0-r7_all.ipk
|
||||
Size: 39170
|
||||
Size: 39169
|
||||
|
||||
Package: secubox-app-zigbee2mqtt
|
||||
Version: 1.0.0-r3
|
||||
@ -1130,7 +1130,7 @@ Installed-Size: 20480
|
||||
Description: Installer, configuration, and service manager for running Zigbee2MQTT
|
||||
inside Docker on SecuBox-powered OpenWrt systems.
|
||||
Filename: secubox-app-zigbee2mqtt_1.0.0-r3_all.ipk
|
||||
Size: 3546
|
||||
Size: 3545
|
||||
|
||||
Package: secubox-core
|
||||
Version: 0.10.0-r11
|
||||
@ -1150,7 +1150,7 @@ Description: SecuBox Core Framework provides the foundational infrastructure fo
|
||||
- Unified CLI interface
|
||||
- ubus RPC backend
|
||||
Filename: secubox-core_0.10.0-r11_all.ipk
|
||||
Size: 87810
|
||||
Size: 87809
|
||||
|
||||
Package: secubox-p2p
|
||||
Version: 0.6.0-r3
|
||||
@ -1169,5 +1169,5 @@ Description: SecuBox P2P Hub backend providing peer discovery, mesh networking
|
||||
and MirrorBox NetMesh Catalog for cross-chain distributed service
|
||||
registry with HAProxy vhost discovery and multi-endpoint access URLs.
|
||||
Filename: secubox-p2p_0.6.0-r3_all.ipk
|
||||
Size: 42021
|
||||
Size: 42014
|
||||
|
||||
|
||||
Binary file not shown.
@ -1,12 +1,12 @@
|
||||
{
|
||||
"feed_url": "/secubox-feed",
|
||||
"generated": "2026-02-01T07:08:21+01:00",
|
||||
"generated": "2026-02-01T07:26:25+01:00",
|
||||
"packages": [
|
||||
{
|
||||
"name": "luci-app-auth-guardian",
|
||||
"version": "0.4.0-r3",
|
||||
"filename": "luci-app-auth-guardian_0.4.0-r3_all.ipk",
|
||||
"size": 12081,
|
||||
"size": 12078,
|
||||
"category": "security",
|
||||
"icon": "key",
|
||||
"description": "Authentication management",
|
||||
@ -18,7 +18,7 @@
|
||||
"name": "luci-app-bandwidth-manager",
|
||||
"version": "0.5.0-r2",
|
||||
"filename": "luci-app-bandwidth-manager_0.5.0-r2_all.ipk",
|
||||
"size": 66970,
|
||||
"size": 66969,
|
||||
"category": "network",
|
||||
"icon": "activity",
|
||||
"description": "Bandwidth monitoring and control",
|
||||
@ -30,7 +30,7 @@
|
||||
"name": "luci-app-cdn-cache",
|
||||
"version": "0.5.0-r3",
|
||||
"filename": "luci-app-cdn-cache_0.5.0-r3_all.ipk",
|
||||
"size": 23190,
|
||||
"size": 23192,
|
||||
"category": "network",
|
||||
"icon": "globe",
|
||||
"description": "CDN caching",
|
||||
@ -42,7 +42,7 @@
|
||||
"name": "luci-app-client-guardian",
|
||||
"version": "0.4.0-r7",
|
||||
"filename": "luci-app-client-guardian_0.4.0-r7_all.ipk",
|
||||
"size": 57045,
|
||||
"size": 57042,
|
||||
"category": "network",
|
||||
"icon": "users",
|
||||
"description": "Client management and monitoring",
|
||||
@ -54,7 +54,7 @@
|
||||
"name": "luci-app-crowdsec-dashboard",
|
||||
"version": "0.7.0-r29",
|
||||
"filename": "luci-app-crowdsec-dashboard_0.7.0-r29_all.ipk",
|
||||
"size": 55583,
|
||||
"size": 55584,
|
||||
"category": "security",
|
||||
"icon": "shield",
|
||||
"description": "CrowdSec security monitoring",
|
||||
@ -66,7 +66,7 @@
|
||||
"name": "luci-app-cyberfeed",
|
||||
"version": "0.1.1-r1",
|
||||
"filename": "luci-app-cyberfeed_0.1.1-r1_all.ipk",
|
||||
"size": 12838,
|
||||
"size": 12837,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -78,7 +78,7 @@
|
||||
"name": "luci-app-exposure",
|
||||
"version": "1.0.0-r3",
|
||||
"filename": "luci-app-exposure_1.0.0-r3_all.ipk",
|
||||
"size": 20535,
|
||||
"size": 20536,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -90,7 +90,7 @@
|
||||
"name": "luci-app-gitea",
|
||||
"version": "1.0.0-r2",
|
||||
"filename": "luci-app-gitea_1.0.0-r2_all.ipk",
|
||||
"size": 15586,
|
||||
"size": 15587,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -102,7 +102,7 @@
|
||||
"name": "luci-app-glances",
|
||||
"version": "1.0.0-r2",
|
||||
"filename": "luci-app-glances_1.0.0-r2_all.ipk",
|
||||
"size": 6969,
|
||||
"size": 6966,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -126,7 +126,7 @@
|
||||
"name": "luci-app-hexojs",
|
||||
"version": "1.0.0-r3",
|
||||
"filename": "luci-app-hexojs_1.0.0-r3_all.ipk",
|
||||
"size": 32980,
|
||||
"size": 32975,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -150,7 +150,7 @@
|
||||
"name": "luci-app-ksm-manager",
|
||||
"version": "0.4.0-r2",
|
||||
"filename": "luci-app-ksm-manager_0.4.0-r2_all.ipk",
|
||||
"size": 18719,
|
||||
"size": 18723,
|
||||
"category": "system",
|
||||
"icon": "cpu",
|
||||
"description": "Kernel memory management",
|
||||
@ -162,7 +162,7 @@
|
||||
"name": "luci-app-localai",
|
||||
"version": "0.1.0-r15",
|
||||
"filename": "luci-app-localai_0.1.0-r15_all.ipk",
|
||||
"size": 14361,
|
||||
"size": 14362,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -174,7 +174,7 @@
|
||||
"name": "luci-app-lyrion",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-lyrion_1.0.0-r1_all.ipk",
|
||||
"size": 6729,
|
||||
"size": 6725,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -186,7 +186,7 @@
|
||||
"name": "luci-app-magicmirror2",
|
||||
"version": "0.4.0-r6",
|
||||
"filename": "luci-app-magicmirror2_0.4.0-r6_all.ipk",
|
||||
"size": 12279,
|
||||
"size": 12277,
|
||||
"category": "iot",
|
||||
"icon": "monitor",
|
||||
"description": "Smart mirror display",
|
||||
@ -198,7 +198,7 @@
|
||||
"name": "luci-app-mailinabox",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-mailinabox_1.0.0-r1_all.ipk",
|
||||
"size": 5482,
|
||||
"size": 5481,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -222,7 +222,7 @@
|
||||
"name": "luci-app-metablogizer",
|
||||
"version": "1.0.0-r3",
|
||||
"filename": "luci-app-metablogizer_1.0.0-r3_all.ipk",
|
||||
"size": 23506,
|
||||
"size": 23505,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -234,7 +234,7 @@
|
||||
"name": "luci-app-metabolizer",
|
||||
"version": "1.0.0-r2",
|
||||
"filename": "luci-app-metabolizer_1.0.0-r2_all.ipk",
|
||||
"size": 4758,
|
||||
"size": 4756,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -258,7 +258,7 @@
|
||||
"name": "luci-app-mmpm",
|
||||
"version": "0.2.0-r3",
|
||||
"filename": "luci-app-mmpm_0.2.0-r3_all.ipk",
|
||||
"size": 7901,
|
||||
"size": 7900,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -270,7 +270,7 @@
|
||||
"name": "luci-app-mqtt-bridge",
|
||||
"version": "0.4.0-r4",
|
||||
"filename": "luci-app-mqtt-bridge_0.4.0-r4_all.ipk",
|
||||
"size": 22782,
|
||||
"size": 22774,
|
||||
"category": "iot",
|
||||
"icon": "message-square",
|
||||
"description": "MQTT bridge",
|
||||
@ -282,7 +282,7 @@
|
||||
"name": "luci-app-ndpid",
|
||||
"version": "1.1.2-r2",
|
||||
"filename": "luci-app-ndpid_1.1.2-r2_all.ipk",
|
||||
"size": 22454,
|
||||
"size": 22456,
|
||||
"category": "security",
|
||||
"icon": "eye",
|
||||
"description": "Deep packet inspection",
|
||||
@ -294,7 +294,7 @@
|
||||
"name": "luci-app-netdata-dashboard",
|
||||
"version": "0.5.0-r2",
|
||||
"filename": "luci-app-netdata-dashboard_0.5.0-r2_all.ipk",
|
||||
"size": 22399,
|
||||
"size": 22401,
|
||||
"category": "monitoring",
|
||||
"icon": "bar-chart-2",
|
||||
"description": "System monitoring dashboard",
|
||||
@ -306,7 +306,7 @@
|
||||
"name": "luci-app-network-modes",
|
||||
"version": "0.5.0-r3",
|
||||
"filename": "luci-app-network-modes_0.5.0-r3_all.ipk",
|
||||
"size": 55611,
|
||||
"size": 55613,
|
||||
"category": "network",
|
||||
"icon": "wifi",
|
||||
"description": "Network configuration",
|
||||
@ -318,7 +318,7 @@
|
||||
"name": "luci-app-network-tweaks",
|
||||
"version": "1.0.0-r7",
|
||||
"filename": "luci-app-network-tweaks_1.0.0-r7_all.ipk",
|
||||
"size": 15460,
|
||||
"size": 15461,
|
||||
"category": "network",
|
||||
"icon": "wifi",
|
||||
"description": "Network configuration",
|
||||
@ -342,7 +342,7 @@
|
||||
"name": "luci-app-ollama",
|
||||
"version": "0.1.0-r1",
|
||||
"filename": "luci-app-ollama_0.1.0-r1_all.ipk",
|
||||
"size": 11998,
|
||||
"size": 11997,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -354,7 +354,7 @@
|
||||
"name": "luci-app-picobrew",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-picobrew_1.0.0-r1_all.ipk",
|
||||
"size": 9981,
|
||||
"size": 9975,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -366,7 +366,7 @@
|
||||
"name": "luci-app-secubox",
|
||||
"version": "0.7.1-r4",
|
||||
"filename": "luci-app-secubox_0.7.1-r4_all.ipk",
|
||||
"size": 49907,
|
||||
"size": 49901,
|
||||
"category": "system",
|
||||
"icon": "box",
|
||||
"description": "SecuBox system component",
|
||||
@ -378,7 +378,7 @@
|
||||
"name": "luci-app-secubox-admin",
|
||||
"version": "1.0.0-r19",
|
||||
"filename": "luci-app-secubox-admin_1.0.0-r19_all.ipk",
|
||||
"size": 57096,
|
||||
"size": 57098,
|
||||
"category": "system",
|
||||
"icon": "box",
|
||||
"description": "SecuBox system component",
|
||||
@ -390,7 +390,7 @@
|
||||
"name": "luci-app-secubox-crowdsec",
|
||||
"version": "1.0.0-r3",
|
||||
"filename": "luci-app-secubox-crowdsec_1.0.0-r3_all.ipk",
|
||||
"size": 13918,
|
||||
"size": 13919,
|
||||
"category": "system",
|
||||
"icon": "box",
|
||||
"description": "SecuBox system component",
|
||||
@ -450,7 +450,7 @@
|
||||
"name": "luci-app-secubox-security-threats",
|
||||
"version": "1.0.0-r4",
|
||||
"filename": "luci-app-secubox-security-threats_1.0.0-r4_all.ipk",
|
||||
"size": 13905,
|
||||
"size": 13903,
|
||||
"category": "system",
|
||||
"icon": "box",
|
||||
"description": "SecuBox system component",
|
||||
@ -462,7 +462,7 @@
|
||||
"name": "luci-app-service-registry",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-service-registry_1.0.0-r1_all.ipk",
|
||||
"size": 39828,
|
||||
"size": 39826,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -472,9 +472,9 @@
|
||||
,
|
||||
{
|
||||
"name": "luci-app-streamlit",
|
||||
"version": "1.0.0-r9",
|
||||
"filename": "luci-app-streamlit_1.0.0-r9_all.ipk",
|
||||
"size": 20472,
|
||||
"version": "1.0.0-r11",
|
||||
"filename": "luci-app-streamlit_1.0.0-r11_all.ipk",
|
||||
"size": 14749,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -486,7 +486,7 @@
|
||||
"name": "luci-app-system-hub",
|
||||
"version": "0.5.1-r4",
|
||||
"filename": "luci-app-system-hub_0.5.1-r4_all.ipk",
|
||||
"size": 66351,
|
||||
"size": 66349,
|
||||
"category": "system",
|
||||
"icon": "settings",
|
||||
"description": "System management",
|
||||
@ -498,7 +498,7 @@
|
||||
"name": "luci-app-tor-shield",
|
||||
"version": "1.0.0-r10",
|
||||
"filename": "luci-app-tor-shield_1.0.0-r10_all.ipk",
|
||||
"size": 24535,
|
||||
"size": 24537,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -510,7 +510,7 @@
|
||||
"name": "luci-app-traffic-shaper",
|
||||
"version": "0.4.0-r2",
|
||||
"filename": "luci-app-traffic-shaper_0.4.0-r2_all.ipk",
|
||||
"size": 15637,
|
||||
"size": 15636,
|
||||
"category": "network",
|
||||
"icon": "filter",
|
||||
"description": "Traffic shaping and QoS",
|
||||
@ -546,7 +546,7 @@
|
||||
"name": "luci-app-zigbee2mqtt",
|
||||
"version": "1.0.0-r2",
|
||||
"filename": "luci-app-zigbee2mqtt_1.0.0-r2_all.ipk",
|
||||
"size": 7090,
|
||||
"size": 7087,
|
||||
"category": "iot",
|
||||
"icon": "radio",
|
||||
"description": "Zigbee device management",
|
||||
@ -558,7 +558,7 @@
|
||||
"name": "luci-theme-secubox",
|
||||
"version": "0.4.7-r1",
|
||||
"filename": "luci-theme-secubox_0.4.7-r1_all.ipk",
|
||||
"size": 111797,
|
||||
"size": 111796,
|
||||
"category": "theme",
|
||||
"icon": "palette",
|
||||
"description": "LuCI theme",
|
||||
@ -570,7 +570,7 @@
|
||||
"name": "secubox-app",
|
||||
"version": "1.0.0-r2",
|
||||
"filename": "secubox-app_1.0.0-r2_all.ipk",
|
||||
"size": 11186,
|
||||
"size": 11184,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -582,7 +582,7 @@
|
||||
"name": "secubox-app-adguardhome",
|
||||
"version": "1.0.0-r2",
|
||||
"filename": "secubox-app-adguardhome_1.0.0-r2_all.ipk",
|
||||
"size": 2882,
|
||||
"size": 2880,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -606,7 +606,7 @@
|
||||
"name": "secubox-app-crowdsec-custom",
|
||||
"version": "1.1.0-r1",
|
||||
"filename": "secubox-app-crowdsec-custom_1.1.0-r1_all.ipk",
|
||||
"size": 5763,
|
||||
"size": 5762,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -630,7 +630,7 @@
|
||||
"name": "secubox-app-cyberfeed",
|
||||
"version": "0.2.1-r1",
|
||||
"filename": "secubox-app-cyberfeed_0.2.1-r1_all.ipk",
|
||||
"size": 12454,
|
||||
"size": 12450,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -642,7 +642,7 @@
|
||||
"name": "secubox-app-domoticz",
|
||||
"version": "1.0.0-r2",
|
||||
"filename": "secubox-app-domoticz_1.0.0-r2_all.ipk",
|
||||
"size": 2548,
|
||||
"size": 2546,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -654,7 +654,7 @@
|
||||
"name": "secubox-app-exposure",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-app-exposure_1.0.0-r1_all.ipk",
|
||||
"size": 6837,
|
||||
"size": 6829,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -678,7 +678,7 @@
|
||||
"name": "secubox-app-glances",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-app-glances_1.0.0-r1_all.ipk",
|
||||
"size": 5536,
|
||||
"size": 5534,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -702,7 +702,7 @@
|
||||
"name": "secubox-app-hexojs",
|
||||
"version": "1.0.0-r8",
|
||||
"filename": "secubox-app-hexojs_1.0.0-r8_all.ipk",
|
||||
"size": 94935,
|
||||
"size": 94931,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -714,7 +714,7 @@
|
||||
"name": "secubox-app-jitsi",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-app-jitsi_1.0.0-r1_all.ipk",
|
||||
"size": 8918,
|
||||
"size": 8913,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -726,7 +726,7 @@
|
||||
"name": "secubox-app-localai",
|
||||
"version": "2.25.0-r1",
|
||||
"filename": "secubox-app-localai_2.25.0-r1_all.ipk",
|
||||
"size": 5721,
|
||||
"size": 5727,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -738,7 +738,7 @@
|
||||
"name": "secubox-app-localai-wb",
|
||||
"version": "2.25.0-r1",
|
||||
"filename": "secubox-app-localai-wb_2.25.0-r1_all.ipk",
|
||||
"size": 7951,
|
||||
"size": 7949,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -750,7 +750,7 @@
|
||||
"name": "secubox-app-lyrion",
|
||||
"version": "2.0.2-r1",
|
||||
"filename": "secubox-app-lyrion_2.0.2-r1_all.ipk",
|
||||
"size": 7287,
|
||||
"size": 7278,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -762,7 +762,7 @@
|
||||
"name": "secubox-app-magicmirror2",
|
||||
"version": "0.4.0-r8",
|
||||
"filename": "secubox-app-magicmirror2_0.4.0-r8_all.ipk",
|
||||
"size": 9253,
|
||||
"size": 9254,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -774,7 +774,7 @@
|
||||
"name": "secubox-app-mailinabox",
|
||||
"version": "2.0.0-r1",
|
||||
"filename": "secubox-app-mailinabox_2.0.0-r1_all.ipk",
|
||||
"size": 7574,
|
||||
"size": 7571,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -786,7 +786,7 @@
|
||||
"name": "secubox-app-metabolizer",
|
||||
"version": "1.0.0-r3",
|
||||
"filename": "secubox-app-metabolizer_1.0.0-r3_all.ipk",
|
||||
"size": 13980,
|
||||
"size": 13978,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -810,7 +810,7 @@
|
||||
"name": "secubox-app-mmpm",
|
||||
"version": "0.2.0-r5",
|
||||
"filename": "secubox-app-mmpm_0.2.0-r5_all.ipk",
|
||||
"size": 3977,
|
||||
"size": 3980,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -822,7 +822,7 @@
|
||||
"name": "secubox-app-nextcloud",
|
||||
"version": "1.0.0-r2",
|
||||
"filename": "secubox-app-nextcloud_1.0.0-r2_all.ipk",
|
||||
"size": 2958,
|
||||
"size": 2961,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -834,7 +834,7 @@
|
||||
"name": "secubox-app-ollama",
|
||||
"version": "0.1.0-r1",
|
||||
"filename": "secubox-app-ollama_0.1.0-r1_all.ipk",
|
||||
"size": 5739,
|
||||
"size": 5735,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -846,7 +846,7 @@
|
||||
"name": "secubox-app-picobrew",
|
||||
"version": "1.0.0-r7",
|
||||
"filename": "secubox-app-picobrew_1.0.0-r7_all.ipk",
|
||||
"size": 5542,
|
||||
"size": 5539,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -858,7 +858,7 @@
|
||||
"name": "secubox-app-streamlit",
|
||||
"version": "1.0.0-r5",
|
||||
"filename": "secubox-app-streamlit_1.0.0-r5_all.ipk",
|
||||
"size": 11722,
|
||||
"size": 11720,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -870,7 +870,7 @@
|
||||
"name": "secubox-app-tor",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-app-tor_1.0.0-r1_all.ipk",
|
||||
"size": 7380,
|
||||
"size": 7382,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -882,7 +882,7 @@
|
||||
"name": "secubox-app-webapp",
|
||||
"version": "1.5.0-r7",
|
||||
"filename": "secubox-app-webapp_1.5.0-r7_all.ipk",
|
||||
"size": 39170,
|
||||
"size": 39169,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -894,7 +894,7 @@
|
||||
"name": "secubox-app-zigbee2mqtt",
|
||||
"version": "1.0.0-r3",
|
||||
"filename": "secubox-app-zigbee2mqtt_1.0.0-r3_all.ipk",
|
||||
"size": 3546,
|
||||
"size": 3545,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -906,7 +906,7 @@
|
||||
"name": "secubox-core",
|
||||
"version": "0.10.0-r11",
|
||||
"filename": "secubox-core_0.10.0-r11_all.ipk",
|
||||
"size": 87810,
|
||||
"size": 87809,
|
||||
"category": "system",
|
||||
"icon": "box",
|
||||
"description": "SecuBox core components",
|
||||
@ -918,7 +918,7 @@
|
||||
"name": "secubox-p2p",
|
||||
"version": "0.6.0-r3",
|
||||
"filename": "secubox-p2p_0.6.0-r3_all.ipk",
|
||||
"size": 42021,
|
||||
"size": 42014,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user