bot_host / templates /bot_proxy.html
ItsBounvy's picture
Upload 21 files
4272701 verified
Raw History Blame Contribute Delete
10.6 kB
{% extends "base.html" %}
{% block title %}{{ _("Proxy") }} · {{ bot.name }}{% endblock %}
{% block crumbs %}
<a href="/bots" class="parent">{{ _("Bots") }}</a>
<span class="sep">/</span>
<a href="/bots/{{ bot.id }}" class="parent">{{ bot.name }}</a>
<span class="sep">/</span>
<h1>{{ _("Proxy") }}</h1>
{% endblock %}
{% block content %}
<div class="page-header">
<div>
<h1>{{ _("Host proxy") }}</h1>
<div class="subtitle">{{ _("Every connection to this bot goes through the panel — one entry point, one audit trail.") }}</div>
</div>
</div>
<div class="alert alert-info" style="margin-bottom: 18px;">
<span class="ico">{{ panel_icon('shield')|safe }}</span>
<div class="alert-body">
<div class="alert-title">{{ _("Why a proxy?") }}</div>
{{ _("The bot's HF Space can stay") }} <strong>{{ _("private") }}</strong>. {{ _("External callers (Telegram webhooks, integrations, scripts) hit") }} <code>/proxy/{{ bot.slug }}/…</code> {{ _("on this panel, and we forward to the bot. Every request is logged to the audit table.") }}
</div>
</div>
<div class="grid-2">
<div class="card">
<div class="card-header">
<div>
<h2>{{ _("Public URL") }}</h2>
<div class="desc">{{ _("What external services call.") }}</div>
</div>
</div>
<div class="card-body">
<div class="detail-list">
<div class="detail-row">
<span class="label">{{ _("Telegram webhook") }}</span>
<span class="value mono" style="font-size: 12.5px;">
<span data-copy="/proxy/{{ bot.slug }}/telegram/webhook">{{ request.url.scheme }}://{{ request.url.netloc }}/proxy/{{ bot.slug }}/telegram/webhook</span>
<button type="button" class="icon-btn" style="display:inline-grid;width:24px;height:24px;margin-left:4px;" data-copy="/proxy/{{ bot.slug }}/telegram/webhook" title="{{ _("Copy") }}">{{ panel_icon('copy')|safe }}</button>
</span>
</div>
<div class="detail-row">
<span class="label">{{ _("Health endpoint") }}</span>
<span class="value mono" style="font-size: 12.5px;">
<a href="/proxy/{{ bot.slug }}/health" target="_blank">/proxy/{{ bot.slug }}/health</a>
</span>
</div>
<div class="detail-row">
<span class="label">{{ _("Catch-all") }}</span>
<span class="value mono" style="font-size: 12.5px;">
/proxy/{{ bot.slug }}/&lt;{{ _("any-path") }}&gt;
</span>
</div>
</div>
<hr>
<h3 class="form-section-title"><span class="ico">{{ panel_icon('key')|safe }}</span> {{ _("Authentication") }}</h3>
<p class="muted" style="font-size: 13px;">{{ _("Two ways to authenticate against the proxy:") }}</p>
<ul style="margin: 0; padding-left: 18px; line-height: 1.7; color: var(--text-muted); font-size: 13px;">
<li><strong>{{ _("Session cookie") }}</strong> — {{ _("for browser access. Admin or bot owner only.") }}</li>
<li><strong>{{ _("Bearer token") }}</strong> — <code>Authorization: Bearer &lt;llm_proxy_token&gt;</code> {{ _("for machine-to-machine traffic.") }}</li>
</ul>
</div>
</div>
<div class="card">
<div class="card-header">
<div>
<h2>{{ _("Health probe") }}</h2>
<div class="desc">{{ _("Round-trip through the proxy to the bot's runtime.") }}</div>
</div>
<button type="button" class="btn btn-sm btn-outline" data-proxy-test>{{ panel_icon('refresh')|safe }} {{ _("Probe") }}</button>
</div>
<div class="card-body">
<div data-proxy-test-result>
<p class="muted">{{ _("Click") }} <strong>{{ _("Probe") }}</strong> {{ _("to send a request through") }} <code>/proxy/{{ bot.slug }}/health</code>.</p>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<div>
<h2>{{ _("Upstream (Render) — legacy") }}</h2>
<div class="desc">{{ _("If your bot uses a separate Render proxy for LLM traffic, its status is shown here.") }}</div>
</div>
<a href="/bots/{{ bot.id }}?edit=1" class="btn btn-sm btn-outline">{{ panel_icon('edit')|safe }} {{ _("Edit URL") }}</a>
</div>
<div class="card-body">
{% if not bot.render_url %}
<p class="muted" style="margin: 0;">{{ _("No Render proxy URL configured. The bot either calls LLMs directly or uses the host's own proxy above.") }}</p>
{% else %}
<div class="detail-list">
<div class="detail-row">
<span class="label">{{ _("Status") }}</span>
<span class="value">
{% if health.ok %}
<span class="health-pill healthy"><span class="ico"></span> {{ _("Healthy") }} · {{ health.latency_ms }}ms · HTTP {{ health.status }}</span>
{% elif health.status %}
<span class="health-pill warn"><span class="ico"></span> HTTP {{ health.status }}</span>
{% else %}
<span class="health-pill down"><span class="ico"></span> {{ _("Unreachable") }}</span>
{% endif %}
</span>
</div>
<div class="detail-row"><span class="label">{{ _("URL") }}</span><span class="value mono" style="font-size: 12.5px;"><a href="{{ bot.render_url }}" target="_blank" rel="noopener">{{ bot.render_url }}</a></span></div>
<div class="detail-row"><span class="label">{{ _("Path probed") }}</span><span class="value mono" style="font-size: 12.5px;">{{ health.path or '—' }}</span></div>
{% if health.error %}<div class="detail-row"><span class="label">{{ _("Error") }}</span><span class="value muted" style="font-size: 12px;">{{ health.error }}</span></div>{% endif %}
</div>
{% endif %}
</div>
</div>
<div class="card">
<div class="card-header">
<div>
<h2>{{ _("Recent proxy traffic") }}</h2>
<div class="desc">{{ _("Last requests routed through") }} <code>/proxy/{{ bot.slug }}/…</code>.</div>
</div>
<a href="/admin/audit?target_type=bot&target_id={{ bot.id }}" class="btn btn-sm btn-ghost">{{ _("Open audit log") }}</a>
</div>
<div class="card-body" style="padding: 0;">
<div class="table-wrap" style="border: 0; border-radius: 0;">
<table class="table">
<thead><tr><th>{{ _("When") }}</th><th>{{ _("Method") }}</th><th>{{ _("Path") }}</th><th>{{ _("Status") }}</th><th>{{ _("Latency") }}</th><th>{{ _("User") }}</th></tr></thead>
<tbody data-proxy-audit-body>
<tr><td colspan="6" class="muted" style="text-align:center; padding: 18px;">{{ _("Loading…") }}</td></tr>
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
(async function () {
const btn = document.querySelector("[data-proxy-test]");
const out = document.querySelector("[data-proxy-test-result]");
if (btn && out) {
btn.addEventListener("click", async () => {
btn.disabled = true;
btn.innerHTML = `<span class="spinner"></span> {{ _("Probing") }}`;
out.innerHTML = `<p class="muted">{{ _("Probing through host proxy…") }}</p>`;
try {
const resp = await fetch("/proxy/{{ bot.slug }}/health", { credentials: "include" });
const text = await resp.text();
out.innerHTML = `
<div class="detail-list">
<div class="detail-row"><span class="label">{{ _("Status") }}</span><span class="value"><span class="badge status-${resp.ok ? 'success' : 'error'}">HTTP ${resp.status}</span></span></div>
<div class="detail-row"><span class="label">{{ _("Content-Type") }}</span><span class="value mono" style="font-size:12.5px;">${escapeHtml(resp.headers.get('content-type') || '—')}</span></div>
</div>
<pre class="mono" style="margin-top:10px;max-height:240px;overflow:auto;font-size:12px;">${escapeHtml(text.slice(0, 4096))}${text.length > 4096 ? '\n…' : ''}</pre>`;
} catch (e) {
out.innerHTML = `<div class="alert alert-error"><span class="ico">⚠️</span><div class="alert-body">${escapeHtml(String(e.message || e))}</div></div>`;
} finally {
btn.disabled = false;
btn.innerHTML = `{{ panel_icon('refresh')|safe }} {{ _("Probe") }}`;
}
});
}
const tbody = document.querySelector("[data-proxy-audit-body]");
if (tbody) {
try {
const resp = await fetch("/api/proxy/recent?bot_id={{ bot.id }}&limit=15", { credentials: "include" });
if (resp.ok) {
const data = await resp.json();
if (!data.events || !data.events.length) {
tbody.innerHTML = `<tr><td colspan="6" class="muted" style="text-align:center; padding: 18px;">{{ _("No proxy traffic yet.") }}</td></tr>`;
} else {
tbody.innerHTML = data.events.map(ev => {
const when = ev.created_at ? new Date(ev.created_at).toLocaleString() : "—";
const tone = ev.status >= 500 ? "error" : ev.status >= 400 ? "warn" : "success";
return `<tr>
<td class="muted" style="font-size: 12px;">${escapeHtml(when)}</td>
<td><code>${escapeHtml(ev.method || '')}</code></td>
<td class="mono" style="font-size: 12px;">${escapeHtml(ev.path || '')}</td>
<td><span class="badge status-${tone}">${ev.status}</span></td>
<td class="muted">${ev.latency_ms ?? '—'}ms</td>
<td>${escapeHtml(ev.user || '—')}</td>
</tr>`;
}).join("");
}
}
} catch (e) {
tbody.innerHTML = `<tr><td colspan="6" class="muted" style="text-align:center;">{{ _("Could not load recent traffic.") }}</td></tr>`;
}
}
})();
</script>
{% endblock %}