Spaces:
Running
Running
| <html> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>AI Proxy Dashboard</title> | |
| <style> | |
| body{font-family:Arial;margin:20px;background:#f5f5f5} | |
| .container{max-width:1400px;margin:0 auto;background:#fff;padding:20px;border-radius:8px} | |
| .login-box{max-width:400px;margin:100px auto;background:#fff;padding:30px;border-radius:8px} | |
| h1{color:#333} | |
| .btn{padding:8px 16px;margin:5px;border:none;border-radius:4px;cursor:pointer} | |
| .btn-primary{background:#007bff;color:#fff} | |
| .btn-success{background:#28a745;color:#fff} | |
| .btn-danger{background:#dc3545;color:#fff} | |
| .btn-warning{background:#ffc107;color:#000} | |
| table{width:100%;border-collapse:collapse;margin:20px 0;font-size:12px} | |
| th,td{padding:8px;text-align:left;border-bottom:1px solid #ddd} | |
| th{background:#f8f9fa} | |
| .status-active{color:#28a745} | |
| .status-limited{color:#ffc107} | |
| .status-error{color:#dc3545} | |
| .status-disable{color:#6c757d} | |
| .form-group{margin:10px 0} | |
| label{display:block;margin:5px 0} | |
| input,select,textarea{width:100%;padding:8px;border:1px solid #ddd;border-radius:4px;box-sizing:border-box} | |
| .modal{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:1000} | |
| .modal-content{background:#fff;margin:50px auto;padding:20px;width:80%;max-width:600px;border-radius:8px;max-height:80vh;overflow-y:auto} | |
| .hidden{display:none} | |
| </style> | |
| </head> | |
| <body> | |
| <div id="loginBox" class="login-box"> | |
| <h1>Dashboard 登录</h1> | |
| <div class="form-group"> | |
| <label>密码</label> | |
| <input id="password" type="password" placeholder="输入密码"> | |
| </div> | |
| <button class="btn btn-primary" onclick="login()">登录</button> | |
| </div> | |
| <div id="mainContent" class="container hidden"> | |
| <h1>AI Proxy Dashboard</h1> | |
| <div style="background:#f8f9fa;padding:15px;border-radius:4px;margin-bottom:20px"> | |
| <h3>API 地址</h3> | |
| <div id="apiUrls"></div> | |
| </div> | |
| <button class="btn btn-primary" onclick="showAddModal()">添加账号</button> | |
| <button class="btn btn-success" onclick="backup()">一键备份</button> | |
| <button class="btn btn-success" onclick="restore()">一键恢复</button> | |
| <br> | |
| <button class="btn btn-success" onclick="backupAccounts()">备份账号</button> | |
| <button class="btn btn-success" onclick="backupCache()">备份聊天</button> | |
| <button class="btn btn-success" onclick="backupVector()">备份向量库</button> | |
| <button class="btn btn-warning" onclick="restoreAccounts()">恢复账号</button> | |
| <button class="btn btn-warning" onclick="restoreCache()">恢复聊天</button> | |
| <button class="btn btn-warning" onclick="restoreVector()">恢复向量库</button> | |
| <table id="accountTable"> | |
| <thead><tr> | |
| <th>AI厂商</th><th>账号类型</th><th>凭证类型</th><th>凭证值</th><th>状态</th><th>今日调用</th><th>总调用</th><th>错误次数</th><th>最近调用</th><th>创建日期</th><th>创建天数</th><th>手机号</th><th>邮箱</th><th>操作</th> | |
| </tr></thead> | |
| <tbody id="accountList"></tbody> | |
| </table> | |
| </div> | |
| <div id="addModal" class="modal"> | |
| <div class="modal-content"> | |
| <h2>添加账号</h2> | |
| <div class="form-group"> | |
| <label>AI厂商</label> | |
| <select id="addProvider"> | |
| <option value="claude">Claude</option> | |
| <option value="chatgpt">ChatGPT</option> | |
| <option value="gemini">Gemini</option> | |
| </select> | |
| </div> | |
| <div class="form-group"> | |
| <label>凭证类型</label> | |
| <select id="addCredType"> | |
| <option value="sessionKey">sessionKey</option> | |
| <option value="apiKey">apiKey</option> | |
| </select> | |
| </div> | |
| <div class="form-group"> | |
| <label>凭证值</label> | |
| <input id="addCredValue" type="text"> | |
| </div> | |
| <div class="form-group"> | |
| <label>手机号</label> | |
| <input id="addPhone" type="text"> | |
| </div> | |
| <div class="form-group"> | |
| <label>邮箱</label> | |
| <input id="addEmail" type="text"> | |
| </div> | |
| <div class="form-group"> | |
| <label>备注</label> | |
| <textarea id="addNote"></textarea> | |
| </div> | |
| <button class="btn btn-primary" onclick="submitAdd()">提交</button> | |
| <button class="btn" onclick="closeModal('addModal')">取消</button> | |
| </div> | |
| </div> | |
| <div id="editModal" class="modal"> | |
| <div class="modal-content"> | |
| <h2>编辑账号</h2> | |
| <input id="editId" type="hidden"> | |
| <div class="form-group"> | |
| <label>AI厂商</label> | |
| <select id="editProvider"> | |
| <option value="claude">Claude</option> | |
| <option value="chatgpt">ChatGPT</option> | |
| <option value="gemini">Gemini</option> | |
| </select> | |
| </div> | |
| <div class="form-group"> | |
| <label>凭证类型</label> | |
| <select id="editCredType"> | |
| <option value="sessionKey">sessionKey</option> | |
| <option value="apiKey">apiKey</option> | |
| </select> | |
| </div> | |
| <div class="form-group"> | |
| <label>凭证值</label> | |
| <input id="editCredValue" type="text"> | |
| </div> | |
| <div class="form-group"> | |
| <label>手机号</label> | |
| <input id="editPhone" type="text"> | |
| </div> | |
| <div class="form-group"> | |
| <label>邮箱</label> | |
| <input id="editEmail" type="text"> | |
| </div> | |
| <div class="form-group"> | |
| <label>备注</label> | |
| <textarea id="editNote"></textarea> | |
| </div> | |
| <div class="form-group"> | |
| <label>状态</label> | |
| <select id="editStatus"> | |
| <option value="Active">Active</option> | |
| <option value="Limited">Limited</option> | |
| <option value="Error">Error</option> | |
| <option value="Disable">Disable</option> | |
| </select> | |
| </div> | |
| <button class="btn btn-primary" onclick="submitEdit()">保存</button> | |
| <button class="btn" onclick="closeModal('editModal')">取消</button> | |
| </div> | |
| </div> | |
| <script> | |
| let password=''; | |
| window.onload=()=>{ | |
| const saved=localStorage.getItem('dashboardPassword'); | |
| if(saved){ | |
| password=saved; | |
| fetch('/dashboard/accounts',{headers:{'X-Dashboard-Password':password}}) | |
| .then(r=>{ | |
| if(r.ok){ | |
| document.getElementById('loginBox').classList.add('hidden'); | |
| document.getElementById('mainContent').classList.remove('hidden'); | |
| loadApiUrls(); | |
| loadAccounts(); | |
| }else{ | |
| localStorage.removeItem('dashboardPassword'); | |
| } | |
| }); | |
| } | |
| }; | |
| async function login(){ | |
| password=document.getElementById('password').value; | |
| const r=await fetch('/dashboard/accounts',{headers:{'X-Dashboard-Password':password}}); | |
| if(r.ok){ | |
| localStorage.setItem('dashboardPassword',password); | |
| document.getElementById('loginBox').classList.add('hidden'); | |
| document.getElementById('mainContent').classList.remove('hidden'); | |
| loadApiUrls(); | |
| loadAccounts(); | |
| }else{ | |
| alert('密码错误'); | |
| } | |
| } | |
| async function loadApiUrls(){ | |
| const r=await fetch('/api/info'); | |
| const data=await r.json(); | |
| document.getElementById('apiUrls').innerHTML=` | |
| <div><strong>Claude:</strong> baseUrl: ${data.claude.baseUrl} | api: ${data.claude.api}</div> | |
| <div><strong>ChatGPT:</strong> baseUrl: ${data.chatgpt.baseUrl} | api: ${data.chatgpt.api}</div> | |
| <div><strong>Gemini:</strong> baseUrl: ${data.gemini.baseUrl} | api: ${data.gemini.api}</div> | |
| `; | |
| } | |
| async function loadAccounts(){ | |
| const r=await fetch('/dashboard/accounts',{headers:{'X-Dashboard-Password':password}}); | |
| const data=await r.json(); | |
| const tbody=document.getElementById('accountList'); | |
| tbody.innerHTML=''; | |
| for(const id in data){ | |
| const a=data[id]; | |
| const tr=document.createElement('tr'); | |
| tr.innerHTML=` | |
| <td>${a.provider}</td> | |
| <td>${a.account_type}</td> | |
| <td>${a.credential_type}</td> | |
| <td>${a.credential_value_masked}</td> | |
| <td> | |
| <select class="status-${a.status.toLowerCase()}" onchange="updateStatus('${id}',this.value)" style="padding:4px;border:1px solid #ddd;border-radius:4px"> | |
| <option value="Active" ${a.status==='Active'?'selected':''}>Active</option> | |
| <option value="Limited" ${a.status==='Limited'?'selected':''}>Limited</option> | |
| <option value="Error" ${a.status==='Error'?'selected':''}>Error</option> | |
| <option value="Disable" ${a.status==='Disable'?'selected':''}>Disable</option> | |
| </select> | |
| </td> | |
| <td>${a.daily_calls}</td> | |
| <td>${a.total_calls}</td> | |
| <td>${a.error_count}</td> | |
| <td>${a.last_call_time?new Date(a.last_call_time).toLocaleString():'未调用'}</td> | |
| <td>${new Date(a.created_at).toLocaleDateString()}</td> | |
| <td>${a.created_days}</td> | |
| <td>${a.phone}</td> | |
| <td>${a.email}</td> | |
| <td> | |
| <button class="btn btn-warning" onclick="showEditModal('${id}')">编辑</button> | |
| <button class="btn btn-danger" onclick="deleteAccount('${id}')">删除</button> | |
| </td> | |
| `; | |
| tbody.appendChild(tr); | |
| } | |
| } | |
| function showAddModal(){ | |
| document.getElementById('addModal').style.display='block'; | |
| } | |
| function showEditModal(id){ | |
| fetch('/dashboard/accounts',{headers:{'X-Dashboard-Password':password}}) | |
| .then(r=>r.json()) | |
| .then(data=>{ | |
| const a=data[id]; | |
| document.getElementById('editId').value=id; | |
| document.getElementById('editProvider').value=a.provider; | |
| document.getElementById('editCredType').value=a.credential_type; | |
| document.getElementById('editCredValue').value=a.credential_value; | |
| document.getElementById('editPhone').value=a.phone; | |
| document.getElementById('editEmail').value=a.email; | |
| document.getElementById('editNote').value=a.note; | |
| document.getElementById('editStatus').value=a.status; | |
| document.getElementById('editModal').style.display='block'; | |
| }); | |
| } | |
| function closeModal(id){ | |
| document.getElementById(id).style.display='none'; | |
| } | |
| async function submitAdd(){ | |
| const data={ | |
| provider:document.getElementById('addProvider').value, | |
| credential_type:document.getElementById('addCredType').value, | |
| credential_value:document.getElementById('addCredValue').value, | |
| phone:document.getElementById('addPhone').value, | |
| email:document.getElementById('addEmail').value, | |
| note:document.getElementById('addNote').value | |
| }; | |
| await fetch('/dashboard/accounts',{method:'POST',headers:{'Content-Type':'application/json','X-Dashboard-Password':password},body:JSON.stringify(data)}); | |
| closeModal('addModal'); | |
| loadAccounts(); | |
| } | |
| async function submitEdit(){ | |
| const id=document.getElementById('editId').value; | |
| const data={ | |
| provider:document.getElementById('editProvider').value, | |
| credential_type:document.getElementById('editCredType').value, | |
| credential_value:document.getElementById('editCredValue').value, | |
| phone:document.getElementById('editPhone').value, | |
| email:document.getElementById('editEmail').value, | |
| note:document.getElementById('editNote').value | |
| }; | |
| await fetch(`/dashboard/accounts/${id}`,{method:'PUT',headers:{'Content-Type':'application/json','X-Dashboard-Password':password},body:JSON.stringify(data)}); | |
| const status=document.getElementById('editStatus').value; | |
| await fetch(`/dashboard/accounts/${id}/status`,{method:'PUT',headers:{'Content-Type':'application/json','X-Dashboard-Password':password},body:JSON.stringify({status})}); | |
| closeModal('editModal'); | |
| loadAccounts(); | |
| } | |
| async function updateStatus(id,status){ | |
| await fetch(`/dashboard/accounts/${id}/status`,{method:'PUT',headers:{'Content-Type':'application/json','X-Dashboard-Password':password},body:JSON.stringify({status})}); | |
| loadAccounts(); | |
| } | |
| async function deleteAccount(id){ | |
| if(confirm('确认删除?')){ | |
| await fetch(`/dashboard/accounts/${id}`,{method:'DELETE',headers:{'X-Dashboard-Password':password}}); | |
| loadAccounts(); | |
| } | |
| } | |
| async function backup(){ | |
| const r=await fetch('/dashboard/backup',{method:'POST',headers:{'X-Dashboard-Password':password}}); | |
| const data=await r.json(); | |
| alert(data.message); | |
| } | |
| async function restore(){ | |
| const r=await fetch('/dashboard/restore',{method:'POST',headers:{'X-Dashboard-Password':password}}); | |
| const data=await r.json(); | |
| alert(data.message); | |
| loadAccounts(); | |
| } | |
| async function backupAccounts(){ | |
| const r=await fetch('/dashboard/backup/accounts',{method:'POST',headers:{'X-Dashboard-Password':password}}); | |
| const data=await r.json(); | |
| alert(data.message); | |
| } | |
| async function backupCache(){ | |
| const r=await fetch('/dashboard/backup/cache',{method:'POST',headers:{'X-Dashboard-Password':password}}); | |
| const data=await r.json(); | |
| alert(data.message); | |
| } | |
| async function backupVector(){ | |
| const r=await fetch('/dashboard/backup/vector',{method:'POST',headers:{'X-Dashboard-Password':password}}); | |
| const data=await r.json(); | |
| alert(data.message); | |
| } | |
| async function restoreAccounts(){ | |
| const r=await fetch('/dashboard/restore/accounts',{method:'POST',headers:{'X-Dashboard-Password':password}}); | |
| const data=await r.json(); | |
| alert(data.message); | |
| loadAccounts(); | |
| } | |
| async function restoreCache(){ | |
| const r=await fetch('/dashboard/restore/cache',{method:'POST',headers:{'X-Dashboard-Password':password}}); | |
| const data=await r.json(); | |
| alert(data.message); | |
| } | |
| async function restoreVector(){ | |
| const r=await fetch('/dashboard/restore/vector',{method:'POST',headers:{'X-Dashboard-Password':password}}); | |
| const data=await r.json(); | |
| alert(data.message); | |
| } | |
| </script> | |
| </body> | |
| </html> | |