ai-proxy / dashboard.html
heiyuheiyu's picture
Upload 2 files
2b0c433 verified
<!DOCTYPE html>
<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>