Files
JieXi/templates/apikey/manage.html
2025-11-30 19:49:25 +08:00

466 lines
14 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>API Key 管理 - 短视频解析平台</title>
<link rel="stylesheet" href="/static/css/ui-components.css?v=3">
<style>
body {
background: linear-gradient(135deg, #f0f9ff 0%, #e0e7ff 100%);
min-height: 100vh;
padding: 2rem 1rem;
}
.container {
max-width: 900px;
margin: 0 auto;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.page-title {
font-size: 1.75rem;
font-weight: 700;
color: var(--secondary-900);
}
.header-actions {
display: flex;
gap: 0.75rem;
}
.info-card {
background: white;
border-radius: var(--radius-lg);
padding: 1.5rem;
box-shadow: var(--shadow-md);
margin-bottom: 2rem;
}
.info-card h3 {
font-size: 1.125rem;
font-weight: 600;
color: var(--secondary-900);
margin-bottom: 1rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid var(--border-color);
}
.key-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.key-item {
background: var(--secondary-50);
border-radius: var(--radius-md);
padding: 1.25rem;
border: 1px solid var(--border-color);
}
.key-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
}
.key-name {
font-weight: 600;
color: var(--secondary-900);
font-size: 1rem;
}
.key-status {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 500;
}
.key-status.active {
background: #dcfce7;
color: #166534;
}
.key-status.inactive {
background: #fee2e2;
color: #991b1b;
}
.key-value {
font-family: monospace;
background: white;
padding: 0.5rem 0.75rem;
border-radius: var(--radius-sm);
font-size: 0.875rem;
color: var(--secondary-700);
margin-bottom: 0.75rem;
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid var(--border-color);
}
.key-meta {
display: flex;
gap: 1.5rem;
font-size: 0.8rem;
color: var(--text-muted);
margin-bottom: 1rem;
}
.key-actions {
display: flex;
gap: 0.5rem;
}
.key-actions button {
padding: 0.4rem 0.75rem;
font-size: 0.8rem;
}
.empty-state {
text-align: center;
padding: 3rem;
color: var(--text-muted);
}
.create-form {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
}
.create-form input {
flex: 1;
padding: 0.75rem 1rem;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
font-size: 0.9rem;
}
.create-form input:focus {
outline: none;
border-color: var(--primary-500);
box-shadow: 0 0 0 3px var(--primary-100);
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.3s;
}
.modal-overlay.show {
opacity: 1;
visibility: visible;
}
.modal-content {
background: white;
border-radius: var(--radius-lg);
padding: 2rem;
max-width: 500px;
width: 90%;
box-shadow: var(--shadow-xl);
}
.modal-title {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 1rem;
color: var(--secondary-900);
}
.modal-key {
background: var(--secondary-50);
padding: 1rem;
border-radius: var(--radius-md);
font-family: monospace;
font-size: 0.9rem;
word-break: break-all;
margin-bottom: 1rem;
border: 1px solid var(--border-color);
}
.modal-warning {
background: #fef3c7;
color: #92400e;
padding: 0.75rem 1rem;
border-radius: var(--radius-md);
font-size: 0.875rem;
margin-bottom: 1.5rem;
}
.modal-actions {
display: flex;
gap: 0.75rem;
justify-content: flex-end;
}
.tip-box {
background: #eff6ff;
border: 1px solid #bfdbfe;
border-radius: var(--radius-md);
padding: 1rem;
margin-bottom: 1.5rem;
font-size: 0.875rem;
color: #1e40af;
}
@media (max-width: 640px) {
.page-header {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.create-form {
flex-direction: column;
}
.key-meta {
flex-direction: column;
gap: 0.5rem;
}
.key-actions {
flex-wrap: wrap;
}
}
</style>
</head>
<body>
<div class="container">
<div class="page-header">
<h1 class="page-title">API Key 管理</h1>
<div class="header-actions">
<a href="/api-docs" class="ui-btn ui-btn-secondary">API 文档</a>
<a href="/auth/profile" class="ui-btn ui-btn-secondary">个人中心</a>
<a href="/" class="ui-btn ui-btn-secondary">返回首页</a>
</div>
</div>
<div class="info-card">
<h3>创建新的 API Key</h3>
<div class="tip-box">
API Key 用于调用解析接口,请妥善保管。每个用户最多创建 5 个 Key。
</div>
<div class="create-form">
<input type="text" id="keyName" placeholder="输入 Key 名称(如:我的应用)" maxlength="100">
<button class="ui-btn ui-btn-primary" onclick="createKey()">创建 Key</button>
</div>
</div>
<div class="info-card">
<h3>我的 API Keys</h3>
<div class="key-list" id="keyList">
<div class="empty-state">加载中...</div>
</div>
</div>
</div>
<!-- 新建成功弹窗 -->
<div class="modal-overlay" id="keyModal">
<div class="modal-content">
<h3 class="modal-title">API Key 创建成功</h3>
<div class="modal-key" id="newKeyValue"></div>
<div class="modal-warning">
请立即复制并保存此 Key关闭后将无法再次查看完整内容
</div>
<div class="modal-actions">
<button class="ui-btn ui-btn-secondary" onclick="copyNewKey()">复制 Key</button>
<button class="ui-btn ui-btn-primary" onclick="closeModal()">我已保存</button>
</div>
</div>
</div>
<script src="/static/js/ui-components.js"></script>
<script>
let newApiKey = '';
async function loadKeys() {
try {
const response = await fetch('/user/apikey/list');
const result = await response.json();
if (!result.success) {
if (response.status === 401) {
window.location.href = '/auth/login';
return;
}
throw new Error(result.message || '加载失败');
}
renderKeys(result.data);
} catch (error) {
UI.notify('加载失败: ' + error.message, 'error');
}
}
function renderKeys(keys) {
const container = document.getElementById('keyList');
if (keys.length === 0) {
container.innerHTML = '<div class="empty-state">暂无 API Key请创建一个</div>';
return;
}
container.innerHTML = keys.map(key => `
<div class="key-item">
<div class="key-header">
<span class="key-name">${escapeHtml(key.name)}</span>
<span class="key-status ${key.is_active ? 'active' : 'inactive'}">
${key.is_active ? '启用' : '禁用'}
</span>
</div>
<div class="key-value">
<span>${key.api_key}</span>
</div>
<div class="key-meta">
<span>每日限额: ${key.daily_limit} 次</span>
<span>今日已用: ${key.today_calls} 次</span>
<span>总调用: ${key.total_calls} 次</span>
<span>创建时间: ${key.created_at}</span>
</div>
<div class="key-actions">
<button class="ui-btn ui-btn-secondary" onclick="toggleKey(${key.id}, ${key.is_active})">
${key.is_active ? '禁用' : '启用'}
</button>
<button class="ui-btn ui-btn-danger" onclick="deleteKey(${key.id}, '${escapeHtml(key.name)}')">
删除
</button>
</div>
</div>
`).join('');
}
async function createKey() {
const nameInput = document.getElementById('keyName');
const name = nameInput.value.trim();
if (!name) {
UI.notify('请输入 Key 名称', 'warning');
return;
}
try {
const response = await fetch('/user/apikey/create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name })
});
const result = await response.json();
if (!result.success) {
throw new Error(result.message || '创建失败');
}
// 显示新 Key
newApiKey = result.data.api_key;
document.getElementById('newKeyValue').textContent = newApiKey;
document.getElementById('keyModal').classList.add('show');
nameInput.value = '';
loadKeys();
} catch (error) {
UI.notify(error.message, 'error');
}
}
async function toggleKey(keyId, currentStatus) {
try {
const response = await fetch(`/user/apikey/toggle/${keyId}`, {
method: 'POST'
});
const result = await response.json();
if (!result.success) {
throw new Error(result.message || '操作失败');
}
UI.notify(result.message, 'success');
loadKeys();
} catch (error) {
UI.notify(error.message, 'error');
}
}
async function deleteKey(keyId, keyName) {
if (!confirm(`确定要删除 "${keyName}" 吗?此操作不可恢复。`)) {
return;
}
try {
const response = await fetch(`/user/apikey/delete/${keyId}`, {
method: 'DELETE'
});
const result = await response.json();
if (!result.success) {
throw new Error(result.message || '删除失败');
}
UI.notify('删除成功', 'success');
loadKeys();
} catch (error) {
UI.notify(error.message, 'error');
}
}
function copyNewKey() {
navigator.clipboard.writeText(newApiKey).then(() => {
UI.notify('已复制到剪贴板', 'success');
}).catch(() => {
UI.notify('复制失败,请手动复制', 'error');
});
}
function closeModal() {
document.getElementById('keyModal').classList.remove('show');
newApiKey = '';
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// 点击遮罩关闭
document.getElementById('keyModal').addEventListener('click', function(e) {
if (e.target === this) {
closeModal();
}
});
loadKeys();
</script>
</body>
</html>