""" Models package for ScriptsManager Contains all database models and related classes """ from app.models.user import User, UserRole from app.models.script import Script, ScriptGroup # Import model classes that were defined in this file originally from datetime import datetime from app.config.database import db class UserProject(db.Model): """User project model for organizing user work.""" __tablename__ = "user_projects" id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False) project_name = db.Column(db.String(100), nullable=False) group_id = db.Column(db.Integer, db.ForeignKey("script_groups.id"), nullable=False) description = db.Column(db.Text) is_default = db.Column(db.Boolean, default=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) last_accessed = db.Column(db.DateTime) def to_dict(self): """Convert user project to dictionary.""" return { "id": self.id, "user_id": self.user_id, "project_name": self.project_name, "group_id": self.group_id, "description": self.description, "is_default": self.is_default, "created_at": self.created_at.isoformat() if self.created_at else None, "last_accessed": ( self.last_accessed.isoformat() if self.last_accessed else None ), } class UserScriptTag(db.Model): """User script tags for personal organization.""" __tablename__ = "user_script_tags" id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False) script_id = db.Column(db.Integer, db.ForeignKey("scripts.id"), nullable=False) tags = db.Column(db.Text) # Comma-separated user-specific tags created_at = db.Column(db.DateTime, default=datetime.utcnow) __table_args__ = (db.UniqueConstraint("user_id", "script_id"),) def get_tags_list(self): """Get tags as list.""" if self.tags: return [tag.strip() for tag in self.tags.split(",") if tag.strip()] return [] def set_tags_list(self, tags_list): """Set tags from list.""" self.tags = ",".join(tags_list) if tags_list else None class CondaEnvironment(db.Model): """Conda environment model.""" __tablename__ = "conda_environments" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), unique=True, nullable=False) path = db.Column(db.String(255), nullable=False) python_version = db.Column(db.String(20)) is_available = db.Column(db.Boolean, default=True) detected_at = db.Column(db.DateTime, default=datetime.utcnow) last_verified = db.Column(db.DateTime) def to_dict(self): """Convert conda environment to dictionary.""" return { "id": self.id, "name": self.name, "path": self.path, "python_version": self.python_version, "is_available": self.is_available, "detected_at": self.detected_at.isoformat() if self.detected_at else None, "last_verified": ( self.last_verified.isoformat() if self.last_verified else None ), } class ExecutionLog(db.Model): """Execution log model for tracking script runs.""" __tablename__ = "execution_logs" id = db.Column(db.Integer, primary_key=True) script_id = db.Column(db.Integer, db.ForeignKey("scripts.id"), nullable=False) user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False) project_id = db.Column(db.Integer, db.ForeignKey("user_projects.id"), nullable=True) # Added project support status = db.Column(db.String(20), nullable=False) start_time = db.Column(db.DateTime, default=datetime.utcnow) end_time = db.Column(db.DateTime) output = db.Column(db.Text) error_output = db.Column(db.Text) exit_code = db.Column(db.Integer) # Debug fields command_executed = db.Column(db.Text) # Full command that was executed conda_environment = db.Column(db.String(100)) # Conda env used working_directory = db.Column(db.String(500)) # Working directory process_id = db.Column(db.Integer) # Process ID port_allocated = db.Column(db.Integer) # Port allocated to script # Proxy-specific fields proxy_port = db.Column(db.Integer) # Internal proxy port proxy_url = db.Column(db.String(255)) # Proxy URL path execution_mode = db.Column(db.String(20), default='proxy') # 'proxy', 'direct', 'celery' def to_dict(self): """Convert execution log to dictionary.""" return { "id": self.id, "script_id": self.script_id, "user_id": self.user_id, "project_id": self.project_id, "status": self.status, "start_time": self.start_time.isoformat() if self.start_time else None, "end_time": self.end_time.isoformat() if self.end_time else None, "output": self.output, "error_output": self.error_output, "exit_code": self.exit_code, "command_executed": self.command_executed, "conda_environment": self.conda_environment, "working_directory": self.working_directory, "process_id": self.process_id, "port_allocated": self.port_allocated, "proxy_port": self.proxy_port, "proxy_url": self.proxy_url, "execution_mode": self.execution_mode, } class ScriptProxyExecution(db.Model): """Proxy execution model for tracking running script instances.""" __tablename__ = "script_proxy_executions" id = db.Column(db.Integer, primary_key=True) script_id = db.Column(db.Integer, db.ForeignKey("scripts.id"), nullable=False) user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False) project_id = db.Column(db.Integer, db.ForeignKey("user_projects.id"), nullable=False) # Proxy configuration internal_port = db.Column(db.Integer, nullable=False, unique=True) proxy_path = db.Column(db.String(255), nullable=False) # /project/{pid}/script/{sid}/user/{uid} workspace_path = db.Column(db.String(500), nullable=False) # Execution details process_id = db.Column(db.Integer) status = db.Column(db.String(20), default='starting') # starting, running, stopping, stopped, error conda_environment = db.Column(db.String(100)) # Timing created_at = db.Column(db.DateTime, default=datetime.utcnow) started_at = db.Column(db.DateTime) last_activity = db.Column(db.DateTime, default=datetime.utcnow) stopped_at = db.Column(db.DateTime) # Metadata script_name = db.Column(db.String(100)) parameters = db.Column(db.Text) # JSON string of parameters environment_vars = db.Column(db.Text) # JSON string of env vars # Relationships script = db.relationship("Script", backref="proxy_executions") user = db.relationship("User", backref="proxy_executions") project = db.relationship("UserProject", backref="proxy_executions") __table_args__ = ( db.UniqueConstraint("project_id", "script_id", "user_id", name="unique_project_script_user"), ) @property def script_key(self) -> str: """Generate unique script key.""" return f"{self.project_id}_{self.script_id}_{self.user_id}" @property def is_active(self) -> bool: """Check if script is active (running or recently active).""" if self.status not in ['running']: return False if not self.last_activity: return False return (datetime.utcnow() - self.last_activity).total_seconds() < 300 # 5 minutes def update_activity(self): """Update last activity timestamp.""" self.last_activity = datetime.utcnow() def to_dict(self): """Convert to dictionary.""" return { "id": self.id, "script_id": self.script_id, "user_id": self.user_id, "project_id": self.project_id, "internal_port": self.internal_port, "proxy_path": self.proxy_path, "workspace_path": self.workspace_path, "process_id": self.process_id, "status": self.status, "conda_environment": self.conda_environment, "created_at": self.created_at.isoformat() if self.created_at else None, "started_at": self.started_at.isoformat() if self.started_at else None, "last_activity": self.last_activity.isoformat() if self.last_activity else None, "stopped_at": self.stopped_at.isoformat() if self.stopped_at else None, "script_name": self.script_name, "parameters": self.parameters, "environment_vars": self.environment_vars, "script_key": self.script_key, "is_active": self.is_active, } # Make sure all models are imported __all__ = [ 'User', 'UserRole', 'Script', 'ScriptGroup', 'UserProject', 'UserScriptTag', 'CondaEnvironment', 'ExecutionLog', 'ScriptProxyExecution' ]