secubox-openwrt/package/secubox/luci-app-backup/htdocs/luci-static/resources/view/backup/overview.js
CyberMind-FR c6fb79ed3b feat: Add unified backup manager, custom mail server, DNS subdomain generator
New packages:
- secubox-app-backup: Unified backup for LXC containers, UCI config, services
- luci-app-backup: KISS dashboard with container list and backup history
- secubox-app-mailserver: Custom Postfix+Dovecot in LXC with mesh backup

Enhanced dnsctl with:
- generate: Auto-create subdomain A records
- suggest: Name suggestions by category
- mail-setup: MX, SPF, DMARC record creation
- dkim-add: DKIM TXT record management

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 10:40:32 +01:00

236 lines
6.2 KiB
JavaScript

'use strict';
'require view';
'require rpc';
'require ui';
'require poll';
var callStatus = rpc.declare({
object: 'luci.backup',
method: 'status',
expect: {}
});
var callList = rpc.declare({
object: 'luci.backup',
method: 'list',
params: ['type'],
expect: {}
});
var callContainerList = rpc.declare({
object: 'luci.backup',
method: 'container_list',
expect: {}
});
var callCreate = rpc.declare({
object: 'luci.backup',
method: 'create',
params: ['type'],
expect: {}
});
var callContainerBackup = rpc.declare({
object: 'luci.backup',
method: 'container_backup',
params: ['name'],
expect: {}
});
function formatDate(ts) {
if (!ts || ts === 0) return '-';
var d = new Date(ts * 1000);
return d.toLocaleString();
}
return view.extend({
load: function() {
return Promise.all([
callStatus(),
callList('all'),
callContainerList()
]);
},
render: function(data) {
var status = data[0] || {};
var backups = (data[1] || {}).backups || [];
var containers = (data[2] || {}).containers || [];
var view = E('div', { 'class': 'cbi-map' }, [
E('h2', {}, 'Backup Manager'),
// Status Card
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, 'Status'),
E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td', 'style': 'width:200px' }, 'Storage Path'),
E('td', { 'class': 'td' }, status.storage_path || '/srv/backups')
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, 'Storage Used'),
E('td', { 'class': 'td' }, status.storage_used || '0')
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, 'Containers'),
E('td', { 'class': 'td' }, String(status.container_count || 0))
])
])
]),
// Quick Actions
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, 'Quick Actions'),
E('div', { 'style': 'display:flex;gap:10px;flex-wrap:wrap' }, [
E('button', {
'class': 'btn cbi-button-action',
'click': ui.createHandlerFn(this, function() {
return this.doBackup('full');
})
}, 'Full Backup'),
E('button', {
'class': 'btn cbi-button-neutral',
'click': ui.createHandlerFn(this, function() {
return this.doBackup('config');
})
}, 'Config Only'),
E('button', {
'class': 'btn cbi-button-neutral',
'click': ui.createHandlerFn(this, function() {
return this.doBackup('containers');
})
}, 'Containers Only')
])
]),
// Containers Section
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, 'LXC Containers'),
this.renderContainerTable(containers)
]),
// Backup History
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, 'Backup History'),
this.renderBackupTable(backups)
])
]);
return view;
},
renderContainerTable: function(containers) {
if (!containers || containers.length === 0) {
return E('p', { 'class': 'cbi-value-description' }, 'No LXC containers found.');
}
var rows = containers.map(L.bind(function(c) {
var stateClass = c.state === 'running' ? 'badge success' : 'badge';
return E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, c.name),
E('td', { 'class': 'td' }, [
E('span', { 'style': c.state === 'running' ? 'color:green' : 'color:gray' },
c.state === 'running' ? '● Running' : '○ Stopped')
]),
E('td', { 'class': 'td' }, c.size || '-'),
E('td', { 'class': 'td' }, String(c.backups || 0)),
E('td', { 'class': 'td' }, [
E('button', {
'class': 'btn cbi-button-action',
'style': 'padding:2px 8px;font-size:12px',
'click': ui.createHandlerFn(this, function(name) {
return this.doContainerBackup(name);
}, c.name)
}, 'Backup')
])
]);
}, this));
return E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, 'Name'),
E('th', { 'class': 'th' }, 'State'),
E('th', { 'class': 'th' }, 'Size'),
E('th', { 'class': 'th' }, 'Backups'),
E('th', { 'class': 'th' }, 'Actions')
])
].concat(rows));
},
renderBackupTable: function(backups) {
if (!backups || backups.length === 0) {
return E('p', { 'class': 'cbi-value-description' }, 'No backups found.');
}
// Sort by timestamp descending
backups.sort(function(a, b) { return (b.timestamp || 0) - (a.timestamp || 0); });
var rows = backups.slice(0, 20).map(function(b) {
var typeLabel = {
'config': 'Config',
'container': 'Container',
'service': 'Service'
}[b.type] || b.type;
return E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, b.file),
E('td', { 'class': 'td' }, typeLabel),
E('td', { 'class': 'td' }, b.size || '-'),
E('td', { 'class': 'td' }, formatDate(b.timestamp))
]);
});
return E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, 'File'),
E('th', { 'class': 'th' }, 'Type'),
E('th', { 'class': 'th' }, 'Size'),
E('th', { 'class': 'th' }, 'Date')
])
].concat(rows));
},
doBackup: function(type) {
ui.showModal('Creating Backup', [
E('p', { 'class': 'spinning' }, 'Creating ' + type + ' backup...')
]);
return callCreate(type).then(function(res) {
ui.hideModal();
if (res.code === 0) {
ui.addNotification(null, E('p', 'Backup created successfully.'), 'success');
} else {
ui.addNotification(null, E('p', 'Backup failed: ' + (res.output || 'Unknown error')), 'error');
}
window.location.reload();
}).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', 'Error: ' + err.message), 'error');
});
},
doContainerBackup: function(name) {
ui.showModal('Backing Up Container', [
E('p', { 'class': 'spinning' }, 'Backing up container: ' + name + '...')
]);
return callContainerBackup(name).then(function(res) {
ui.hideModal();
if (res.code === 0) {
ui.addNotification(null, E('p', 'Container backup created.'), 'success');
} else {
ui.addNotification(null, E('p', 'Backup failed: ' + (res.output || 'Unknown error')), 'error');
}
window.location.reload();
}).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', 'Error: ' + err.message), 'error');
});
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});