from flask_sqlalchemy import SQLAlchemy from datetime import datetime db = SQLAlchemy() # --------------------------------------------------------------------------- # Helpers partagés # --------------------------------------------------------------------------- def _size_human(size_bytes): if not size_bytes: return "—" n = float(size_bytes) for unit in ("o", "Ko", "Mo", "Go"): if n < 1024: return f"{n:.0f} {unit}" n /= 1024 return f"{n:.1f} To" class Destination(db.Model): __tablename__ = "destinations" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.Text, nullable=False) host = db.Column(db.Text, nullable=False) port = db.Column(db.Integer, default=22) user = db.Column(db.Text, nullable=False, default="root") remote_path = db.Column(db.Text, nullable=False) key_name = db.Column(db.Text) # nom du fichier clé dans data_dir/keys/ enabled = db.Column(db.Boolean, default=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) jobs = db.relationship("Job", backref="destination", lazy=True) @property def remote_str(self): return f"{self.user}@{self.host}:{self.remote_path}" class Job(db.Model): __tablename__ = "jobs" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.Text, nullable=False) type = db.Column(db.Text, nullable=False) # ynh_app|ynh_system|custom_dir|mysql|postgresql config_json = db.Column(db.Text) cron_expr = db.Column(db.Text, nullable=False) retention_mode = db.Column(db.Text, nullable=False) # count|daily|gfs retention_value = db.Column(db.Integer, nullable=False) enabled = db.Column(db.Boolean, default=True) core_only = db.Column(db.Boolean, default=False) destination_id = db.Column(db.Integer, db.ForeignKey("destinations.id"), nullable=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) runs = db.relationship("Run", backref="job", lazy=True, cascade="all, delete-orphan") def next_run(self): from scheduler import get_next_run return get_next_run(self.id) class Setting(db.Model): __tablename__ = "settings" key = db.Column(db.Text, primary_key=True) value = db.Column(db.Text, nullable=False, default="") class Run(db.Model): __tablename__ = "runs" id = db.Column(db.Integer, primary_key=True) job_id = db.Column(db.Integer, db.ForeignKey("jobs.id"), nullable=False) started_at = db.Column(db.DateTime) finished_at = db.Column(db.DateTime) status = db.Column(db.Text) # running|success|error log_text = db.Column(db.Text) archive_name = db.Column(db.Text) size_bytes = db.Column(db.Integer) @property def duration_seconds(self): if self.started_at and self.finished_at: return int((self.finished_at - self.started_at).total_seconds()) return None @property def size_human(self): return _size_human(self.size_bytes) # --------------------------------------------------------------------------- # Fédération # --------------------------------------------------------------------------- class RemoteInstance(db.Model): __tablename__ = "remote_instances" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.Text, nullable=False) url = db.Column(db.Text, nullable=False) # https://tom.domaine.fr api_key = db.Column(db.Text, nullable=False) last_seen = db.Column(db.DateTime) status = db.Column(db.Text, default="unknown") # online|offline|error|unknown created_at = db.Column(db.DateTime, default=datetime.utcnow) remote_runs = db.relationship("RemoteRun", backref="instance", lazy=True, cascade="all, delete-orphan") @property def url_display(self): return self.url.rstrip("/") class RemoteRun(db.Model): __tablename__ = "remote_runs" id = db.Column(db.Integer, primary_key=True) instance_id = db.Column(db.Integer, db.ForeignKey("remote_instances.id"), nullable=False) job_id = db.Column(db.Integer) # id sur l'instance distante job_name = db.Column(db.Text) job_type = db.Column(db.Text) last_run_at = db.Column(db.DateTime) last_status = db.Column(db.Text) last_archive_name = db.Column(db.Text) last_size_bytes = db.Column(db.Integer) # Alias pour compatibilité avec le template dashboard_network @property def name(self): return self.job_name @property def type(self): return self.job_type @property def size_human(self): return _size_human(self.last_size_bytes) @property def last_size_human(self): return _size_human(self.last_size_bytes) class Upload(db.Model): __tablename__ = "uploads" upload_id = db.Column(db.Text, primary_key=True) # uuid4 filename = db.Column(db.Text) total_size = db.Column(db.Integer) chunk_size = db.Column(db.Integer) chunks_total = db.Column(db.Integer) chunks_received = db.Column(db.Integer, default=0) checksum = db.Column(db.Text) # SHA256 de l'archive complète started_at = db.Column(db.DateTime, default=datetime.utcnow) status = db.Column(db.Text, default="pending") # pending|in_progress|complete|error