Add WeCom machine name for multi-node push labels.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-30 17:28:32 +08:00
parent 021b0d0a22
commit 8ceb521049
6 changed files with 62 additions and 5 deletions
+2
View File
@@ -59,3 +59,5 @@ OPTION_QTY_ETH=2
# 企业微信群机器人(系统设置页可改) # 企业微信群机器人(系统设置页可改)
WECOM_ENABLED=0 WECOM_ENABLED=0
WECOM_WEBHOOK_URL= WECOM_WEBHOOK_URL=
# 推送标题前缀,多机时区分,如 云A / 云B
WECOM_MACHINE_NAME=
+7 -1
View File
@@ -558,19 +558,20 @@ async def put_runtime_settings(
class NotifySettingsBody(BaseModel): class NotifySettingsBody(BaseModel):
enabled: bool | None = None enabled: bool | None = None
webhook_url: str | None = None webhook_url: str | None = None
machine_name: str | None = Field(default=None, max_length=64)
def _notify_payload() -> dict: def _notify_payload() -> dict:
from ..notify import wecom from ..notify import wecom
from ..env_store import mask_secret from ..env_store import mask_secret
s = get_settings()
url = wecom.wecom_webhook_url() url = wecom.wecom_webhook_url()
return { return {
"enabled": wecom.wecom_enabled(), "enabled": wecom.wecom_enabled(),
"webhook_configured": bool(url), "webhook_configured": bool(url),
"webhook_url_masked": mask_secret(url) if url else None, "webhook_url_masked": mask_secret(url) if url else None,
"venue_label": wecom.venue_label(), "venue_label": wecom.venue_label(),
"machine_name": wecom.wecom_machine_name() or "",
} }
@@ -591,8 +592,13 @@ async def put_notify_settings(
if body.webhook_url is not None and body.webhook_url.strip(): if body.webhook_url is not None and body.webhook_url.strip():
updates["WECOM_WEBHOOK_URL"] = body.webhook_url.strip() updates["WECOM_WEBHOOK_URL"] = body.webhook_url.strip()
get_db().set_setting("wecom_webhook_url", body.webhook_url.strip()) get_db().set_setting("wecom_webhook_url", body.webhook_url.strip())
if body.machine_name is not None:
name = body.machine_name.strip()[:64]
updates["WECOM_MACHINE_NAME"] = name
get_db().set_setting("wecom_machine_name", name)
if updates: if updates:
upsert_env_keys(updates) upsert_env_keys(updates)
get_settings.cache_clear()
return _notify_payload() return _notify_payload()
+1
View File
@@ -85,6 +85,7 @@ class Settings(BaseSettings):
# 企业微信群机器人 # 企业微信群机器人
wecom_enabled: bool = False wecom_enabled: bool = False
wecom_webhook_url: str = "" wecom_webhook_url: str = ""
wecom_machine_name: str = ""
@property @property
def is_sim(self) -> bool: def is_sim(self) -> bool:
+24 -1
View File
@@ -55,6 +55,18 @@ def wecom_webhook_url() -> str:
return "" return ""
def wecom_machine_name() -> str:
"""多机推送区分用的机器名(设置页 / WECOM_MACHINE_NAME)。"""
s = get_settings()
name = (getattr(s, "wecom_machine_name", None) or "").strip()
if name:
return name[:64]
try:
return (get_db().get_setting("wecom_machine_name", "") or "").strip()[:64]
except Exception:
return ""
def venue_label() -> str | None: def venue_label() -> str | None:
"""实盘时返回「实盘·交易所」;SIM 不展示盘口标签。""" """实盘时返回「实盘·交易所」;SIM 不展示盘口标签。"""
s = get_settings() s = get_settings()
@@ -74,13 +86,21 @@ def venue_label() -> str | None:
def build_markdown(*, tag: str, title: str, lines: list[str] | None = None) -> str: def build_markdown(*, tag: str, title: str, lines: list[str] | None = None) -> str:
body = "\n".join(f"> {ln}" if not ln.startswith(">") else ln for ln in (lines or [])) body = "\n".join(f"> {ln}" if not ln.startswith(">") else ln for ln in (lines or []))
machine = wecom_machine_name()
venue = venue_label() venue = venue_label()
head = f"## 【{venue}{title}" if venue else f"## {title}" prefix_parts: list[str] = []
if machine:
prefix_parts.append(f"{machine}")
if venue:
prefix_parts.append(f"{venue}")
head = f"## {''.join(prefix_parts)}{title}"
parts = [ parts = [
head, head,
f"> **标识**: `{tag}`", f"> **标识**: `{tag}`",
f"> **时间**: {time.strftime('%Y-%m-%d %H:%M:%S')}", f"> **时间**: {time.strftime('%Y-%m-%d %H:%M:%S')}",
] ]
if machine:
parts.append(f"> **机器**: {machine}")
if body: if body:
parts.append("") parts.append("")
parts.append(body) parts.append(body)
@@ -216,7 +236,10 @@ def notify_fault(*, title: str, detail: str, dedupe_key: str | None = None) -> N
def notify_test() -> tuple[bool, str]: def notify_test() -> tuple[bool, str]:
venue = venue_label() venue = venue_label()
machine = wecom_machine_name()
lines = ["企业微信通知已连通。"] lines = ["企业微信通知已连通。"]
if machine:
lines.append(f"机器名称: **{machine}**")
if venue: if venue:
lines.append(f"当前盘口标签: **{venue}**") lines.append(f"当前盘口标签: **{venue}**")
content = build_markdown( content = build_markdown(
+1
View File
@@ -359,6 +359,7 @@ export type NotifySettings = {
webhook_configured: boolean; webhook_configured: boolean;
webhook_url_masked: string | null; webhook_url_masked: string | null;
venue_label: string; venue_label: string;
machine_name?: string;
}; };
export async function downloadBackup(name: string): Promise<void> { export async function downloadBackup(name: string): Promise<void> {
+27 -3
View File
@@ -117,6 +117,7 @@ export default function SettingsPage() {
const [runtimeOk, setRuntimeOk] = useState(""); const [runtimeOk, setRuntimeOk] = useState("");
const [wecomEnabled, setWecomEnabled] = useState(false); const [wecomEnabled, setWecomEnabled] = useState(false);
const [wecomWebhook, setWecomWebhook] = useState(""); const [wecomWebhook, setWecomWebhook] = useState("");
const [wecomMachineName, setWecomMachineName] = useState("");
const [wecomMeta, setWecomMeta] = useState<NotifySettings | null>(null); const [wecomMeta, setWecomMeta] = useState<NotifySettings | null>(null);
const [wecomOk, setWecomOk] = useState(""); const [wecomOk, setWecomOk] = useState("");
@@ -153,6 +154,7 @@ export default function SettingsPage() {
.then((n) => { .then((n) => {
setWecomMeta(n); setWecomMeta(n);
setWecomEnabled(n.enabled === true); setWecomEnabled(n.enabled === true);
setWecomMachineName(n.machine_name || "");
}) })
.catch(() => undefined); .catch(() => undefined);
} }
@@ -452,7 +454,10 @@ export default function SettingsPage() {
setErr(""); setErr("");
setWecomOk(""); setWecomOk("");
try { try {
const body: Record<string, unknown> = { enabled: wecomEnabled }; const body: Record<string, unknown> = {
enabled: wecomEnabled,
machine_name: wecomMachineName.trim(),
};
if (wecomWebhook.trim()) body.webhook_url = wecomWebhook.trim(); if (wecomWebhook.trim()) body.webhook_url = wecomWebhook.trim();
const n = await apiFetch<NotifySettings>("/api/settings/notify", { const n = await apiFetch<NotifySettings>("/api/settings/notify", {
method: "PUT", method: "PUT",
@@ -460,6 +465,7 @@ export default function SettingsPage() {
}); });
setWecomMeta(n); setWecomMeta(n);
setWecomEnabled(n.enabled === true); setWecomEnabled(n.enabled === true);
setWecomMachineName(n.machine_name || "");
setWecomWebhook(""); setWecomWebhook("");
setWecomOk( setWecomOk(
n.enabled n.enabled
@@ -483,6 +489,7 @@ export default function SettingsPage() {
); );
setWecomMeta(n); setWecomMeta(n);
setWecomOk(n.detail || "测试消息已发送,请查看企业微信群"); setWecomOk(n.detail || "测试消息已发送,请查看企业微信群");
if (n.machine_name != null) setWecomMachineName(n.machine_name || "");
} catch (ex) { } catch (ex) {
setErr(ex instanceof Error ? ex.message : String(ex)); setErr(ex instanceof Error ? ex.message : String(ex));
} }
@@ -1379,6 +1386,18 @@ export default function SettingsPage() {
<section className="settings-section"> <section className="settings-section">
<h3></h3> <h3></h3>
<div className="settings-fields"> <div className="settings-fields">
<div className="field">
<label htmlFor="wecomMachine"></label>
<input
id="wecomMachine"
className="mono"
autoComplete="off"
maxLength={64}
placeholder="例如 云A / 云B(推送标题前缀)"
value={wecomMachineName}
onChange={(e) => setWecomMachineName(e.target.value)}
/>
</div>
<div className="field"> <div className="field">
<label htmlFor="wecomEn"></label> <label htmlFor="wecomEn"></label>
<select <select
@@ -1450,14 +1469,19 @@ export default function SettingsPage() {
</li> </li>
<li> <li>
Webhook MarkdownOPEN/CLOSE/START/PAUSE/FAULT Webhook MarkdownOPEN/CLOSE/START/PAUSE/FAULT
A·OKX LIVE
<span className="mono">
{wecomMachineName || wecomMeta?.machine_name || "未命名"}
</span>
<span className="mono"> <span className="mono">
{wecomMeta?.venue_label || {wecomMeta?.venue_label ||
(runtime?.mode === "LIVE" (runtime?.mode === "LIVE"
? `实盘·${(runtime.exchange || "okx").toUpperCase()}` ? `实盘·${(runtime.exchange || "okx").toUpperCase()}`
: "模拟盘")} : "模拟盘")}
</span> </span>
////· ////
</li> </li>
<li> <li>
Webhook Webhook