feat: add Django backend foundation and Docker runtime
Add a Django admin-backed management layer for pobsync configs, runs, snapshots, and schedules. Keep the existing CLI engine as the execution source of truth, add import/run management commands, and provide SQLite default plus optional MariaDB Docker Compose support.
This commit is contained in:
110
src/pobsync_backend/models.py
Normal file
110
src/pobsync_backend/models.py
Normal file
@@ -0,0 +1,110 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class TimestampedModel(models.Model):
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class GlobalConfig(TimestampedModel):
|
||||
name = models.CharField(max_length=64, default="default", unique=True)
|
||||
backup_root = models.CharField(max_length=512)
|
||||
pobsync_home = models.CharField(max_length=512, default="/opt/pobsync")
|
||||
data = models.JSONField(default=dict, blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "global config"
|
||||
verbose_name_plural = "global configs"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
|
||||
class HostConfig(TimestampedModel):
|
||||
host = models.CharField(max_length=255, unique=True)
|
||||
address = models.CharField(max_length=255)
|
||||
enabled = models.BooleanField(default=True)
|
||||
config = models.JSONField(default=dict, blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ["host"]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.host
|
||||
|
||||
|
||||
class BackupRun(models.Model):
|
||||
class RunType(models.TextChoices):
|
||||
SCHEDULED = "scheduled", "Scheduled"
|
||||
MANUAL = "manual", "Manual"
|
||||
|
||||
class Status(models.TextChoices):
|
||||
QUEUED = "queued", "Queued"
|
||||
RUNNING = "running", "Running"
|
||||
SUCCESS = "success", "Success"
|
||||
FAILED = "failed", "Failed"
|
||||
CANCELLED = "cancelled", "Cancelled"
|
||||
|
||||
host = models.ForeignKey(HostConfig, on_delete=models.PROTECT, related_name="runs")
|
||||
run_type = models.CharField(max_length=16, choices=RunType.choices, default=RunType.SCHEDULED)
|
||||
status = models.CharField(max_length=16, choices=Status.choices, default=Status.QUEUED)
|
||||
started_at = models.DateTimeField(null=True, blank=True)
|
||||
ended_at = models.DateTimeField(null=True, blank=True)
|
||||
snapshot_path = models.CharField(max_length=1024, blank=True)
|
||||
base_path = models.CharField(max_length=1024, blank=True)
|
||||
rsync_exit_code = models.IntegerField(null=True, blank=True)
|
||||
result = models.JSONField(default=dict, blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ["-created_at"]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.host} {self.run_type} {self.status}"
|
||||
|
||||
|
||||
class SnapshotRecord(models.Model):
|
||||
class Kind(models.TextChoices):
|
||||
SCHEDULED = "scheduled", "Scheduled"
|
||||
MANUAL = "manual", "Manual"
|
||||
INCOMPLETE = "incomplete", "Incomplete"
|
||||
|
||||
host = models.ForeignKey(HostConfig, on_delete=models.CASCADE, related_name="snapshots")
|
||||
kind = models.CharField(max_length=16, choices=Kind.choices)
|
||||
dirname = models.CharField(max_length=255)
|
||||
path = models.CharField(max_length=1024)
|
||||
status = models.CharField(max_length=32, blank=True)
|
||||
started_at = models.DateTimeField(null=True, blank=True)
|
||||
ended_at = models.DateTimeField(null=True, blank=True)
|
||||
metadata = models.JSONField(default=dict, blank=True)
|
||||
discovered_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=["host", "kind", "dirname"], name="unique_snapshot_per_host_kind"),
|
||||
]
|
||||
ordering = ["host__host", "-started_at", "dirname"]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.host}/{self.kind}/{self.dirname}"
|
||||
|
||||
|
||||
class ScheduleConfig(TimestampedModel):
|
||||
host = models.OneToOneField(HostConfig, on_delete=models.CASCADE, related_name="schedule")
|
||||
cron_expr = models.CharField(max_length=128)
|
||||
user = models.CharField(max_length=64, default="root")
|
||||
enabled = models.BooleanField(default=True)
|
||||
prune = models.BooleanField(default=False)
|
||||
prune_max_delete = models.PositiveIntegerField(default=10)
|
||||
prune_protect_bases = models.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
ordering = ["host__host"]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.host} {self.cron_expr}"
|
||||
Reference in New Issue
Block a user