perf: embed shell soft nav, tab cache, and lighter options page boot
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -290,7 +290,7 @@ def resolve_path(path_value):
|
|||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.secret_key = os.getenv("FLASK_SECRET_KEY", "crypto_monitor_2026_secret_key")
|
app.secret_key = os.getenv("FLASK_SECRET_KEY", "crypto_monitor_2026_secret_key")
|
||||||
from lib.instance.instance_embed_lib import attach_embed_templates
|
from lib.instance.instance_embed_lib import attach_embed_templates, redirect_to_embed_shell_if_enabled
|
||||||
|
|
||||||
attach_embed_templates(app, _REPO_ROOT)
|
attach_embed_templates(app, _REPO_ROOT)
|
||||||
|
|
||||||
@@ -6510,7 +6510,11 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
options_trading_usdc = None
|
options_trading_usdc = None
|
||||||
options_funding_usdc = None
|
options_funding_usdc = None
|
||||||
options_funding_usdt = None
|
options_funding_usdt = None
|
||||||
if OKX_OPTIONS_ENABLED and exchange_options.apiKey:
|
if (
|
||||||
|
OKX_OPTIONS_ENABLED
|
||||||
|
and exchange_options.apiKey
|
||||||
|
and embed_mode != "fragment"
|
||||||
|
):
|
||||||
try:
|
try:
|
||||||
from lib.exchange.okx_options_lib import (
|
from lib.exchange.okx_options_lib import (
|
||||||
fetch_options_funding_usdc,
|
fetch_options_funding_usdc,
|
||||||
@@ -6601,6 +6605,13 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
hard_limit=DAILY_OPEN_HARD_LIMIT,
|
hard_limit=DAILY_OPEN_HARD_LIMIT,
|
||||||
extra_blocks=not risk_status.get("can_trade", True),
|
extra_blocks=not risk_status.get("can_trade", True),
|
||||||
)
|
)
|
||||||
|
key_rule_ctx = {}
|
||||||
|
if page in ("key_monitor", "trade") or page in (
|
||||||
|
"strategy",
|
||||||
|
"strategy_trend",
|
||||||
|
"strategy_roll",
|
||||||
|
"strategy_records",
|
||||||
|
):
|
||||||
key_rule_ctx = key_monitor_rule_template_context(
|
key_rule_ctx = key_monitor_rule_template_context(
|
||||||
kline_timeframe=KLINE_TIMEFRAME,
|
kline_timeframe=KLINE_TIMEFRAME,
|
||||||
key_breakout_amp_min_pct=KEY_BREAKOUT_AMP_MIN_PCT,
|
key_breakout_amp_min_pct=KEY_BREAKOUT_AMP_MIN_PCT,
|
||||||
@@ -6768,36 +6779,54 @@ def index():
|
|||||||
@app.route("/key_monitor")
|
@app.route("/key_monitor")
|
||||||
@login_required
|
@login_required
|
||||||
def key_monitor_page():
|
def key_monitor_page():
|
||||||
|
redir = redirect_to_embed_shell_if_enabled("key_monitor")
|
||||||
|
if redir is not None:
|
||||||
|
return redir
|
||||||
return render_main_page("key_monitor")
|
return render_main_page("key_monitor")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/trade")
|
@app.route("/trade")
|
||||||
@login_required
|
@login_required
|
||||||
def trade_page():
|
def trade_page():
|
||||||
|
redir = redirect_to_embed_shell_if_enabled("trade")
|
||||||
|
if redir is not None:
|
||||||
|
return redir
|
||||||
return render_main_page("trade")
|
return render_main_page("trade")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/records")
|
@app.route("/records")
|
||||||
@login_required
|
@login_required
|
||||||
def records_page():
|
def records_page():
|
||||||
|
redir = redirect_to_embed_shell_if_enabled("records")
|
||||||
|
if redir is not None:
|
||||||
|
return redir
|
||||||
return render_main_page("records")
|
return render_main_page("records")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/stats")
|
@app.route("/stats")
|
||||||
@login_required
|
@login_required
|
||||||
def stats_page():
|
def stats_page():
|
||||||
|
redir = redirect_to_embed_shell_if_enabled("stats")
|
||||||
|
if redir is not None:
|
||||||
|
return redir
|
||||||
return render_main_page("stats")
|
return render_main_page("stats")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/settings")
|
@app.route("/settings")
|
||||||
@login_required
|
@login_required
|
||||||
def settings_page():
|
def settings_page():
|
||||||
|
redir = redirect_to_embed_shell_if_enabled("settings")
|
||||||
|
if redir is not None:
|
||||||
|
return redir
|
||||||
return render_main_page("settings")
|
return render_main_page("settings")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/options")
|
@app.route("/options")
|
||||||
@login_required
|
@login_required
|
||||||
def options_main_page():
|
def options_main_page():
|
||||||
|
redir = redirect_to_embed_shell_if_enabled("options")
|
||||||
|
if redir is not None:
|
||||||
|
return redir
|
||||||
return render_main_page("options")
|
return render_main_page("options")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
trade: "/trade",
|
trade: "/trade",
|
||||||
strategy: "/strategy",
|
strategy: "/strategy",
|
||||||
strategy_records: "/strategy/records",
|
strategy_records: "/strategy/records",
|
||||||
|
options: "/options",
|
||||||
records: "/records",
|
records: "/records",
|
||||||
stats: "/stats",
|
stats: "/stats",
|
||||||
settings: "/settings",
|
settings: "/settings",
|
||||||
@@ -14,6 +15,7 @@
|
|||||||
|
|
||||||
let navToken = 0;
|
let navToken = 0;
|
||||||
let loadingTab = false;
|
let loadingTab = false;
|
||||||
|
const tabHtmlCache = new Map();
|
||||||
|
|
||||||
/** 自带校验后 form.submit() 的表单,勿在捕获阶段再 fetch 一份(会双发 POST) */
|
/** 自带校验后 form.submit() 的表单,勿在捕获阶段再 fetch 一份(会双发 POST) */
|
||||||
const CUSTOM_SUBMIT_FORM_IDS = new Set(["add-order-form", "key-form", "roll-form"]);
|
const CUSTOM_SUBMIT_FORM_IDS = new Set(["add-order-form", "key-form", "roll-form"]);
|
||||||
@@ -112,20 +114,61 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function embedPageUrl(tab) {
|
||||||
|
const qs = listWindowQueryString();
|
||||||
|
let url = "/api/embed/page/" + encodeURIComponent(tab);
|
||||||
|
const parts = [];
|
||||||
|
if (qs) parts.push(qs);
|
||||||
|
parts.push("embed=1");
|
||||||
|
return url + "?" + parts.join("&");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchTabHtml(tab) {
|
||||||
|
const r = await fetch(embedPageUrl(tab), {
|
||||||
|
credentials: "same-origin",
|
||||||
|
headers: { "X-Instance-Soft-Nav": "1" },
|
||||||
|
});
|
||||||
|
const j = await r.json();
|
||||||
|
if (!j.ok || !j.html) throw new Error(j.msg || "加载失败");
|
||||||
|
return j.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function warmTabCache(tab) {
|
||||||
|
if (!tab || tabHtmlCache.has(tab)) return;
|
||||||
|
fetchTabHtml(tab)
|
||||||
|
.then((html) => {
|
||||||
|
tabHtmlCache.set(tab, html);
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearTabCache() {
|
||||||
|
tabHtmlCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
async function loadTab(tab, opts) {
|
async function loadTab(tab, opts) {
|
||||||
const options = opts || {};
|
const options = opts || {};
|
||||||
if (!tab || loadingTab) return;
|
if (!tab || loadingTab) return;
|
||||||
const token = ++navToken;
|
const token = ++navToken;
|
||||||
|
const force = !!options.force;
|
||||||
|
const cached = !force && tabHtmlCache.get(tab);
|
||||||
|
|
||||||
|
if (cached) {
|
||||||
|
injectFragment(cached);
|
||||||
|
setNavActive(tab);
|
||||||
|
if (!options.skipUrl) syncUrl(tab, !!options.replace);
|
||||||
|
runPageInit(tab);
|
||||||
|
warmTabCache(tab);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
loadingTab = true;
|
loadingTab = true;
|
||||||
setRootLoading(true);
|
setRootLoading(true);
|
||||||
try {
|
try {
|
||||||
const qs = listWindowQueryString();
|
const html = await fetchTabHtml(tab);
|
||||||
const url = "/api/embed/page/" + encodeURIComponent(tab) + (qs ? "?" + qs : "");
|
|
||||||
const r = await fetch(url, { credentials: "same-origin" });
|
|
||||||
if (token !== navToken) return;
|
if (token !== navToken) return;
|
||||||
const j = await r.json();
|
tabHtmlCache.set(tab, html);
|
||||||
if (!j.ok || !j.html) throw new Error(j.msg || "加载失败");
|
injectFragment(html);
|
||||||
injectFragment(j.html);
|
|
||||||
setNavActive(tab);
|
setNavActive(tab);
|
||||||
if (!options.skipUrl) syncUrl(tab, !!options.replace);
|
if (!options.skipUrl) syncUrl(tab, !!options.replace);
|
||||||
runPageInit(tab);
|
runPageInit(tab);
|
||||||
@@ -146,7 +189,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function reloadCurrentTab() {
|
function reloadCurrentTab() {
|
||||||
return loadTab(getTab(), { replace: true, skipUrl: true });
|
tabHtmlCache.delete(getTab());
|
||||||
|
return loadTab(getTab(), { replace: true, skipUrl: true, force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
function postFormAndReload(form, label) {
|
function postFormAndReload(form, label) {
|
||||||
@@ -172,6 +216,7 @@
|
|||||||
function patchApplyListWindow() {
|
function patchApplyListWindow() {
|
||||||
if (typeof global.applyListWindow !== "function") return;
|
if (typeof global.applyListWindow !== "function") return;
|
||||||
global.applyListWindow = function embedApplyListWindow() {
|
global.applyListWindow = function embedApplyListWindow() {
|
||||||
|
clearTabCache();
|
||||||
const qs = listWindowQueryString();
|
const qs = listWindowQueryString();
|
||||||
const tab = getTab();
|
const tab = getTab();
|
||||||
const q = new URLSearchParams(qs);
|
const q = new URLSearchParams(qs);
|
||||||
@@ -239,6 +284,9 @@
|
|||||||
|
|
||||||
function bindNav() {
|
function bindNav() {
|
||||||
document.querySelectorAll(".embed-top-nav [data-embed-tab]").forEach((a) => {
|
document.querySelectorAll(".embed-top-nav [data-embed-tab]").forEach((a) => {
|
||||||
|
a.addEventListener("mouseenter", () => {
|
||||||
|
warmTabCache(a.getAttribute("data-embed-tab"));
|
||||||
|
});
|
||||||
a.addEventListener("click", (ev) => {
|
a.addEventListener("click", (ev) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
const tab = a.getAttribute("data-embed-tab");
|
const tab = a.getAttribute("data-embed-tab");
|
||||||
@@ -268,6 +316,7 @@
|
|||||||
reloadCurrentTab,
|
reloadCurrentTab,
|
||||||
getTab,
|
getTab,
|
||||||
postFormAndReload,
|
postFormAndReload,
|
||||||
|
clearTabCache,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (document.readyState === "loading") {
|
if (document.readyState === "loading") {
|
||||||
|
|||||||
@@ -4,10 +4,12 @@
|
|||||||
const root = document.getElementById("options-root");
|
const root = document.getElementById("options-root");
|
||||||
if (!root) return;
|
if (!root) return;
|
||||||
|
|
||||||
|
const panelCache = (window.__optionsPanelCache = window.__optionsPanelCache || {});
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
underlying: root.dataset.defaultUnderly || "ETH",
|
underlying: root.dataset.defaultUnderly || "ETH",
|
||||||
optType: "C",
|
optType: "C",
|
||||||
chain: null,
|
chain: panelCache.chain || null,
|
||||||
selectedInst: null,
|
selectedInst: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -254,6 +256,9 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state.chain = d;
|
state.chain = d;
|
||||||
|
panelCache.chain = d;
|
||||||
|
panelCache.underlying = state.underlying;
|
||||||
|
panelCache.optType = state.optType;
|
||||||
state.selectedInst = null;
|
state.selectedInst = null;
|
||||||
parkOrderPanel();
|
parkOrderPanel();
|
||||||
renderExpiries();
|
renderExpiries();
|
||||||
@@ -457,6 +462,28 @@
|
|||||||
refreshHistory();
|
refreshHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function bootOptionsPanel() {
|
||||||
|
updateSizeInputs();
|
||||||
|
const hasCache =
|
||||||
|
panelCache.chain &&
|
||||||
|
panelCache.underlying === state.underlying &&
|
||||||
|
panelCache.optType === state.optType;
|
||||||
|
if (hasCache) {
|
||||||
|
state.chain = panelCache.chain;
|
||||||
|
renderExpiries();
|
||||||
|
renderStrikes();
|
||||||
|
refreshAllPositions();
|
||||||
|
requestAnimationFrame(function () {
|
||||||
|
loadChain();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
requestAnimationFrame(function () {
|
||||||
|
loadChain();
|
||||||
|
refreshAllPositions();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
document.querySelectorAll(".opt-uly-btn").forEach(function (btn) {
|
document.querySelectorAll(".opt-uly-btn").forEach(function (btn) {
|
||||||
btn.addEventListener("click", function () {
|
btn.addEventListener("click", function () {
|
||||||
document.querySelectorAll(".opt-uly-btn").forEach(function (b) { b.classList.remove("active"); });
|
document.querySelectorAll(".opt-uly-btn").forEach(function (b) { b.classList.remove("active"); });
|
||||||
@@ -495,7 +522,5 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
updateSizeInputs();
|
bootOptionsPanel();
|
||||||
loadChain();
|
|
||||||
refreshAllPositions();
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -72,6 +72,22 @@ def embed_shell_enabled() -> bool:
|
|||||||
return (os.getenv("HUB_EMBED_SHELL") or "1").strip().lower() in ("1", "true", "yes", "on")
|
return (os.getenv("HUB_EMBED_SHELL") or "1").strip().lower() in ("1", "true", "yes", "on")
|
||||||
|
|
||||||
|
|
||||||
|
def redirect_to_embed_shell_if_enabled(page: str):
|
||||||
|
"""直连 /trade 等整页路由时,重定向到 embed 壳(顶栏常驻、tab 软切换)。"""
|
||||||
|
from flask import redirect, request, urlencode
|
||||||
|
|
||||||
|
if not embed_shell_enabled():
|
||||||
|
return None
|
||||||
|
if (request.args.get("embed") or "").strip() == "1":
|
||||||
|
return None
|
||||||
|
if (request.path or "").rstrip("/") == "/embed":
|
||||||
|
return None
|
||||||
|
q = {k: v for k, v in request.args.items()}
|
||||||
|
q["tab"] = page
|
||||||
|
q["embed"] = "1"
|
||||||
|
return redirect("/embed?" + urlencode(q))
|
||||||
|
|
||||||
|
|
||||||
def rewrite_embed_dest(path: str, hub_theme: str | None = None) -> str:
|
def rewrite_embed_dest(path: str, hub_theme: str | None = None) -> str:
|
||||||
"""embed=1 打开时:/trade → /embed?tab=trade&embed=1"""
|
"""embed=1 打开时:/trade → /embed?tab=trade&embed=1"""
|
||||||
if not embed_shell_enabled():
|
if not embed_shell_enabled():
|
||||||
|
|||||||
@@ -90,6 +90,6 @@ const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | toj
|
|||||||
<script src="/static/strategy_roll.js?v=6"></script>
|
<script src="/static/strategy_roll.js?v=6"></script>
|
||||||
<script src="/static/key_monitor_form.js?v=2"></script>
|
<script src="/static/key_monitor_form.js?v=2"></script>
|
||||||
{% include 'embed_boot_scripts.html' %}
|
{% include 'embed_boot_scripts.html' %}
|
||||||
<script src="/static/instance_embed.js?v=7"></script>
|
<script src="/static/instance_embed.js?v=8"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -121,4 +121,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="/static/options_panel.js?v=7"></script>
|
<script src="/static/options_panel.js?v=8"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user