78 lines
4.9 KiB
HTML
78 lines
4.9 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}中转隧道 - RustProxyAdmin{% endblock %}
|
||
{% block page_title %}中转隧道{% endblock %}
|
||
{% block page_desc %}管理中转隧道条目{% endblock %}
|
||
{% block content %}
|
||
<div class="toolbar">
|
||
<input id="f-q" placeholder="搜索名称 / 地址 / 备注" style="width:220px">
|
||
<span id="common-filters"></span>
|
||
<button class="btn" onclick="__entryPage.page=1;__entryPage.reload()">筛选</button>
|
||
<button class="btn btn-primary" onclick="__entryPage.openForm()">新建隧道</button>
|
||
</div>
|
||
<table class="tbl"><thead><tr id="thead"></tr></thead><tbody id="tbody"></tbody></table>
|
||
<div id="pager"></div>
|
||
<div class="batchbar" id="batchbar" style="display:none"></div>
|
||
<script src="/static/js/entries.js"></script>
|
||
<script>
|
||
(async () => {
|
||
await loadGT();
|
||
document.getElementById('common-filters').innerHTML = commonFiltersHtml();
|
||
document.getElementById('batchbar').innerHTML = batchBarHtml();
|
||
const L = initEntryPage({
|
||
runtimeBadge: true,
|
||
api: '/api/tunnel', etype: 'tunnel',
|
||
columns: [
|
||
{ label: '名称', cell: r => `<b>${esc(r.name)}</b>${r.remark ? `<div class="muted">${esc(r.remark)}</div>` : ''}` },
|
||
{ label: '节点', cell: r => esc(r.node_name || r.node_id) },
|
||
{ label: '模式', cell: r => `<span class="badge">${esc(r.mode)}</span>${r.tls_enabled ? ' <span class="badge ok">TLS</span>' : ''}` },
|
||
{ label: '本地监听', cell: r => `<span class="mono">${esc(r.local_addr)}</span>` },
|
||
{ label: '远端', cell: r => `<span class="mono">${esc(r.remote_addr)}</span>` },
|
||
{ label: '分组', cell: r => groupChip(r.group) },
|
||
{ label: '标签', cell: r => tagChips(r.tags) },
|
||
{ label: '配额', cell: quotaCell },
|
||
{ label: '到期', cell: expireCell },
|
||
{ label: '状态', cell: r => badgeFor(r.enabled, r.disabled_reason) },
|
||
{ label: '运行', cell: r => `<span data-rt="tunnel:${r.id}">-</span>` },
|
||
],
|
||
formHtml(item, isNew) {
|
||
const v = k => item ? (item[k] ?? '') : '';
|
||
return `
|
||
<div class="form-grid">
|
||
<div class="form-row"><label>名称 *</label><input id="fm-name" value="${esc(v('name'))}"></div>
|
||
<div class="form-row"><label>节点 *</label>${nodeSelectHtml('fm-node', item ? item.node_id : 1)}</div>
|
||
<div class="form-row"><label>模式 *</label><select id="fm-mode">${['tcp','ws'].map(p => `<option ${v('mode') === p ? 'selected' : ''}>${p}</option>`).join('')}</select></div>
|
||
<div class="form-row"><label>分组</label>${groupSelectHtml('fm-group', item ? item.group_id : null)}</div>
|
||
<div class="form-row"><label>本地监听地址 *</label><input id="fm-local" value="${esc(v('local_addr'))}" placeholder="0.0.0.0:9000"></div>
|
||
<div class="form-row"><label>远端地址 *</label><input id="fm-remote" value="${esc(v('remote_addr'))}" placeholder="1.2.3.4:9000"></div>
|
||
<div class="form-row"><label>TLS</label><select id="fm-tls"><option value="0">关闭</option><option value="1" ${item && item.tls_enabled ? 'selected' : ''}>开启</option></select></div>
|
||
<div class="form-row"><label>心跳(秒)</label><input id="fm-hb" type="number" min="5" max="300" value="${item ? v('heartbeat_sec') : '30'}"></div>
|
||
<div class="form-row"><label>预共享密钥 PSK</label><input id="fm-psk" value="${esc(v('psk'))}" placeholder="可选"></div>
|
||
<div class="form-row"><label>有效期</label>${expirePresetHtml(isNew ? 'permanent' : null)}</div>
|
||
<div class="form-row"><label>备注</label><input id="fm-remark" value="${esc(v('remark'))}"></div>
|
||
</div>
|
||
<div class="form-row"><label>证书 PEM(TLS 用,可选)</label><textarea id="fm-cert" rows="3" placeholder="-----BEGIN CERTIFICATE-----">${esc(v('cert_pem'))}</textarea></div>
|
||
<div class="form-row"><label>私钥 PEM(TLS 用,可选)</label><textarea id="fm-key" rows="3" placeholder="-----BEGIN PRIVATE KEY-----">${esc(v('key_pem'))}</textarea></div>
|
||
<div class="form-row"><label>标签</label><div>${tagCheckHtml(item ? item.tags.map(t => t.id) : [])}</div></div>`;
|
||
},
|
||
formData() {
|
||
const val = id => document.getElementById(id).value.trim();
|
||
return {
|
||
name: val('fm-name'), node_id: +document.getElementById('fm-node').value,
|
||
mode: document.getElementById('fm-mode').value,
|
||
local_addr: val('fm-local'), remote_addr: val('fm-remote'),
|
||
tls_enabled: document.getElementById('fm-tls').value === '1',
|
||
cert_pem: val('fm-cert') || null, key_pem: val('fm-key') || null,
|
||
psk: val('fm-psk') || null,
|
||
heartbeat_sec: +document.getElementById('fm-hb').value || 30,
|
||
remark: val('fm-remark') || null,
|
||
group_id: document.getElementById('fm-group').value ? +document.getElementById('fm-group').value : null,
|
||
expire_preset: document.getElementById('fm-preset').value,
|
||
tag_ids: readTagIds(),
|
||
};
|
||
},
|
||
});
|
||
L.reload().catch(e => toast(e.message, true));
|
||
})();
|
||
</script>
|
||
{% endblock %}
|