Update next time execution time when starting function
This commit is contained in:
175
routes/timer.py
175
routes/timer.py
@@ -149,6 +149,75 @@ DEFAULT_ENVIRONMENT = """{
|
|||||||
|
|
||||||
timer = Blueprint('timer', __name__)
|
timer = Blueprint('timer', __name__)
|
||||||
|
|
||||||
|
def calculate_next_run(trigger_type, frequency_minutes=None, run_date=None, cron_expression=None, base_time=None):
|
||||||
|
"""
|
||||||
|
Calculate the next execution time based on trigger configuration.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
trigger_type: One of 'interval', 'date', or 'cron'
|
||||||
|
frequency_minutes: Minutes between executions (for interval type)
|
||||||
|
run_date: Specific datetime to run (for date type)
|
||||||
|
cron_expression: Cron expression string (for cron type)
|
||||||
|
base_time: Base datetime to calculate from (defaults to now)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (next_run datetime, error_response dict or None)
|
||||||
|
If error_response is not None, it contains the error to return to client
|
||||||
|
"""
|
||||||
|
if base_time is None:
|
||||||
|
base_time = datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
next_run = None
|
||||||
|
|
||||||
|
if trigger_type == 'interval':
|
||||||
|
if frequency_minutes is None:
|
||||||
|
return None, {
|
||||||
|
"status": "error",
|
||||||
|
"message": "frequency_minutes is required for interval trigger type"
|
||||||
|
}
|
||||||
|
next_run = base_time + timedelta(minutes=int(frequency_minutes))
|
||||||
|
|
||||||
|
elif trigger_type == 'date':
|
||||||
|
if run_date is None:
|
||||||
|
return None, {
|
||||||
|
"status": "error",
|
||||||
|
"message": "run_date is required for date trigger type"
|
||||||
|
}
|
||||||
|
# run_date should already be a datetime object
|
||||||
|
if isinstance(run_date, str):
|
||||||
|
run_date = datetime.fromisoformat(run_date)
|
||||||
|
next_run = run_date
|
||||||
|
|
||||||
|
elif trigger_type == 'cron':
|
||||||
|
if cron_expression is None:
|
||||||
|
return None, {
|
||||||
|
"status": "error",
|
||||||
|
"message": "cron_expression is required for cron trigger type"
|
||||||
|
}
|
||||||
|
from croniter import croniter
|
||||||
|
|
||||||
|
# Validate cron expression
|
||||||
|
try:
|
||||||
|
if not croniter.is_valid(cron_expression):
|
||||||
|
return None, {
|
||||||
|
"status": "error",
|
||||||
|
"message": "Invalid cron expression format"
|
||||||
|
}
|
||||||
|
# Calculate next run time from base_time
|
||||||
|
next_run = croniter(cron_expression, base_time).get_next(datetime)
|
||||||
|
except Exception as e:
|
||||||
|
return None, {
|
||||||
|
"status": "error",
|
||||||
|
"message": f"Invalid cron expression: {str(e)}"
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return None, {
|
||||||
|
"status": "error",
|
||||||
|
"message": f"Invalid trigger type: {trigger_type}"
|
||||||
|
}
|
||||||
|
|
||||||
|
return next_run, None
|
||||||
|
|
||||||
@timer.route('/overview')
|
@timer.route('/overview')
|
||||||
@login_required
|
@login_required
|
||||||
def overview():
|
def overview():
|
||||||
@@ -192,37 +261,29 @@ def new():
|
|||||||
"message": "Invalid trigger type"
|
"message": "Invalid trigger type"
|
||||||
}), 400
|
}), 400
|
||||||
|
|
||||||
# Calculate next_run based on trigger type
|
# Calculate next_run based on trigger type using centralized function
|
||||||
next_run = None
|
next_run, error = calculate_next_run(
|
||||||
|
trigger_type,
|
||||||
|
frequency_minutes=data.get('frequency_minutes'),
|
||||||
|
run_date=data.get('run_date'),
|
||||||
|
cron_expression=data.get('cron_expression')
|
||||||
|
)
|
||||||
|
|
||||||
|
if error:
|
||||||
|
return jsonify(error), 400
|
||||||
|
|
||||||
|
# Extract individual variables for database storage
|
||||||
frequency_minutes = None
|
frequency_minutes = None
|
||||||
run_date = None
|
run_date = None
|
||||||
cron_expression = None
|
cron_expression = None
|
||||||
|
|
||||||
if trigger_type == 'interval':
|
if trigger_type == 'interval':
|
||||||
frequency_minutes = int(data.get('frequency_minutes'))
|
frequency_minutes = int(data.get('frequency_minutes'))
|
||||||
next_run = datetime.now(timezone.utc) + timedelta(minutes=frequency_minutes)
|
|
||||||
elif trigger_type == 'date':
|
elif trigger_type == 'date':
|
||||||
run_date = datetime.fromisoformat(data.get('run_date'))
|
run_date = datetime.fromisoformat(data.get('run_date'))
|
||||||
next_run = run_date
|
|
||||||
elif trigger_type == 'cron':
|
elif trigger_type == 'cron':
|
||||||
from croniter import croniter
|
|
||||||
cron_expression = data.get('cron_expression')
|
cron_expression = data.get('cron_expression')
|
||||||
|
|
||||||
# Validate cron expression
|
|
||||||
try:
|
|
||||||
if not croniter.is_valid(cron_expression):
|
|
||||||
return jsonify({
|
|
||||||
"status": "error",
|
|
||||||
"message": "Invalid cron expression format"
|
|
||||||
}), 400
|
|
||||||
# Calculate first run time
|
|
||||||
next_run = croniter(cron_expression, datetime.now(timezone.utc)).get_next(datetime)
|
|
||||||
except Exception as e:
|
|
||||||
return jsonify({
|
|
||||||
"status": "error",
|
|
||||||
"message": f"Invalid cron expression: {str(e)}"
|
|
||||||
}), 400
|
|
||||||
|
|
||||||
# Insert new timer function
|
# Insert new timer function
|
||||||
db.execute("""
|
db.execute("""
|
||||||
INSERT INTO timer_functions
|
INSERT INTO timer_functions
|
||||||
@@ -301,37 +362,29 @@ def edit(function_id):
|
|||||||
"message": "Invalid trigger type"
|
"message": "Invalid trigger type"
|
||||||
}), 400
|
}), 400
|
||||||
|
|
||||||
# Calculate next_run based on trigger type
|
# Calculate next_run based on trigger type using centralized function
|
||||||
next_run = None
|
next_run, error = calculate_next_run(
|
||||||
|
trigger_type,
|
||||||
|
frequency_minutes=data.get('frequency_minutes'),
|
||||||
|
run_date=data.get('run_date'),
|
||||||
|
cron_expression=data.get('cron_expression')
|
||||||
|
)
|
||||||
|
|
||||||
|
if error:
|
||||||
|
return jsonify(error), 400
|
||||||
|
|
||||||
|
# Extract individual variables for database storage
|
||||||
frequency_minutes = None
|
frequency_minutes = None
|
||||||
run_date = None
|
run_date = None
|
||||||
cron_expression = None
|
cron_expression = None
|
||||||
|
|
||||||
if trigger_type == 'interval':
|
if trigger_type == 'interval':
|
||||||
frequency_minutes = int(data.get('frequency_minutes'))
|
frequency_minutes = int(data.get('frequency_minutes'))
|
||||||
next_run = datetime.now(timezone.utc) + timedelta(minutes=frequency_minutes)
|
|
||||||
elif trigger_type == 'date':
|
elif trigger_type == 'date':
|
||||||
run_date = datetime.fromisoformat(data.get('run_date'))
|
run_date = datetime.fromisoformat(data.get('run_date'))
|
||||||
next_run = run_date
|
|
||||||
elif trigger_type == 'cron':
|
elif trigger_type == 'cron':
|
||||||
from croniter import croniter
|
|
||||||
cron_expression = data.get('cron_expression')
|
cron_expression = data.get('cron_expression')
|
||||||
|
|
||||||
# Validate cron expression
|
|
||||||
try:
|
|
||||||
if not croniter.is_valid(cron_expression):
|
|
||||||
return jsonify({
|
|
||||||
"status": "error",
|
|
||||||
"message": "Invalid cron expression format"
|
|
||||||
}), 400
|
|
||||||
# Calculate first run time
|
|
||||||
next_run = croniter(cron_expression, datetime.now(timezone.utc)).get_next(datetime)
|
|
||||||
except Exception as e:
|
|
||||||
return jsonify({
|
|
||||||
"status": "error",
|
|
||||||
"message": f"Invalid cron expression: {str(e)}"
|
|
||||||
}), 400
|
|
||||||
|
|
||||||
# Update timer function
|
# Update timer function
|
||||||
db.execute("""
|
db.execute("""
|
||||||
UPDATE timer_functions
|
UPDATE timer_functions
|
||||||
@@ -406,20 +459,48 @@ def delete(function_id):
|
|||||||
@login_required
|
@login_required
|
||||||
def toggle(function_id):
|
def toggle(function_id):
|
||||||
try:
|
try:
|
||||||
# Toggle the enabled status
|
# Fetch timer function details first to get trigger configuration
|
||||||
result = db.execute("""
|
timer_function = db.execute("""
|
||||||
UPDATE timer_functions
|
SELECT enabled, trigger_type, frequency_minutes, run_date, cron_expression
|
||||||
SET enabled = NOT enabled
|
FROM timer_functions
|
||||||
WHERE id = %s AND user_id = %s
|
WHERE id = %s AND user_id = %s
|
||||||
RETURNING enabled
|
""", [function_id, current_user.id], one=True)
|
||||||
""", [function_id, current_user.id], commit=True, one=True)
|
|
||||||
|
|
||||||
if not result:
|
if not timer_function:
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"message": "Timer function not found or unauthorized"
|
"message": "Timer function not found or unauthorized"
|
||||||
}), 404
|
}), 404
|
||||||
|
|
||||||
|
# Determine new enabled state (toggling)
|
||||||
|
new_enabled = not timer_function['enabled']
|
||||||
|
|
||||||
|
# If enabling the timer, recalculate next_run to prevent stale execution times
|
||||||
|
if new_enabled:
|
||||||
|
next_run, error = calculate_next_run(
|
||||||
|
timer_function['trigger_type'],
|
||||||
|
frequency_minutes=timer_function['frequency_minutes'],
|
||||||
|
run_date=timer_function['run_date'],
|
||||||
|
cron_expression=timer_function['cron_expression']
|
||||||
|
)
|
||||||
|
|
||||||
|
if error:
|
||||||
|
return jsonify(error), 400
|
||||||
|
|
||||||
|
# Update both enabled status and next_run
|
||||||
|
db.execute("""
|
||||||
|
UPDATE timer_functions
|
||||||
|
SET enabled = %s, next_run = %s
|
||||||
|
WHERE id = %s AND user_id = %s
|
||||||
|
""", [new_enabled, next_run, function_id, current_user.id], commit=True)
|
||||||
|
else:
|
||||||
|
# Just toggle the enabled status
|
||||||
|
db.execute("""
|
||||||
|
UPDATE timer_functions
|
||||||
|
SET enabled = %s
|
||||||
|
WHERE id = %s AND user_id = %s
|
||||||
|
""", [new_enabled, function_id, current_user.id], commit=True)
|
||||||
|
|
||||||
# Fetch updated timer functions for the overview template
|
# Fetch updated timer functions for the overview template
|
||||||
timer_functions = db.execute("""
|
timer_functions = db.execute("""
|
||||||
SELECT id, name, code, environment, trigger_type,
|
SELECT id, name, code, environment, trigger_type,
|
||||||
|
|||||||
Reference in New Issue
Block a user