总览支持向客户端下发买一全平:确认后转发,无效买一拒绝。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,245 +1,268 @@
|
||||
"""crypto_okx_hub — 期权多机中控."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from functools import wraps
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from flask import (
|
||||
Flask,
|
||||
flash,
|
||||
jsonify,
|
||||
redirect,
|
||||
render_template,
|
||||
request,
|
||||
session,
|
||||
url_for,
|
||||
)
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
ENV_PATH = ROOT / ".env"
|
||||
load_dotenv(ENV_PATH)
|
||||
|
||||
from lib.bridge_client_lib import pull_all_enabled
|
||||
from lib.db_lib import init_db
|
||||
from lib.env_lib import apply_env_updates
|
||||
from lib.instances_lib import (
|
||||
create_instance,
|
||||
delete_instance,
|
||||
get_instance,
|
||||
list_instances,
|
||||
update_instance,
|
||||
)
|
||||
from lib.sso_lib import build_embed_open_url
|
||||
|
||||
APP_HOST = (os.getenv("APP_HOST") or "0.0.0.0").strip() or "0.0.0.0"
|
||||
APP_PORT = int(os.getenv("APP_PORT") or 5100)
|
||||
SECRET = (os.getenv("FLASK_SECRET_KEY") or "hub-dev-secret").strip()
|
||||
|
||||
# 可变登录态:系统设置页可改,并立刻生效
|
||||
_auth = {
|
||||
"username": (os.getenv("HUB_USERNAME") or "admin").strip() or "admin",
|
||||
"password": (os.getenv("HUB_PASSWORD") or "admin123").strip() or "admin123",
|
||||
}
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = SECRET
|
||||
app.config["TEMPLATES_AUTO_RELOAD"] = True
|
||||
|
||||
init_db()
|
||||
|
||||
|
||||
def hub_username() -> str:
|
||||
return _auth["username"]
|
||||
|
||||
|
||||
def hub_password() -> str:
|
||||
return _auth["password"]
|
||||
|
||||
|
||||
def set_hub_credentials(username: str, password: str) -> None:
|
||||
u = (username or "").strip() or "admin"
|
||||
p = password if password is not None else ""
|
||||
_auth["username"] = u
|
||||
_auth["password"] = p
|
||||
os.environ["HUB_USERNAME"] = u
|
||||
os.environ["HUB_PASSWORD"] = p
|
||||
|
||||
|
||||
def login_required(f):
|
||||
@wraps(f)
|
||||
def wrapped(*args, **kwargs):
|
||||
if session.get("logged_in"):
|
||||
return f(*args, **kwargs)
|
||||
if request.path.startswith("/api/"):
|
||||
return jsonify({"ok": False, "msg": "未登录"}), 401
|
||||
return redirect(url_for("login", next=request.path))
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
@app.route("/login", methods=["GET", "POST"])
|
||||
def login():
|
||||
if request.method == "POST":
|
||||
u = (request.form.get("username") or "").strip()
|
||||
p = request.form.get("password") or ""
|
||||
if u == hub_username() and p == hub_password():
|
||||
session["logged_in"] = True
|
||||
session.permanent = True
|
||||
nxt = (request.args.get("next") or request.form.get("next") or "/desk").strip() or "/desk"
|
||||
if not nxt.startswith("/"):
|
||||
nxt = "/desk"
|
||||
return redirect(nxt)
|
||||
flash("用户名或密码错误")
|
||||
if session.get("logged_in"):
|
||||
return redirect("/desk")
|
||||
return render_template("login.html")
|
||||
|
||||
|
||||
@app.route("/logout")
|
||||
def logout():
|
||||
session.clear()
|
||||
return redirect(url_for("login"))
|
||||
|
||||
|
||||
@app.route("/")
|
||||
@login_required
|
||||
def dashboard():
|
||||
return render_template("dashboard.html")
|
||||
|
||||
|
||||
@app.route("/instances", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def instances_page():
|
||||
if request.method == "POST":
|
||||
action = (request.form.get("action") or "create").strip()
|
||||
if action == "create":
|
||||
_, err = create_instance(
|
||||
name=request.form.get("name") or "",
|
||||
base_url=request.form.get("base_url") or "",
|
||||
bridge_token=request.form.get("bridge_token") or "",
|
||||
embed_path=request.form.get("embed_path") or "/embed",
|
||||
tags=request.form.get("tags") or "",
|
||||
enabled=bool(request.form.get("enabled")),
|
||||
)
|
||||
flash(err or "已添加")
|
||||
elif action == "update":
|
||||
iid = int(request.form.get("id") or 0)
|
||||
_, err = update_instance(
|
||||
iid,
|
||||
name=request.form.get("name") or "",
|
||||
base_url=request.form.get("base_url") or "",
|
||||
bridge_token=request.form.get("bridge_token") or "",
|
||||
embed_path=request.form.get("embed_path") or "/embed",
|
||||
tags=request.form.get("tags") or "",
|
||||
enabled=bool(request.form.get("enabled")),
|
||||
)
|
||||
flash(err or "已保存")
|
||||
elif action == "delete":
|
||||
iid = int(request.form.get("id") or 0)
|
||||
ok = delete_instance(iid)
|
||||
flash("已删除" if ok else "删除失败")
|
||||
return redirect(url_for("instances_page"))
|
||||
return render_template("instances.html", instances=list_instances())
|
||||
|
||||
|
||||
@app.route("/settings", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def settings_page():
|
||||
if request.method == "POST":
|
||||
current_pw = request.form.get("current_password") or ""
|
||||
new_username = (request.form.get("new_username") or "").strip()
|
||||
new_password = request.form.get("new_password") or ""
|
||||
confirm = request.form.get("confirm_password") or ""
|
||||
|
||||
if current_pw != hub_password():
|
||||
flash("当前密码错误")
|
||||
return redirect(url_for("settings_page"))
|
||||
if len(new_username) < 2 or len(new_username) > 64:
|
||||
flash("用户名长度需为 2–64")
|
||||
return redirect(url_for("settings_page"))
|
||||
if any(ch.isspace() for ch in new_username):
|
||||
flash("用户名不能包含空格")
|
||||
return redirect(url_for("settings_page"))
|
||||
|
||||
updates: dict[str, str] = {"HUB_USERNAME": new_username}
|
||||
final_password = hub_password()
|
||||
if new_password or confirm:
|
||||
if len(new_password) < 6:
|
||||
flash("新密码至少 6 位")
|
||||
return redirect(url_for("settings_page"))
|
||||
if new_password != confirm:
|
||||
flash("两次输入的新密码不一致")
|
||||
return redirect(url_for("settings_page"))
|
||||
updates["HUB_PASSWORD"] = new_password
|
||||
final_password = new_password
|
||||
|
||||
try:
|
||||
apply_env_updates(ENV_PATH, updates)
|
||||
except OSError as exc:
|
||||
flash(f"写入 .env 失败: {exc}")
|
||||
return redirect(url_for("settings_page"))
|
||||
|
||||
set_hub_credentials(new_username, final_password)
|
||||
flash("账号已更新,立即生效")
|
||||
return redirect(url_for("settings_page"))
|
||||
|
||||
return render_template("settings.html", current_username=hub_username())
|
||||
|
||||
|
||||
@app.route("/desk")
|
||||
@login_required
|
||||
def desk():
|
||||
instances = list_instances(enabled_only=True)
|
||||
iid = request.args.get("id")
|
||||
# 未指定客户端时默认打开第一个
|
||||
if (not iid or not str(iid).isdigit()) and instances:
|
||||
return redirect(url_for("desk", id=instances[0]["id"]))
|
||||
inst = None
|
||||
open_url = ""
|
||||
if iid and str(iid).isdigit():
|
||||
inst = get_instance(int(iid))
|
||||
if inst:
|
||||
open_url = build_embed_open_url(
|
||||
inst["base_url"],
|
||||
bridge_token=inst["bridge_token"],
|
||||
embed_path=inst.get("embed_path") or "/embed",
|
||||
ttl_sec=3600,
|
||||
)
|
||||
return render_template(
|
||||
"desk.html",
|
||||
instances=instances,
|
||||
current=inst,
|
||||
open_url=open_url,
|
||||
)
|
||||
|
||||
|
||||
@app.route("/api/overview")
|
||||
@login_required
|
||||
def api_overview():
|
||||
rows = pull_all_enabled()
|
||||
return jsonify({"ok": True, "instances": rows})
|
||||
|
||||
|
||||
@app.route("/api/instances/<int:iid>/open_url")
|
||||
@login_required
|
||||
def api_open_url(iid: int):
|
||||
inst = get_instance(iid)
|
||||
if not inst:
|
||||
return jsonify({"ok": False, "msg": "不存在"}), 404
|
||||
url = build_embed_open_url(
|
||||
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"]})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host=APP_HOST, port=APP_PORT, debug=True, threaded=True)
|
||||
"""crypto_okx_hub — 期权多机中控."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from functools import wraps
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from flask import (
|
||||
Flask,
|
||||
flash,
|
||||
jsonify,
|
||||
redirect,
|
||||
render_template,
|
||||
request,
|
||||
session,
|
||||
url_for,
|
||||
)
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
ENV_PATH = ROOT / ".env"
|
||||
load_dotenv(ENV_PATH)
|
||||
|
||||
from lib.bridge_client_lib import post_close_full, pull_all_enabled
|
||||
from lib.db_lib import init_db
|
||||
from lib.env_lib import apply_env_updates
|
||||
from lib.instances_lib import (
|
||||
create_instance,
|
||||
delete_instance,
|
||||
get_instance,
|
||||
list_instances,
|
||||
update_instance,
|
||||
)
|
||||
from lib.sso_lib import build_embed_open_url
|
||||
|
||||
APP_HOST = (os.getenv("APP_HOST") or "0.0.0.0").strip() or "0.0.0.0"
|
||||
APP_PORT = int(os.getenv("APP_PORT") or 5100)
|
||||
SECRET = (os.getenv("FLASK_SECRET_KEY") or "hub-dev-secret").strip()
|
||||
|
||||
# 可变登录态:系统设置页可改,并立刻生效
|
||||
_auth = {
|
||||
"username": (os.getenv("HUB_USERNAME") or "admin").strip() or "admin",
|
||||
"password": (os.getenv("HUB_PASSWORD") or "admin123").strip() or "admin123",
|
||||
}
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = SECRET
|
||||
app.config["TEMPLATES_AUTO_RELOAD"] = True
|
||||
|
||||
init_db()
|
||||
|
||||
|
||||
def hub_username() -> str:
|
||||
return _auth["username"]
|
||||
|
||||
|
||||
def hub_password() -> str:
|
||||
return _auth["password"]
|
||||
|
||||
|
||||
def set_hub_credentials(username: str, password: str) -> None:
|
||||
u = (username or "").strip() or "admin"
|
||||
p = password if password is not None else ""
|
||||
_auth["username"] = u
|
||||
_auth["password"] = p
|
||||
os.environ["HUB_USERNAME"] = u
|
||||
os.environ["HUB_PASSWORD"] = p
|
||||
|
||||
|
||||
def login_required(f):
|
||||
@wraps(f)
|
||||
def wrapped(*args, **kwargs):
|
||||
if session.get("logged_in"):
|
||||
return f(*args, **kwargs)
|
||||
if request.path.startswith("/api/"):
|
||||
return jsonify({"ok": False, "msg": "未登录"}), 401
|
||||
return redirect(url_for("login", next=request.path))
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
@app.route("/login", methods=["GET", "POST"])
|
||||
def login():
|
||||
if request.method == "POST":
|
||||
u = (request.form.get("username") or "").strip()
|
||||
p = request.form.get("password") or ""
|
||||
if u == hub_username() and p == hub_password():
|
||||
session["logged_in"] = True
|
||||
session.permanent = True
|
||||
nxt = (request.args.get("next") or request.form.get("next") or "/desk").strip() or "/desk"
|
||||
if not nxt.startswith("/"):
|
||||
nxt = "/desk"
|
||||
return redirect(nxt)
|
||||
flash("用户名或密码错误")
|
||||
if session.get("logged_in"):
|
||||
return redirect("/desk")
|
||||
return render_template("login.html")
|
||||
|
||||
|
||||
@app.route("/logout")
|
||||
def logout():
|
||||
session.clear()
|
||||
return redirect(url_for("login"))
|
||||
|
||||
|
||||
@app.route("/")
|
||||
@login_required
|
||||
def dashboard():
|
||||
return render_template("dashboard.html")
|
||||
|
||||
|
||||
@app.route("/instances", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def instances_page():
|
||||
if request.method == "POST":
|
||||
action = (request.form.get("action") or "create").strip()
|
||||
if action == "create":
|
||||
_, err = create_instance(
|
||||
name=request.form.get("name") or "",
|
||||
base_url=request.form.get("base_url") or "",
|
||||
bridge_token=request.form.get("bridge_token") or "",
|
||||
embed_path=request.form.get("embed_path") or "/embed",
|
||||
tags=request.form.get("tags") or "",
|
||||
enabled=bool(request.form.get("enabled")),
|
||||
)
|
||||
flash(err or "已添加")
|
||||
elif action == "update":
|
||||
iid = int(request.form.get("id") or 0)
|
||||
_, err = update_instance(
|
||||
iid,
|
||||
name=request.form.get("name") or "",
|
||||
base_url=request.form.get("base_url") or "",
|
||||
bridge_token=request.form.get("bridge_token") or "",
|
||||
embed_path=request.form.get("embed_path") or "/embed",
|
||||
tags=request.form.get("tags") or "",
|
||||
enabled=bool(request.form.get("enabled")),
|
||||
)
|
||||
flash(err or "已保存")
|
||||
elif action == "delete":
|
||||
iid = int(request.form.get("id") or 0)
|
||||
ok = delete_instance(iid)
|
||||
flash("已删除" if ok else "删除失败")
|
||||
return redirect(url_for("instances_page"))
|
||||
return render_template("instances.html", instances=list_instances())
|
||||
|
||||
|
||||
@app.route("/settings", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def settings_page():
|
||||
if request.method == "POST":
|
||||
current_pw = request.form.get("current_password") or ""
|
||||
new_username = (request.form.get("new_username") or "").strip()
|
||||
new_password = request.form.get("new_password") or ""
|
||||
confirm = request.form.get("confirm_password") or ""
|
||||
|
||||
if current_pw != hub_password():
|
||||
flash("当前密码错误")
|
||||
return redirect(url_for("settings_page"))
|
||||
if len(new_username) < 2 or len(new_username) > 64:
|
||||
flash("用户名长度需为 2–64")
|
||||
return redirect(url_for("settings_page"))
|
||||
if any(ch.isspace() for ch in new_username):
|
||||
flash("用户名不能包含空格")
|
||||
return redirect(url_for("settings_page"))
|
||||
|
||||
updates: dict[str, str] = {"HUB_USERNAME": new_username}
|
||||
final_password = hub_password()
|
||||
if new_password or confirm:
|
||||
if len(new_password) < 6:
|
||||
flash("新密码至少 6 位")
|
||||
return redirect(url_for("settings_page"))
|
||||
if new_password != confirm:
|
||||
flash("两次输入的新密码不一致")
|
||||
return redirect(url_for("settings_page"))
|
||||
updates["HUB_PASSWORD"] = new_password
|
||||
final_password = new_password
|
||||
|
||||
try:
|
||||
apply_env_updates(ENV_PATH, updates)
|
||||
except OSError as exc:
|
||||
flash(f"写入 .env 失败: {exc}")
|
||||
return redirect(url_for("settings_page"))
|
||||
|
||||
set_hub_credentials(new_username, final_password)
|
||||
flash("账号已更新,立即生效")
|
||||
return redirect(url_for("settings_page"))
|
||||
|
||||
return render_template("settings.html", current_username=hub_username())
|
||||
|
||||
|
||||
@app.route("/desk")
|
||||
@login_required
|
||||
def desk():
|
||||
instances = list_instances(enabled_only=True)
|
||||
iid = request.args.get("id")
|
||||
# 未指定客户端时默认打开第一个
|
||||
if (not iid or not str(iid).isdigit()) and instances:
|
||||
return redirect(url_for("desk", id=instances[0]["id"]))
|
||||
inst = None
|
||||
open_url = ""
|
||||
if iid and str(iid).isdigit():
|
||||
inst = get_instance(int(iid))
|
||||
if inst:
|
||||
open_url = build_embed_open_url(
|
||||
inst["base_url"],
|
||||
bridge_token=inst["bridge_token"],
|
||||
embed_path=inst.get("embed_path") or "/embed",
|
||||
ttl_sec=3600,
|
||||
)
|
||||
return render_template(
|
||||
"desk.html",
|
||||
instances=instances,
|
||||
current=inst,
|
||||
open_url=open_url,
|
||||
)
|
||||
|
||||
|
||||
@app.route("/api/overview")
|
||||
@login_required
|
||||
def api_overview():
|
||||
rows = pull_all_enabled()
|
||||
return jsonify({"ok": True, "instances": rows})
|
||||
|
||||
|
||||
@app.route("/api/instances/<int:iid>/open_url")
|
||||
@login_required
|
||||
def api_open_url(iid: int):
|
||||
inst = get_instance(iid)
|
||||
if not inst:
|
||||
return jsonify({"ok": False, "msg": "不存在"}), 404
|
||||
url = build_embed_open_url(
|
||||
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"]})
|
||||
|
||||
|
||||
@app.route("/api/instances/<int:iid>/options/close", methods=["POST"])
|
||||
@login_required
|
||||
def api_instance_options_close(iid: int):
|
||||
"""总览下发全平:中控只转发,客户端本地买一限价执行."""
|
||||
inst = get_instance(iid)
|
||||
if not inst:
|
||||
return jsonify({"ok": False, "msg": "实例不存在"}), 404
|
||||
if not inst.get("enabled"):
|
||||
return jsonify({"ok": False, "msg": "实例未启用"}), 400
|
||||
data = request.get_json(silent=True) or {}
|
||||
opt_inst = (data.get("inst_id") or "").strip()
|
||||
if not opt_inst:
|
||||
return jsonify({"ok": False, "msg": "缺少 inst_id"})
|
||||
result = post_close_full(inst, inst_id=opt_inst)
|
||||
status = 200
|
||||
if not result.get("ok"):
|
||||
# 客户端业务拒绝(残档等)仍 200 带 ok:false;仅传输层失败用 502
|
||||
msg = str(result.get("msg") or "")
|
||||
if msg.startswith("HTTP ") or "超时" in msg or "下发失败" in msg or "缺少 URL" in msg:
|
||||
status = 502
|
||||
return jsonify(result), status
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host=APP_HOST, port=APP_PORT, debug=True, threaded=True)
|
||||
|
||||
Reference in New Issue
Block a user