Add Docker update support via Watchtower integration

Add web-based Docker container updates using Watchtower HTTP API.
When configured with WATCHTOWER_API_URL and WATCHTOWER_API_TOKEN
environment variables, administrators can trigger container updates
from the Update Manager page.

Features:
- WatchtowerClient service for Watchtower HTTP API communication
- Docker update progress page with animated Docker whale logo
- Real-time step tracking: Trigger, Pull, Stop, Restart, Health Check, Verify
- CSP-compatible progress bar using CSS classes
- Translated UI strings via Stimulus values
- Health endpoint polling to detect container restart
- Watchtower setup documentation for Docker installations
- WatchtowerClient made nullable for non-Docker installations
- Unit tests for WatchtowerClient
This commit is contained in:
Sebastian Almberg 2026-03-31 10:08:11 +02:00
parent 4206b702ff
commit 3cdd085d3b
14 changed files with 1553 additions and 55 deletions

View file

@ -12941,6 +12941,312 @@ Buerklin-API Authentication server:
<target>Backup download allowed</target>
</segment>
</unit>
<unit id="wt_setup_title" name="update_manager.docker.setup_title">
<segment state="translated">
<source>update_manager.docker.setup_title</source>
<target>Enable One-Click Docker Updates with Watchtower</target>
</segment>
</unit>
<unit id="wt_setup_desc" name="update_manager.docker.setup_description">
<segment state="translated">
<source>update_manager.docker.setup_description</source>
<target>Part-DB can update your Docker container automatically using Watchtower, an open-source container updater. Add Watchtower as a companion container and configure the connection below.</target>
</segment>
</unit>
<unit id="wt_setup_s1" name="update_manager.docker.setup_step1">
<segment state="translated">
<source>update_manager.docker.setup_step1</source>
<target>1. Add Watchtower to your docker-compose.yml:</target>
</segment>
</unit>
<unit id="wt_setup_s2" name="update_manager.docker.setup_step2">
<segment state="translated">
<source>update_manager.docker.setup_step2</source>
<target>2. Add these environment variables to your Part-DB container:</target>
</segment>
</unit>
<unit id="wt_setup_net" name="update_manager.docker.setup_network_hint">
<segment state="translated">
<source>update_manager.docker.setup_network_hint</source>
<target>Make sure Part-DB and Watchtower are on the same Docker network. If you use label-based filtering in Watchtower (WATCHTOWER_LABEL_ENABLE=true), add the label "com.centurylinklabs.watchtower.enable=true" to your Part-DB container.</target>
</segment>
</unit>
<unit id="wt_unreach_t" name="update_manager.docker.watchtower_unreachable_title">
<segment state="translated">
<source>update_manager.docker.watchtower_unreachable_title</source>
<target>Watchtower Not Reachable</target>
</segment>
</unit>
<unit id="wt_unreach_d" name="update_manager.docker.watchtower_unreachable_description">
<segment state="translated">
<source>update_manager.docker.watchtower_unreachable_description</source>
<target>Watchtower is configured but cannot be reached. Please verify that the Watchtower container is running and that the API URL and token are correct.</target>
</segment>
</unit>
<unit id="wt_confirm" name="update_manager.docker.confirm_update">
<segment state="translated">
<source>update_manager.docker.confirm_update</source>
<target>Are you sure you want to update Part-DB via Watchtower? The container will be restarted with the new image. Unlike Git updates, Docker updates cannot be automatically rolled back.</target>
</segment>
</unit>
<unit id="wt_update_btn" name="update_manager.docker.update_via_watchtower">
<segment state="translated">
<source>update_manager.docker.update_via_watchtower</source>
<target>Update via Watchtower to</target>
</segment>
</unit>
<unit id="wt_no_rollback" name="update_manager.docker.no_rollback_warning">
<segment state="translated">
<source>update_manager.docker.no_rollback_warning</source>
<target>Docker updates cannot be automatically rolled back. A database backup will be created before updating so you can restore your data if needed.</target>
</segment>
</unit>
<unit id="wt_prog_title" name="update_manager.docker.progress_title">
<segment state="translated">
<source>update_manager.docker.progress_title</source>
<target>Docker Update in Progress</target>
</segment>
</unit>
<unit id="wt_waiting" name="update_manager.docker.waiting_for_watchtower">
<segment state="translated">
<source>update_manager.docker.waiting_for_watchtower</source>
<target>Waiting for Watchtower to pull the new image...</target>
</segment>
</unit>
<unit id="wt_elapsed" name="update_manager.docker.elapsed">
<segment state="translated">
<source>update_manager.docker.elapsed</source>
<target>Elapsed</target>
</segment>
</unit>
<unit id="wt_wait_t" name="update_manager.docker.waiting_title">
<segment state="translated">
<source>update_manager.docker.waiting_title</source>
<target>Update Triggered</target>
</segment>
</unit>
<unit id="wt_wait_d" name="update_manager.docker.waiting_description">
<segment state="translated">
<source>update_manager.docker.waiting_description</source>
<target>Watchtower has been notified. It will pull the latest Docker image and restart the Part-DB container.</target>
</segment>
</unit>
<unit id="wt_working" name="update_manager.docker.watchtower_working">
<segment state="translated">
<source>update_manager.docker.watchtower_working</source>
<target>Watchtower is processing the update...</target>
</segment>
</unit>
<unit id="wt_work_hint" name="update_manager.docker.watchtower_working_hint">
<segment state="translated">
<source>update_manager.docker.watchtower_working_hint</source>
<target>This may take a few minutes depending on your internet speed and image size.</target>
</segment>
</unit>
<unit id="wt_restart_t" name="update_manager.docker.restarting_title">
<segment state="translated">
<source>update_manager.docker.restarting_title</source>
<target>Container Restarting</target>
</segment>
</unit>
<unit id="wt_restart_d" name="update_manager.docker.restarting_description">
<segment state="translated">
<source>update_manager.docker.restarting_description</source>
<target>Watchtower has pulled the new image and is restarting the Part-DB container.</target>
</segment>
</unit>
<unit id="wt_restart_h" name="update_manager.docker.restarting_hint">
<segment state="translated">
<source>update_manager.docker.restarting_hint</source>
<target>The page will automatically detect when the server comes back online. This usually takes 10-30 seconds.</target>
</segment>
</unit>
<unit id="wt_success_t" name="update_manager.docker.success_title">
<segment state="translated">
<source>update_manager.docker.success_title</source>
<target>Update Complete!</target>
</segment>
</unit>
<unit id="wt_success_m" name="update_manager.docker.success_message">
<segment state="translated">
<source>update_manager.docker.success_message</source>
<target>Part-DB has been successfully updated via Watchtower.</target>
</segment>
</unit>
<unit id="wt_prev_ver" name="update_manager.docker.previous_version">
<segment state="translated">
<source>update_manager.docker.previous_version</source>
<target>Previous version</target>
</segment>
</unit>
<unit id="wt_new_ver" name="update_manager.docker.new_version">
<segment state="translated">
<source>update_manager.docker.new_version</source>
<target>New version</target>
</segment>
</unit>
<unit id="wt_back" name="update_manager.docker.back_to_update_manager">
<segment state="translated">
<source>update_manager.docker.back_to_update_manager</source>
<target>Back to Update Manager</target>
</segment>
</unit>
<unit id="wt_home" name="update_manager.docker.go_to_homepage">
<segment state="translated">
<source>update_manager.docker.go_to_homepage</source>
<target>Go to Homepage</target>
</segment>
</unit>
<unit id="wt_timeout_t" name="update_manager.docker.timeout_title">
<segment state="translated">
<source>update_manager.docker.timeout_title</source>
<target>Update Taking Longer Than Expected</target>
</segment>
</unit>
<unit id="wt_timeout_m" name="update_manager.docker.timeout_message">
<segment state="translated">
<source>update_manager.docker.timeout_message</source>
<target>The update is taking longer than expected. Check the Watchtower container logs for details. The update may still be in progress.</target>
</segment>
</unit>
<unit id="wt_retry" name="update_manager.docker.retry">
<segment state="translated">
<source>update_manager.docker.retry</source>
<target>Retry</target>
</segment>
</unit>
<unit id="wt_warning" name="update_manager.docker.warning">
<segment state="translated">
<source>update_manager.docker.warning</source>
<target>Warning</target>
</segment>
</unit>
<unit id="wt_no_close" name="update_manager.docker.do_not_close">
<segment state="translated">
<source>update_manager.docker.do_not_close</source>
<target>Do not close this page. It will automatically detect when the update is complete.</target>
</segment>
</unit>
<unit id="wt_via" name="update_manager.docker.updating_via_watchtower">
<segment state="translated">
<source>update_manager.docker.updating_via_watchtower</source>
<target>Updating via Watchtower</target>
</segment>
</unit>
<unit id="wt_step_wait" name="update_manager.docker.step_waiting">
<segment state="translated">
<source>update_manager.docker.step_waiting</source>
<target>Pulling Image</target>
</segment>
</unit>
<unit id="wt_steps" name="update_manager.docker.steps">
<segment state="translated">
<source>update_manager.docker.steps</source>
<target>Update Steps</target>
</segment>
</unit>
<unit id="wt_step_trig" name="update_manager.docker.step_trigger">
<segment state="translated">
<source>update_manager.docker.step_trigger</source>
<target>Trigger Update</target>
</segment>
</unit>
<unit id="wt_step_trig_d" name="update_manager.docker.step_trigger_desc">
<segment state="translated">
<source>update_manager.docker.step_trigger_desc</source>
<target>Watchtower has been notified to check for updates</target>
</segment>
</unit>
<unit id="wt_step_pull" name="update_manager.docker.step_pull">
<segment state="translated">
<source>update_manager.docker.step_pull</source>
<target>Pull New Image</target>
</segment>
</unit>
<unit id="wt_step_pull_d" name="update_manager.docker.step_pull_desc">
<segment state="translated">
<source>update_manager.docker.step_pull_desc</source>
<target>Downloading the latest Docker image from the registry</target>
</segment>
</unit>
<unit id="wt_step_rest" name="update_manager.docker.step_restart">
<segment state="translated">
<source>update_manager.docker.step_restart</source>
<target>Restart Container</target>
</segment>
</unit>
<unit id="wt_step_rest_d" name="update_manager.docker.step_restart_desc">
<segment state="translated">
<source>update_manager.docker.step_restart_desc</source>
<target>Stopping old container and starting new one</target>
</segment>
</unit>
<unit id="wt_step_ver" name="update_manager.docker.step_verify">
<segment state="translated">
<source>update_manager.docker.step_verify</source>
<target>Verify</target>
</segment>
</unit>
<unit id="wt_step_ver_d" name="update_manager.docker.step_verify_desc">
<segment state="translated">
<source>update_manager.docker.step_verify_desc</source>
<target>Confirming Part-DB is running on the new version</target>
</segment>
</unit>
<unit id="wt_status" name="update_manager.docker.watchtower_status">
<segment state="translated">
<source>update_manager.docker.watchtower_status</source>
<target>Watchtower</target>
</segment>
</unit>
<unit id="wt_connected" name="update_manager.docker.watchtower_connected">
<segment state="translated">
<source>update_manager.docker.watchtower_connected</source>
<target>Connected</target>
</segment>
</unit>
<unit id="wt_unreach_s" name="update_manager.docker.watchtower_unreachable_short">
<segment state="translated">
<source>update_manager.docker.watchtower_unreachable_short</source>
<target>Unreachable</target>
</segment>
</unit>
<unit id="wt_not_conf" name="update_manager.docker.watchtower_not_configured">
<segment state="translated">
<source>update_manager.docker.watchtower_not_configured</source>
<target>Not configured</target>
</segment>
</unit>
<unit id="wt_step_stop" name="update_manager.docker.step_stop">
<segment state="translated">
<source>update_manager.docker.step_stop</source>
<target>Stop Container</target>
</segment>
</unit>
<unit id="wt_step_stop_d" name="update_manager.docker.step_stop_desc">
<segment state="translated">
<source>update_manager.docker.step_stop_desc</source>
<target>Gracefully stopping the current container before recreation</target>
</segment>
</unit>
<unit id="wt_step_health" name="update_manager.docker.step_health">
<segment state="translated">
<source>update_manager.docker.step_health</source>
<target>Health Check</target>
</segment>
</unit>
<unit id="wt_step_health_d" name="update_manager.docker.step_health_desc">
<segment state="translated">
<source>update_manager.docker.step_health_desc</source>
<target>Waiting for the new container to pass health checks</target>
</segment>
</unit>
<unit id="wt_updating" name="update_manager.docker.updating">
<segment state="translated">
<source>update_manager.docker.updating</source>
<target>Updating Part-DB via Docker...</target>
</segment>
</unit>
<unit id="b8JxfcX" name="part.create_from_info_provider.lot_filled_from_barcode">
<segment state="translated">
<source>part.create_from_info_provider.lot_filled_from_barcode</source>