Spaces:
Running
Running
from flask import Flask, render_template_string, jsonify | |
from apscheduler.schedulers.background import BackgroundScheduler | |
import subprocess | |
import threading | |
import pytz | |
import logging | |
from datetime import datetime | |
# Initialize Flask app | |
app = Flask(__name__) | |
# Setup logging | |
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") | |
# Execution logs | |
execution_logs = [] | |
MAX_LOG_ENTRIES = 50 | |
log_lock = threading.Lock() # Prevents race conditions when modifying logs | |
def add_log_entry(entry): | |
"""Safely add log entries while maintaining MAX_LOG_ENTRIES limit.""" | |
with log_lock: | |
execution_logs.append(entry) | |
if len(execution_logs) > MAX_LOG_ENTRIES: | |
execution_logs.pop(0) | |
def get_ist_time(): | |
"""Get the current time in IST (Indian Standard Time).""" | |
utc_now = datetime.utcnow() | |
ist_timezone = pytz.timezone("Asia/Kolkata") | |
return utc_now.replace(tzinfo=pytz.utc).astimezone(ist_timezone) | |
def run_cli_script(): | |
"""Runs cli.py and streams logs in real-time to both UI and terminal.""" | |
timestamp = get_ist_time().strftime("%Y-%m-%d %H:%M:%S IST") | |
try: | |
process = subprocess.Popen( | |
["python", "cli.py"], | |
stdout=subprocess.PIPE, | |
stderr=subprocess.PIPE, | |
text=True | |
) | |
stdout, stderr = process.communicate() # Wait for process to complete | |
if stdout: | |
add_log_entry({'time': timestamp, 'output': stdout, 'error': ''}) | |
logging.info(stdout.strip()) | |
if stderr: | |
add_log_entry({'time': timestamp, 'output': '', 'error': stderr}) | |
logging.error(stderr.strip()) | |
except Exception as e: | |
error_msg = str(e) | |
add_log_entry({'time': timestamp, 'output': '', 'error': error_msg}) | |
logging.error(f"Error: {error_msg}") | |
def start_initial_run(): | |
"""Runs the CLI script immediately upon startup in a separate thread.""" | |
threading.Thread(target=run_cli_script, daemon=True).start() | |
# Initialize scheduler | |
scheduler = BackgroundScheduler(daemon=True) | |
scheduler.add_job( | |
run_cli_script, | |
'interval', | |
hours=3, | |
id='main_job', | |
next_run_time=datetime.now() | |
) | |
scheduler.start() | |
# Ensure script runs once on startup | |
start_initial_run() | |
def home(): | |
"""Main UI displaying logs and next run time.""" | |
job = scheduler.get_job('main_job') | |
next_run = get_ist_time().strftime('%Y-%m-%d %H:%M:%S IST') if job else 'N/A' | |
return render_template_string(''' | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Script Scheduler</title> | |
<script> | |
function fetchLogs() { | |
fetch('/logs') | |
.then(response => response.json()) | |
.then(data => { | |
let logBox = document.getElementById("log-box"); | |
logBox.innerHTML = ""; | |
data.logs.forEach(log => { | |
let logEntry = "<div class='timestamp'>" + log.time + "</div>"; | |
if (log.output) logEntry += "<div class='output'>" + log.output + "</div>"; | |
if (log.error) logEntry += "<div class='error'>" + log.error + "</div>"; | |
logEntry += "<hr>"; | |
logBox.innerHTML += logEntry; | |
}); | |
logBox.scrollTop = logBox.scrollHeight; | |
}); | |
} | |
setInterval(fetchLogs, 2000); | |
window.onload = fetchLogs; | |
</script> | |
<style> | |
body { font-family: Arial, sans-serif; padding: 20px; } | |
.log-box { | |
background: #000; | |
color: #0f0; | |
padding: 15px; | |
border-radius: 5px; | |
margin-top: 20px; | |
white-space: pre-wrap; | |
max-height: 400px; | |
overflow-y: auto; | |
} | |
.timestamp { color: #888; margin-bottom: 10px; } | |
.error { color: #ff4444; } | |
</style> | |
</head> | |
<body> | |
<h1>Script Scheduler</h1> | |
<p>Next run: {{ next_run }}</p> | |
<h2>Latest Execution Logs</h2> | |
<div id="log-box" class="log-box"></div> | |
<p><a href="/force-run">Trigger Manual Run</a></p> | |
<p><a href="/run-check">Check Scheduler Status</a></p> | |
</body> | |
</html> | |
''', next_run=next_run) | |
def logs(): | |
"""Returns logs as JSON for AJAX polling.""" | |
return jsonify({'logs': execution_logs}) | |
def force_run(): | |
"""Manually trigger the script execution.""" | |
threading.Thread(target=run_cli_script, daemon=True).start() | |
logging.info("Manual script execution triggered") | |
return "Script executed manually", 200 | |
def run_check(): | |
"""Check if the scheduler is still running and restart if necessary.""" | |
if not scheduler.running: | |
logging.warning("Scheduler was stopped! Restarting...") | |
scheduler.start() | |
return "Scheduler restarted", 200 | |
return "Scheduler is running", 200 | |
if __name__ == '__main__': | |
app.run(host='0.0.0.0', port=7860) | |