db.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. from flask_sqlalchemy import SQLAlchemy
  2. from datetime import datetime
  3. db = SQLAlchemy()
  4. # ---------------------------------------------------------------------------
  5. # Helpers partagés
  6. # ---------------------------------------------------------------------------
  7. def _size_human(size_bytes):
  8. if not size_bytes:
  9. return "—"
  10. n = float(size_bytes)
  11. for unit in ("o", "Ko", "Mo", "Go"):
  12. if n < 1024:
  13. return f"{n:.0f} {unit}"
  14. n /= 1024
  15. return f"{n:.1f} To"
  16. class Destination(db.Model):
  17. __tablename__ = "destinations"
  18. id = db.Column(db.Integer, primary_key=True)
  19. name = db.Column(db.Text, nullable=False)
  20. host = db.Column(db.Text, nullable=False)
  21. port = db.Column(db.Integer, default=22)
  22. user = db.Column(db.Text, nullable=False, default="root")
  23. remote_path = db.Column(db.Text, nullable=False)
  24. key_name = db.Column(db.Text) # nom du fichier clé dans data_dir/keys/
  25. enabled = db.Column(db.Boolean, default=True)
  26. created_at = db.Column(db.DateTime, default=datetime.utcnow)
  27. jobs = db.relationship("Job", backref="destination", lazy=True)
  28. @property
  29. def remote_str(self):
  30. return f"{self.user}@{self.host}:{self.remote_path}"
  31. class Job(db.Model):
  32. __tablename__ = "jobs"
  33. id = db.Column(db.Integer, primary_key=True)
  34. name = db.Column(db.Text, nullable=False)
  35. type = db.Column(db.Text, nullable=False) # ynh_app|ynh_system|custom_dir|mysql|postgresql
  36. config_json = db.Column(db.Text)
  37. cron_expr = db.Column(db.Text, nullable=False, default="") # "" = déclenchement manuel uniquement
  38. retention_mode = db.Column(db.Text, nullable=False) # count|daily|gfs
  39. retention_value = db.Column(db.Integer, nullable=False)
  40. retention_gfs_config = db.Column(db.Text, nullable=True) # JSON {"daily":N,"weekly":M,"monthly":P}
  41. enabled = db.Column(db.Boolean, default=True)
  42. core_only = db.Column(db.Boolean, default=False)
  43. destination_id = db.Column(db.Integer, db.ForeignKey("destinations.id"), nullable=True)
  44. remote_instance_id = db.Column(db.Integer, db.ForeignKey("remote_instances.id"), nullable=True)
  45. remote_instance = db.relationship("RemoteInstance", foreign_keys=[remote_instance_id], lazy="joined")
  46. created_at = db.Column(db.DateTime, default=datetime.utcnow)
  47. updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
  48. runs = db.relationship("Run", backref="job", lazy=True, cascade="all, delete-orphan")
  49. def next_run(self):
  50. from scheduler import get_next_run
  51. return get_next_run(self.id)
  52. class Setting(db.Model):
  53. __tablename__ = "settings"
  54. key = db.Column(db.Text, primary_key=True)
  55. value = db.Column(db.Text, nullable=False, default="")
  56. class Run(db.Model):
  57. __tablename__ = "runs"
  58. id = db.Column(db.Integer, primary_key=True)
  59. job_id = db.Column(db.Integer, db.ForeignKey("jobs.id"), nullable=False)
  60. started_at = db.Column(db.DateTime)
  61. finished_at = db.Column(db.DateTime)
  62. status = db.Column(db.Text) # running|success|error
  63. log_text = db.Column(db.Text)
  64. archive_name = db.Column(db.Text)
  65. size_bytes = db.Column(db.Integer)
  66. @property
  67. def duration_seconds(self):
  68. if self.started_at and self.finished_at:
  69. return int((self.finished_at - self.started_at).total_seconds())
  70. return None
  71. @property
  72. def size_human(self):
  73. return _size_human(self.size_bytes)
  74. # ---------------------------------------------------------------------------
  75. # Fédération
  76. # ---------------------------------------------------------------------------
  77. class RemoteInstance(db.Model):
  78. __tablename__ = "remote_instances"
  79. id = db.Column(db.Integer, primary_key=True)
  80. name = db.Column(db.Text, nullable=False)
  81. url = db.Column(db.Text, nullable=False) # https://tom.domaine.fr
  82. api_key = db.Column(db.Text, nullable=False)
  83. last_seen = db.Column(db.DateTime)
  84. status = db.Column(db.Text, default="unknown") # online|offline|error|unknown
  85. created_at = db.Column(db.DateTime, default=datetime.utcnow)
  86. remote_runs = db.relationship("RemoteRun", backref="instance", lazy=True,
  87. cascade="all, delete-orphan")
  88. @property
  89. def url_display(self):
  90. return self.url.rstrip("/")
  91. class RemoteRun(db.Model):
  92. __tablename__ = "remote_runs"
  93. id = db.Column(db.Integer, primary_key=True)
  94. instance_id = db.Column(db.Integer, db.ForeignKey("remote_instances.id"), nullable=False)
  95. job_id = db.Column(db.Integer) # id sur l'instance distante
  96. job_name = db.Column(db.Text)
  97. job_type = db.Column(db.Text)
  98. last_run_at = db.Column(db.DateTime)
  99. last_status = db.Column(db.Text)
  100. last_archive_name = db.Column(db.Text)
  101. last_size_bytes = db.Column(db.Integer)
  102. # Alias pour compatibilité avec le template dashboard_network
  103. @property
  104. def name(self):
  105. return self.job_name
  106. @property
  107. def type(self):
  108. return self.job_type
  109. @property
  110. def size_human(self):
  111. return _size_human(self.last_size_bytes)
  112. @property
  113. def last_size_human(self):
  114. return _size_human(self.last_size_bytes)
  115. class Upload(db.Model):
  116. __tablename__ = "uploads"
  117. upload_id = db.Column(db.Text, primary_key=True) # uuid4
  118. filename = db.Column(db.Text)
  119. total_size = db.Column(db.Integer)
  120. chunk_size = db.Column(db.Integer)
  121. chunks_total = db.Column(db.Integer)
  122. chunks_received = db.Column(db.Integer, default=0)
  123. checksum = db.Column(db.Text) # SHA256 de l'archive complète
  124. started_at = db.Column(db.DateTime, default=datetime.utcnow)
  125. status = db.Column(db.Text, default="pending") # pending|in_progress|complete|error