diff --git a/app/server.py b/app/server.py index 9ccdd68..d08233b 100644 --- a/app/server.py +++ b/app/server.py @@ -149,7 +149,6 @@ class Handler(BaseHTTPRequestHandler): "/rules/create": self.create_rule, "/rules/update": self.update_rule, "/rules/delete": self.delete_rule, - "/rules/preview": self.preview_rule, "/test/send": self.send_test, "/account/password": self.change_password, "/deliveries/retry": self.retry_deliveries, @@ -421,8 +420,6 @@ class Handler(BaseHTTPRequestHandler): title: str, action: str, rule: dict[str, Any] | None = None, - preview_html: str = "", - sample_payload: str | None = None, ) -> None: targets = self.list_targets() rule = rule or { @@ -440,11 +437,6 @@ class Handler(BaseHTTPRequestHandler): selected_targets = target_checkbox_options(targets, rule.get("target_ids", [])) hidden_id = f'' if rule.get("id") else "" button_text = "保存修改" if rule.get("id") else "创建规则" - sample_payload = sample_payload or json.dumps( - {"timeframe": "5m", "symbol": "BTCUSDT", "strategy": "breakout", "action": "buy", "price": 68000}, - ensure_ascii=False, - indent=2, - ) body = f"""

{html.escape(title)}

消息统一使用飞书卡片。周期、品种、策略至少填写一个,空字段表示不限。

{hidden_id} @@ -458,12 +450,9 @@ class Handler(BaseHTTPRequestHandler):
发送到
{selected_targets}
- -

规则命中与卡片预览

- - -
返回列表
-
{preview_html}""" + +
返回列表
+""" self.send_html(title, body) def render_rule_new(self) -> None: @@ -500,72 +489,6 @@ class Handler(BaseHTTPRequestHandler): """ self.send_html("删除路由规则", body) - def build_rule_from_form(self, form: dict[str, list[str]]) -> dict[str, Any]: - return { - "id": form.get("id", [""])[-1], - "name": form.get("name", [""])[-1].strip(), - "timeframe": form.get("timeframe", [""])[-1].strip(), - "symbol": form.get("symbol", [""])[-1].strip().upper(), - "strategy": form.get("strategy", [""])[-1].strip(), - "priority": int(form.get("priority", ["100"])[-1] or 100), - "card_title_template": form.get("card_title_template", ["TradingView {{symbol}} {{action}}"])[-1].strip(), - "card_body_template": form.get("card_body_template", [""])[-1].strip(), - "target_ids": [int(value) for value in form.get("target_ids", []) if value], - "enabled": 1 if form.get("enabled", [""])[-1] == "on" else 0, - } - - def preview_rule(self) -> None: - form = parse_form_multi(self) - rule = self.build_rule_from_form(form) - sample_payload = form.get("sample_payload", ["{}"])[-1] - source_action = form.get("source_action", ["/rules/create"])[-1] - title = "编辑路由规则" if rule.get("id") else "新增路由规则" - try: - alert = normalize_alert(json.loads(sample_payload)) - message = build_feishu_message(alert, rule) - matched = self.context.dispatcher.find_matching_rule(alert) - current_matches = self.rule_matches_alert(rule, alert) - preview_html = self.render_preview_result(rule, message, matched, current_matches) - except (json.JSONDecodeError, ValidationError, ValueError) as exc: - preview_html = f"""

预览失败

{html.escape(str(exc))}

""" - self.render_rule_form(title, source_action, rule, preview_html, sample_payload) - - def rule_matches_alert(self, rule: dict[str, Any], alert: dict[str, Any]) -> bool: - if not any((rule.get("timeframe"), rule.get("symbol"), rule.get("strategy"))): - return False - if rule.get("timeframe") and rule["timeframe"] != alert.get("timeframe"): - return False - if rule.get("symbol") and rule["symbol"].upper() != alert.get("symbol"): - return False - if rule.get("strategy") and rule["strategy"] != alert.get("strategy"): - return False - return True - - def render_preview_result( - self, - rule: dict[str, Any], - message: dict[str, Any], - matched: dict[str, Any] | None, - current_matches: bool, - ) -> str: - title = message["card"]["header"]["title"]["content"] - content = message["card"]["elements"][0]["text"]["content"] - matched_text = f"当前已保存规则 #{matched['id']} {matched['name']}" if matched else "没有已保存规则会命中" - current_text = "当前表单会匹配样例 Alert" if current_matches else "当前表单不会匹配样例 Alert" - return f"""
-

预览结果

-
-
当前表单{html.escape(current_text)}
-
系统实际命中{html.escape(matched_text)}
-
规则优先级{rule.get('priority')}
-
消息类型飞书卡片
-
-
-
{html.escape(title)}
-
{html.escape(content)}
-
-
""" - def render_logs(self) -> None: logs = self.list_logs() alert_rows = "" diff --git a/app/static/styles.css b/app/static/styles.css index 93c7599..a2c7886 100644 --- a/app/static/styles.css +++ b/app/static/styles.css @@ -238,6 +238,55 @@ td textarea { width: auto; } +.switch { + display: inline-flex; + align-items: center; + gap: 10px; + margin: 4px 0 18px; + cursor: pointer; +} + +.switch input { + position: absolute; + opacity: 0; + pointer-events: none; +} + +.switch span { + position: relative; + width: 52px; + height: 30px; + border-radius: 999px; + background: #c8c2b5; + transition: background 0.18s ease; +} + +.switch span::after { + content: ""; + position: absolute; + top: 4px; + left: 4px; + width: 22px; + height: 22px; + border-radius: 50%; + background: #fff; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.22); + transition: transform 0.18s ease; +} + +.switch input:checked + span { + background: var(--accent); +} + +.switch input:checked + span::after { + transform: translateX(22px); +} + +.switch strong { + color: var(--ink); + font: 800 14px ui-sans-serif, system-ui, sans-serif; +} + .checks { margin: 8px 0 12px; }