Compare commits
2 Commits
02ca76ea7c
...
0327f49f4e
| Author | SHA1 | Date | |
|---|---|---|---|
| 0327f49f4e | |||
| 470c6ba261 |
@@ -362,6 +362,8 @@ def api_hub_backup_create():
|
|||||||
env_path=str(ENV_PATH),
|
env_path=str(ENV_PATH),
|
||||||
db_path=_resolved_hub_db_path(),
|
db_path=_resolved_hub_db_path(),
|
||||||
label="manual",
|
label="manual",
|
||||||
|
username=hub_username(),
|
||||||
|
password=hub_password(),
|
||||||
)
|
)
|
||||||
return jsonify(result)
|
return jsonify(result)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@@ -395,6 +397,12 @@ def api_hub_backup_restore():
|
|||||||
env_path=str(ENV_PATH),
|
env_path=str(ENV_PATH),
|
||||||
db_path=_resolved_hub_db_path(),
|
db_path=_resolved_hub_db_path(),
|
||||||
)
|
)
|
||||||
|
if result.get("ok"):
|
||||||
|
u = str(result.get("hub_username") or "").strip()
|
||||||
|
p = result.get("hub_password")
|
||||||
|
if u or p is not None:
|
||||||
|
set_hub_credentials(u or hub_username(), "" if p is None else str(p))
|
||||||
|
result.pop("hub_password", None)
|
||||||
return jsonify(result), (200 if result.get("ok") else 400)
|
return jsonify(result), (200 if result.get("ok") else 400)
|
||||||
|
|
||||||
|
|
||||||
@@ -517,6 +525,7 @@ try:
|
|||||||
env_path=str(ENV_PATH),
|
env_path=str(ENV_PATH),
|
||||||
db_path_fn=_resolved_hub_db_path,
|
db_path_fn=_resolved_hub_db_path,
|
||||||
hour=3,
|
hour=3,
|
||||||
|
get_credentials=lambda: (hub_username(), hub_password()),
|
||||||
)
|
)
|
||||||
except Exception as _hub_bak_err:
|
except Exception as _hub_bak_err:
|
||||||
print(f"[backup] scheduler: {_hub_bak_err}")
|
print(f"[backup] scheduler: {_hub_bak_err}")
|
||||||
@@ -781,6 +790,21 @@ def api_cond_orders_exit(hub_order_id: str):
|
|||||||
return jsonify(post_cond_exit(inst, hub_order_id, payload))
|
return jsonify(post_cond_exit(inst, hub_order_id, payload))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/manifest.webmanifest")
|
||||||
|
def pwa_manifest():
|
||||||
|
path = ROOT / "static" / "icons" / "manifest.webmanifest"
|
||||||
|
return send_file(path, mimetype="application/manifest+json; charset=utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/sw.js")
|
||||||
|
def pwa_service_worker():
|
||||||
|
path = ROOT / "static" / "sw.js"
|
||||||
|
resp = send_file(path, mimetype="application/javascript; charset=utf-8")
|
||||||
|
resp.headers["Cache-Control"] = "no-cache"
|
||||||
|
resp.headers["Service-Worker-Allowed"] = "/"
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# PM2 下禁止 debug reloader,否则会 Bad file descriptor 崩循环
|
# PM2 下禁止 debug reloader,否则会 Bad file descriptor 崩循环
|
||||||
_debug = (os.getenv("APP_DEBUG") or "").strip().lower() in ("1", "true", "yes", "on")
|
_debug = (os.getenv("APP_DEBUG") or "").strip().lower() in ("1", "true", "yes", "on")
|
||||||
|
|||||||
@@ -101,20 +101,35 @@ def prune_backups(*, keep_days: int | None = None) -> int:
|
|||||||
return removed
|
return removed
|
||||||
|
|
||||||
|
|
||||||
def create_backup(*, root_dir: str, env_path: str, db_path: str, label: str = "manual") -> dict[str, Any]:
|
def create_backup(
|
||||||
|
*,
|
||||||
|
root_dir: str,
|
||||||
|
env_path: str,
|
||||||
|
db_path: str,
|
||||||
|
label: str = "manual",
|
||||||
|
username: str | None = None,
|
||||||
|
password: str | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
with _LOCK:
|
with _LOCK:
|
||||||
now = beijing_now()
|
now = beijing_now()
|
||||||
name = f"crypto_okx_hub_backup_{now.strftime('%Y%m%d_%H%M%S')}.tar.gz"
|
name = f"crypto_okx_hub_backup_{now.strftime('%Y%m%d_%H%M%S')}.tar.gz"
|
||||||
out_path = os.path.join(backup_dir(), name)
|
out_path = os.path.join(backup_dir(), name)
|
||||||
|
u = (username if username is not None else os.getenv("HUB_USERNAME") or "admin").strip() or "admin"
|
||||||
|
p = password if password is not None else (os.getenv("HUB_PASSWORD") or "admin123")
|
||||||
|
if p is None:
|
||||||
|
p = "admin123"
|
||||||
manifest = {
|
manifest = {
|
||||||
"kind": "crypto_okx_hub",
|
"kind": "crypto_okx_hub",
|
||||||
"created_bj": now.strftime("%Y-%m-%d %H:%M:%S"),
|
"created_bj": now.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
"label": (label or "manual").strip() or "manual",
|
"label": (label or "manual").strip() or "manual",
|
||||||
"includes": [".env", "database"],
|
"includes": [".env", "database", "hub_login"],
|
||||||
|
"hub_username": u,
|
||||||
}
|
}
|
||||||
tmp_fd, tmp_path = tempfile.mkstemp(suffix=".tar.gz", prefix="hub_bak_")
|
tmp_fd, tmp_path = tempfile.mkstemp(suffix=".tar.gz", prefix="hub_bak_")
|
||||||
os.close(tmp_fd)
|
os.close(tmp_fd)
|
||||||
|
env_snap = ""
|
||||||
try:
|
try:
|
||||||
|
env_snap = _snapshot_env_with_login(env_path, u, str(p))
|
||||||
with tarfile.open(tmp_path, "w:gz") as tar:
|
with tarfile.open(tmp_path, "w:gz") as tar:
|
||||||
manifest_path = tmp_path + ".manifest.json"
|
manifest_path = tmp_path + ".manifest.json"
|
||||||
with open(manifest_path, "w", encoding="utf-8") as f:
|
with open(manifest_path, "w", encoding="utf-8") as f:
|
||||||
@@ -126,8 +141,8 @@ def create_backup(*, root_dir: str, env_path: str, db_path: str, label: str = "m
|
|||||||
os.remove(manifest_path)
|
os.remove(manifest_path)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
if env_path and os.path.isfile(env_path):
|
if env_snap and os.path.isfile(env_snap):
|
||||||
tar.add(env_path, arcname=".env")
|
tar.add(env_snap, arcname=".env")
|
||||||
if db_path and os.path.isfile(db_path):
|
if db_path and os.path.isfile(db_path):
|
||||||
tar.add(db_path, arcname="database/" + os.path.basename(db_path))
|
tar.add(db_path, arcname="database/" + os.path.basename(db_path))
|
||||||
shutil.move(tmp_path, out_path)
|
shutil.move(tmp_path, out_path)
|
||||||
@@ -137,6 +152,12 @@ def create_backup(*, root_dir: str, env_path: str, db_path: str, label: str = "m
|
|||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
raise
|
raise
|
||||||
|
finally:
|
||||||
|
if env_snap:
|
||||||
|
try:
|
||||||
|
os.remove(env_snap)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
prune_backups()
|
prune_backups()
|
||||||
st = os.stat(out_path)
|
st = os.stat(out_path)
|
||||||
return {
|
return {
|
||||||
@@ -149,6 +170,21 @@ def create_backup(*, root_dir: str, env_path: str, db_path: str, label: str = "m
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _snapshot_env_with_login(env_path: str, username: str, password: str) -> str:
|
||||||
|
"""复制 .env 并写入当前中控登录账号,避免包内缺 HUB_USERNAME/PASSWORD."""
|
||||||
|
fd, snap = tempfile.mkstemp(suffix=".env", prefix="hub_env_")
|
||||||
|
os.close(fd)
|
||||||
|
if env_path and os.path.isfile(env_path):
|
||||||
|
shutil.copy2(env_path, snap)
|
||||||
|
else:
|
||||||
|
with open(snap, "w", encoding="utf-8") as f:
|
||||||
|
f.write("")
|
||||||
|
from lib.env_lib import apply_env_updates
|
||||||
|
|
||||||
|
apply_env_updates(snap, {"HUB_USERNAME": username, "HUB_PASSWORD": password})
|
||||||
|
return snap
|
||||||
|
|
||||||
|
|
||||||
def restore_backup(name: str, *, root_dir: str, env_path: str, db_path: str) -> dict[str, Any]:
|
def restore_backup(name: str, *, root_dir: str, env_path: str, db_path: str) -> dict[str, Any]:
|
||||||
path = backup_path(name)
|
path = backup_path(name)
|
||||||
if not path or not os.path.isfile(path):
|
if not path or not os.path.isfile(path):
|
||||||
@@ -168,10 +204,23 @@ def restore_backup(name: str, *, root_dir: str, env_path: str, db_path: str) ->
|
|||||||
os.makedirs(os.path.dirname(os.path.abspath(db_path)) or ".", exist_ok=True)
|
os.makedirs(os.path.dirname(os.path.abspath(db_path)) or ".", exist_ok=True)
|
||||||
shutil.copy2(src, db_path)
|
shutil.copy2(src, db_path)
|
||||||
break
|
break
|
||||||
|
restored_user = ""
|
||||||
|
restored_password = ""
|
||||||
|
if env_path and os.path.isfile(env_path):
|
||||||
|
from lib.env_lib import env_get
|
||||||
|
|
||||||
|
restored_user = env_get(env_path, "HUB_USERNAME", "")
|
||||||
|
restored_password = env_get(env_path, "HUB_PASSWORD", "")
|
||||||
|
if restored_user:
|
||||||
|
os.environ["HUB_USERNAME"] = restored_user
|
||||||
|
if restored_password:
|
||||||
|
os.environ["HUB_PASSWORD"] = restored_password
|
||||||
return {
|
return {
|
||||||
"ok": True,
|
"ok": True,
|
||||||
"msg": "已恢复 .env / 数据库,建议重启中控进程使配置完全生效",
|
"msg": "已恢复 .env / 数据库 / 登录账号,建议重启中控进程使配置完全生效",
|
||||||
"name": os.path.basename(path),
|
"name": os.path.basename(path),
|
||||||
|
"hub_username": restored_user,
|
||||||
|
"hub_password": restored_password,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -237,6 +286,8 @@ def maybe_run_auto_backup(
|
|||||||
env_path: str,
|
env_path: str,
|
||||||
db_path: str,
|
db_path: str,
|
||||||
hour: int = 3,
|
hour: int = 3,
|
||||||
|
username: str | None = None,
|
||||||
|
password: str | None = None,
|
||||||
) -> dict[str, Any] | None:
|
) -> dict[str, Any] | None:
|
||||||
global _LAST_AUTO_DAY
|
global _LAST_AUTO_DAY
|
||||||
now = beijing_now()
|
now = beijing_now()
|
||||||
@@ -255,6 +306,8 @@ def maybe_run_auto_backup(
|
|||||||
env_path=env_path,
|
env_path=env_path,
|
||||||
db_path=db_path,
|
db_path=db_path,
|
||||||
label="auto",
|
label="auto",
|
||||||
|
username=username,
|
||||||
|
password=password,
|
||||||
)
|
)
|
||||||
_LAST_AUTO_DAY = day
|
_LAST_AUTO_DAY = day
|
||||||
return result
|
return result
|
||||||
@@ -266,15 +319,24 @@ def start_backup_scheduler(
|
|||||||
env_path: str,
|
env_path: str,
|
||||||
db_path_fn: Callable[[], str],
|
db_path_fn: Callable[[], str],
|
||||||
hour: int = 3,
|
hour: int = 3,
|
||||||
|
get_credentials: Callable[[], tuple[str, str]] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
def loop() -> None:
|
def loop() -> None:
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
u, p = ("", "")
|
||||||
|
if get_credentials:
|
||||||
|
try:
|
||||||
|
u, p = get_credentials()
|
||||||
|
except Exception:
|
||||||
|
u, p = "", ""
|
||||||
maybe_run_auto_backup(
|
maybe_run_auto_backup(
|
||||||
root_dir=root_dir,
|
root_dir=root_dir,
|
||||||
env_path=env_path,
|
env_path=env_path,
|
||||||
db_path=db_path_fn(),
|
db_path=db_path_fn(),
|
||||||
hour=hour,
|
hour=hour,
|
||||||
|
username=u or None,
|
||||||
|
password=p if p != "" else None,
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -169,11 +169,16 @@ a:hover { color: #7dd3fc; }
|
|||||||
.nav-spot--flat .nav-spot-last,
|
.nav-spot--flat .nav-spot-last,
|
||||||
.nav-spot--flat .nav-spot-chg,
|
.nav-spot--flat .nav-spot-chg,
|
||||||
.nav-spot--flat .nav-spot-pct { color: #9aa3bf; }
|
.nav-spot--flat .nav-spot-pct { color: #9aa3bf; }
|
||||||
.topbar-logout {
|
.topbar-actions {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 16px;
|
top: 16px;
|
||||||
right: 22px;
|
right: 22px;
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.topbar-logout {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-size: 0.82rem;
|
font-size: 0.82rem;
|
||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
@@ -181,6 +186,39 @@ a:hover { color: #7dd3fc; }
|
|||||||
.topbar-logout:hover {
|
.topbar-logout:hover {
|
||||||
color: #e8f1ff;
|
color: #e8f1ff;
|
||||||
}
|
}
|
||||||
|
.topbar-install {
|
||||||
|
appearance: none;
|
||||||
|
border: 1px solid #304164;
|
||||||
|
background: #151a2a;
|
||||||
|
color: #dbe4ff;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
.topbar-install:hover {
|
||||||
|
color: #ffffff;
|
||||||
|
border-color: #3d5278;
|
||||||
|
}
|
||||||
|
.topbar-install-hint {
|
||||||
|
position: fixed;
|
||||||
|
top: 46px;
|
||||||
|
right: 22px;
|
||||||
|
z-index: 20;
|
||||||
|
margin: 0;
|
||||||
|
max-width: min(360px, calc(100vw - 40px));
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
@media (display-mode: standalone) {
|
||||||
|
.topbar-install,
|
||||||
|
.topbar-install-hint {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@media (max-width: 1100px) {
|
@media (max-width: 1100px) {
|
||||||
.topbar-row {
|
.topbar-row {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
(function (global) {
|
||||||
|
var deferredPrompt = null;
|
||||||
|
|
||||||
|
function isStandalone() {
|
||||||
|
return (
|
||||||
|
(global.matchMedia && global.matchMedia("(display-mode: standalone)").matches) ||
|
||||||
|
global.navigator.standalone === true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function btn() {
|
||||||
|
return document.getElementById("hub-install-btn");
|
||||||
|
}
|
||||||
|
|
||||||
|
function setHint(msg) {
|
||||||
|
var el = document.getElementById("hub-install-hint");
|
||||||
|
if (el) el.textContent = msg || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function showInstall() {
|
||||||
|
var el = btn();
|
||||||
|
if (!el || isStandalone()) return;
|
||||||
|
el.hidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideInstall() {
|
||||||
|
var el = btn();
|
||||||
|
if (el) el.hidden = true;
|
||||||
|
setHint("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadShortcut() {
|
||||||
|
var origin = global.location.origin;
|
||||||
|
var body = "[InternetShortcut]\r\nURL=" + origin + "/\r\n";
|
||||||
|
var blob = new Blob([body], { type: "application/internet-shortcut" });
|
||||||
|
var a = document.createElement("a");
|
||||||
|
a.href = URL.createObjectURL(blob);
|
||||||
|
a.download = "期权中控.url";
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
setTimeout(function () {
|
||||||
|
URL.revokeObjectURL(a.href);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onInstallClick() {
|
||||||
|
setHint("");
|
||||||
|
if (deferredPrompt) {
|
||||||
|
deferredPrompt.prompt();
|
||||||
|
try {
|
||||||
|
var choice = await deferredPrompt.userChoice;
|
||||||
|
deferredPrompt = null;
|
||||||
|
if (choice && choice.outcome === "accepted") {
|
||||||
|
hideInstall();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
deferredPrompt = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
downloadShortcut();
|
||||||
|
setHint("已下载快捷方式,拖到桌面即可。Chrome / Edge 也可在地址栏右侧点「安装」。");
|
||||||
|
}
|
||||||
|
|
||||||
|
function boot() {
|
||||||
|
if (isStandalone()) {
|
||||||
|
hideInstall();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showInstall();
|
||||||
|
global.addEventListener("beforeinstallprompt", function (ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
deferredPrompt = ev;
|
||||||
|
showInstall();
|
||||||
|
});
|
||||||
|
global.addEventListener("appinstalled", function () {
|
||||||
|
deferredPrompt = null;
|
||||||
|
hideInstall();
|
||||||
|
});
|
||||||
|
var el = btn();
|
||||||
|
if (el) el.addEventListener("click", onInstallClick);
|
||||||
|
if ("serviceWorker" in navigator) {
|
||||||
|
navigator.serviceWorker.register("/sw.js", { scope: "/" }).catch(function () {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global.HubPwa = { boot: boot };
|
||||||
|
})(window);
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 281 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 378 B |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||||
|
<rect width="512" height="512" rx="112" fill="#0b0d14"/>
|
||||||
|
<rect x="36" y="36" width="440" height="440" rx="88" fill="#121828" stroke="#ffffff" stroke-width="10"/>
|
||||||
|
<circle cx="256" cy="236" r="108" fill="none" stroke="#4285f4" stroke-width="22"/>
|
||||||
|
<path d="M196 176 L316 296 M316 176 L196 296" stroke="#ffffff" stroke-width="28" stroke-linecap="round"/>
|
||||||
|
<rect x="186" y="392" width="140" height="18" rx="9" fill="#7b42ff"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 508 B |
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"name": "加密货币|OKX 期权中控",
|
||||||
|
"short_name": "期权中控",
|
||||||
|
"description": "多机期权持仓总览与工作台",
|
||||||
|
"id": "/",
|
||||||
|
"start_url": "/",
|
||||||
|
"scope": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#070b14",
|
||||||
|
"theme_color": "#070b14",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/static/icons/icon-192.png?v=1",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/static/icons/icon-512.png?v=1",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/static/icons/icon-192-maskable.png?v=1",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/static/icons/icon-512-maskable.png?v=1",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/* 期权中控 PWA:仅占位使可安装,请求一律走网络 */
|
||||||
|
self.addEventListener("install", function (event) {
|
||||||
|
event.waitUntil(self.skipWaiting());
|
||||||
|
});
|
||||||
|
self.addEventListener("activate", function (event) {
|
||||||
|
event.waitUntil(self.clients.claim());
|
||||||
|
});
|
||||||
@@ -4,12 +4,23 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{% block title %}加密货币|OKX 期权中控{% endblock %}</title>
|
<title>{% block title %}加密货币|OKX 期权中控{% endblock %}</title>
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='hub.css') }}?v=36">
|
<meta name="theme-color" content="#070b14">
|
||||||
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="期权中控">
|
||||||
|
<link rel="icon" href="{{ url_for('static', filename='icons/favicon.ico') }}?v=1" sizes="any">
|
||||||
|
<link rel="apple-touch-icon" href="{{ url_for('static', filename='icons/apple-touch-icon.png') }}?v=1">
|
||||||
|
<link rel="manifest" href="/manifest.webmanifest">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='hub.css') }}?v=37">
|
||||||
</head>
|
</head>
|
||||||
<body class="{% if request.path.startswith('/login') %}page-login{% elif request.path.startswith('/desk') %}page-desk{% elif request.path.startswith('/stats') %}page-stats{% elif request.path.startswith('/instances') %}page-instances{% elif request.path.startswith('/settings') %}page-settings{% elif request.path.startswith('/cond_orders') %}page-cond-orders{% elif request.path == '/' %}page-dashboard{% endif %}">
|
<body class="{% if request.path.startswith('/login') %}page-login{% elif request.path.startswith('/desk') %}page-desk{% elif request.path.startswith('/stats') %}page-stats{% elif request.path.startswith('/instances') %}page-instances{% elif request.path.startswith('/settings') %}page-settings{% elif request.path.startswith('/cond_orders') %}page-cond-orders{% elif request.path == '/' %}page-dashboard{% endif %}">
|
||||||
|
<div class="topbar-actions">
|
||||||
|
<button type="button" class="topbar-install" id="hub-install-btn" hidden>安装到桌面</button>
|
||||||
{% if session.get('logged_in') %}
|
{% if session.get('logged_in') %}
|
||||||
<a href="/logout" class="topbar-logout">退出</a>
|
<a href="/logout" class="topbar-logout">退出</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<p class="topbar-install-hint" id="hub-install-hint"></p>
|
||||||
<main class="main">
|
<main class="main">
|
||||||
{% if session.get('logged_in') %}
|
{% if session.get('logged_in') %}
|
||||||
<a href="/" class="topbar-brand">加密货币|OKX 期权中控</a>
|
<a href="/" class="topbar-brand">加密货币|OKX 期权中控</a>
|
||||||
@@ -54,5 +65,7 @@
|
|||||||
{% if session.get('logged_in') and not request.path.startswith('/desk') %}
|
{% if session.get('logged_in') and not request.path.startswith('/desk') %}
|
||||||
<script>if (window.HubQuotes) HubQuotes.boot();</script>
|
<script>if (window.HubQuotes) HubQuotes.boot();</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<script src="{{ url_for('static', filename='hub_pwa.js') }}?v=1"></script>
|
||||||
|
<script>if (window.HubPwa) HubPwa.boot();</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -103,7 +103,7 @@
|
|||||||
<div class="backup-block">
|
<div class="backup-block">
|
||||||
<div class="backup-block-head">
|
<div class="backup-block-head">
|
||||||
<h3>中控备份</h3>
|
<h3>中控备份</h3>
|
||||||
<p class="muted field-hint">目录 <code id="hub-backup-dir">/root/crypto_okx_hub_backups</code> · 含 <code>.env</code> 与 <code>hub.db</code>。换机时先上传家里下载的包,再点恢复。</p>
|
<p class="muted field-hint">目录 <code id="hub-backup-dir">/root/crypto_okx_hub_backups</code> · 含 <code>.env</code>、<code>hub.db</code>、当前登录用户名和密码。换机时先上传家里下载的包,再点恢复。</p>
|
||||||
<div class="settings-actions">
|
<div class="settings-actions">
|
||||||
<button type="button" class="settings-primary" id="hub-backup-create">立即备份</button>
|
<button type="button" class="settings-primary" id="hub-backup-create">立即备份</button>
|
||||||
<button type="button" class="btn-secondary" id="hub-backup-refresh">刷新</button>
|
<button type="button" class="btn-secondary" id="hub-backup-refresh">刷新</button>
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="{{ url_for('static', filename='settings_backup.js') }}?v=3"></script>
|
<script src="{{ url_for('static', filename='settings_backup.js') }}?v=4"></script>
|
||||||
<script src="{{ url_for('static', filename='settings_update.js') }}?v=5"></script>
|
<script src="{{ url_for('static', filename='settings_update.js') }}?v=5"></script>
|
||||||
<script>
|
<script>
|
||||||
HubSettingsBackup.boot();
|
HubSettingsBackup.boot();
|
||||||
|
|||||||