重做实例页 UI:列表卡片为主,添加收起,Token 默认隐藏,风格对齐系统设置。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+181
-1
@@ -329,7 +329,6 @@ button, .btn {
|
|||||||
align-items: end;
|
align-items: end;
|
||||||
}
|
}
|
||||||
.check { display: flex !important; align-items: center; gap: 8px; }
|
.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; }
|
|
||||||
.update-version {
|
.update-version {
|
||||||
font-size: 0.82rem;
|
font-size: 0.82rem;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
@@ -345,6 +344,187 @@ button, .btn {
|
|||||||
.update-status.ok { color: var(--ok); }
|
.update-status.ok { color: var(--ok); }
|
||||||
.update-status.err { color: var(--bad); }
|
.update-status.err { color: var(--bad); }
|
||||||
.update-table td { vertical-align: middle; }
|
.update-table td { vertical-align: middle; }
|
||||||
|
|
||||||
|
/* 实例页 */
|
||||||
|
.inst-page-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.inst-page-head-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.inst-add-trigger {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.inst-add-check {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
.inst-shell {
|
||||||
|
max-width: 920px;
|
||||||
|
}
|
||||||
|
.inst-add-panel {
|
||||||
|
display: none;
|
||||||
|
border-bottom: 1px solid rgba(56, 189, 248, 0.12);
|
||||||
|
}
|
||||||
|
#inst-add-toggle:checked ~ .inst-shell .inst-add-panel {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.inst-list-head {
|
||||||
|
border-bottom: 1px solid rgba(56, 189, 248, 0.12);
|
||||||
|
}
|
||||||
|
.inst-card-list {
|
||||||
|
padding: 16px 18px 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.inst-empty {
|
||||||
|
margin: 8px 4px 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
}
|
||||||
|
.inst-card {
|
||||||
|
border: 1px solid rgba(56, 189, 248, 0.16);
|
||||||
|
border-radius: 14px;
|
||||||
|
background: rgba(5, 12, 22, 0.45);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.inst-card[open] {
|
||||||
|
border-color: rgba(56, 189, 248, 0.35);
|
||||||
|
box-shadow: 0 0 0 1px rgba(34, 211, 238, 0.08);
|
||||||
|
}
|
||||||
|
.inst-card-summary {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.inst-card-summary::-webkit-details-marker { display: none; }
|
||||||
|
.inst-card-main { flex: 1; min-width: 0; }
|
||||||
|
.inst-card-title-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.inst-card-name {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 650;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
.inst-card-url {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
word-break: break-all;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
.inst-card-aside {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
.inst-card-chevron {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-right: 2px solid var(--muted);
|
||||||
|
border-bottom: 2px solid var(--muted);
|
||||||
|
transform: rotate(45deg);
|
||||||
|
transition: transform 0.15s ease;
|
||||||
|
margin-top: -4px;
|
||||||
|
}
|
||||||
|
.inst-card[open] .inst-card-chevron {
|
||||||
|
transform: rotate(225deg);
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
.inst-pill {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
.inst-pill--on {
|
||||||
|
color: #86efac;
|
||||||
|
background: rgba(22, 101, 52, 0.35);
|
||||||
|
border: 1px solid rgba(74, 222, 128, 0.35);
|
||||||
|
}
|
||||||
|
.inst-pill--off {
|
||||||
|
color: #fda4af;
|
||||||
|
background: rgba(127, 29, 29, 0.35);
|
||||||
|
border: 1px solid rgba(251, 113, 133, 0.35);
|
||||||
|
}
|
||||||
|
.inst-pill--tag {
|
||||||
|
color: #bae6fd;
|
||||||
|
background: rgba(14, 116, 144, 0.28);
|
||||||
|
border: 1px solid rgba(56, 189, 248, 0.28);
|
||||||
|
max-width: 12rem;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.inst-card-body {
|
||||||
|
padding: 0 8px 14px;
|
||||||
|
border-top: 1px solid rgba(56, 189, 248, 0.1);
|
||||||
|
}
|
||||||
|
.inst-card-body .inst-form {
|
||||||
|
padding-top: 14px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
.inst-card-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
.inst-card-actions .btn,
|
||||||
|
.inst-card-actions .settings-primary {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
.inst-delete-form {
|
||||||
|
padding: 0 12px 4px;
|
||||||
|
}
|
||||||
|
.inst-token-wrap {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: stretch;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
.inst-token-wrap .inst-token-input {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.inst-token-wrap .inst-token-toggle {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
align-self: center;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.inst-page-head { align-items: flex-start; }
|
||||||
|
.settings-row-2 { grid-template-columns: 1fr; }
|
||||||
|
.inst-card-summary { flex-direction: column; align-items: stretch; gap: 8px; }
|
||||||
|
.inst-card-aside { justify-content: space-between; }
|
||||||
|
}
|
||||||
.overview-table-wrap {
|
.overview-table-wrap {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
(function () {
|
||||||
|
function fmtVer(v) {
|
||||||
|
if (!v || !v.online) return v && v.msg ? String(v.msg) : "离线";
|
||||||
|
const short = v.short_sha || (v.sha ? String(v.sha).slice(0, 7) : "");
|
||||||
|
const branch = v.branch ? v.branch + " · " : "";
|
||||||
|
if (!short) return "未知版本";
|
||||||
|
return branch + short + (v.dirty ? " (dirty)" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshVersion(card) {
|
||||||
|
const iid = card.getAttribute("data-id");
|
||||||
|
const verEl = card.querySelector("[data-inst-version]");
|
||||||
|
if (!iid || !verEl) return;
|
||||||
|
verEl.textContent = "…";
|
||||||
|
verEl.className = "update-version muted";
|
||||||
|
try {
|
||||||
|
const r = await fetch("/api/instances/" + iid + "/version", {
|
||||||
|
credentials: "same-origin",
|
||||||
|
headers: { Accept: "application/json" },
|
||||||
|
});
|
||||||
|
const v = await r.json();
|
||||||
|
if (v && v.online) {
|
||||||
|
verEl.textContent = fmtVer(v);
|
||||||
|
verEl.className = "update-version online";
|
||||||
|
} else {
|
||||||
|
verEl.textContent = (v && v.msg) || "离线";
|
||||||
|
verEl.className = "update-version offline";
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
verEl.textContent = "探测失败";
|
||||||
|
verEl.className = "update-version offline";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindTokenToggles(root) {
|
||||||
|
(root || document).querySelectorAll(".inst-token-toggle").forEach(function (btn) {
|
||||||
|
if (btn.dataset.bound) return;
|
||||||
|
btn.dataset.bound = "1";
|
||||||
|
btn.addEventListener("click", function () {
|
||||||
|
const wrap = btn.closest(".inst-token-wrap");
|
||||||
|
const input = wrap && wrap.querySelector(".inst-token-input");
|
||||||
|
if (!input) return;
|
||||||
|
const show = input.type === "password";
|
||||||
|
input.type = show ? "text" : "password";
|
||||||
|
btn.textContent = show ? "隐藏" : "显示";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function boot() {
|
||||||
|
bindTokenToggles(document);
|
||||||
|
document.querySelectorAll(".inst-card[data-id]").forEach(function (card) {
|
||||||
|
refreshVersion(card);
|
||||||
|
const btn = card.querySelector("[data-inst-refresh-ver]");
|
||||||
|
if (btn) {
|
||||||
|
btn.addEventListener("click", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
refreshVersion(card);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === "loading") {
|
||||||
|
document.addEventListener("DOMContentLoaded", boot);
|
||||||
|
} else {
|
||||||
|
boot();
|
||||||
|
}
|
||||||
|
})();
|
||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
<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 %}期权中控{% endblock %}</title>
|
<title>{% block title %}期权中控{% endblock %}</title>
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='hub.css') }}?v=11">
|
<link rel="stylesheet" href="{{ url_for('static', filename='hub.css') }}?v=12">
|
||||||
</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 == '/' %}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 == '/' %}page-dashboard{% endif %}">
|
||||||
{% if session.get('logged_in') %}
|
{% if session.get('logged_in') %}
|
||||||
|
|||||||
+143
-47
@@ -1,55 +1,151 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block title %}实例 · 期权中控{% endblock %}
|
{% block title %}实例 · 期权中控{% endblock %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<section class="page-head"><h1>客户端实例</h1></section>
|
{% set open_add = (not instances) or ((request.args.get('add') or '') == '1') %}
|
||||||
|
<section class="page-head settings-head inst-page-head">
|
||||||
<section class="panel">
|
<div>
|
||||||
<h2>添加实例</h2>
|
<p class="settings-eyebrow">Instances</p>
|
||||||
<form method="post" class="form-grid">
|
<h1>客户端实例</h1>
|
||||||
<input type="hidden" name="action" value="create">
|
<p class="muted settings-lead">登记各交易机地址与 Bridge Token;远程更新请到系统设置。</p>
|
||||||
<label>名称 <input name="name" required placeholder="ETH-主策略"></label>
|
</div>
|
||||||
<label>公网 URL <input name="base_url" required placeholder="https://okx-eth.example.com"></label>
|
<div class="inst-page-head-actions">
|
||||||
<label>Bridge Token <input name="bridge_token" required placeholder="与客户端 HUB_BRIDGE_TOKEN 一致"></label>
|
<a class="btn btn-secondary" href="/settings?tab=update">系统更新</a>
|
||||||
<label>Embed 路径 <input name="embed_path" value="/embed"></label>
|
<label for="inst-add-toggle" class="settings-primary inst-add-trigger">添加实例</label>
|
||||||
<label>标签 <input name="tags" placeholder="eth,sim"></label>
|
</div>
|
||||||
<label class="check"><input type="checkbox" name="enabled" value="1" checked> 启用拉取</label>
|
|
||||||
<button type="submit">添加</button>
|
|
||||||
</form>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="panel">
|
<input type="checkbox" id="inst-add-toggle" class="inst-add-check"{% if open_add %} checked{% endif %}>
|
||||||
<h2>已登记 ({{ instances|length }})</h2>
|
|
||||||
{% if not instances %}
|
<section class="settings-shell panel inst-shell">
|
||||||
<p class="muted">暂无实例.先在 crypto_okx 配置 HUB_BRIDGE_TOKEN 并公网可访问.</p>
|
<div class="inst-add-panel">
|
||||||
{% endif %}
|
<div class="settings-card-head">
|
||||||
{% for inst in instances %}
|
<span class="settings-badge">New</span>
|
||||||
<details class="inst-row">
|
<h2>添加实例</h2>
|
||||||
<summary>
|
<p class="muted">Token 需与客户端 <code>HUB_BRIDGE_TOKEN</code> 一致;公网 URL 需可被中控访问。</p>
|
||||||
<strong>{{ inst.name }}</strong>
|
</div>
|
||||||
<span class="muted">{{ inst.base_url }}</span>
|
<form method="post" class="settings-form inst-form" autocomplete="off">
|
||||||
{% if inst.enabled %}启用{% else %}停用{% endif %}
|
<input type="hidden" name="action" value="create">
|
||||||
</summary>
|
<div class="settings-row-2">
|
||||||
<form method="post" class="form-grid">
|
<label class="settings-field">名称
|
||||||
<input type="hidden" name="action" value="update">
|
<input name="name" required placeholder="ETH-主策略" autocomplete="off">
|
||||||
<input type="hidden" name="id" value="{{ inst.id }}">
|
</label>
|
||||||
<label>名称 <input name="name" value="{{ inst.name }}" required></label>
|
<label class="settings-field">标签
|
||||||
<label>公网 URL <input name="base_url" value="{{ inst.base_url }}" required></label>
|
<input name="tags" placeholder="eth,sim" autocomplete="off">
|
||||||
<label>Bridge Token <input name="bridge_token" value="{{ inst.bridge_token }}" required></label>
|
</label>
|
||||||
<label>Embed 路径 <input name="embed_path" value="{{ inst.embed_path }}"></label>
|
</div>
|
||||||
<label>标签 <input name="tags" value="{{ inst.tags or '' }}"></label>
|
<label class="settings-field">公网 URL
|
||||||
<label class="check"><input type="checkbox" name="enabled" value="1" {% if inst.enabled %}checked{% endif %}> 启用</label>
|
<input name="base_url" required placeholder="https://okx-eth.example.com" autocomplete="off">
|
||||||
<button type="submit">保存</button>
|
</label>
|
||||||
|
<div class="settings-row-2">
|
||||||
|
<label class="settings-field">Bridge Token
|
||||||
|
<span class="inst-token-wrap">
|
||||||
|
<input type="password" name="bridge_token" required placeholder="与客户端 HUB_BRIDGE_TOKEN 一致" autocomplete="new-password" class="inst-token-input">
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary inst-token-toggle" aria-label="显示 Token">显示</button>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label class="settings-field">Embed 路径
|
||||||
|
<input name="embed_path" value="/embed" autocomplete="off">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<label class="settings-switch">
|
||||||
|
<input type="checkbox" name="enabled" value="1" checked>
|
||||||
|
<span class="settings-switch-ui" aria-hidden="true"></span>
|
||||||
|
<span class="settings-switch-text">
|
||||||
|
<strong>启用拉取</strong>
|
||||||
|
<small>关闭后总览不再巡检此机</small>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div class="settings-actions">
|
||||||
|
<button type="submit" class="settings-primary">添加</button>
|
||||||
|
<label for="inst-add-toggle" class="btn btn-secondary">取消</label>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<form method="post" class="inline" onsubmit="return confirm('确认删除?')">
|
</div>
|
||||||
<input type="hidden" name="action" value="delete">
|
|
||||||
<input type="hidden" name="id" value="{{ inst.id }}">
|
<div class="settings-card-head inst-list-head">
|
||||||
<button type="submit" class="btn-danger">删除</button>
|
<span class="settings-badge">Registry</span>
|
||||||
</form>
|
<h2>已登记 · {{ instances|length }}</h2>
|
||||||
<p>
|
<p class="muted">点击卡片展开编辑;Token 默认隐藏。</p>
|
||||||
<a class="btn" href="/desk?id={{ inst.id }}">内嵌打开</a>
|
</div>
|
||||||
<a class="btn btn-secondary" href="/settings?tab=update">系统更新</a>
|
|
||||||
</p>
|
<div class="inst-card-list">
|
||||||
</details>
|
{% if not instances %}
|
||||||
{% endfor %}
|
<p class="muted inst-empty">暂无实例。先在客户端配置 <code>HUB_BRIDGE_TOKEN</code> 并保证公网可访问,再点右上角「添加实例」。</p>
|
||||||
|
{% endif %}
|
||||||
|
{% for inst in instances %}
|
||||||
|
<details class="inst-card" data-id="{{ inst.id }}" data-name="{{ inst.name }}">
|
||||||
|
<summary class="inst-card-summary">
|
||||||
|
<div class="inst-card-main">
|
||||||
|
<div class="inst-card-title-row">
|
||||||
|
<strong class="inst-card-name">{{ inst.name }}</strong>
|
||||||
|
{% if inst.enabled %}
|
||||||
|
<span class="inst-pill inst-pill--on">启用</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="inst-pill inst-pill--off">停用</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if inst.tags %}
|
||||||
|
<span class="inst-pill inst-pill--tag">{{ inst.tags }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="inst-card-url muted">{{ inst.base_url }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="inst-card-aside">
|
||||||
|
<span class="update-version muted" data-inst-version title="点击卡片展开后可刷新">—</span>
|
||||||
|
<span class="inst-card-chevron" aria-hidden="true"></span>
|
||||||
|
</div>
|
||||||
|
</summary>
|
||||||
|
<div class="inst-card-body">
|
||||||
|
<form method="post" class="settings-form inst-form" autocomplete="off">
|
||||||
|
<input type="hidden" name="action" value="update">
|
||||||
|
<input type="hidden" name="id" value="{{ inst.id }}">
|
||||||
|
<div class="settings-row-2">
|
||||||
|
<label class="settings-field">名称
|
||||||
|
<input name="name" value="{{ inst.name }}" required autocomplete="off">
|
||||||
|
</label>
|
||||||
|
<label class="settings-field">标签
|
||||||
|
<input name="tags" value="{{ inst.tags or '' }}" autocomplete="off">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<label class="settings-field">公网 URL
|
||||||
|
<input name="base_url" value="{{ inst.base_url }}" required autocomplete="off">
|
||||||
|
</label>
|
||||||
|
<div class="settings-row-2">
|
||||||
|
<label class="settings-field">Bridge Token
|
||||||
|
<span class="inst-token-wrap">
|
||||||
|
<input type="password" name="bridge_token" value="{{ inst.bridge_token }}" required autocomplete="new-password" class="inst-token-input">
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary inst-token-toggle" aria-label="显示 Token">显示</button>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label class="settings-field">Embed 路径
|
||||||
|
<input name="embed_path" value="{{ inst.embed_path }}" autocomplete="off">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<label class="settings-switch">
|
||||||
|
<input type="checkbox" name="enabled" value="1" {% if inst.enabled %}checked{% endif %}>
|
||||||
|
<span class="settings-switch-ui" aria-hidden="true"></span>
|
||||||
|
<span class="settings-switch-text">
|
||||||
|
<strong>启用拉取</strong>
|
||||||
|
<small>关闭后总览不再巡检此机</small>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div class="inst-card-actions">
|
||||||
|
<button type="submit" class="settings-primary">保存</button>
|
||||||
|
<a class="btn" href="/desk?id={{ inst.id }}">内嵌打开</a>
|
||||||
|
<a class="btn btn-secondary" href="/settings?tab=update">系统更新</a>
|
||||||
|
<button type="button" class="btn btn-secondary btn-sm" data-inst-refresh-ver>刷新版本</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<form method="post" class="inst-delete-form" onsubmit="return confirm('确认删除「{{ inst.name }}」?')">
|
||||||
|
<input type="hidden" name="action" value="delete">
|
||||||
|
<input type="hidden" name="id" value="{{ inst.id }}">
|
||||||
|
<button type="submit" class="btn-danger btn-sm">删除实例</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% block scripts %}
|
||||||
|
<script src="{{ url_for('static', filename='instances_page.js') }}?v=1"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user