diff --git a/backend/app/task/__init__.py b/backend/app/task/__init__.py index b84e18e..32d2017 100644 --- a/backend/app/task/__init__.py +++ b/backend/app/task/__init__.py @@ -1,5 +1,5 @@ # app/task/__init__.py -from flask import Blueprint, jsonify +from flask import Blueprint, jsonify, request import logging from ..db import get_db from .tasks import TaskScheduler @@ -78,6 +78,63 @@ def get_task(task_id): return jsonify({"error": "An unexpected error occurred"}), 500 +@bp.route('/', methods=['PUT']) +def update_task(task_id): + try: + data = request.get_json() + if not data: + return jsonify({"error": "No data provided"}), 400 + + interval_minutes = data.get('interval_minutes') + if interval_minutes is None: + return jsonify({"error": "interval_minutes is required"}), 400 + + if not isinstance(interval_minutes, int) or interval_minutes < 1: + return jsonify({"error": "interval_minutes must be a positive integer"}), 400 + + with get_db() as conn: + # Check if task exists + task = conn.execute('SELECT * FROM scheduled_tasks WHERE id = ?', + (task_id, )).fetchone() + + if not task: + return jsonify({"error": "Task not found"}), 404 + + # Update the interval in database + conn.execute( + 'UPDATE scheduled_tasks SET interval_minutes = ? WHERE id = ?', + (interval_minutes, task_id) + ) + conn.commit() + + # Update the scheduler + scheduler_instance = TaskScheduler.get_instance() + if scheduler_instance and interval_minutes > 0: + # Remove old job + scheduler_instance.scheduler.remove_job(str(task_id)) + + # Create new task instance with updated interval + task_class = TaskScheduler.get_task_class(task['type']) + if task_class: + new_task = task_class( + id=task_id, + name=task['name'], + interval_minutes=interval_minutes + ) + scheduler_instance.schedule_task(new_task) + + logger.info(f"Updated task {task_id} interval to {interval_minutes} minutes") + + return jsonify({ + "success": True, + "message": f"Task interval updated to {interval_minutes} minutes" + }), 200 + + except Exception as e: + logger.exception(f"Failed to update task {task_id}") + return jsonify({"error": f"Failed to update task: {str(e)}"}), 500 + + @bp.route('//run', methods=['POST']) def trigger_task(task_id): try: diff --git a/frontend/src/api/task.js b/frontend/src/api/task.js index 930cec3..9db9c43 100644 --- a/frontend/src/api/task.js +++ b/frontend/src/api/task.js @@ -1,4 +1,5 @@ import axios from 'axios'; +import Alert from '@ui/Alert'; export const getAllTasks = async () => { try { @@ -37,3 +38,23 @@ export const triggerTask = async taskId => { }; } }; + +export const updateTaskInterval = async (taskId, intervalMinutes) => { + try { + const response = await axios.put(`/api/tasks/${taskId}`, { + interval_minutes: intervalMinutes + }); + Alert.success(response.data.message || 'Task interval updated successfully'); + return { + success: true, + data: response.data + }; + } catch (error) { + const errorMessage = error.response?.data?.error || 'Failed to update task interval'; + Alert.error(errorMessage); + return { + success: false, + error: errorMessage + }; + } +}; diff --git a/frontend/src/components/settings/tasks/TaskCard.jsx b/frontend/src/components/settings/tasks/TaskCard.jsx index 887591b..defd6ed 100644 --- a/frontend/src/components/settings/tasks/TaskCard.jsx +++ b/frontend/src/components/settings/tasks/TaskCard.jsx @@ -1,8 +1,15 @@ // components/settings/TaskCard.jsx -import React from 'react'; -import {Play, Loader} from 'lucide-react'; +import React, {useState, useEffect} from 'react'; +import {Play, Loader, Edit2, Check, X} from 'lucide-react'; +import NumberInput from '@ui/NumberInput'; +import {updateTaskInterval} from '@/api/task'; -const TaskCard = ({task, onTrigger, isTriggering}) => { +const TaskCard = ({task, onTrigger, isTriggering, isLast, onIntervalUpdate}) => { + const [intervalValue, setIntervalValue] = useState(task.interval_minutes); + const [originalValue, setOriginalValue] = useState(task.interval_minutes); + + // Only allow editing for Repository Sync and Backup tasks + const isEditable = task.type === 'Sync' || task.type === 'Backup'; const formatDateTime = dateString => { if (!dateString) return 'Never'; return new Date(dateString).toLocaleString(); @@ -13,8 +20,32 @@ const TaskCard = ({task, onTrigger, isTriggering}) => { return `${duration}s`; }; + useEffect(() => { + setIntervalValue(task.interval_minutes); + setOriginalValue(task.interval_minutes); + }, [task.interval_minutes]); + + useEffect(() => { + if (intervalValue !== originalValue && intervalValue > 0) { + const updateInterval = async () => { + const result = await updateTaskInterval(task.id, intervalValue); + if (result.success) { + setOriginalValue(intervalValue); + // Refresh task data to get new next_run time + if (onIntervalUpdate) { + onIntervalUpdate(); + } + } else { + // Reset to original value if update failed + setIntervalValue(originalValue); + } + }; + updateInterval(); + } + }, [intervalValue]); + return ( - +
@@ -23,7 +54,21 @@ const TaskCard = ({task, onTrigger, isTriggering}) => {
- {task.interval_minutes} minutes + {isEditable ? ( +
+ + minutes +
+ ) : ( + {task.interval_minutes} minutes + )} {formatDateTime(task.last_run)} diff --git a/frontend/src/components/settings/tasks/TaskContainer.jsx b/frontend/src/components/settings/tasks/TaskContainer.jsx index 2e9e689..02bf7be 100644 --- a/frontend/src/components/settings/tasks/TaskContainer.jsx +++ b/frontend/src/components/settings/tasks/TaskContainer.jsx @@ -77,12 +77,14 @@ const TaskContainer = () => { - {tasks.map(task => ( + {tasks.map((task, index) => ( ))} diff --git a/frontend/src/components/ui/NumberInput.jsx b/frontend/src/components/ui/NumberInput.jsx index e5c8430..87b9375 100644 --- a/frontend/src/components/ui/NumberInput.jsx +++ b/frontend/src/components/ui/NumberInput.jsx @@ -5,6 +5,8 @@ import {ChevronUp, ChevronDown} from 'lucide-react'; const NumberInput = ({ value, onChange, + onBlur = () => {}, + onFocus = () => {}, className = '', step = 1, disabled = false, @@ -24,26 +26,26 @@ const NumberInput = ({ } }; - const handleBlur = () => { + const handleBlur = (e) => { setIsFocused(false); const numValue = localValue === '' || localValue === '-' ? 0 : parseInt(localValue); if (min !== undefined && numValue < min) { onChange(min); - return; - } - if (max !== undefined && numValue > max) { + } else if (max !== undefined && numValue > max) { onChange(max); - return; + } else { + onChange(numValue); } - - onChange(numValue); + + onBlur(e); }; - const handleFocus = () => { + const handleFocus = (e) => { setIsFocused(true); setLocalValue(value.toString()); + onFocus(e); }; const increment = () => {