feat: simplify account create/edit and add row copy
All checks were successful
技能自动化发布 / release (push) Successful in 8s

Data-management add/edit now use short forms with enums; copy clones a platform account into a new empty profile.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-22 14:48:46 +08:00
parent 097dde0ebb
commit 9a20e7fc69
18 changed files with 935 additions and 66 deletions

View File

@@ -471,6 +471,58 @@ def update_account(
)
def list_account_labels(conn) -> list[str]:
cur = conn.cursor()
cur.execute("SELECT account_label FROM accounts WHERE account_label IS NOT NULL")
return [str(row[0]) for row in cur.fetchall() if row[0]]
def update_account_user_fields(
conn,
account_id: int,
*,
account_label: Optional[str] = None,
login_id: Any = _UNSET,
login_id_type: Optional[str] = None,
url: Any = _UNSET,
status: Optional[str] = None,
) -> None:
"""Patch fields the data-management edit form is allowed to change."""
sets: list[str] = []
params: list[Any] = []
if account_label is not None:
label = str(account_label).strip()
if not label:
raise ValueError("account_label must not be empty")
sets.append("account_label = ?")
params.append(label)
if login_id is not _UNSET:
text = str(login_id).strip() if login_id is not None else ""
sets.append("login_id = ?")
params.append(text or None)
if login_id_type is not None:
typed = str(login_id_type).strip() or "unknown"
sets.append("login_id_type = ?")
params.append(typed)
if url is not _UNSET:
text = str(url).strip() if url is not None else ""
sets.append("url = ?")
params.append(text)
if status is not None:
sets.append("status = ?")
params.append(status)
if not sets:
return
sets.append("updated_at = ?")
params.append(_now_unix())
params.append(int(account_id))
cur = conn.cursor()
cur.execute(
f"UPDATE accounts SET {', '.join(sets)} WHERE id = ?",
tuple(params),
)
def account_exists_with_login_id(conn, platform_key: str, login_id: str, environment: str, role: str) -> bool:
cur = conn.cursor()
cur.execute(

View File

@@ -41,6 +41,83 @@ ACCOUNT_ID_LOOKUP_JSON = json.dumps(
ensure_ascii=False,
)
def _enum_options(pairs: tuple[tuple[str, str], ...]) -> str:
return json.dumps(
[{"value": value, "label": label} for value, label in pairs],
ensure_ascii=False,
)
LOGIN_ID_TYPE_OPTIONS_JSON = _enum_options(
(
("unknown", "未指定"),
("phone", "手机号"),
("email", "邮箱"),
("username", "用户名"),
("customer_code", "客户代码"),
("api_account", "API 账号"),
)
)
ENVIRONMENT_OPTIONS_JSON = _enum_options(
(
("production", "生产"),
("simulator", "仿真"),
("staging", "预发"),
("test", "测试"),
)
)
ROLE_OPTIONS_JSON = _enum_options(
(
("default", "默认"),
("booking", "订舱"),
("tracking", "跟踪"),
("api", "API"),
("admin", "管理"),
("sales", "销售"),
)
)
ACCOUNT_STATUS_OPTIONS_JSON = _enum_options(
(
("active", "启用"),
("disabled", "停用"),
("needs_review", "待复核"),
)
)
SESSION_STATUS_OPTIONS_JSON = _enum_options(
(
("unknown", "未知"),
("likely_valid", "可能有效"),
("needs_login", "需要登录"),
("expired", "已过期"),
)
)
AUTH_STRATEGY_OPTIONS_JSON = _enum_options(
(
("password_auto", "账密自动"),
("password_with_captcha", "账密+验证码"),
("password_plus_2fa", "账密+2FA"),
("qr_code_manual", "扫码"),
("per_session_manual", "每次手动"),
("device_bound", "设备绑定"),
("client_certificate", "客户端证书"),
("api_token", "API Token"),
("sso_redirect", "SSO 跳转"),
)
)
YES_NO_OPTIONS_JSON = _enum_options((("1", "是"), ("0", "否")))
ENABLED_OPTIONS_JSON = _enum_options((("1", "启用"), ("0", "停用")))
DOMAIN_OPTIONS_JSON = _enum_options(
(
("logistics", "物流"),
("llm", "大模型"),
("content", "内容"),
("dev", "开发"),
("sim", "仿真"),
("generic", "通用"),
)
)
USER_BUSINESS_TABLES = (
PLATFORMS_TABLE,
ACCOUNTS_TABLE,
@@ -122,13 +199,28 @@ PLATFORMS_TABLE_METADATA = {
PLATFORMS_COLUMN_METADATA: tuple[dict[str, object], ...] = (
_col("platform_key", "平台键", description="平台唯一标识(英文 kebab-case)", editable=0),
_col("display_name", "展示名称", description="平台在界面上的显示名称"),
_col("domain", "业务域", description="logistics / llm / content / dev / sim 等"),
_col(
"domain",
"业务域",
description="物流 / 大模型 / 内容等",
options_json=DOMAIN_OPTIONS_JSON,
),
_col("provider_code", "供应商代码", description="供应商或平台代码,可空"),
_col("default_url", "默认链接", description="平台默认入口 URL"),
_col("default_url", "默认链接", description="平台默认入口 URL", display_type="url"),
_col("aliases_json", "别名列表", description="平台别名 JSON 数组", editable=0),
_col("capabilities_json", "能力声明", description='能力 JSON,如 {"web":true,"api_key":false}', editable=0),
_col("enabled", "是否启用", description="1=启用,0=停用"),
_col("default_auth_strategy", "默认登录策略", description="新建账号时的默认 auth_strategy"),
_col(
"enabled",
"是否启用",
description="启用或停用该平台",
options_json=ENABLED_OPTIONS_JSON,
),
_col(
"default_auth_strategy",
"默认登录策略",
description="新建账号时的默认登录方式",
options_json=AUTH_STRATEGY_OPTIONS_JSON,
),
_ts("created_at", "创建时间", "记录创建时间(Unix 秒级时间戳)"),
_ts("updated_at", "更新时间", "记录最后更新时间(Unix 秒级时间戳)"),
)
@@ -150,26 +242,67 @@ ACCOUNTS_COLUMN_METADATA: tuple[dict[str, object], ...] = (
description="所属平台键(来自平台信息表)",
display_type="lookup",
options_json=PLATFORM_KEY_LOOKUP_JSON,
editable=0,
),
_col("account_label", "账号名称", description="人可读的账号名称"),
_col("login_id", "登录账号", description="手机号、邮箱、用户名等登录标识"),
_col("login_id_type", "登录标识类型", description="phone / email / username / unknown 等"),
_col("tenant_id", "租户编号", description="租户或客户标识,可空"),
_col("environment", "环境", description="production / simulator / staging 等"),
_col("role", "角色", description="booking / tracking / api / default 等"),
_col("provider_code", "供应商代码", description="冗余供应商码,便于查询"),
_col(
"login_id_type",
"登录标识类型",
description="未指定 / 手机号 / 邮箱 / 用户名等",
options_json=LOGIN_ID_TYPE_OPTIONS_JSON,
),
_col("tenant_id", "租户编号", description="租户或客户标识,可空", editable=0),
_col(
"environment",
"环境",
description="生产 / 仿真 / 预发 / 测试",
options_json=ENVIRONMENT_OPTIONS_JSON,
editable=0,
),
_col(
"role",
"角色",
description="默认 / 订舱 / 跟踪 / API 等",
options_json=ROLE_OPTIONS_JSON,
editable=0,
),
_col("provider_code", "供应商代码", description="冗余供应商码,便于查询", editable=0),
_col("profile_dir", "浏览器配置目录", description="Playwright 持久化 Profile 路径", editable=0),
_col("url", "入口链接", description="账号默认入口 URL"),
_col("status", "状态", description="active / disabled / needs_review 等"),
_col("session_status", "会话状态", description="unknown / likely_valid / needs_login 等"),
_col("url", "入口链接", description="账号默认入口 URL", display_type="url"),
_col(
"status",
"状态",
description="启用 / 停用 / 待复核",
options_json=ACCOUNT_STATUS_OPTIONS_JSON,
),
_col(
"session_status",
"会话状态",
description="运行时登录态,由技能回写",
options_json=SESSION_STATUS_OPTIONS_JSON,
editable=0,
),
_ts("last_used_at", "最后使用时间", "最近一次被业务技能使用的时间"),
_ts("last_login_check_at", "最后登录检查时间", "最近一次登录状态检查时间"),
_col("last_error_code", "最近错误码", description="最近一次错误代码", editable=0),
_col("last_error_message", "最近错误信息", description="最近一次错误说明", editable=0),
_col("extra_json", "扩展信息", description="扩展 JSON 配置", editable=0),
_col("auth_strategy", "登录方式", description="九种登录策略之一"),
_col("session_persistent", "持久会话", description="是否需要持久化浏览器会话(0/1)"),
_col("device_fingerprint", "设备指纹", description="device_bound 策略专用,可空"),
_col(
"auth_strategy",
"登录方式",
description="跟随平台默认,一般无需手改",
options_json=AUTH_STRATEGY_OPTIONS_JSON,
editable=0,
),
_col(
"session_persistent",
"持久会话",
description="是否需要持久化浏览器会话",
options_json=YES_NO_OPTIONS_JSON,
editable=0,
),
_col("device_fingerprint", "设备指纹", description="设备绑定策略专用,可空", editable=0),
_ts("created_at", "创建时间", "记录创建时间(Unix 秒级时间戳)"),
_ts("updated_at", "更新时间", "记录最后更新时间(Unix 秒级时间戳)"),
)