diff --git a/app.py b/app.py index aa370b3..a9b3d7a 100644 --- a/app.py +++ b/app.py @@ -205,6 +205,7 @@ def desk(): inst["base_url"], bridge_token=inst["bridge_token"], embed_path=inst.get("embed_path") or "/embed", + ttl_sec=3600, ) return render_template( "desk.html", @@ -231,6 +232,7 @@ def api_open_url(iid: int): inst["base_url"], bridge_token=inst["bridge_token"], embed_path=inst.get("embed_path") or "/embed", + ttl_sec=3600, ) return jsonify({"ok": True, "url": url, "base_url": inst["base_url"]}) diff --git a/static/hub.css b/static/hub.css index 23b7e6c..00b2c8f 100644 --- a/static/hub.css +++ b/static/hub.css @@ -28,6 +28,12 @@ a { color: #9ec1ff; text-decoration: none; } .topbar nav a.active { color: #fff; } .brand { font-weight: 700; } .main { padding: 16px; max-width: 1200px; margin: 0 auto; } +body.page-desk .main, +body.page-dashboard .main { + max-width: none; + width: 100%; + padding: 12px 16px 20px; +} .page-head { display: flex; align-items: center; gap: 12px; } .muted { color: var(--muted); font-size: 0.9rem; } .flash { @@ -80,6 +86,43 @@ button, .btn { } .check { display: flex !important; align-items: center; gap: 8px; } .inst-row { margin: 10px 0; border: 1px solid var(--line); border-radius: 10px; padding: 8px 12px; } +.overview-table-wrap { + overflow-x: auto; + margin-top: 12px; + border: 1px solid var(--line); + border-radius: 12px; + background: var(--panel); +} +.overview-table { + width: 100%; + border-collapse: collapse; + font-size: 0.88rem; +} +.overview-table th, +.overview-table td { + padding: 10px 12px; + border-bottom: 1px solid var(--line); + text-align: left; + vertical-align: top; +} +.overview-table th { + color: var(--muted); + font-weight: 600; + background: #12161f; + white-space: nowrap; +} +.overview-table tr:last-child td { border-bottom: 0; } +.overview-table .status-on { color: var(--ok); } +.overview-table .status-off { color: var(--bad); } +.overview-actions { display: flex; gap: 8px; flex-wrap: wrap; } +.overview-actions .btn { margin-top: 0; } +.pos-brief { + margin: 0; + padding-left: 1.1em; + color: var(--muted); + font-size: 0.8rem; +} +.pos-brief li { margin: 2px 0; } .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); @@ -96,7 +139,7 @@ button, .btn { .card .status-off { color: var(--bad); } .card table { width: 100%; font-size: 0.8rem; border-collapse: collapse; } .card td, .card th { border-bottom: 1px solid var(--line); padding: 4px 2px; text-align: left; } -.desk { display: grid; grid-template-columns: 200px 1fr; gap: 12px; min-height: 70vh; max-width: none; } +.desk { display: grid; grid-template-columns: 200px 1fr; gap: 12px; min-height: calc(100vh - 70px); max-width: none; } .desk-side { background: var(--panel); border: 1px solid var(--line); @@ -106,7 +149,7 @@ button, .btn { .desk-list { list-style: none; padding: 0; margin: 0; } .desk-list li { margin: 6px 0; } .desk-list li.active a { color: #fff; font-weight: 600; } -.desk-main { display: flex; flex-direction: column; min-height: 70vh; } +.desk-main { display: flex; flex-direction: column; min-height: calc(100vh - 70px); min-width: 0; } .desk-toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 8px; @@ -114,7 +157,7 @@ button, .btn { .desk-frame { flex: 1; width: 100%; - min-height: 65vh; + min-height: calc(100vh - 130px); border: 1px solid var(--line); border-radius: 10px; background: #000; diff --git a/static/hub.js b/static/hub.js index 9cf99cb..8b87126 100644 --- a/static/hub.js +++ b/static/hub.js @@ -6,60 +6,6 @@ return (n > 0 ? "+" : "") + n.toFixed(2) + " U"; } - function renderCard(row) { - const snap = row.snapshot || {}; - const inst = snap.instance || {}; - const pnl = (snap.pnl && snap.pnl.unrealized_usdt != null) - ? snap.pnl.unrealized_usdt - : snap.upl_total_usdc; - const brief = snap.positions_brief || snap.positions || []; - const online = !!row.online; - const status = online - ? '在线' - : '离线'; - const mode = inst.mode || "—"; - const err = row.error ? '
错误: ' + escapeHtml(row.error) + "
" : ""; - let rows = ""; - (brief || []).slice(0, 6).forEach(function (p) { - rows += - "" + - escapeHtml(p.inst_id || "") + - "" + - escapeHtml(String(p.sheets != null ? p.sheets : "")) + - "" + - escapeHtml(fmtPnl(p.pnl_usdt)) + - ""; - }); - if (!rows) rows = '暂无持仓'; - return ( - '
' + - "

" + - escapeHtml(row.name || "") + - "

" + - "
" + - status + - " · " + - escapeHtml(mode) + - " · 浮盈 " + - escapeHtml(fmtPnl(pnl)) + - "
" + - '
拉取: ' + - escapeHtml(row.pulled_at || "") + - "
" + - err + - "" + - rows + - "
合约张盈亏
" + - '

内嵌打开 ' + - '

' + - "
" - ); - } - function escapeHtml(s) { return String(s) .replace(/&/g, "&") @@ -68,6 +14,71 @@ .replace(/"/g, """); } + function positionSummary(brief) { + const list = brief || []; + if (!list.length) return '暂无持仓'; + const items = list.slice(0, 8).map(function (p) { + const id = escapeHtml(p.inst_id || ""); + const sheets = escapeHtml(String(p.sheets != null ? p.sheets : "—")); + const pnl = escapeHtml(fmtPnl(p.pnl_usdt)); + return "
  • " + id + " · " + sheets + "张 · " + pnl + "
  • "; + }); + const more = + list.length > 8 + ? '
  • …共 ' + list.length + " 笔
  • " + : ""; + return ""; + } + + function renderRow(row) { + const snap = row.snapshot || {}; + const inst = snap.instance || {}; + const pnl = + snap.pnl && snap.pnl.unrealized_usdt != null + ? snap.pnl.unrealized_usdt + : snap.upl_total_usdc; + const brief = snap.positions_brief || snap.positions || []; + const online = !!row.online; + const status = online + ? '在线' + : '离线'; + const err = row.error + ? '
    错误: ' + escapeHtml(row.error) + "
    " + : ""; + return ( + "" + + "" + + escapeHtml(row.name || "") + + "" + + err + + "" + + "" + + status + + "" + + "" + + escapeHtml(inst.mode || "—") + + "" + + "" + + escapeHtml(fmtPnl(pnl)) + + "" + + "" + + positionSummary(brief) + + "" + + '' + + escapeHtml(row.pulled_at || "—") + + "" + + '
    ' + + '内嵌打开' + + '' + + "
    " + + "" + ); + } + async function refresh() { const status = document.getElementById("overview-status"); const grid = document.getElementById("overview-grid"); @@ -79,9 +90,16 @@ if (!d.ok) throw new Error(d.msg || "失败"); const list = d.instances || []; if (!list.length) { - grid.innerHTML = '

    暂无启用实例,请先到「实例」添加.

    '; + grid.innerHTML = + '

    暂无启用实例,请先到「实例」添加.

    '; } else { - grid.innerHTML = list.map(renderCard).join(""); + grid.innerHTML = + '
    ' + + "" + + "" + + "" + + list.map(renderRow).join("") + + "
    名称状态模式浮盈持仓拉取时间操作
    "; grid.querySelectorAll("[data-open-tab]").forEach(function (btn) { btn.addEventListener("click", async function () { const id = btn.getAttribute("data-open-tab"); diff --git a/templates/base.html b/templates/base.html index 083496f..2da854a 100644 --- a/templates/base.html +++ b/templates/base.html @@ -6,7 +6,7 @@ {% block title %}期权中控{% endblock %} - + {% if session.get('logged_in') %}
    crypto_okx_hub
    diff --git a/templates/dashboard.html b/templates/dashboard.html index 5e6a809..693a3ab 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -6,7 +6,7 @@

    加载中…

    -
    +
    {% endblock %} {% block scripts %}