评估发现项目只有台账能力,缺失真正的多云管理链路。本次按缺口逐项补齐: 适配层 - 新增 aws / linode / namesilo 适配器,registry 登记;阿里云、腾讯云补 list_domains 与 get_account 余额 - NormalizedVPS 补 expiry_date + charge_type:此前云端同步进来的 VPS 拿不到 到期日,永远进不了续费提醒——而续费提醒正是这套台账存在的理由 - base.py 统一云端错误可读化(http_error_message / cloud_error_text):各厂商 把原因放的位置都不同(Linode errors[].reason、Cloudflare errors[].message、 腾讯 Response.Error.Message、AWS XML),此前直接 str(e) 会把 httpx 的多行文本 连同 MDN 链接弹给用户 动作层与 DNS - 新增 /api/actions:实例启停/重启/续费,写操作全部二次确认 + 审计 - 新增 Cloudflare DNS 记录的增删改查 定时同步 - scripts/sync_all_assets.py + /api/sync/assets + vps-sync-assets.timer(08:30) - 08:30 而非随机时刻:必须早于 09:00 的续费检查,否则提醒读到上一天的旧到期日 - sync_all_assets 修正计数:每一项子同步都失败时不再计为「成功」,否则凭证过期 会显示「成功 1」,用户以为数据拉到了、实际一条没有 数据一致性 - AssetRead 输出 external_id(只出不进):前端「云操作」「DNS」按钮靠它渲染, 缺了则所有按钮永远出不来 - import_assets 落库 external_id:导出带了去重键而导入丢弃,会让迁移后首次同步 把所有云端资产当新增再建一份 - _align_preset_providers 取代 _backfill_provider_services:库里声明但注册表未 实现的 sdk_type 清空(声明支持却不支持,比明说不支持更糟),services 只增量 补标签不覆盖,用户自建平台不动 前端如实展示 - cost 为 0 统一显示「未填」而非「0 USD」:0 既可能是没填、也可能是云端不返回 价格,而汇总处又把 0 当「无费用」排除,两处口径不一致 - 总览「本年支出」增加 year_cost_missing,把合计的覆盖缺口说出来 - DNS 弹窗补 MX/SRV 优先级与备注;列表请求失败时 provider 用资产自带 slug 兜底, 否则 Cloudflare 代理勾选框永远不出现 测试:257 -> 309 passed
94 lines
5.7 KiB
Python
94 lines
5.7 KiB
Python
"""平台预设种子数据
|
|
|
|
首次部署或调用 POST /api/providers/seed 时初始化常见平台。
|
|
每个平台有唯一 slug,供未来按 sdk_type 对接自动同步扩展。
|
|
"""
|
|
|
|
from sqlmodel import Session, select
|
|
|
|
from app.models.provider import Provider
|
|
|
|
PRESET_PROVIDERS = [
|
|
# ---- VPS / 云服务商 ----
|
|
# 综合平台:services 列出可提供的服务,卡片上会显示多标签
|
|
{"slug": "aliyun", "name": "阿里云", "name_en": "Aliyun", "category": "vps",
|
|
"services": "vps,domain,ssl_cert,cdn,dns",
|
|
"website": "https://aliyun.com", "console_url": "https://ecs.console.aliyun.com", "sdk_type": "aliyun-sdk"},
|
|
{"slug": "tencent", "name": "腾讯云", "name_en": "Tencent Cloud", "category": "vps",
|
|
"services": "vps,domain,ssl_cert,cdn,dns",
|
|
"website": "https://cloud.tencent.com", "console_url": "https://console.cloud.tencent.com/cvm", "sdk_type": "tencent-sdk"},
|
|
{"slug": "aliyun-intl", "name": "阿里国际", "name_en": "Alibaba Cloud", "category": "vps",
|
|
"services": "vps,domain,ssl_cert,cdn,dns",
|
|
"website": "https://alibabacloud.com", "console_url": "https://ecs.console.alibabacloud.com", "sdk_type": "alibabacloud-sdk"},
|
|
{"slug": "tencent-intl", "name": "腾讯国际", "name_en": "Tencent Cloud Intl", "category": "vps",
|
|
"services": "vps,domain,ssl_cert,cdn,dns",
|
|
"website": "https://intl.cloud.tencent.com", "console_url": "https://console.intl.cloud.tencent.com", "sdk_type": "tencent-intl-sdk"},
|
|
{"slug": "aws", "name": "AWS", "name_en": "Amazon Web Services", "category": "vps",
|
|
"services": "vps,domain,dns,cdn,ssl_cert",
|
|
"website": "https://aws.amazon.com", "console_url": "https://console.aws.amazon.com/ec2/", "sdk_type": "aws-api"},
|
|
{"slug": "vultr", "name": "Vultr", "name_en": "Vultr", "category": "vps",
|
|
"services": "vps,domain,dns",
|
|
"website": "https://vultr.com", "console_url": "https://my.vultr.com", "sdk_type": "vultr-api"},
|
|
{"slug": "digitalocean", "name": "DigitalOcean", "name_en": "DigitalOcean", "category": "vps",
|
|
"services": "vps,domain,dns",
|
|
"website": "https://digitalocean.com", "console_url": "https://cloud.digitalocean.com", "sdk_type": "do-api"},
|
|
{"slug": "linode", "name": "Linode", "name_en": "Akamai Linode", "category": "vps",
|
|
"services": "vps,domain,dns",
|
|
"website": "https://linode.com", "console_url": "https://cloud.linode.com", "sdk_type": "linode-api"},
|
|
# 以下平台暂无适配器:sdk_type 必须留空。
|
|
# 填了不存在的 sdk_type 会让平台卡片显示「API 对接: xxx」,用户建账号点同步才撞
|
|
# 「暂不支持的 SDK 类型」——声明支持却不支持,比明说不支持更糟。
|
|
{"slug": "cloudcone", "name": "CloudCone", "name_en": "CloudCone", "category": "vps",
|
|
"services": "vps",
|
|
"website": "https://cloudcone.com", "console_url": "https://app.cloudcone.com", "sdk_type": None},
|
|
{"slug": "zeabur", "name": "Zeabur", "name_en": "Zeabur", "category": "vps",
|
|
"services": "vps,domain",
|
|
"website": "https://zeabur.com", "console_url": "https://dash.zeabur.com", "sdk_type": None},
|
|
# ---- 域名注册商 ----
|
|
{"slug": "namesilo", "name": "Namesilo", "name_en": "Namesilo", "category": "domain",
|
|
"services": "domain,dns",
|
|
"website": "https://namesilo.com", "console_url": "https://www.namesilo.com/account_domains.php", "sdk_type": "namesilo-api"},
|
|
{"slug": "xinwang", "name": "新网", "name_en": "Xinnet", "category": "domain",
|
|
"services": "domain,dns",
|
|
"website": "https://xinnet.com", "console_url": "https://www.xinnet.com", "sdk_type": None},
|
|
{"slug": "dynadot", "name": "Dynadot", "name_en": "Dynadot", "category": "domain",
|
|
"services": "domain,dns",
|
|
"website": "https://dynadot.com", "console_url": "https://www.dynadot.com/account/domains", "sdk_type": None},
|
|
# ---- AI 服务商 ----
|
|
{"slug": "openai", "name": "OpenAI", "name_en": "OpenAI", "category": "ai_agent",
|
|
"services": "ai_agent",
|
|
"website": "https://openai.com", "console_url": "https://platform.openai.com", "sdk_type": "openai-api"},
|
|
{"slug": "minimax", "name": "Minimax", "name_en": "Minimax", "category": "ai_agent",
|
|
"services": "ai_agent",
|
|
"website": "https://minimax.io", "console_url": "https://platform.minimaxi.com", "sdk_type": "minimax-api"},
|
|
{"slug": "kimi", "name": "Kimi", "name_en": "Moonshot", "category": "ai_agent",
|
|
"services": "ai_agent",
|
|
"website": "https://moonshot.cn", "console_url": "https://platform.moonshot.cn", "sdk_type": "moonshot-api"},
|
|
{"slug": "agnes", "name": "Agnes", "name_en": "Agnes", "category": "ai_agent",
|
|
"services": "ai_agent",
|
|
"website": None, "console_url": None, "sdk_type": None},
|
|
{"slug": "deepseek", "name": "DeepSeek", "name_en": "DeepSeek", "category": "ai_agent",
|
|
"services": "ai_agent",
|
|
"website": "https://deepseek.com", "console_url": "https://platform.deepseek.com", "sdk_type": "deepseek-api"},
|
|
# ---- Cloudflare ----
|
|
{"slug": "cloudflare", "name": "Cloudflare", "name_en": "Cloudflare", "category": "cloudflare",
|
|
"services": "cloudflare,dns,ssl_cert,cdn",
|
|
"website": "https://cloudflare.com", "console_url": "https://dash.cloudflare.com", "sdk_type": "cloudflare-api"},
|
|
]
|
|
|
|
|
|
def seed_providers(session: Session) -> int:
|
|
"""初始化预设平台(已存在的 slug 跳过),返回新增数量
|
|
|
|
单次查询获取全部已存在 slug,避免逐条 SELECT 的 N 次往返。
|
|
"""
|
|
existing_slugs = set(session.exec(select(Provider.slug)).all())
|
|
added = 0
|
|
for data in PRESET_PROVIDERS:
|
|
if data["slug"] not in existing_slugs:
|
|
session.add(Provider(**data))
|
|
added += 1
|
|
if added:
|
|
session.commit()
|
|
return added
|