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:
CyberMind-FR 2026-02-01 08:06:22 +01:00
parent e07fec6cb4
commit 163364843e
88 changed files with 1238 additions and 534 deletions

View File

@ -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

View File

@ -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] || {}
};
});
}
});

View File

@ -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
});

View File

@ -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
;;

View File

@ -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": {

View File

@ -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",

View File

@ -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

View File

@ -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",