Spaces:
Paused
Paused
refactor: clean HF Space - remove stale admin HTML, add .gitignore
Browse files- .gitignore +5 -0
- emergent2api/static/admin/config.html +0 -112
- emergent2api/static/admin/docs.html +0 -145
- emergent2api/static/admin/login.html +0 -40
- emergent2api/static/admin/token.html +0 -204
.gitignore
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.pyc
|
| 3 |
+
*.pyo
|
| 4 |
+
.env
|
| 5 |
+
*.log
|
emergent2api/static/admin/config.html
DELETED
|
@@ -1,112 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
| 5 |
-
<title>Emergent2API - Config</title>
|
| 6 |
-
<style>
|
| 7 |
-
*{margin:0;padding:0;box-sizing:border-box}
|
| 8 |
-
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#f5f5f5;color:#1a1a1a}
|
| 9 |
-
nav{background:#fff;border-bottom:1px solid #e5e5e5;padding:0 24px;display:flex;align-items:center;height:56px;position:sticky;top:0;z-index:10}
|
| 10 |
-
nav .brand{font-weight:700;font-size:18px;margin-right:32px;color:#4f46e5}
|
| 11 |
-
nav a{text-decoration:none;color:#666;padding:16px 12px;font-size:14px;border-bottom:2px solid transparent;transition:all .2s}
|
| 12 |
-
nav a:hover{color:#1a1a1a}nav a.active{color:#4f46e5;border-bottom-color:#4f46e5;font-weight:600}
|
| 13 |
-
nav .spacer{flex:1}
|
| 14 |
-
nav .logout{color:#ef4444;cursor:pointer;font-size:13px}
|
| 15 |
-
.container{max-width:700px;margin:0 auto;padding:28px 24px}
|
| 16 |
-
.card{background:#fff;border-radius:10px;box-shadow:0 1px 4px rgba(0,0,0,.06);padding:28px;margin-bottom:20px}
|
| 17 |
-
.card h2{font-size:17px;margin-bottom:18px;padding-bottom:10px;border-bottom:1px solid #f0f0f0}
|
| 18 |
-
.field{margin-bottom:18px}
|
| 19 |
-
.field label{display:block;font-size:13px;font-weight:600;color:#555;margin-bottom:6px}
|
| 20 |
-
.field input,.field select{width:100%;padding:10px 14px;border:1px solid #ddd;border-radius:6px;font-size:14px;outline:none;transition:border .2s}
|
| 21 |
-
.field input:focus,.field select:focus{border-color:#4f46e5}
|
| 22 |
-
.field .hint{font-size:11px;color:#aaa;margin-top:4px}
|
| 23 |
-
.row{display:flex;gap:10px;align-items:end}
|
| 24 |
-
.row .field{flex:1}
|
| 25 |
-
.btn{padding:10px 20px;border:none;border-radius:6px;font-size:14px;cursor:pointer;font-weight:500;transition:all .2s}
|
| 26 |
-
.btn-primary{background:#4f46e5;color:#fff}.btn-primary:hover{background:#4338ca}
|
| 27 |
-
.btn-outline{background:#fff;color:#333;border:1px solid #ddd}.btn-outline:hover{background:#f3f4f6}
|
| 28 |
-
.actions{display:flex;gap:10px;justify-content:flex-end;margin-top:6px}
|
| 29 |
-
.toast{position:fixed;bottom:24px;right:24px;background:#1a1a1a;color:#fff;padding:12px 20px;border-radius:8px;font-size:13px;z-index:30;opacity:0;transition:opacity .3s}
|
| 30 |
-
.toast.show{opacity:1}
|
| 31 |
-
</style>
|
| 32 |
-
</head>
|
| 33 |
-
<body>
|
| 34 |
-
<nav>
|
| 35 |
-
<span class="brand">Emergent2API</span>
|
| 36 |
-
<a href="/admin/token">Token Management</a>
|
| 37 |
-
<a href="/admin/config" class="active">Config</a>
|
| 38 |
-
<a href="/admin/docs">Usage Docs</a>
|
| 39 |
-
<span class="spacer"></span>
|
| 40 |
-
<span class="logout" onclick="logout()">Logout</span>
|
| 41 |
-
</nav>
|
| 42 |
-
<div class="container">
|
| 43 |
-
<div class="card">
|
| 44 |
-
<h2>API Settings</h2>
|
| 45 |
-
<div class="row">
|
| 46 |
-
<div class="field"><label>API Key</label><input id="api_key" placeholder="sk-..."></div>
|
| 47 |
-
<div style="padding-bottom:18px"><span class="btn btn-outline" onclick="genKey()">Generate</span></div>
|
| 48 |
-
</div>
|
| 49 |
-
<div class="field">
|
| 50 |
-
<label>Backend</label>
|
| 51 |
-
<select id="backend"><option value="jobs">Jobs API (no IP restriction)</option><option value="integrations">Integrations API (faster, needs US proxy)</option></select>
|
| 52 |
-
</div>
|
| 53 |
-
<div class="field"><label>Proxy</label><input id="proxy" placeholder="http://user:pass@host:port"><div class="hint">Required for Integrations backend; optional for Jobs</div></div>
|
| 54 |
-
<div class="row">
|
| 55 |
-
<div class="field"><label>Poll Interval (s)</label><input id="poll_interval" type="number" min="1"></div>
|
| 56 |
-
<div class="field"><label>Poll Timeout (s)</label><input id="poll_timeout" type="number" min="10"></div>
|
| 57 |
-
</div>
|
| 58 |
-
</div>
|
| 59 |
-
<div class="card">
|
| 60 |
-
<h2>Admin Settings</h2>
|
| 61 |
-
<div class="field"><label>Admin Password</label><input id="admin_password" type="password"></div>
|
| 62 |
-
</div>
|
| 63 |
-
<div class="actions">
|
| 64 |
-
<span class="btn btn-outline" onclick="load()">Reset</span>
|
| 65 |
-
<span class="btn btn-primary" onclick="save()">Save Changes</span>
|
| 66 |
-
</div>
|
| 67 |
-
</div>
|
| 68 |
-
<div class="toast" id="toast"></div>
|
| 69 |
-
<script>
|
| 70 |
-
const API='/v1/admin';
|
| 71 |
-
function toast(msg,dur=3000){const t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');setTimeout(()=>t.classList.remove('show'),dur)}
|
| 72 |
-
|
| 73 |
-
async function api(path,opts={}){
|
| 74 |
-
const r=await fetch(API+path,opts);
|
| 75 |
-
if(r.status===401){window.location.href='/admin/login';return null}
|
| 76 |
-
return r;
|
| 77 |
-
}
|
| 78 |
-
|
| 79 |
-
async function load(){
|
| 80 |
-
const r=await api('/config');if(!r)return;
|
| 81 |
-
const d=await r.json();
|
| 82 |
-
document.getElementById('api_key').value=d.api_key||'';
|
| 83 |
-
document.getElementById('backend').value=d.backend||'jobs';
|
| 84 |
-
document.getElementById('proxy').value=d.proxy||'';
|
| 85 |
-
document.getElementById('poll_interval').value=d.poll_interval||'5';
|
| 86 |
-
document.getElementById('poll_timeout').value=d.poll_timeout||'120';
|
| 87 |
-
document.getElementById('admin_password').value=d.admin_password||'';
|
| 88 |
-
}
|
| 89 |
-
|
| 90 |
-
async function save(){
|
| 91 |
-
const body={
|
| 92 |
-
api_key:document.getElementById('api_key').value,
|
| 93 |
-
backend:document.getElementById('backend').value,
|
| 94 |
-
proxy:document.getElementById('proxy').value,
|
| 95 |
-
poll_interval:document.getElementById('poll_interval').value,
|
| 96 |
-
poll_timeout:document.getElementById('poll_timeout').value,
|
| 97 |
-
admin_password:document.getElementById('admin_password').value,
|
| 98 |
-
};
|
| 99 |
-
const r=await api('/config',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
|
| 100 |
-
if(r){const d=await r.json();toast(`Saved: ${d.saved.join(', ')}`)}
|
| 101 |
-
}
|
| 102 |
-
|
| 103 |
-
async function genKey(){
|
| 104 |
-
const r=await api('/config/generate-key',{method:'POST'});
|
| 105 |
-
if(r){const d=await r.json();document.getElementById('api_key').value=d.api_key;toast('New key generated')}
|
| 106 |
-
}
|
| 107 |
-
|
| 108 |
-
async function logout(){await api('/logout',{method:'POST'});window.location.href='/admin/login'}
|
| 109 |
-
load();
|
| 110 |
-
</script>
|
| 111 |
-
</body>
|
| 112 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
emergent2api/static/admin/docs.html
DELETED
|
@@ -1,145 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
| 5 |
-
<title>Emergent2API - Usage Docs</title>
|
| 6 |
-
<style>
|
| 7 |
-
*{margin:0;padding:0;box-sizing:border-box}
|
| 8 |
-
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#f5f5f5;color:#1a1a1a}
|
| 9 |
-
nav{background:#fff;border-bottom:1px solid #e5e5e5;padding:0 24px;display:flex;align-items:center;height:56px;position:sticky;top:0;z-index:10}
|
| 10 |
-
nav .brand{font-weight:700;font-size:18px;margin-right:32px;color:#4f46e5}
|
| 11 |
-
nav a{text-decoration:none;color:#666;padding:16px 12px;font-size:14px;border-bottom:2px solid transparent;transition:all .2s}
|
| 12 |
-
nav a:hover{color:#1a1a1a}nav a.active{color:#4f46e5;border-bottom-color:#4f46e5;font-weight:600}
|
| 13 |
-
nav .spacer{flex:1}
|
| 14 |
-
nav .logout{color:#ef4444;cursor:pointer;font-size:13px}
|
| 15 |
-
.container{max-width:800px;margin:0 auto;padding:28px 24px}
|
| 16 |
-
.card{background:#fff;border-radius:10px;box-shadow:0 1px 4px rgba(0,0,0,.06);padding:28px;margin-bottom:20px}
|
| 17 |
-
.card h2{font-size:17px;margin-bottom:14px;color:#4f46e5}
|
| 18 |
-
.card h3{font-size:14px;margin:16px 0 8px;color:#333}
|
| 19 |
-
p{font-size:14px;line-height:1.6;color:#555;margin-bottom:8px}
|
| 20 |
-
pre{background:#1a1a2e;color:#e2e8f0;padding:16px;border-radius:8px;overflow-x:auto;font-size:12px;line-height:1.5;margin:8px 0 14px}
|
| 21 |
-
code{font-family:'SFMono-Regular',Consolas,monospace;font-size:12px}
|
| 22 |
-
.inline-code{background:#f1f5f9;padding:2px 6px;border-radius:4px;color:#4f46e5;font-size:13px}
|
| 23 |
-
table{width:100%;border-collapse:collapse;margin:8px 0 14px;font-size:13px}
|
| 24 |
-
th{text-align:left;padding:8px 12px;background:#f9fafb;color:#888;font-weight:600;border-bottom:1px solid #eee}
|
| 25 |
-
td{padding:8px 12px;border-bottom:1px solid #f3f3f3}
|
| 26 |
-
td code{background:#f1f5f9;padding:1px 4px;border-radius:3px}
|
| 27 |
-
</style>
|
| 28 |
-
</head>
|
| 29 |
-
<body>
|
| 30 |
-
<nav>
|
| 31 |
-
<span class="brand">Emergent2API</span>
|
| 32 |
-
<a href="/admin/token">Token Management</a>
|
| 33 |
-
<a href="/admin/config">Config</a>
|
| 34 |
-
<a href="/admin/docs" class="active">Usage Docs</a>
|
| 35 |
-
<span class="spacer"></span>
|
| 36 |
-
<span class="logout" onclick="logout()">Logout</span>
|
| 37 |
-
</nav>
|
| 38 |
-
<div class="container">
|
| 39 |
-
|
| 40 |
-
<div class="card">
|
| 41 |
-
<h2>Quick Start</h2>
|
| 42 |
-
<p>Emergent2API exposes Emergent.sh accounts as standard OpenAI and Anthropic-compatible API endpoints. Use your configured API key in the <span class="inline-code">Authorization</span> header.</p>
|
| 43 |
-
<pre>Base URL: https://<your-host>/v1
|
| 44 |
-
API Key: sk-6loA0HwMQP1mdhPvI (default, changeable in Config)</pre>
|
| 45 |
-
</div>
|
| 46 |
-
|
| 47 |
-
<div class="card">
|
| 48 |
-
<h2>Available Models</h2>
|
| 49 |
-
<table>
|
| 50 |
-
<tr><th>Model ID</th><th>Description</th></tr>
|
| 51 |
-
<tr><td><code>claude-opus-4-6</code></td><td>Claude Opus 4.6 (full, uncapped)</td></tr>
|
| 52 |
-
<tr><td><code>claude-sonnet-4-5</code></td><td>Claude Sonnet 4.5</td></tr>
|
| 53 |
-
<tr><td><code>claude-sonnet-4-5-thinking</code></td><td>Claude Sonnet 4.5 with extended thinking</td></tr>
|
| 54 |
-
</table>
|
| 55 |
-
</div>
|
| 56 |
-
|
| 57 |
-
<div class="card">
|
| 58 |
-
<h2>API Endpoints</h2>
|
| 59 |
-
<table>
|
| 60 |
-
<tr><th>Method</th><th>Endpoint</th><th>Format</th></tr>
|
| 61 |
-
<tr><td>POST</td><td><code>/v1/chat/completions</code></td><td>OpenAI Chat Completions</td></tr>
|
| 62 |
-
<tr><td>POST</td><td><code>/v1/messages</code></td><td>Anthropic Messages</td></tr>
|
| 63 |
-
<tr><td>POST</td><td><code>/v1/responses</code></td><td>OpenAI Response API</td></tr>
|
| 64 |
-
<tr><td>GET</td><td><code>/v1/models</code></td><td>Model list</td></tr>
|
| 65 |
-
</table>
|
| 66 |
-
</div>
|
| 67 |
-
|
| 68 |
-
<div class="card">
|
| 69 |
-
<h2>Examples</h2>
|
| 70 |
-
|
| 71 |
-
<h3>OpenAI Chat (curl)</h3>
|
| 72 |
-
<pre>curl -X POST https://your-host/v1/chat/completions \
|
| 73 |
-
-H "Authorization: Bearer sk-6loA0HwMQP1mdhPvI" \
|
| 74 |
-
-H "Content-Type: application/json" \
|
| 75 |
-
-d '{
|
| 76 |
-
"model": "claude-opus-4-6",
|
| 77 |
-
"messages": [
|
| 78 |
-
{"role": "user", "content": "Hello!"}
|
| 79 |
-
],
|
| 80 |
-
"stream": true
|
| 81 |
-
}'</pre>
|
| 82 |
-
|
| 83 |
-
<h3>Anthropic Messages (curl)</h3>
|
| 84 |
-
<pre>curl -X POST https://your-host/v1/messages \
|
| 85 |
-
-H "x-api-key: sk-6loA0HwMQP1mdhPvI" \
|
| 86 |
-
-H "Content-Type: application/json" \
|
| 87 |
-
-H "anthropic-version: 2023-06-01" \
|
| 88 |
-
-d '{
|
| 89 |
-
"model": "claude-opus-4-6",
|
| 90 |
-
"max_tokens": 1024,
|
| 91 |
-
"messages": [
|
| 92 |
-
{"role": "user", "content": "Hello!"}
|
| 93 |
-
]
|
| 94 |
-
}'</pre>
|
| 95 |
-
|
| 96 |
-
<h3>Python (OpenAI SDK)</h3>
|
| 97 |
-
<pre>from openai import OpenAI
|
| 98 |
-
|
| 99 |
-
client = OpenAI(
|
| 100 |
-
api_key="sk-6loA0HwMQP1mdhPvI",
|
| 101 |
-
base_url="https://your-host/v1"
|
| 102 |
-
)
|
| 103 |
-
|
| 104 |
-
response = client.chat.completions.create(
|
| 105 |
-
model="claude-opus-4-6",
|
| 106 |
-
messages=[{"role": "user", "content": "Hello!"}],
|
| 107 |
-
stream=True
|
| 108 |
-
)
|
| 109 |
-
for chunk in response:
|
| 110 |
-
print(chunk.choices[0].delta.content or "", end="")</pre>
|
| 111 |
-
</div>
|
| 112 |
-
|
| 113 |
-
<div class="card">
|
| 114 |
-
<h2>CherryStudio Configuration</h2>
|
| 115 |
-
<p>1. Open CherryStudio → Settings → Model Provider → Add Custom Provider</p>
|
| 116 |
-
<p>2. Set the following:</p>
|
| 117 |
-
<table>
|
| 118 |
-
<tr><th>Field</th><th>Value</th></tr>
|
| 119 |
-
<tr><td>Base URL</td><td><code>https://your-host/v1</code></td></tr>
|
| 120 |
-
<tr><td>API Key</td><td><code>sk-6loA0HwMQP1mdhPvI</code></td></tr>
|
| 121 |
-
<tr><td>Model</td><td><code>claude-opus-4-6</code></td></tr>
|
| 122 |
-
</table>
|
| 123 |
-
<p>3. Enable "Stream" mode for real-time responses.</p>
|
| 124 |
-
</div>
|
| 125 |
-
|
| 126 |
-
<div class="card">
|
| 127 |
-
<h2>Batch Import via CLI</h2>
|
| 128 |
-
<p>Import accounts from a zip artifact (produced by GitHub Actions):</p>
|
| 129 |
-
<pre>curl -X POST https://your-host/v1/admin/tokens/import-zip \
|
| 130 |
-
-H "Cookie: emergent_admin_session=YOUR_SESSION" \
|
| 131 |
-
-H "Content-Type: application/octet-stream" \
|
| 132 |
-
--data-binary @0316Emergent.zip</pre>
|
| 133 |
-
<p>Or import JSONL text directly:</p>
|
| 134 |
-
<pre>curl -X POST https://your-host/v1/admin/tokens/import \
|
| 135 |
-
-H "Cookie: emergent_admin_session=YOUR_SESSION" \
|
| 136 |
-
-H "Content-Type: application/json" \
|
| 137 |
-
-d '{"jsonl": "{\"email\":\"a@b.com\",\"password\":\"p\",\"jwt\":\"j\"}\n..."}'</pre>
|
| 138 |
-
</div>
|
| 139 |
-
|
| 140 |
-
</div>
|
| 141 |
-
<script>
|
| 142 |
-
async function logout(){await fetch('/v1/admin/logout',{method:'POST'});window.location.href='/admin/login'}
|
| 143 |
-
</script>
|
| 144 |
-
</body>
|
| 145 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
emergent2api/static/admin/login.html
DELETED
|
@@ -1,40 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
| 5 |
-
<title>Emergent2API - Login</title>
|
| 6 |
-
<style>
|
| 7 |
-
*{margin:0;padding:0;box-sizing:border-box}
|
| 8 |
-
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#f5f5f5;display:flex;align-items:center;justify-content:center;min-height:100vh}
|
| 9 |
-
.card{background:#fff;border-radius:12px;box-shadow:0 2px 12px rgba(0,0,0,.08);padding:40px;width:380px;text-align:center}
|
| 10 |
-
.card h1{font-size:22px;margin-bottom:6px;color:#1a1a1a}
|
| 11 |
-
.card p{color:#888;font-size:14px;margin-bottom:28px}
|
| 12 |
-
input{width:100%;padding:12px 16px;border:1px solid #ddd;border-radius:8px;font-size:15px;outline:none;transition:border .2s}
|
| 13 |
-
input:focus{border-color:#4f46e5}
|
| 14 |
-
button{width:100%;padding:12px;background:#4f46e5;color:#fff;border:none;border-radius:8px;font-size:15px;cursor:pointer;margin-top:16px;transition:background .2s}
|
| 15 |
-
button:hover{background:#4338ca}
|
| 16 |
-
.err{color:#ef4444;font-size:13px;margin-top:12px;display:none}
|
| 17 |
-
</style>
|
| 18 |
-
</head>
|
| 19 |
-
<body>
|
| 20 |
-
<div class="card">
|
| 21 |
-
<h1>Emergent2API</h1>
|
| 22 |
-
<p>Admin Panel Login</p>
|
| 23 |
-
<input type="password" id="pwd" placeholder="Admin Password" autofocus>
|
| 24 |
-
<button onclick="doLogin()">Login</button>
|
| 25 |
-
<div class="err" id="err">Invalid password</div>
|
| 26 |
-
</div>
|
| 27 |
-
<script>
|
| 28 |
-
const API='/v1/admin';
|
| 29 |
-
document.getElementById('pwd').addEventListener('keydown',e=>{if(e.key==='Enter')doLogin()});
|
| 30 |
-
async function doLogin(){
|
| 31 |
-
const pwd=document.getElementById('pwd').value;
|
| 32 |
-
try{
|
| 33 |
-
const r=await fetch(API+'/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({password:pwd})});
|
| 34 |
-
if(r.ok){window.location.href='/admin/token'}
|
| 35 |
-
else{document.getElementById('err').style.display='block'}
|
| 36 |
-
}catch(e){document.getElementById('err').style.display='block'}
|
| 37 |
-
}
|
| 38 |
-
</script>
|
| 39 |
-
</body>
|
| 40 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
emergent2api/static/admin/token.html
DELETED
|
@@ -1,204 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
| 5 |
-
<title>Emergent2API - Token Management</title>
|
| 6 |
-
<style>
|
| 7 |
-
*{margin:0;padding:0;box-sizing:border-box}
|
| 8 |
-
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#f5f5f5;color:#1a1a1a}
|
| 9 |
-
nav{background:#fff;border-bottom:1px solid #e5e5e5;padding:0 24px;display:flex;align-items:center;height:56px;position:sticky;top:0;z-index:10}
|
| 10 |
-
nav .brand{font-weight:700;font-size:18px;margin-right:32px;color:#4f46e5}
|
| 11 |
-
nav a{text-decoration:none;color:#666;padding:16px 12px;font-size:14px;border-bottom:2px solid transparent;transition:all .2s}
|
| 12 |
-
nav a:hover{color:#1a1a1a}nav a.active{color:#4f46e5;border-bottom-color:#4f46e5;font-weight:600}
|
| 13 |
-
nav .spacer{flex:1}
|
| 14 |
-
nav .logout{color:#ef4444;cursor:pointer;font-size:13px}
|
| 15 |
-
.container{max-width:1200px;margin:0 auto;padding:20px 24px}
|
| 16 |
-
.toolbar{display:flex;gap:10px;margin-bottom:16px;flex-wrap:wrap;align-items:center}
|
| 17 |
-
.toolbar select,.toolbar input{padding:8px 12px;border:1px solid #ddd;border-radius:6px;font-size:13px;background:#fff}
|
| 18 |
-
.btn{padding:8px 16px;border:none;border-radius:6px;font-size:13px;cursor:pointer;font-weight:500;transition:all .2s}
|
| 19 |
-
.btn-primary{background:#4f46e5;color:#fff}.btn-primary:hover{background:#4338ca}
|
| 20 |
-
.btn-danger{background:#ef4444;color:#fff}.btn-danger:hover{background:#dc2626}
|
| 21 |
-
.btn-warning{background:#f59e0b;color:#fff}.btn-warning:hover{background:#d97706}
|
| 22 |
-
.btn-success{background:#10b981;color:#fff}.btn-success:hover{background:#059669}
|
| 23 |
-
.btn-outline{background:#fff;color:#333;border:1px solid #ddd}.btn-outline:hover{background:#f3f4f6}
|
| 24 |
-
.stats{display:flex;gap:16px;margin-bottom:16px}
|
| 25 |
-
.stat{background:#fff;border-radius:8px;padding:16px 20px;box-shadow:0 1px 4px rgba(0,0,0,.06);flex:1}
|
| 26 |
-
.stat .num{font-size:28px;font-weight:700;color:#4f46e5}.stat .label{font-size:12px;color:#888;margin-top:4px}
|
| 27 |
-
table{width:100%;border-collapse:collapse;background:#fff;border-radius:8px;overflow:hidden;box-shadow:0 1px 4px rgba(0,0,0,.06)}
|
| 28 |
-
th{text-align:left;padding:10px 14px;font-size:12px;color:#888;background:#fafafa;font-weight:600;border-bottom:1px solid #eee}
|
| 29 |
-
td{padding:10px 14px;font-size:13px;border-bottom:1px solid #f3f3f3}
|
| 30 |
-
tr:hover td{background:#fafafe}
|
| 31 |
-
.badge{padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600}
|
| 32 |
-
.badge-ok{background:#d1fae5;color:#059669}.badge-off{background:#fee2e2;color:#ef4444}
|
| 33 |
-
.modal{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.4);z-index:20;align-items:center;justify-content:center}
|
| 34 |
-
.modal.open{display:flex}
|
| 35 |
-
.modal-box{background:#fff;border-radius:12px;padding:28px;width:520px;max-height:80vh;overflow-y:auto;box-shadow:0 8px 30px rgba(0,0,0,.15)}
|
| 36 |
-
.modal-box h3{margin-bottom:14px;font-size:17px}
|
| 37 |
-
textarea{width:100%;height:200px;padding:10px;border:1px solid #ddd;border-radius:6px;font-size:12px;font-family:monospace;resize:vertical}
|
| 38 |
-
.modal-actions{display:flex;gap:8px;justify-content:flex-end;margin-top:14px}
|
| 39 |
-
input[type="checkbox"]{width:16px;height:16px;cursor:pointer}
|
| 40 |
-
.toast{position:fixed;bottom:24px;right:24px;background:#1a1a1a;color:#fff;padding:12px 20px;border-radius:8px;font-size:13px;z-index:30;opacity:0;transition:opacity .3s}
|
| 41 |
-
.toast.show{opacity:1}
|
| 42 |
-
</style>
|
| 43 |
-
</head>
|
| 44 |
-
<body>
|
| 45 |
-
<nav>
|
| 46 |
-
<span class="brand">Emergent2API</span>
|
| 47 |
-
<a href="/admin/token" class="active">Token Management</a>
|
| 48 |
-
<a href="/admin/config">Config</a>
|
| 49 |
-
<a href="/admin/docs">Usage Docs</a>
|
| 50 |
-
<span class="spacer"></span>
|
| 51 |
-
<span class="logout" onclick="logout()">Logout</span>
|
| 52 |
-
</nav>
|
| 53 |
-
<div class="container">
|
| 54 |
-
<div class="stats">
|
| 55 |
-
<div class="stat"><div class="num" id="s-total">-</div><div class="label">Total Tokens</div></div>
|
| 56 |
-
<div class="stat"><div class="num" id="s-active">-</div><div class="label">Active</div></div>
|
| 57 |
-
<div class="stat"><div class="num" id="s-inactive">-</div><div class="label">Inactive</div></div>
|
| 58 |
-
</div>
|
| 59 |
-
<div class="toolbar">
|
| 60 |
-
<select id="filter" onchange="render()">
|
| 61 |
-
<option value="all">All</option>
|
| 62 |
-
<option value="active">Active</option>
|
| 63 |
-
<option value="inactive">Inactive</option>
|
| 64 |
-
</select>
|
| 65 |
-
<span class="btn btn-outline" onclick="selectAll()">Select All</span>
|
| 66 |
-
<span class="btn btn-outline" onclick="selectNone()">Deselect All</span>
|
| 67 |
-
<span style="flex:1"></span>
|
| 68 |
-
<span class="btn btn-primary" onclick="openImport()">Import</span>
|
| 69 |
-
<span class="btn btn-outline" onclick="doExport()">Export</span>
|
| 70 |
-
<span class="btn btn-success" onclick="batchAction('test')">Test Selected</span>
|
| 71 |
-
<span class="btn btn-warning" onclick="batchAction('refresh')">Refresh JWT</span>
|
| 72 |
-
<span class="btn btn-danger" onclick="batchAction('delete')">Delete Selected</span>
|
| 73 |
-
<span class="btn btn-danger" onclick="deleteInactive()">Delete Inactive</span>
|
| 74 |
-
</div>
|
| 75 |
-
<table>
|
| 76 |
-
<thead><tr>
|
| 77 |
-
<th><input type="checkbox" id="chk-all" onchange="toggleAll(this.checked)"></th>
|
| 78 |
-
<th>ID</th><th>Email</th><th>Balance</th><th>Status</th><th>Last Used</th><th>Created</th><th>Actions</th>
|
| 79 |
-
</tr></thead>
|
| 80 |
-
<tbody id="tbody"></tbody>
|
| 81 |
-
</table>
|
| 82 |
-
</div>
|
| 83 |
-
|
| 84 |
-
<div class="modal" id="modal-import">
|
| 85 |
-
<div class="modal-box">
|
| 86 |
-
<h3>Import Accounts</h3>
|
| 87 |
-
<p style="font-size:13px;color:#888;margin-bottom:10px">Paste JSONL data (one account per line: email, password, jwt required)</p>
|
| 88 |
-
<textarea id="import-text" placeholder='{"email":"...","password":"...","jwt":"..."}'></textarea>
|
| 89 |
-
<p style="font-size:12px;color:#888;margin-top:8px">Or upload a .zip file containing accounts.jsonl:</p>
|
| 90 |
-
<input type="file" id="import-file" accept=".zip" style="margin-top:6px">
|
| 91 |
-
<div class="modal-actions">
|
| 92 |
-
<span class="btn btn-outline" onclick="closeImport()">Cancel</span>
|
| 93 |
-
<span class="btn btn-primary" onclick="doImportText()">Import JSONL</span>
|
| 94 |
-
<span class="btn btn-primary" onclick="doImportZip()">Import Zip</span>
|
| 95 |
-
</div>
|
| 96 |
-
</div>
|
| 97 |
-
</div>
|
| 98 |
-
|
| 99 |
-
<div class="toast" id="toast"></div>
|
| 100 |
-
|
| 101 |
-
<script>
|
| 102 |
-
const API='/v1/admin';
|
| 103 |
-
let tokens=[];let selected=new Set();
|
| 104 |
-
|
| 105 |
-
async function api(path,opts={}){
|
| 106 |
-
const r=await fetch(API+path,opts);
|
| 107 |
-
if(r.status===401){window.location.href='/admin/login';return null}
|
| 108 |
-
return r;
|
| 109 |
-
}
|
| 110 |
-
|
| 111 |
-
function toast(msg,dur=3000){const t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');setTimeout(()=>t.classList.remove('show'),dur)}
|
| 112 |
-
|
| 113 |
-
async function load(){
|
| 114 |
-
const r=await api('/tokens');if(!r)return;
|
| 115 |
-
const d=await r.json();
|
| 116 |
-
tokens=d.tokens||[];
|
| 117 |
-
document.getElementById('s-total').textContent=d.total;
|
| 118 |
-
document.getElementById('s-active').textContent=d.active;
|
| 119 |
-
document.getElementById('s-inactive').textContent=d.total-d.active;
|
| 120 |
-
render();
|
| 121 |
-
}
|
| 122 |
-
|
| 123 |
-
function render(){
|
| 124 |
-
const f=document.getElementById('filter').value;
|
| 125 |
-
const tbody=document.getElementById('tbody');
|
| 126 |
-
let rows=tokens;
|
| 127 |
-
if(f==='active')rows=rows.filter(t=>t.is_active);
|
| 128 |
-
else if(f==='inactive')rows=rows.filter(t=>!t.is_active);
|
| 129 |
-
tbody.innerHTML=rows.map(t=>`<tr>
|
| 130 |
-
<td><input type="checkbox" class="row-chk" data-id="${t.id}" ${selected.has(t.id)?'checked':''} onchange="toggleSel(${t.id},this.checked)"></td>
|
| 131 |
-
<td>${t.id}</td>
|
| 132 |
-
<td style="font-family:monospace;font-size:12px">${t.email}</td>
|
| 133 |
-
<td>${t.balance!=null?'$'+t.balance.toFixed(2):'-'}</td>
|
| 134 |
-
<td><span class="badge ${t.is_active?'badge-ok':'badge-off'}">${t.is_active?'Active':'Inactive'}</span></td>
|
| 135 |
-
<td style="font-size:12px;color:#888">${t.last_used?new Date(t.last_used).toLocaleString():'-'}</td>
|
| 136 |
-
<td style="font-size:12px;color:#888">${t.created_at?new Date(t.created_at).toLocaleDateString():'-'}</td>
|
| 137 |
-
<td>
|
| 138 |
-
<span class="btn btn-outline" style="padding:4px 8px;font-size:11px" onclick="toggleOne(${t.id})">${t.is_active?'Disable':'Enable'}</span>
|
| 139 |
-
</td>
|
| 140 |
-
</tr>`).join('');
|
| 141 |
-
}
|
| 142 |
-
|
| 143 |
-
function toggleSel(id,c){if(c)selected.add(id);else selected.delete(id)}
|
| 144 |
-
function selectAll(){tokens.forEach(t=>selected.add(t.id));render()}
|
| 145 |
-
function selectNone(){selected.clear();render()}
|
| 146 |
-
function toggleAll(c){if(c)selectAll();else selectNone()}
|
| 147 |
-
|
| 148 |
-
async function toggleOne(id){
|
| 149 |
-
await api('/tokens/toggle',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ids:[id],active:!tokens.find(t=>t.id===id)?.is_active})});
|
| 150 |
-
load();
|
| 151 |
-
}
|
| 152 |
-
|
| 153 |
-
async function batchAction(action){
|
| 154 |
-
const ids=[...selected];
|
| 155 |
-
if(!ids.length){toast('Select accounts first');return}
|
| 156 |
-
if(action==='delete'&&!confirm(`Delete ${ids.length} accounts?`))return;
|
| 157 |
-
const endpoint=`/tokens/${action}`;
|
| 158 |
-
toast(`Processing ${ids.length} accounts...`);
|
| 159 |
-
const r=await api(endpoint,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ids})});
|
| 160 |
-
if(r){const d=await r.json();toast(JSON.stringify(d))}
|
| 161 |
-
selected.clear();load();
|
| 162 |
-
}
|
| 163 |
-
|
| 164 |
-
async function deleteInactive(){
|
| 165 |
-
if(!confirm('Delete all inactive accounts?'))return;
|
| 166 |
-
const r=await api('/tokens/delete-inactive',{method:'POST'});
|
| 167 |
-
if(r){const d=await r.json();toast(`Deleted ${d.deleted} inactive accounts`)}
|
| 168 |
-
load();
|
| 169 |
-
}
|
| 170 |
-
|
| 171 |
-
function openImport(){document.getElementById('modal-import').classList.add('open')}
|
| 172 |
-
function closeImport(){document.getElementById('modal-import').classList.remove('open')}
|
| 173 |
-
|
| 174 |
-
async function doImportText(){
|
| 175 |
-
const text=document.getElementById('import-text').value.trim();
|
| 176 |
-
if(!text){toast('Paste JSONL data first');return}
|
| 177 |
-
const r=await api('/tokens/import',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({jsonl:text})});
|
| 178 |
-
if(r){const d=await r.json();toast(`Imported ${d.imported} accounts`+(d.errors.length?`, ${d.errors.length} errors`:''));closeImport();load()}
|
| 179 |
-
}
|
| 180 |
-
|
| 181 |
-
async function doImportZip(){
|
| 182 |
-
const f=document.getElementById('import-file').files[0];
|
| 183 |
-
if(!f){toast('Select a zip file');return}
|
| 184 |
-
const buf=await f.arrayBuffer();
|
| 185 |
-
const r=await api('/tokens/import-zip',{method:'POST',headers:{'Content-Type':'application/octet-stream'},body:buf});
|
| 186 |
-
if(r){const d=await r.json();toast(`Imported ${d.imported} accounts`);closeImport();load()}
|
| 187 |
-
}
|
| 188 |
-
|
| 189 |
-
async function doExport(){
|
| 190 |
-
const r=await api('/tokens/export');
|
| 191 |
-
if(!r)return;
|
| 192 |
-
const blob=await r.blob();
|
| 193 |
-
const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download='emergent_accounts.zip';a.click();
|
| 194 |
-
}
|
| 195 |
-
|
| 196 |
-
async function logout(){
|
| 197 |
-
await api('/logout',{method:'POST'});
|
| 198 |
-
window.location.href='/admin/login';
|
| 199 |
-
}
|
| 200 |
-
|
| 201 |
-
load();
|
| 202 |
-
</script>
|
| 203 |
-
</body>
|
| 204 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|