diff --git a/lib/common/static/autofill_guard.js b/lib/common/static/autofill_guard.js new file mode 100644 index 0000000..b016d41 --- /dev/null +++ b/lib/common/static/autofill_guard.js @@ -0,0 +1,148 @@ +/** + * 全局防浏览器自动填充登录账号/密码进业务输入框. + * 跳过真正的登录/改密字段;对划转数量等易中招框用 readonly 到聚焦. + */ +(function () { + "use strict"; + + var GUARD_ATTRS = { + autocomplete: "off", + autocorrect: "off", + autocapitalize: "off", + spellcheck: "false", + "data-lpignore": "true", + "data-1p-ignore": "true", + "data-bwignore": "true", + "data-form-type": "other", + }; + + function looksLikeUsername(v) { + return /^[a-z][a-z0-9._-]{1,31}$/i.test(String(v || "").trim()); + } + + function isAuthField(el) { + if (!el || !el.getAttribute) return true; + var t = String(el.type || "").toLowerCase(); + if (t === "hidden" || t === "checkbox" || t === "radio" || t === "file" || t === "submit" || t === "button") { + return true; + } + if (el.getAttribute("aria-hidden") === "true") return true; + if (el.tabIndex === -1 && String(el.getAttribute("autocomplete") || "").toLowerCase() === "username") { + return true; // 诱饵账号框 + } + var idName = String(el.id || "") + " " + String(el.name || ""); + if (/^(pwd-|hub-pwd-|login-)/i.test(String(el.id || ""))) return true; + if (el.closest) { + if (el.closest(".login-form, #login-form, form.login-form, .password-settings, [data-password-settings]")) { + return true; + } + } + // env API Key 等 type=password 仍要防登录密码灌入,不在此跳过 + if (t === "password" && /^(username|password)$/i.test(String(el.name || ""))) { + if (el.closest && el.closest("form[method='post'], form[method='POST']")) return true; + } + return false; + } + + function isAmountLike(el) { + var key = String(el.id || "") + " " + String(el.name || "") + " " + String(el.placeholder || ""); + return /amount|xfer|transfer|划转|数量|金额/i.test(key); + } + + function wipeBad(el) { + if (!el || isAuthField(el)) return; + var v = String(el.value || "").trim(); + if (!looksLikeUsername(v)) return; + var t = String(el.type || "text").toLowerCase(); + if (t === "number" || isAmountLike(el) || /price|sheets|qty|sl|tp|target|entry|strike/i.test(String(el.id || "") + String(el.name || ""))) { + el.value = ""; + } + } + + function harden(el) { + if (!el || el.nodeType !== 1) return; + if (isAuthField(el)) return; + if (el.getAttribute("aria-hidden") === "true") return; + if (el.dataset && el.dataset.autofillGuarded === "1") { + wipeBad(el); + return; + } + if (el.dataset) el.dataset.autofillGuarded = "1"; + + Object.keys(GUARD_ATTRS).forEach(function (k) { + var cur = el.getAttribute(k); + if (k === "autocomplete" && cur && /^(username|current-password)/i.test(cur)) { + return; + } + // env 密钥框用 new-password 更抗登录密码灌入 + if (k === "autocomplete" && String(el.type || "").toLowerCase() === "password") { + el.setAttribute(k, "new-password"); + return; + } + if (!cur || cur === "on") el.setAttribute(k, GUARD_ATTRS[k]); + }); + + if (String(el.type || "").toLowerCase() === "password" || isAmountLike(el)) { + el.setAttribute("readonly", "readonly"); + el.addEventListener("focus", function () { + el.removeAttribute("readonly"); + }); + el.addEventListener("blur", function () { + if (!el.value) el.setAttribute("readonly", "readonly"); + }); + } + + wipeBad(el); + setTimeout(function () { + wipeBad(el); + }, 250); + setTimeout(function () { + wipeBad(el); + }, 900); + setTimeout(function () { + wipeBad(el); + }, 2000); + } + + function scan(root) { + var scope = root && root.querySelectorAll ? root : document; + var list = scope.querySelectorAll( + 'input[type="text"], input[type="number"], input[type="search"], input[type="url"], input[type="email"], input[type="tel"], input[type="password"], input:not([type]), textarea' + ); + for (var i = 0; i < list.length; i++) harden(list[i]); + } + + function boot() { + scan(document); + if (typeof MutationObserver === "undefined") return; + var obs = new MutationObserver(function (mutations) { + for (var i = 0; i < mutations.length; i++) { + var m = mutations[i]; + if (m.type === "childList") { + for (var j = 0; j < m.addedNodes.length; j++) { + var n = m.addedNodes[j]; + if (!n || n.nodeType !== 1) continue; + if (n.matches && n.matches("input, textarea")) harden(n); + else if (n.querySelectorAll) scan(n); + } + } else if (m.type === "attributes" && m.target) { + harden(m.target); + } + } + }); + obs.observe(document.documentElement, { + childList: true, + subtree: true, + attributes: true, + attributeFilter: ["value"], + }); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", boot); + } else { + boot(); + } + + window.cmAutofillGuardScan = scan; +})(); diff --git a/lib/hub/hub_bridge.py b/lib/hub/hub_bridge.py index 4cfb1a5..9c8d0b7 100644 --- a/lib/hub/hub_bridge.py +++ b/lib/hub/hub_bridge.py @@ -61,6 +61,7 @@ def install_instance_theme_static(app) -> None: "records_review_page.js": "application/javascript; charset=utf-8", "ai_review_render.js": "application/javascript; charset=utf-8", "form_submit_guard.js": "application/javascript; charset=utf-8", + "autofill_guard.js": "application/javascript; charset=utf-8", "key_monitor_form.js": "application/javascript; charset=utf-8", "time_close_ui.js": "application/javascript; charset=utf-8", "manual_order_rr_preview.js": "application/javascript; charset=utf-8", diff --git a/lib/instance/templates/embed_shell.html b/lib/instance/templates/embed_shell.html index f285a2b..e2d680b 100644 --- a/lib/instance/templates/embed_shell.html +++ b/lib/instance/templates/embed_shell.html @@ -4,6 +4,7 @@ + @@ -117,7 +118,7 @@ const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | toj - + diff --git a/lib/instance/templates/env_config_panel.html b/lib/instance/templates/env_config_panel.html index 1df2d97..4e5d70e 100644 --- a/lib/instance/templates/env_config_panel.html +++ b/lib/instance/templates/env_config_panel.html @@ -64,7 +64,13 @@ type="password" data-env-key="{{ field.key }}" placeholder="{% if field.has_value %}修改时填写新值,留空不修改{% else %}请输入{% endif %}" - autocomplete="off" + autocomplete="new-password" + data-lpignore="true" + data-1p-ignore="true" + data-bwignore="true" + data-form-type="other" + readonly + onfocus="this.removeAttribute('readonly')" > {% else %} {% endif %} diff --git a/lib/instance/templates/index.html b/lib/instance/templates/index.html index 0a8cba5..9aa9de8 100644 --- a/lib/instance/templates/index.html +++ b/lib/instance/templates/index.html @@ -5,6 +5,7 @@ + @@ -2019,6 +2020,6 @@ document.addEventListener("DOMContentLoaded", function () { }); {% endif %} - + \ No newline at end of file diff --git a/lib/instance/templates/instance_transfer_panel.html b/lib/instance/templates/instance_transfer_panel.html index 2b7411f..1a7e89e 100644 --- a/lib/instance/templates/instance_transfer_panel.html +++ b/lib/instance/templates/instance_transfer_panel.html @@ -8,14 +8,17 @@ 不足从 {{ auto_transfer_from }} 划入,超出划回 {{ auto_transfer_from }}; 持仓中不划转并微信通知.

-
- - + + - diff --git a/lib/instance/templates/password_settings_panel.html b/lib/instance/templates/password_settings_panel.html index 80c72ec..e607726 100644 --- a/lib/instance/templates/password_settings_panel.html +++ b/lib/instance/templates/password_settings_panel.html @@ -1,7 +1,7 @@ {# 系统设置 · 账户密码(外层 card 由 settings_panel 提供) #}

账户密码修改

修改网页登录账号密码,写入 .env 后需重启实例生效.

-
+
diff --git a/lib/instance/templates/records_panel.html b/lib/instance/templates/records_panel.html index 53dda4e..cf8a6f3 100644 --- a/lib/instance/templates/records_panel.html +++ b/lib/instance/templates/records_panel.html @@ -92,7 +92,7 @@
- +
diff --git a/lib/strategy/templates/gate_transfer_block.html b/lib/strategy/templates/gate_transfer_block.html index 002009f..bedceb9 100644 --- a/lib/strategy/templates/gate_transfer_block.html +++ b/lib/strategy/templates/gate_transfer_block.html @@ -7,14 +7,17 @@ 划转:自动划转 {{ '开启' if auto_transfer_enabled else '关闭' }}(每天北京时间 {{ auto_transfer_bj_hour }}:00起该整点小时内尝试;账簿按 UTC 自然日去重;将 {{ auto_transfer_to }} 调整至 {{ transfer_amount_fmt|default(funds_fmt(auto_transfer_amount)) }}U:不足从 {{ auto_transfer_from }} 划入,超出划回 {{ auto_transfer_from }};持仓中不划转并微信通知) -
- - + + - diff --git a/lib/strategy/templates/journal_form_fields.html b/lib/strategy/templates/journal_form_fields.html index a0746a0..3ce3126 100644 --- a/lib/strategy/templates/journal_form_fields.html +++ b/lib/strategy/templates/journal_form_fields.html @@ -1,13 +1,13 @@ {# 复盘表单:首行按字段宽度比例;下单类型/开仓类型与离场触发同一行 #} {% macro journal_form_fields(entry_reason_options, order_type_options) -%}
- - - - - - - + + + + + + +
- +
{%- endmacro %} diff --git a/lib/strategy/templates/trade_policy_fields.html b/lib/strategy/templates/trade_policy_fields.html index f1e1a00..137f1d9 100644 --- a/lib/strategy/templates/trade_policy_fields.html +++ b/lib/strategy/templates/trade_policy_fields.html @@ -11,7 +11,7 @@ {% endfor %} {% else %} - + {% endif %} {%- endmacro %} diff --git a/manual_trading_hub/hub.py b/manual_trading_hub/hub.py index c2fc0a3..6dd325b 100644 --- a/manual_trading_hub/hub.py +++ b/manual_trading_hub/hub.py @@ -777,6 +777,17 @@ _ACCOUNT_RISK_BADGE_CSS = _REPO_STATIC / "account_risk_badge.css" _ACCOUNT_RISK_BADGE_JS = _REPO_STATIC / "account_risk_badge.js" _OPTIONS_EXPIRY_COUNTDOWN_JS = _REPO_STATIC / "options_expiry_countdown.js" _OPTIONS_POSITION_CARDS_JS = _REPO_STATIC / "options_position_cards.js" +_AUTOFILL_GUARD_JS = _REPO_STATIC / "autofill_guard.js" + + +@app.get("/assets/autofill_guard.js") +def hub_autofill_guard_js(): + if not _AUTOFILL_GUARD_JS.is_file(): + raise HTTPException(status_code=404, detail="autofill_guard.js not found") + return FileResponse( + str(_AUTOFILL_GUARD_JS), + media_type="application/javascript; charset=utf-8", + ) @app.get("/assets/account_risk_badge.css") diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index 5d475f4..a2013f3 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -5009,12 +5009,12 @@
${envOff} - +
-
-
-
+
+
+
diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index 2328eb6..207fbd0 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -3,6 +3,7 @@ + @@ -113,15 +114,15 @@
@@ -1077,10 +1078,10 @@

修改中控网页登录账号密码,写入 manual_trading_hub/.env 后需重启中控生效.当前用户:

-
-
-
-
+
+
+
+
@@ -1378,7 +1379,7 @@ - + @@ -1393,6 +1394,6 @@ - + diff --git a/manual_trading_hub/static/plan.js b/manual_trading_hub/static/plan.js index d37a2bc..cc564e8 100644 --- a/manual_trading_hub/static/plan.js +++ b/manual_trading_hub/static/plan.js @@ -481,7 +481,7 @@ "" + '' + + '" required autocomplete="off" data-lpignore="true" data-1p-ignore="true" data-form-type="other" />' + '" + @@ -496,10 +496,10 @@ "" + '' + + '" autocomplete="off" data-lpignore="true" data-1p-ignore="true" data-form-type="other" />' + '' + + '" autocomplete="off" data-lpignore="true" data-1p-ignore="true" data-form-type="other" />' + '" +