基于官方 nousresearch/hermes-agent 镜像的 CLI、Docker Compose 与 Web 管理界面,支持多实例部署;敏感配置与实例数据由 .gitignore 排除。 Co-authored-by: Cursor <cursoragent@cursor.com>
78 lines
3.3 KiB
HTML
78 lines
3.3 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}实例 {{ iid }} · Docker 终端 · Hermes Agent Web{% endblock %}
|
||
{% block body %}
|
||
<p><a href="{{ url_for('index') }}">← 返回实例列表</a></p>
|
||
<h2 style="margin-top:0;">实例 <code>{{ iid }}</code> · Docker 终端</h2>
|
||
<p class="muted">打开本页后会连接到该实例<strong>运行中的</strong> Hermes <code>gateway</code> 容器 shell(/opt/data)。
|
||
若容器尚未启动,请先返回列表页点「启动」;退出终端可输入 <code>exit</code>。</p>
|
||
<div style="margin:1rem 0 1.1rem;padding:0.9rem 1rem;border:1px solid var(--border);border-radius:10px;background:#111723;">
|
||
<div style="font-size:0.95rem;font-weight:600;margin-bottom:0.65rem;">常用指令</div>
|
||
<div style="display:grid;gap:0.65rem;">
|
||
<div>
|
||
<div class="muted" style="margin-bottom:0.25rem;">切换到 Codex Provider 登录</div>
|
||
<code style="display:block;padding:0.65rem 0.8rem;white-space:pre-wrap;">hermes config</code>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="term-wrap" style="height:min(75vh,620px);border:1px solid var(--border);border-radius:8px;padding:2px;background:#0c1017;"></div>
|
||
<p class="muted" style="margin-top:0.75rem;">关闭本页请先退出容器 shell。需联网加载 xterm(CDN)。</p>
|
||
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.min.css" crossorigin="anonymous" />
|
||
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.min.js" crossorigin="anonymous"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0.11.0/lib/addon-fit.min.js" crossorigin="anonymous"></script>
|
||
<script>
|
||
(function () {
|
||
var wrap = document.getElementById('term-wrap');
|
||
var term = new Terminal({
|
||
cursorBlink: true,
|
||
fontSize: 14,
|
||
fontFamily: 'ui-monospace, SF Mono, Menlo, Consolas, monospace',
|
||
theme: { background: '#0c1017', foreground: '#e7edf4' }
|
||
});
|
||
var FitCtor = (typeof FitAddon !== 'undefined' && FitAddon.FitAddon) ? FitAddon.FitAddon : FitAddon;
|
||
var fit = new FitCtor();
|
||
term.loadAddon(fit);
|
||
term.open(wrap);
|
||
fit.fit();
|
||
term.writeln('\x1b[90m正在连接 Docker 终端…\x1b[0m');
|
||
|
||
var proto = (location.protocol === 'https:') ? 'wss:' : 'ws:';
|
||
var tok = {{ ws_token | tojson }};
|
||
var ws = new WebSocket(proto + '//' + location.host + '/ws/instance/{{ iid }}/docker-pty?token=' + encodeURIComponent(tok));
|
||
ws.onopen = function () {
|
||
term.reset();
|
||
fit.fit();
|
||
sendResize();
|
||
};
|
||
ws.onmessage = function (ev) {
|
||
var d = ev.data;
|
||
if (typeof d === 'string') term.write(d);
|
||
else term.write(new Uint8Array(d));
|
||
};
|
||
ws.onerror = function () {
|
||
term.write('\r\n\x1b[31m[WebSocket 错误]\x1b[0m\r\n');
|
||
};
|
||
ws.onclose = function (ev) {
|
||
term.write('\r\n\x1b[33m[连接已关闭' + (ev && ev.code ? ' code=' + ev.code : '') + ']\x1b[0m\r\n');
|
||
};
|
||
term.onData(function (data) {
|
||
if (ws.readyState === WebSocket.OPEN) ws.send(data);
|
||
});
|
||
|
||
function sendResize() {
|
||
if (ws.readyState !== WebSocket.OPEN) return;
|
||
try {
|
||
fit.fit();
|
||
ws.send(JSON.stringify({ t: 'resize', cols: term.cols, rows: term.rows }));
|
||
} catch (e) {}
|
||
}
|
||
window.addEventListener('resize', function () {
|
||
fit.fit();
|
||
sendResize();
|
||
});
|
||
new ResizeObserver(sendResize).observe(wrap);
|
||
setTimeout(sendResize, 100);
|
||
})();
|
||
</script>
|
||
{% endblock %}
|