Add backend support for script execution

This commit is contained in:
Sami Abuzakuk
2025-10-11 12:38:03 +02:00
parent 037d525905
commit 78b19a03a8
4 changed files with 89 additions and 2 deletions

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@ __pycache__
.venv .venv
*.db *.db
.envrc .envrc
exec_folder/

View File

@@ -4,6 +4,7 @@ from fastapi.exceptions import HTTPException
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel from pydantic import BaseModel
from model import Log, SessionLocal, Script from model import Log, SessionLocal, Script
from run_scripts import run_scripts
import uvicorn import uvicorn
app = FastAPI() app = FastAPI()
@@ -28,15 +29,22 @@ class ScriptCreate(ScriptBase):
pass pass
class ScriptUpdate(ScriptBase):
enabled: bool
class ScriptResponse(ScriptBase): class ScriptResponse(ScriptBase):
id: int id: int
created_at: datetime created_at: datetime
enabled: bool
model_config = {"from_attributes": True} model_config = {"from_attributes": True}
class ScriptLogCreate(BaseModel): class ScriptLogCreate(BaseModel):
message: str message: str
error_code: int
error_message: str
@app.get("/") @app.get("/")
@@ -80,19 +88,25 @@ def delete_script(script_id: int):
if not script: if not script:
raise HTTPException(status_code=404, detail="Script not found") raise HTTPException(status_code=404, detail="Script not found")
db.delete(script) db.delete(script)
logs = db.query(Log).filter(Log.script_id == script_id).all()
for log in logs:
db.delete(log)
db.commit() db.commit()
db.close() db.close()
return {"message": "Script deleted"} return {"message": "Script deleted"}
@app.put("/script/{script_id}", response_model=ScriptResponse) @app.put("/script/{script_id}", response_model=ScriptResponse)
def update_script(script_id: int, script: ScriptCreate): def update_script(script_id: int, script: ScriptUpdate):
db = SessionLocal() db = SessionLocal()
existing_script = db.query(Script).filter(Script.id == script_id).first() existing_script = db.query(Script).filter(Script.id == script_id).first()
if not existing_script: if not existing_script:
raise HTTPException(status_code=404, detail="Script not found") raise HTTPException(status_code=404, detail="Script not found")
existing_script.name = script.name existing_script.name = script.name
existing_script.script_content = script.script_content existing_script.script_content = script.script_content
existing_script.enabled = script.enabled
db.commit() db.commit()
db.refresh(existing_script) db.refresh(existing_script)
db.close() db.close()
@@ -110,7 +124,12 @@ def get_script_logs(script_id: int):
@app.post("/script/{script_id}/log") @app.post("/script/{script_id}/log")
def create_script_log(script_id: int, log: ScriptLogCreate): def create_script_log(script_id: int, log: ScriptLogCreate):
db = SessionLocal() db = SessionLocal()
new_log = Log(script_id=script_id, message=log.message) new_log = Log(
script_id=script_id,
message=log.message,
error_code=log.error_code,
error_message=log.error_message,
)
db.add(new_log) db.add(new_log)
db.commit() db.commit()
db.refresh(new_log) db.refresh(new_log)
@@ -130,6 +149,12 @@ def delete_script_log(script_id: int, log_id: int):
return {"message": "Log deleted"} return {"message": "Log deleted"}
@app.post("/script/{script_id}/execute")
def execute_script(script_id: int):
run_scripts([script_id])
return {"run_script": True}
@app.get("/health") @app.get("/health")
def health_check(): def health_check():
return {"status": "healthy"} return {"status": "healthy"}

View File

@@ -3,6 +3,7 @@ from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
from sqlalchemy.sql.functions import func from sqlalchemy.sql.functions import func
from sqlalchemy.sql.sqltypes import DateTime from sqlalchemy.sql.sqltypes import DateTime
from sqlalchemy.types import Boolean
# Initialize the database # Initialize the database
DATABASE_URL = "sqlite:///./project_monitor.db" DATABASE_URL = "sqlite:///./project_monitor.db"
@@ -22,6 +23,7 @@ class Script(Base):
id = Column(Integer, primary_key=True, index=True) id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False) name = Column(String, nullable=False)
script_content = Column(Text, nullable=True) script_content = Column(Text, nullable=True)
enabled = Column(Boolean, default=False)
created_at = Column( created_at = Column(
DateTime(timezone=True), nullable=False, server_default=func.now() DateTime(timezone=True), nullable=False, server_default=func.now()
) )
@@ -32,6 +34,8 @@ class Log(Base):
id = Column(Integer, primary_key=True, index=True) id = Column(Integer, primary_key=True, index=True)
message = Column(String, nullable=False) message = Column(String, nullable=False)
error_code = Column(Integer, nullable=False, default=0)
error_message = Column(String, nullable=True)
created_at = Column( created_at = Column(
DateTime(timezone=True), nullable=False, server_default=func.now() DateTime(timezone=True), nullable=False, server_default=func.now()
) )

57
backend/run_scripts.py Normal file
View File

@@ -0,0 +1,57 @@
import model
import subprocess
import os
def run_scripts(script_ids: list[int] | None = None):
db = model.SessionLocal()
if script_ids:
scripts = db.query(model.Script).filter(model.Script.id.in_(script_ids)).all()
else:
scripts = db.query(model.Script).filter(model.Script.enabled).all()
for script in scripts:
print(f"Running script: {script.name}")
dump_script_to_file(script, f"exec_folder/{script.name}.py")
result = execute_script(f"exec_folder/{script.name}.py")
db.add(
model.Log(
script_id=script.id,
error_code=result.returncode,
message=result.stdout,
error_message=result.stderr,
)
)
db.commit()
delete_script(f"exec_folder/{script.name}.py")
db.close()
def dump_script_to_file(script, filename):
with open(filename, "w") as file:
file.write(script.script_content)
def execute_script(filename) -> subprocess.CompletedProcess:
result = subprocess.run(["python", filename], capture_output=True, text=True)
return result
def delete_script(filename):
try:
os.remove(filename)
except FileNotFoundError:
pass
def main():
run_scripts()
if __name__ == "__main__":
main()