mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-22 02:41:11 +01:00
feat(task): add update logic for task intervals for backup/sync
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
# app/task/__init__.py
|
# app/task/__init__.py
|
||||||
from flask import Blueprint, jsonify
|
from flask import Blueprint, jsonify, request
|
||||||
import logging
|
import logging
|
||||||
from ..db import get_db
|
from ..db import get_db
|
||||||
from .tasks import TaskScheduler
|
from .tasks import TaskScheduler
|
||||||
@@ -78,6 +78,63 @@ def get_task(task_id):
|
|||||||
return jsonify({"error": "An unexpected error occurred"}), 500
|
return jsonify({"error": "An unexpected error occurred"}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/<int:task_id>', 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('/<int:task_id>/run', methods=['POST'])
|
@bp.route('/<int:task_id>/run', methods=['POST'])
|
||||||
def trigger_task(task_id):
|
def trigger_task(task_id):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import Alert from '@ui/Alert';
|
||||||
|
|
||||||
export const getAllTasks = async () => {
|
export const getAllTasks = async () => {
|
||||||
try {
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
// components/settings/TaskCard.jsx
|
// components/settings/TaskCard.jsx
|
||||||
import React from 'react';
|
import React, {useState, useEffect} from 'react';
|
||||||
import {Play, Loader} from 'lucide-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 => {
|
const formatDateTime = dateString => {
|
||||||
if (!dateString) return 'Never';
|
if (!dateString) return 'Never';
|
||||||
return new Date(dateString).toLocaleString();
|
return new Date(dateString).toLocaleString();
|
||||||
@@ -13,8 +20,32 @@ const TaskCard = ({task, onTrigger, isTriggering}) => {
|
|||||||
return `${duration}s`;
|
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 (
|
return (
|
||||||
<tr className='bg-gray-900 border-b border-gray-700'>
|
<tr className={`bg-gray-900 ${!isLast ? 'border-b border-gray-700' : ''}`}>
|
||||||
<td className='py-4 px-4'>
|
<td className='py-4 px-4'>
|
||||||
<div className='flex items-center space-x-3'>
|
<div className='flex items-center space-x-3'>
|
||||||
<span className='font-medium text-gray-100'>
|
<span className='font-medium text-gray-100'>
|
||||||
@@ -23,7 +54,21 @@ const TaskCard = ({task, onTrigger, isTriggering}) => {
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className='py-4 px-4 text-gray-300'>
|
<td className='py-4 px-4 text-gray-300'>
|
||||||
{task.interval_minutes} minutes
|
{isEditable ? (
|
||||||
|
<div className='flex items-center space-x-2'>
|
||||||
|
<NumberInput
|
||||||
|
value={intervalValue}
|
||||||
|
onChange={setIntervalValue}
|
||||||
|
min={1}
|
||||||
|
max={43200}
|
||||||
|
step={1}
|
||||||
|
className='w-24'
|
||||||
|
/>
|
||||||
|
<span className='text-gray-400 text-sm'>minutes</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span>{task.interval_minutes} minutes</span>
|
||||||
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td className='py-4 px-4 text-gray-300'>
|
<td className='py-4 px-4 text-gray-300'>
|
||||||
{formatDateTime(task.last_run)}
|
{formatDateTime(task.last_run)}
|
||||||
|
|||||||
@@ -77,12 +77,14 @@ const TaskContainer = () => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{tasks.map(task => (
|
{tasks.map((task, index) => (
|
||||||
<TaskCard
|
<TaskCard
|
||||||
key={task.id}
|
key={task.id}
|
||||||
task={task}
|
task={task}
|
||||||
onTrigger={handleTriggerTask}
|
onTrigger={handleTriggerTask}
|
||||||
isTriggering={triggeringTask === task.id}
|
isTriggering={triggeringTask === task.id}
|
||||||
|
isLast={index === tasks.length - 1}
|
||||||
|
onIntervalUpdate={fetchTasks}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import {ChevronUp, ChevronDown} from 'lucide-react';
|
|||||||
const NumberInput = ({
|
const NumberInput = ({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
|
onBlur = () => {},
|
||||||
|
onFocus = () => {},
|
||||||
className = '',
|
className = '',
|
||||||
step = 1,
|
step = 1,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
@@ -24,26 +26,26 @@ const NumberInput = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBlur = () => {
|
const handleBlur = (e) => {
|
||||||
setIsFocused(false);
|
setIsFocused(false);
|
||||||
const numValue =
|
const numValue =
|
||||||
localValue === '' || localValue === '-' ? 0 : parseInt(localValue);
|
localValue === '' || localValue === '-' ? 0 : parseInt(localValue);
|
||||||
|
|
||||||
if (min !== undefined && numValue < min) {
|
if (min !== undefined && numValue < min) {
|
||||||
onChange(min);
|
onChange(min);
|
||||||
return;
|
} else if (max !== undefined && numValue > max) {
|
||||||
}
|
|
||||||
if (max !== undefined && numValue > max) {
|
|
||||||
onChange(max);
|
onChange(max);
|
||||||
return;
|
} else {
|
||||||
|
onChange(numValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange(numValue);
|
onBlur(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFocus = () => {
|
const handleFocus = (e) => {
|
||||||
setIsFocused(true);
|
setIsFocused(true);
|
||||||
setLocalValue(value.toString());
|
setLocalValue(value.toString());
|
||||||
|
onFocus(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
const increment = () => {
|
const increment = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user