|
31 | 31 | load_dotenv() |
32 | 32 |
|
33 | 33 | # Version |
34 | | -VERSION = "0.4.14" |
| 34 | +VERSION = "0.4.15" |
35 | 35 |
|
36 | 36 | HOST_METRICS_CACHE = {} |
37 | 37 | HOST_METRICS_CACHE_TTL_SECONDS = 20 |
@@ -1300,6 +1300,29 @@ def update_schedule_container_id(schedule_id, container_id): |
1300 | 1300 | except Exception as e: |
1301 | 1301 | logger.error(f"Failed to update schedule {schedule_id} container_id to {container_id}: {e}") |
1302 | 1302 |
|
| 1303 | +def disable_container_schedules(container_id: str, container_name: str, host_id: int) -> int: |
| 1304 | + """Disable schedules linked to a container that has been removed.""" |
| 1305 | + try: |
| 1306 | + short_id = (container_id or '')[:12] |
| 1307 | + conn = get_db() |
| 1308 | + cursor = conn.cursor() |
| 1309 | + cursor.execute( |
| 1310 | + ''' |
| 1311 | + UPDATE schedules |
| 1312 | + SET enabled = 0 |
| 1313 | + WHERE host_id = ? |
| 1314 | + AND (container_id = ? OR container_id = ? OR container_name = ?) |
| 1315 | + ''', |
| 1316 | + (host_id, container_id, short_id, container_name) |
| 1317 | + ) |
| 1318 | + affected = cursor.rowcount |
| 1319 | + conn.commit() |
| 1320 | + conn.close() |
| 1321 | + return affected |
| 1322 | + except Exception as e: |
| 1323 | + logger.error(f"Failed to disable schedules for container {container_name}: {e}") |
| 1324 | + return 0 |
| 1325 | + |
1303 | 1326 | def restart_container(container_id: str, container_name: str, schedule_id: Optional[int] = None, host_id: int = 1) -> Tuple[bool, str]: |
1304 | 1327 | """ |
1305 | 1328 | Restart a Docker container and log the action. |
@@ -1489,6 +1512,52 @@ def stop_container(container_id: str, container_name: str, schedule_id: Optional |
1489 | 1512 | send_ntfy_notification(container_name, 'stop', 'error', message, schedule_id) |
1490 | 1513 | return False, message |
1491 | 1514 |
|
| 1515 | +def delete_container(container_id: str, container_name: str, remove_volumes: bool = False, force: bool = False, host_id: int = 1) -> Tuple[bool, str]: |
| 1516 | + """ |
| 1517 | + Delete a Docker container and log the action. |
| 1518 | +
|
| 1519 | + Removes the specified container on the given Docker host. Optionally removes |
| 1520 | + associated volumes. If the container is running and force is False, it will |
| 1521 | + be stopped before removal. Any schedules linked to the container are disabled |
| 1522 | + to prevent future failures. |
| 1523 | + """ |
| 1524 | + try: |
| 1525 | + docker_client = docker_manager.get_client(host_id) |
| 1526 | + if not docker_client: |
| 1527 | + raise Exception(f"Cannot connect to Docker host {host_id}") |
| 1528 | + |
| 1529 | + container, _ = resolve_container(docker_client, container_id, container_name) |
| 1530 | + if not container: |
| 1531 | + raise docker.errors.NotFound(f"No such container: {container_id}") |
| 1532 | + |
| 1533 | + container.reload() |
| 1534 | + if container.status == 'running' and not force: |
| 1535 | + container.stop() |
| 1536 | + |
| 1537 | + container.remove(v=remove_volumes, force=force) |
| 1538 | + |
| 1539 | + disabled = disable_container_schedules(container.id, container_name, host_id) |
| 1540 | + |
| 1541 | + message = f"Container {container_name} deleted successfully" |
| 1542 | + if remove_volumes: |
| 1543 | + message += " (volumes removed)" |
| 1544 | + if disabled: |
| 1545 | + message += f"; disabled {disabled} schedule(s)" |
| 1546 | + |
| 1547 | + logger.info(message) |
| 1548 | + log_action(None, container_name, 'delete', 'success', message, host_id) |
| 1549 | + send_discord_notification(container_name, 'delete', 'success', message, None) |
| 1550 | + send_ntfy_notification(container_name, 'delete', 'success', message, None) |
| 1551 | + |
| 1552 | + return True, message |
| 1553 | + except Exception as e: |
| 1554 | + message = f"Failed to delete container {container_name}: {str(e)}" |
| 1555 | + logger.error(message) |
| 1556 | + log_action(None, container_name, 'delete', 'error', message, host_id) |
| 1557 | + send_discord_notification(container_name, 'delete', 'error', message, None) |
| 1558 | + send_ntfy_notification(container_name, 'delete', 'error', message, None) |
| 1559 | + return False, message |
| 1560 | + |
1492 | 1561 | def pause_container(container_id: str, container_name: str, schedule_id: Optional[int] = None, host_id: int = 1) -> Tuple[bool, str]: |
1493 | 1562 | """ |
1494 | 1563 | Pause a Docker container and log the action. |
@@ -2373,6 +2442,30 @@ def api_unpause_container(container_id): |
2373 | 2442 | success, message = unpause_container(container_id, container_name, host_id=host_id) |
2374 | 2443 | return jsonify({'success': success, 'message': message}) |
2375 | 2444 |
|
| 2445 | +@app.route('/api/container/<container_id>/delete', methods=['POST']) |
| 2446 | +@api_key_or_login_required |
| 2447 | +def api_delete_container(container_id): |
| 2448 | + """API endpoint to delete a container""" |
| 2449 | + is_valid, error_msg = validate_container_id(container_id) |
| 2450 | + if not is_valid: |
| 2451 | + return jsonify({'error': error_msg}), 400 |
| 2452 | + |
| 2453 | + if getattr(request, 'api_key_auth', False) and request.api_key_permissions == 'read': |
| 2454 | + return jsonify({'error': 'API key does not have write permission'}), 403 |
| 2455 | + |
| 2456 | + data = request.json or {} |
| 2457 | + container_name = sanitize_string(data.get('name', 'unknown'), max_length=255) |
| 2458 | + host_id = data.get('host_id', 1) |
| 2459 | + remove_volumes = bool(data.get('remove_volumes', False)) |
| 2460 | + force = bool(data.get('force', False)) |
| 2461 | + |
| 2462 | + is_valid, error_msg = validate_host_id(host_id) |
| 2463 | + if not is_valid: |
| 2464 | + return jsonify({'error': error_msg}), 400 |
| 2465 | + |
| 2466 | + success, message = delete_container(container_id, container_name, remove_volumes=remove_volumes, force=force, host_id=host_id) |
| 2467 | + return jsonify({'success': success, 'message': message}) |
| 2468 | + |
2376 | 2469 | @app.route('/api/container/<container_id>/check-update', methods=['GET']) |
2377 | 2470 | def api_check_container_update(container_id): |
2378 | 2471 | """API endpoint to check if a container has an update available""" |
|
0 commit comments