feat: fix export/import, add backup download/upload, restore service checkboxes

- export_config: clean output (no internal _keys), identity exposed as 'identity'
- import_config: handle 'identity' key, merge into existing config (not replace)
- restore_config: accept optional services list for selective restore
- backup_config: include 'identity' in manifest services list
- new GET /api/config/backups/<id>/download → zip file download
- new POST /api/config/backup/upload → zip file upload
- webui: Download + Upload buttons, restore modal with per-service checkboxes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-24 08:51:40 -04:00
parent 2bd6545f0e
commit 15e009bd94
4 changed files with 281 additions and 36 deletions
+14 -3
View File
@@ -39,10 +39,21 @@ export const cellAPI = {
updateConfig: (config) => api.put('/api/config', config),
createBackup: () => api.post('/api/config/backup'),
listBackups: () => api.get('/api/config/backups'),
restoreBackup: (id) => api.post(`/api/config/restore/${id}`),
restoreBackup: (id, services = null) => api.post(`/api/config/restore/${id}`, services ? { services } : {}),
deleteBackup: (id) => api.delete(`/api/config/backups/${id}`),
exportConfig: (format = 'json') => api.get('/api/config/export', { params: { format } }),
importConfig: (config, format = 'json') => api.post('/api/config/import', { config, format }),
downloadBackup: (id) => api.get(`/api/config/backups/${id}/download`, { responseType: 'blob' }),
uploadBackup: (file) => {
const form = new FormData();
form.append('file', file);
return api.post('/api/config/backup/upload', form, { headers: { 'Content-Type': 'multipart/form-data' } });
},
exportConfig: (format = 'json', services = null) => {
const params = { format };
if (services) params.services = services.join(',');
return api.get('/api/config/export', { params });
},
importConfig: (config, format = 'json', services = null) =>
api.post('/api/config/import', { config, format, ...(services ? { services } : {}) }),
getPending: () => api.get('/api/config/pending'),
cancelPending: () => api.delete('/api/config/pending'),
applyPending: () => api.post('/api/config/apply'),