Files
simplex-manager/manager/templates/index.html
Jon 11e799188d Add Python manager: FastAPI backend + web UI
- main.py: FastAPI app with profile CRUD, start/stop, send message endpoints
- profiles.py: asyncio bot lifecycle using simplex-chat Python SDK
- db.py: SQLite registry tracking profiles, types, config, addresses
- templates/: Jinja2 + HTMX web UI
  - login.html: token-based auth
  - index.html: profile list with live status polling, create dialog
  - profile.html: per-bot dashboard with QR code, contacts/groups, event log, send form
- requirements.txt: fastapi, uvicorn, jinja2, simplex-chat
- start.sh: one-command startup with venv bootstrap

Bot types: echo, broadcast, support (business address), directory, deadmans
Run: cd manager && MANAGER_TOKEN=secret ./start.sh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 00:53:41 +01:00

113 lines
4.1 KiB
HTML

{% extends "base.html" %}
{% block title %}Profiles — SimpleX Manager{% endblock %}
{% block content %}
<div class="flex-between" style="margin-bottom: 24px;">
<h1 style="margin:0">Bot Profiles</h1>
<button class="btn btn-primary" onclick="document.getElementById('create-dialog').showModal()">+ New Profile</button>
</div>
{% if profiles %}
<div id="profile-list">
{% for p in profiles %}
<div class="card" id="profile-{{ p.id }}">
<div class="flex-between">
<div class="flex gap-8">
<strong>{{ p.name }}</strong>
<span class="tag">{{ p.bot_type }}</span>
<span class="badge {% if p.running %}badge-green{% else %}badge-red{% endif %}"
id="status-{{ p.id }}"
hx-get="/api/profiles/{{ p.id }}/status"
hx-trigger="every 5s"
hx-swap="none"
hx-on::after-request="updateStatus({{ p.id }}, event)">
{% if p.running %}running{% else %}stopped{% endif %}
</span>
</div>
<div class="flex gap-8">
<a href="/profile/{{ p.id }}" class="btn btn-ghost" style="padding: 6px 14px; font-size: 13px;">View</a>
<button class="btn btn-success" style="padding: 6px 14px; font-size: 13px;"
hx-post="/api/profiles/{{ p.id }}/start"
hx-headers='{"X-Token": "{{ request.cookies.get(\"token\", \"\") }}"}'
hx-swap="none"
onclick="this.textContent='Starting…'">Start</button>
<button class="btn btn-danger" style="padding: 6px 14px; font-size: 13px;"
hx-post="/api/profiles/{{ p.id }}/stop"
hx-headers='{"X-Token": "{{ request.cookies.get(\"token\", \"\") }}"}'
hx-swap="none"
onclick="this.textContent='Stopping…'">Stop</button>
</div>
</div>
{% if p.address %}
<div class="muted mt-8 monospace" style="word-break:break-all;">{{ p.address }}</div>
{% endif %}
</div>
{% endfor %}
</div>
{% else %}
<div class="card" style="text-align:center; padding: 48px; color: var(--muted);">
No profiles yet. Create one to get started.
</div>
{% endif %}
<!-- Create dialog -->
<dialog id="create-dialog">
<h2 style="margin-bottom:20px;">New Bot Profile</h2>
<form id="create-form">
<div class="field">
<label>Name</label>
<input type="text" name="name" placeholder="My Support Bot" required>
</div>
<div class="field">
<label>Bot Type</label>
<select name="bot_type">
{% for t in bot_types %}
<option value="{{ t }}">{{ t }}</option>
{% endfor %}
</select>
</div>
<div class="field">
<label>Welcome Message</label>
<input type="text" name="welcome_message" placeholder="Welcome! How can I help?">
</div>
<div class="flex gap-8 mt-16" style="justify-content: flex-end;">
<button type="button" class="btn btn-ghost" onclick="document.getElementById('create-dialog').close()">Cancel</button>
<button type="submit" class="btn btn-primary">Create</button>
</div>
</form>
</dialog>
<script>
function updateStatus(id, event) {
try {
const data = JSON.parse(event.detail.xhr.responseText)
const badge = document.getElementById('status-' + id)
if (!badge) return
badge.textContent = data.running ? 'running' : 'stopped'
badge.className = 'badge ' + (data.running ? 'badge-green' : 'badge-red')
} catch(e) {}
}
document.getElementById('create-form').addEventListener('submit', async (e) => {
e.preventDefault()
const fd = new FormData(e.target)
const config = {}
const welcome = fd.get('welcome_message')
if (welcome) config.welcome_message = welcome
const resp = await fetch('/api/profiles', {
method: 'POST',
headers: {'Content-Type': 'application/json', 'X-Token': document.cookie.match(/token=([^;]+)/)?.[1] || ''},
body: JSON.stringify({name: fd.get('name'), bot_type: fd.get('bot_type'), config})
})
if (resp.ok) {
document.getElementById('create-dialog').close()
location.reload()
} else {
const err = await resp.json()
alert('Error: ' + (err.detail || 'unknown'))
}
})
</script>
{% endblock %}