diff --git a/src/Controller/UpdateManagerController.php b/src/Controller/UpdateManagerController.php index 474c86fc..0afc7905 100644 --- a/src/Controller/UpdateManagerController.php +++ b/src/Controller/UpdateManagerController.php @@ -314,6 +314,80 @@ class UpdateManagerController extends AbstractController return $this->json($details); } + /** + * Create a manual backup. + */ + #[Route('/backup', name: 'admin_update_manager_backup', methods: ['POST'])] + public function createBackup(Request $request): Response + { + $this->denyAccessUnlessGranted('@system.manage_updates'); + + if (!$this->isCsrfTokenValid('update_manager_backup', $request->request->get('_token'))) { + $this->addFlash('error', 'Invalid CSRF token.'); + return $this->redirectToRoute('admin_update_manager'); + } + + if ($this->updateExecutor->isLocked()) { + $this->addFlash('error', 'Cannot create backup while an update is in progress.'); + return $this->redirectToRoute('admin_update_manager'); + } + + try { + $backupPath = $this->backupManager->createBackup(null, 'manual'); + $this->addFlash('success', 'update_manager.backup.created'); + } catch (\Exception $e) { + $this->addFlash('error', 'Backup failed: ' . $e->getMessage()); + } + + return $this->redirectToRoute('admin_update_manager'); + } + + /** + * Delete a backup file. + */ + #[Route('/backup/delete', name: 'admin_update_manager_backup_delete', methods: ['POST'])] + public function deleteBackup(Request $request): Response + { + $this->denyAccessUnlessGranted('@system.manage_updates'); + + if (!$this->isCsrfTokenValid('update_manager_delete', $request->request->get('_token'))) { + $this->addFlash('error', 'Invalid CSRF token.'); + return $this->redirectToRoute('admin_update_manager'); + } + + $filename = $request->request->get('filename'); + if ($filename && $this->backupManager->deleteBackup($filename)) { + $this->addFlash('success', 'update_manager.backup.deleted'); + } else { + $this->addFlash('error', 'update_manager.backup.delete_error'); + } + + return $this->redirectToRoute('admin_update_manager'); + } + + /** + * Delete an update log file. + */ + #[Route('/log/delete', name: 'admin_update_manager_log_delete', methods: ['POST'])] + public function deleteLog(Request $request): Response + { + $this->denyAccessUnlessGranted('@system.manage_updates'); + + if (!$this->isCsrfTokenValid('update_manager_delete', $request->request->get('_token'))) { + $this->addFlash('error', 'Invalid CSRF token.'); + return $this->redirectToRoute('admin_update_manager'); + } + + $filename = $request->request->get('filename'); + if ($filename && $this->updateExecutor->deleteLog($filename)) { + $this->addFlash('success', 'update_manager.log.deleted'); + } else { + $this->addFlash('error', 'update_manager.log.delete_error'); + } + + return $this->redirectToRoute('admin_update_manager'); + } + /** * Restore from a backup. */ diff --git a/src/Services/System/UpdateExecutor.php b/src/Services/System/UpdateExecutor.php index 2fe54173..fca7d1fa 100644 --- a/src/Services/System/UpdateExecutor.php +++ b/src/Services/System/UpdateExecutor.php @@ -602,6 +602,33 @@ class UpdateExecutor } + /** + * Delete a specific update log file. + */ + public function deleteLog(string $filename): bool + { + // Validate filename pattern for security + if (!preg_match('/^update-[\w.\-]+\.log$/', $filename)) { + $this->logger->warning('Attempted to delete invalid log filename: ' . $filename); + return false; + } + + $logPath = $this->project_dir . '/' . self::UPDATE_LOG_DIR . '/' . $filename; + + if (!file_exists($logPath)) { + return false; + } + + try { + $this->filesystem->remove($logPath); + $this->logger->info('Deleted update log: ' . $filename); + return true; + } catch (\Exception $e) { + $this->logger->error('Failed to delete update log: ' . $e->getMessage()); + return false; + } + } + /** * Restore from a backup file with maintenance mode and cache clearing. * diff --git a/templates/admin/update_manager/index.html.twig b/templates/admin/update_manager/index.html.twig index 44b9f8c0..8afa4472 100644 --- a/templates/admin/update_manager/index.html.twig +++ b/templates/admin/update_manager/index.html.twig @@ -343,11 +343,26 @@ {{ log.date|date('Y-m-d H:i') }} {{ log.file }} - - - - + +
+ + + + {% if is_granted('@system.manage_updates') %} +
+ + + +
+ {% endif %} +
{% else %} @@ -362,6 +377,17 @@
+ {% if is_granted('@system.manage_updates') and status.can_auto_update and not is_locked %} +
+
+ + +
+
+ {% endif %}
@@ -383,24 +409,38 @@ {{ (backup.size / 1024 / 1024)|number_format(1) }} MB {% else %} diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index d9418563..8220e709 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -12311,6 +12311,72 @@ Buerklin-API Authentication server: Backup restore is disabled by server configuration. + + + update_manager.backup.create + Create Backup + + + + + update_manager.backup.create.confirm + Create a full backup now? This may take a moment. + + + + + update_manager.backup.created + Backup created successfully. + + + + + update_manager.backup.delete.confirm + Are you sure you want to delete this backup? + + + + + update_manager.backup.deleted + Backup deleted successfully. + + + + + update_manager.backup.delete_error + Failed to delete backup. + + + + + update_manager.log.delete.confirm + Are you sure you want to delete this log? + + + + + update_manager.log.deleted + Log deleted successfully. + + + + + update_manager.log.delete_error + Failed to delete log. + + + + + update_manager.view_log + View log + + + + + update_manager.delete + Delete + + settings.ips.conrad
- {% if status.can_auto_update and validation.valid and not backup_restore_disabled %} -
- - - - -
- {% endif %} +
+ {% if status.can_auto_update and validation.valid and not backup_restore_disabled %} +
+ + + + +
+ {% endif %} + {% if is_granted('@system.manage_updates') %} +
+ + + +
+ {% endif %} +