import secrets from django.conf import settings from django.db import models # GitHub Actions 运行状态 -> 中文展示文案 STATUS_LABELS = { "Starting generator...please wait": "正在启动构建任务", "queued": "排队中", "in_progress": "构建中", "success": "构建成功", "failure": "构建失败", "cancelled": "已取消", "timed_out": "已超时", "skipped": "已跳过", "action_required": "等待人工处理", } # 状态对应的徽标样式(模板中使用) STATUS_BADGE = { "Starting generator...please wait": "badge-info", "queued": "badge-info", "in_progress": "badge-info", "success": "badge-success", "failure": "badge-danger", "cancelled": "badge-warning", "timed_out": "badge-warning", "skipped": "badge-secondary", "action_required": "badge-warning", } PLATFORM_LABELS = { "windows": "Windows 64 位", "windows-x86": "Windows 32 位", "linux": "Linux", "android": "Android", "macos": "macOS", } def generate_token_key(): return secrets.token_hex(20) class GithubRun(models.Model): uuid = models.CharField("任务 UUID", max_length=100, db_index=True) status = models.CharField("构建状态", max_length=100, default="queued") github_run_id = models.BigIntegerField("GitHub 运行编号", null=True, blank=True) # 构建平台:github / gitea;历史任务默认 github。切换平台后旧任务仍按此值查询状态 backend = models.CharField("构建平台", max_length=20, default="github") platform = models.CharField("目标平台", max_length=20, blank=True, default="") filename = models.CharField("文件名", max_length=200, blank=True, default="") # 提交时的表单快照(纯字符串/数值,图片为 data URL),供失败后「一键重试」重新派发 config_data = models.JSONField("构建配置快照", blank=True, default=dict) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name="提交用户", null=True, blank=True, on_delete=models.SET_NULL, related_name="builds", ) created_at = models.DateTimeField("提交时间", auto_now_add=True) updated_at = models.DateTimeField("更新时间", auto_now=True) class Meta: verbose_name = "构建任务" verbose_name_plural = "构建任务" ordering = ["-created_at"] def __str__(self): return f"{self.filename or self.uuid}({self.status_label()})" def status_label(self): return STATUS_LABELS.get(self.status, self.status) def status_badge(self): return STATUS_BADGE.get(self.status, "badge-secondary") def platform_label(self): return PLATFORM_LABELS.get(self.platform, self.platform or "未知平台") def is_finished(self): return self.status in ("success", "failure", "cancelled", "timed_out", "skipped") @property def github_log_url(self): if self.github_run_id: # 局部导入避免循环依赖;按任务记录的平台生成对应站点的日志链接 from .build_backends import get_backend return get_backend(self.backend or 'github').log_url(self.github_run_id) return "" @property def backend_label(self): """任务所属构建平台的展示名(用于日志链接等文案)。""" return "Gitea Actions" if (self.backend or "github") == "gitea" else "GitHub Actions" @property def backend_badge_class(self): """任务所属构建平台对应的徽章样式类。""" return "badge-gitea" if (self.backend or "github") == "gitea" else "badge-github" def short_uuid(self): return self.uuid[:8] if len(self.uuid) >= 8 else self.uuid class SiteSetting(models.Model): """后台可改的站点配置(键值存储)。 取值优先级由 app_settings 统一处理:后台配置(本表)→ 环境变量 → 默认值, 因此历史的环境变量部署方式继续有效。 """ key = models.CharField("配置项", max_length=50, primary_key=True) value = models.TextField("配置值", blank=True, default='') updated_at = models.DateTimeField("更新时间", auto_now=True) class Meta: verbose_name = "站点配置" verbose_name_plural = "站点配置" def __str__(self): return self.key class SavedConfig(models.Model): """用户保存的客户端构建配置方案(与账号绑定,可在定制页直接载入、可导出/导入)。""" owner = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name="所属用户", on_delete=models.CASCADE, related_name="saved_configs", ) name = models.CharField("方案名称", max_length=100) data = models.JSONField("配置内容", default=dict) created_at = models.DateTimeField("创建时间", auto_now_add=True) updated_at = models.DateTimeField("更新时间", auto_now=True) class Meta: verbose_name = "构建配置方案" verbose_name_plural = "构建配置方案" unique_together = [('owner', 'name')] ordering = ["-updated_at"] def __str__(self): return f"{self.name}({self.owner})" def platform_label(self): return PLATFORM_LABELS.get(self.data.get('platform', ''), '未指定平台') @property def version_label(self): return self.data.get('version', '') or '未指定版本' class ApiToken(models.Model): """供 JSON API(如 rdgen-cli)使用的个人访问令牌""" key = models.CharField("令牌", max_length=64, primary_key=True, default=generate_token_key, editable=False) name = models.CharField("令牌名称", max_length=100, help_text="便于识别用途,例如:CI 构建机") user = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name="所属用户", on_delete=models.CASCADE, related_name="api_tokens", ) is_active = models.BooleanField("启用", default=True) created_at = models.DateTimeField("创建时间", auto_now_add=True) last_used_at = models.DateTimeField("最后使用时间", null=True, blank=True) class Meta: verbose_name = "API 令牌" verbose_name_plural = "API 令牌" ordering = ["-created_at"] def __str__(self): return f"{self.name}({self.user})"