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>
This commit is contained in:
139
manager/templates/base.html
Normal file
139
manager/templates/base.html
Normal file
@@ -0,0 +1,139 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}SimpleX Manager{% endblock %}</title>
|
||||
<script src="https://unpkg.com/htmx.org@1.9.12/dist/htmx.min.js"></script>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--bg: #f5f5f7;
|
||||
--card: #ffffff;
|
||||
--text: #1d1d1f;
|
||||
--muted: #6e6e73;
|
||||
--accent: #0053D0;
|
||||
--green: #20BD3D;
|
||||
--red: #DD0000;
|
||||
--border: #e0e0e5;
|
||||
--shadow: 0 2px 12px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg: #111827;
|
||||
--card: #0B2A59;
|
||||
--text: #f5f5f7;
|
||||
--muted: #9ca3af;
|
||||
--accent: #70F0F9;
|
||||
--border: #1e3a5f;
|
||||
--shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
|
||||
background: var(--bg); color: var(--text); min-height: 100vh; }
|
||||
|
||||
nav { background: var(--card); border-bottom: 1px solid var(--border);
|
||||
padding: 12px 24px; display: flex; align-items: center; justify-content: space-between;
|
||||
position: sticky; top: 0; z-index: 10; }
|
||||
|
||||
.nav-brand { font-size: 17px; font-weight: 700; color: var(--accent); text-decoration: none; }
|
||||
|
||||
.nav-links a { color: var(--muted); text-decoration: none; font-size: 14px; margin-left: 16px; }
|
||||
.nav-links a:hover { color: var(--accent); }
|
||||
|
||||
.container { max-width: 960px; margin: 0 auto; padding: 32px 20px; }
|
||||
|
||||
h1 { font-size: 28px; font-weight: 700; margin-bottom: 24px; }
|
||||
h2 { font-size: 20px; font-weight: 600; margin-bottom: 16px; }
|
||||
|
||||
.card { background: var(--card); border-radius: 10px; padding: 20px;
|
||||
box-shadow: var(--shadow); margin-bottom: 16px; }
|
||||
|
||||
.btn { display: inline-flex; align-items: center; gap: 6px;
|
||||
padding: 8px 18px; border-radius: 8px; font-size: 14px; font-weight: 600;
|
||||
font-family: inherit; cursor: pointer; border: none; text-decoration: none;
|
||||
transition: opacity 0.15s; }
|
||||
.btn:hover { opacity: 0.85; }
|
||||
.btn-primary { background: var(--accent); color: #fff; }
|
||||
.btn-danger { background: var(--red); color: #fff; }
|
||||
.btn-success { background: var(--green); color: #fff; }
|
||||
.btn-ghost { background: transparent; border: 1px solid var(--border); color: var(--text); }
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.btn-primary { color: #000; }
|
||||
.btn-success { color: #000; }
|
||||
}
|
||||
|
||||
.badge { display: inline-block; padding: 2px 8px; border-radius: 10px;
|
||||
font-size: 12px; font-weight: 600; }
|
||||
.badge-green { background: #d1fae5; color: #065f46; }
|
||||
.badge-red { background: #fee2e2; color: #991b1b; }
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.badge-green { background: #064e3b; color: #6ee7b7; }
|
||||
.badge-red { background: #7f1d1d; color: #fca5a5; }
|
||||
}
|
||||
|
||||
input, select, textarea {
|
||||
width: 100%; padding: 9px 12px; font-size: 14px; font-family: inherit;
|
||||
border: 1px solid var(--border); border-radius: 8px;
|
||||
background: var(--bg); color: var(--text); outline: none;
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
input:focus, select:focus, textarea:focus { border-color: var(--accent); }
|
||||
|
||||
label { display: block; font-size: 13px; font-weight: 600;
|
||||
color: var(--muted); margin-bottom: 4px; }
|
||||
|
||||
.field { margin-bottom: 14px; }
|
||||
|
||||
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
||||
@media (max-width: 640px) { .grid-2 { grid-template-columns: 1fr; } }
|
||||
|
||||
.monospace { font-family: monospace; font-size: 12px; }
|
||||
|
||||
.log-box { background: #0a0a0f; color: #70F0F9; border-radius: 8px;
|
||||
padding: 12px; font-family: monospace; font-size: 12px;
|
||||
height: 200px; overflow-y: auto; white-space: pre-wrap; }
|
||||
|
||||
.tag { display: inline-block; padding: 2px 8px; border-radius: 6px;
|
||||
font-size: 12px; background: var(--border); color: var(--muted); }
|
||||
|
||||
.flex { display: flex; align-items: center; gap: 10px; }
|
||||
.flex-between { display: flex; align-items: center; justify-content: space-between; }
|
||||
.gap-8 { gap: 8px; }
|
||||
.mt-8 { margin-top: 8px; }
|
||||
.mt-16 { margin-top: 16px; }
|
||||
.muted { color: var(--muted); font-size: 13px; }
|
||||
|
||||
table { width: 100%; border-collapse: collapse; font-size: 14px; }
|
||||
th { text-align: left; color: var(--muted); font-size: 12px; font-weight: 600;
|
||||
padding: 8px 12px; border-bottom: 1px solid var(--border); }
|
||||
td { padding: 10px 12px; border-bottom: 1px solid var(--border); }
|
||||
tr:last-child td { border-bottom: none; }
|
||||
|
||||
.htmx-indicator { opacity: 0; transition: opacity 0.2s; }
|
||||
.htmx-request .htmx-indicator { opacity: 1; }
|
||||
|
||||
dialog { background: var(--card); color: var(--text); border: 1px solid var(--border);
|
||||
border-radius: 12px; padding: 28px; max-width: 480px; width: 90%; }
|
||||
dialog::backdrop { background: rgba(0,0,0,0.5); }
|
||||
</style>
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<a class="nav-brand" href="/">SimpleX Manager</a>
|
||||
<div class="nav-links">
|
||||
<a href="/">Profiles</a>
|
||||
<a href="/logout">Logout</a>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user