New
This commit is contained in:
245
web/templates/auth.html
Normal file
245
web/templates/auth.html
Normal file
@@ -0,0 +1,245 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN" class="">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<!-- 块:标题 (继承自旧模板的 block title) -->
|
||||
<title>登录 - GEMINI BALANCER</title>
|
||||
|
||||
<!-- 引入项目主CSS文件,这是独立页面所必需的 -->
|
||||
<link rel="stylesheet" href="/static/css/output.css">
|
||||
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
|
||||
<!-- 块:额外的头部内容 (继承自旧模板的 block head_extra) -->
|
||||
<style>
|
||||
@keyframes fadeInUp {
|
||||
from { opacity: 0; transform: translateY(20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.animate-fade-in-up {
|
||||
animation: fadeInUp 0.8s ease-out forwards;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-zinc-100 dark:bg-zinc-900">
|
||||
|
||||
<!-- 块:主内容区 (继承自旧模板的 block app_container) -->
|
||||
<div class="relative w-full h-screen flex flex-col justify-center items-center p-8 overflow-hidden">
|
||||
|
||||
<div class="absolute top-6 right-6 flex items-center space-x-4 animate-fade-in-up" style="animation-delay: 0.3s;">
|
||||
<!-- 注意:这个按钮现在只在这个页面生效,其JS逻辑也包含在下方 -->
|
||||
<div id="theme-switcher-container">
|
||||
<!-- 我们将您的三态切换器HTML暂时放在这里,虽然JS还没准备好 -->
|
||||
<div id="theme-switcher" class="relative z-10 inline-grid grid-cols-3 gap-1 rounded-full bg-gray-950/5 p-1 text-gray-950 dark:bg-white/10 dark:text-white">
|
||||
<button type="button" data-theme="system" class="theme-btn rounded-full p-1.5 focus:outline-none">
|
||||
<svg class="size-6" viewBox="0 0 28 28" fill="none"><path d="M7.5 8.5C7.5 7.94772 7.94772 7.5 8.5 7.5H19.5C20.0523 7.5 20.5 7.94772 20.5 8.5V16.5C20.5 17.0523 20.0523 17.5 19.5 17.5H8.5C7.94772 17.5 7.5 17.0523 7.5 16.5V8.5Z" stroke="currentColor"></path><path d="M7.5 8.5C7.5 7.94772 7.94772 7.5 8.5 7.5H19.5C20.0523 7.5 20.5 7.94772 20.5 8.5V14.5C20.5 15.0523 20.0523 15.5 19.5 15.5H8.5C7.94772 15.5 7.5 15.0523 7.5 14.5V8.5Z" stroke="currentColor"></path><path d="M16.5 20.5V17.5H11.5V20.5M16.5 20.5H11.5M16.5 20.5H17.5M11.5 20.5H10.5" stroke="currentColor" stroke-linecap="round"></path></svg>
|
||||
</button>
|
||||
<button type="button" data-theme="light" class="theme-btn rounded-full p-1.5 focus:outline-none">
|
||||
<svg class="size-6" viewBox="0 0 28 28" fill="none"><circle cx="14" cy="14" r="3.5" stroke="currentColor"></circle><path d="M14 8.5V6.5" stroke="currentColor" stroke-linecap="round"></path><path d="M17.889 10.1115L19.3032 8.69727" stroke="currentColor" stroke-linecap="round"></path><path d="M19.5 14L21.5 14" stroke="currentColor" stroke-linecap="round"></path><path d="M17.889 17.8885L19.3032 19.3027" stroke="currentColor" stroke-linecap="round"></path><path d="M14 21.5V19.5" stroke="currentColor" stroke-linecap="round"></path><path d="M8.69663 19.3029L10.1108 17.8887" stroke="currentColor" stroke-linecap="round"></path><path d="M6.5 14L8.5 14" stroke="currentColor" stroke-linecap="round"></path><path d="M8.69663 8.69711L10.1108 10.1113" stroke="currentColor" stroke-linecap="round"></path></svg>
|
||||
</button>
|
||||
<button type="button" data-theme="dark" class="theme-btn rounded-full p-1.5 focus:outline-none">
|
||||
<svg class="size-6" viewBox="0 0 28 28" fill="none"><path d="M10.5 9.99914C10.5 14.1413 13.8579 17.4991 18 17.4991C19.0332 17.4991 20.0176 17.2902 20.9132 16.9123C19.7761 19.6075 17.109 21.4991 14 21.4991C9.85786 21.4991 6.5 18.1413 6.5 13.9991C6.5 10.8902 8.39167 8.22304 11.0868 7.08594C10.7089 7.98159 10.5 8.96597 10.5 9.99914Z" stroke="currentColor" stroke-linejoin="round"></path><path d="M16.3561 6.50754L16.5 5.5L16.6439 6.50754C16.7068 6.94752 17.0525 7.29321 17.4925 7.35607L18.5 7.5L17.4925 7.64393C17.0525 7.70679 16.7068 8.05248 16.6439 8.49246L16.5 9.5L16.3561 8.49246C16.2932 8.05248 15.9475 7.70679 15.5075 7.64393L14.5 7.5L15.5075 7.35607C15.9475 7.29321 16.2932 6.94752 16.3561 6.50754Z" fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"></path><path d="M20.3561 11.5075L20.5 10.5L20.6439 11.5075C20.7068 11.9475 21.0525 12.2932 21.4925 12.3561L22.5 12.5L21.4925 12.6439C21.0525 12.7068 20.7068 13.0525 20.6439 13.4925L20.5 14.5L20.3561 13.4925C20.2932 13.0525 19.9475 12.7068 19.5075 12.6439L18.5 12.5L19.5075 12.3561C19.9475 12.2932 20.2932 11.9475 20.3561 11.5075Z" fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full max-w-lg mx-auto text-center animate-fade-in-up">
|
||||
<div class="mb-12">
|
||||
<img src="/static/icons/logo.png" id="logo-image" alt="GEMINI BALANCER Logo" class="h-20 w-20 mx-auto mb-6 transition-transform duration-300 ease-in-out">
|
||||
<h1 class="text-4xl font-bold text-zinc-800 dark:text-zinc-100">
|
||||
GEMINI BALANCER
|
||||
</h1>
|
||||
<p class="mt-4 text-lg text-zinc-500 dark:text-zinc-400">
|
||||
一个强大且优雅的管理后台
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p class="mb-8 max-w-md mx-auto text-zinc-600 dark:text-zinc-400">
|
||||
请输入授权令牌以访问系统。
|
||||
</p>
|
||||
<form id="auth-form" class="max-w-md mx-auto" novalidate>
|
||||
<div class="relative flex items-center group transition-all duration-300 rounded-full bg-white dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700
|
||||
hover:-translate-y-1 hover:border-blue-500 dark:hover:border-blue-400
|
||||
focus-within:ring-2 focus-within:ring-blue-500 focus-within:ring-opacity-50 focus-within:border-transparent">
|
||||
|
||||
<!-- [修正#2] 移除了冲突的 focus:border-* 和 focus:ring-* 类 -->
|
||||
<input
|
||||
type="password"
|
||||
id="auth-token"
|
||||
name="auth_token"
|
||||
required
|
||||
placeholder="在此处粘贴您的令牌..."
|
||||
class="w-full h-14 pl-6 pr-20 rounded-full text-lg bg-white dark:bg-zinc-800 border-2 border-transparent focus:ring-blue-500/20 transition-all duration-300 outline-none text-zinc-800 dark:text-zinc-200"
|
||||
>
|
||||
<!-- [修正#1] 按钮恢复为原始的 span/i 结构,解决了椭圆问题 -->
|
||||
<button type="submit" id="login-button" class="absolute right-2 h-10 w-10 flex items-center justify-center rounded-full bg-blue-600 hover:bg-blue-700 text-white transition-all duration-300 transform hover:scale-110">
|
||||
<span id="button-icon" class="transition-opacity duration-200">
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
</span>
|
||||
<i id="button-spinner" class="fas fa-spinner fa-spin absolute transition-opacity duration-200 opacity-0 pointer-events-none"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="error-container" class="mt-6 max-w-md mx-auto min-h-[56px]"></div>
|
||||
</div>
|
||||
|
||||
<footer class="absolute bottom-6 text-zinc-500 dark:text-zinc-600 text-sm animate-fade-in-up" style="animation-delay: 0.2s;">
|
||||
<p>© 2024 GEMINI BALANCER. All Rights Reserved.</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- 块:页面脚本 (继承自旧模板的 block body_scripts) -->
|
||||
<script>
|
||||
|
||||
const ThemeManager = {
|
||||
// 初始化,绑定所有事件
|
||||
init: function() {
|
||||
this.html = document.documentElement;
|
||||
this.buttons = document.querySelectorAll('.theme-btn');
|
||||
this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
this.buttons.forEach(btn => {
|
||||
btn.addEventListener('click', () => this.setTheme(btn.dataset.theme));
|
||||
});
|
||||
this.mediaQuery.addEventListener('change', () => this.applyTheme());
|
||||
this.applyTheme();
|
||||
},
|
||||
// 核心函数:应用当前主题
|
||||
applyTheme: function() {
|
||||
let theme = this.getTheme();
|
||||
// 解析 'system' 模式
|
||||
if (theme === 'system') {
|
||||
theme = this.mediaQuery.matches ? 'dark' : 'light';
|
||||
}
|
||||
// 应用 'dark' 或 'light' 类到 <html>
|
||||
if (theme === 'dark') {
|
||||
this.html.classList.add('dark');
|
||||
} else {
|
||||
this.html.classList.remove('dark');
|
||||
}
|
||||
// 更新按钮的选中状态
|
||||
this.updateButtons();
|
||||
},
|
||||
// 设置新主题并存入 localStorage
|
||||
setTheme: function(theme) {
|
||||
localStorage.setItem('theme', theme);
|
||||
this.applyTheme();
|
||||
},
|
||||
|
||||
// 从 localStorage 获取主题,若无则默认为 'system'
|
||||
getTheme: function() {
|
||||
return localStorage.getItem('theme') || 'system';
|
||||
},
|
||||
// 更新按钮UI
|
||||
updateButtons: function() {
|
||||
const currentTheme = this.getTheme();
|
||||
this.buttons.forEach(btn => {
|
||||
if (btn.dataset.theme === currentTheme) {
|
||||
// 这是选中状态
|
||||
btn.classList.add('bg-white', 'dark:bg-zinc-700');
|
||||
} else {
|
||||
// 这是非选中状态
|
||||
btn.classList.remove('bg-white', 'dark:bg-zinc-700');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 初始化主题管理器
|
||||
ThemeManager.init();
|
||||
|
||||
// 原有的登录页逻辑
|
||||
localStorage.removeItem('bearerToken');
|
||||
const logo = document.getElementById('logo-image');
|
||||
if (logo) {
|
||||
let rotationAngle = 0;
|
||||
setInterval(() => {
|
||||
rotationAngle += 90;
|
||||
logo.style.transform = `rotate(${rotationAngle}deg)`;
|
||||
}, 2000);
|
||||
}
|
||||
// --- 登录表单逻辑 (优化版) ---
|
||||
const form = document.getElementById('auth-form');
|
||||
const tokenInput = document.getElementById('auth-token');
|
||||
const loginButton = document.getElementById('login-button');
|
||||
const buttonIcon = document.getElementById('button-icon');
|
||||
const buttonSpinner = document.getElementById('button-spinner');
|
||||
const errorContainer = document.getElementById('error-container');
|
||||
// [新增] UI状态管理函数,确保图标互斥
|
||||
function setButtonLoading(isLoading) {
|
||||
// 获取最新的元素引用
|
||||
const buttonIcon = document.getElementById('button-icon');
|
||||
const buttonSpinner = document.getElementById('button-spinner');
|
||||
const loginButton = document.getElementById('login-button');
|
||||
const tokenInput = document.getElementById('auth-token');
|
||||
if (isLoading) {
|
||||
// 隐藏箭头,显示Spinner
|
||||
buttonIcon.classList.add('opacity-0', 'pointer-events-none');
|
||||
buttonSpinner.classList.remove('opacity-0', 'pointer-events-none');
|
||||
loginButton.disabled = true;
|
||||
tokenInput.disabled = true;
|
||||
} else {
|
||||
// 显示箭头,隐藏Spinner
|
||||
buttonIcon.classList.remove('opacity-0', 'pointer-events-none');
|
||||
buttonSpinner.classList.add('opacity-0', 'pointer-events-none');
|
||||
loginButton.disabled = false;
|
||||
tokenInput.disabled = false;
|
||||
}
|
||||
}
|
||||
form.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
errorContainer.innerHTML = '';
|
||||
const token = tokenInput.value.trim();
|
||||
if (!token) {
|
||||
displayError('请输入验证令牌');
|
||||
return;
|
||||
}
|
||||
// 进入加载状态
|
||||
setButtonLoading(true);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ token: token }),
|
||||
});
|
||||
const data = await response.json();
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || '无效的令牌或服务器错误');
|
||||
}
|
||||
|
||||
localStorage.setItem('bearerToken', data.token);
|
||||
|
||||
displaySuccess('验证成功!正在进入...');
|
||||
|
||||
// 成功后,不需要重置按钮状态,因为页面即将跳转
|
||||
setTimeout(() => {
|
||||
window.location.href = '/dashboard';
|
||||
}, 800);
|
||||
} catch (error) {
|
||||
displayError(error.message);
|
||||
// 发生错误,恢复按钮为可交互状态
|
||||
setButtonLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
function displayError(message) {
|
||||
errorContainer.innerHTML = `<p class="text-red-500 font-medium p-3 bg-red-100 dark:bg-red-900/40 rounded-full">${message}</p>`;
|
||||
}
|
||||
function displaySuccess(message) {
|
||||
errorContainer.innerHTML = `<p class="text-green-500 font-medium p-3 bg-green-100 dark:bg-green-900/40 rounded-full">${message}</p>`;
|
||||
}
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const error = urlParams.get('error');
|
||||
if (error) {
|
||||
displayError(error);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
220
web/templates/base.html
Normal file
220
web/templates/base.html
Normal file
@@ -0,0 +1,220 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN" class=""> <!-- [核心] <html> 标签是 dark class 的应用目标 -->
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/png" href="/static/icons/favicon.png" sizes="any">
|
||||
<!--<script src="https://cdn.tailwindcss.com"></script>-->
|
||||
<link href="/static/css/output.css" rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.cn/css2?family=Pixelify+Sans:wght@400..700&display=swap">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<title>{% block title %}GEMINI BALANCER{% endblock %}</title>
|
||||
|
||||
{% block head_extra %}{% endblock %}
|
||||
</head>
|
||||
<body data-page-id="{{ PageID }}" class="bg-zinc-800 dark:bg-zinc-900 text-zinc-900 dark:text-zinc-200 overflow-hidden h-screen">
|
||||
|
||||
<div id="app-container" class="relative h-full w-full max-w-[1660px] mx-auto">
|
||||
<aside id="sidebar" class="absolute top-0 left-0 h-full z-10 flex flex-col w-16 lg:w-64 bg-zinc-900 border-r border-zinc-700/50 dark:bg-zinc-950 transition-all duration-300 ease-in-out">
|
||||
<!-- Logo 部分也做响应式处理 -->
|
||||
<div class="flex items-center justify-center h-16 border-b border-zinc-700/50 lg:pl-4 lg:pr-8 shrink-0">
|
||||
<img src="/static/icons/logo.png" alt="Logo" class="h-6 w-6 mr-3"/><span class="font-bold text-md text-zinc-100 hidden lg:inline">GEMINI BALANCER</span>
|
||||
</div>
|
||||
|
||||
<nav class="flex-1 flex flex-col py-2 space-y-1">
|
||||
{% block sidebar_nav %}
|
||||
<!-- 监控面板 -->
|
||||
<div class="nav-item-wrapper group"> <!-- .group 类现在被封装在 .nav-item-wrapper 内部了 -->
|
||||
<span class="nav-indicator"></span>
|
||||
<a href="/dashboard" class="nav-link">
|
||||
<!-- 关键: 响应式 transform 逻辑保留在 HTML 中,因为它更清晰 -->
|
||||
<i class="nav-icon fas fa-bar-chart -translate-x-1 lg:translate-x-0"></i>
|
||||
<span class="ml-3 whitespace-nowrap hidden lg:inline">监控面板</span>
|
||||
<span class="pixel-decoration hidden lg:inline">SYS.STATUS</span>
|
||||
</a>
|
||||
</div>
|
||||
<!-- 全局设置 -->
|
||||
<div class="nav-item-wrapper group">
|
||||
<span class="nav-indicator"></span>
|
||||
<a href="/settings" class="nav-link">
|
||||
<i class="nav-icon fas fa-cog -translate-x-1 lg:translate-x-0"></i>
|
||||
<span class="ml-3 whitespace-nowrap hidden lg:inline">全局设置</span>
|
||||
<span class="pixel-decoration hidden lg:inline">GL.SETTINGS</span>
|
||||
</a>
|
||||
</div>
|
||||
<!-- API 管理 -->
|
||||
<div class="nav-item-wrapper group">
|
||||
<span class="nav-indicator"></span>
|
||||
<a href="/keys" class="nav-link">
|
||||
<i class="nav-icon fas fa-key -translate-x-1 lg:translate-x-0"></i>
|
||||
<span class="ml-3 whitespace-nowrap hidden lg:inline">API 管理</span>
|
||||
<span class="pixel-decoration hidden lg:inline">KEY.MANAGER</span>
|
||||
</a>
|
||||
</div>
|
||||
<!-- 请求日志 -->
|
||||
<div class="nav-item-wrapper group">
|
||||
<span class="nav-indicator"></span>
|
||||
<a href="/logs" class="nav-link">
|
||||
<i class="nav-icon fas fa-clipboard-list -translate-x-1 lg:translate-x-0"></i>
|
||||
<span class="ml-3 whitespace-nowrap hidden lg:inline">请求日志</span>
|
||||
<span class="pixel-decoration hidden lg:inline">LOG.VIEW</span>
|
||||
</a>
|
||||
</div>
|
||||
<!-- 计划任务 -->
|
||||
<div class="nav-item-wrapper group">
|
||||
<span class="nav-indicator"></span>
|
||||
<a href="/tasks" class="nav-link">
|
||||
<i class="nav-icon fas fa-clock -translate-x-1 lg:translate-x-0"></i>
|
||||
<span class="ml-3 whitespace-nowrap hidden lg:inline">计划任务</span>
|
||||
<span class="pixel-decoration hidden lg:inline">TASK.SCHEDULER</span>
|
||||
</a>
|
||||
</div>
|
||||
<!-- WebChat -->
|
||||
<div class="nav-item-wrapper group">
|
||||
<span class="nav-indicator"></span>
|
||||
<a href="/chat" class="nav-link">
|
||||
<i class="nav-icon fas fa-comments -translate-x-1 lg:translate-x-0"></i>
|
||||
<span class="ml-3 whitespace-nowrap hidden lg:inline">WebChat</span>
|
||||
<span class="pixel-decoration hidden lg:inline">LIVE.CHAT</span>
|
||||
</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</nav>
|
||||
|
||||
|
||||
<div class="py-4 border-t border-zinc-700/50 shrink-0 flex justify-center lg:justify-start left-5 lg:pl-8 lg:pr-8">
|
||||
<div class="flex items-center space-x-4 -translate-x-2 lg:transform-none">
|
||||
|
||||
<!-- [精简版] 三态主题切换器 -->
|
||||
<div id="theme-switcher" class="hidden lg:inline-grid relative z-10 grid-cols-3 gap-1 rounded-full bg-gray-950/50 p-1 text-gray-500 dark:bg-white/10 dark:text-white">
|
||||
|
||||
<!-- 1. 系统模式按钮 -->
|
||||
<button type="button" data-theme="system" class="theme-btn rounded-full p-1 focus:outline-none">
|
||||
<svg class="h-6 w-6" viewBox="0 0 28 28" fill="none"><path d="M7.5 8.5C7.5 7.94772 7.94772 7.5 8.5 7.5H19.5C20.0523 7.5 20.5 7.94772 20.5 8.5V16.5C20.5 17.0523 20.0523 17.5 19.5 17.5H8.5C7.94772 17.5 7.5 17.0523 7.5 16.5V8.5Z" stroke="currentColor"></path><path d="M7.5 8.5C7.5 7.94772 7.94772 7.5 8.5 7.5H19.5C20.0523 7.5 20.5 7.94772 20.5 8.5V14.5C20.5 15.0523 20.0523 15.5 19.5 15.5H8.5C7.94772 15.5 7.5 15.0523 7.5 14.5V8.5Z" stroke="currentColor"></path><path d="M16.5 20.5V17.5H11.5V20.5M16.5 20.5H11.5M16.5 20.5H17.5M11.5 20.5H10.5" stroke="currentColor" stroke-linecap="round"></path></svg>
|
||||
</button>
|
||||
|
||||
<!-- 2. 亮色模式按钮 -->
|
||||
<button type="button" data-theme="light" class="theme-btn rounded-full p-1 focus:outline-none">
|
||||
<svg class="h-6 w-6" viewBox="0 0 28 28" fill="none"><circle cx="14" cy="14" r="3.5" stroke="currentColor"></circle><path d="M14 8.5V6.5" stroke="currentColor" stroke-linecap="round"></path><path d="M17.889 10.1115L19.3032 8.69727" stroke="currentColor" stroke-linecap="round"></path><path d="M19.5 14L21.5 14" stroke="currentColor" stroke-linecap="round"></path><path d="M17.889 17.8885L19.3032 19.3027" stroke="currentColor" stroke-linecap="round"></path><path d="M14 21.5V19.5" stroke="currentColor" stroke-linecap="round"></path><path d="M8.69663 19.3029L10.1108 17.8887" stroke="currentColor" stroke-linecap="round"></path><path d="M6.5 14L8.5 14" stroke="currentColor" stroke-linecap="round"></path><path d="M8.69663 8.69711L10.1108 10.1113" stroke="currentColor" stroke-linecap="round"></path></svg>
|
||||
</button>
|
||||
|
||||
<!-- 3. 暗色模式按钮 -->
|
||||
<button type="button" data-theme="dark" class="theme-btn rounded-full p-1 focus:outline-none">
|
||||
<svg class="h-6 w-6" viewBox="0 0 28 28" fill="none"><path d="M10.5 9.99914C10.5 14.1413 13.8579 17.4991 18 17.4991C19.0332 17.4991 20.0176 17.2902 20.9132 16.9123C19.7761 19.6075 17.109 21.4991 14 21.4991C9.85786 21.4991 6.5 18.1413 6.5 13.9991C6.5 10.8902 8.39167 8.22304 11.0868 7.08594C10.7089 7.98159 10.5 8.96597 10.5 9.99914Z" stroke="currentColor" stroke-linejoin="round"></path><path d="M16.3561 6.50754L16.5 5.5L16.6439 6.50754C16.7068 6.94752 17.0525 7.29321 17.4925 7.35607L18.5 7.5L17.4925 7.64393C17.0525 7.70679 16.7068 8.05248 16.6439 8.49246L16.5 9.5L16.3561 8.49246C16.2932 8.05248 15.9475 7.70679 15.5075 7.64393L14.5 7.5L15.5075 7.35607C15.9475 7.29321 16.2932 6.94752 16.3561 6.50754Z" fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"></path><path d="M20.3561 11.5075L20.5 10.5L20.6439 11.5075C20.7068 11.9475 21.0525 12.2932 21.4925 12.3561L22.5 12.5L21.4925 12.6439C21.0525 12.7068 20.7068 13.0525 20.6439 13.4925L20.5 14.5L20.3561 13.4925C20.2932 13.0525 19.9475 12.7068 19.5075 12.6439L18.5 12.5L19.5075 12.3561C19.9475 12.2932 20.2932 11.9475 20.3561 11.5075Z" fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
<!-- 2. 移动端形态 -->
|
||||
<button id="theme-cycler-btn" type="button" class="flex lg:hidden items-center justify-center rounded-full ml-4 bg-zinc-800 p-1 text-gray-200 dark:bg-white/10 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<span id="theme-cycler-icon" class="h-6 w-6"></span>
|
||||
</button>
|
||||
<div id="app-version" class="text-xs text-zinc-400 hidden lg:inline">v1.0.0</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main id="main-content-wrapper" class="absolute top-4 right-4 bottom-4 left-12 lg:left-60 z-20 rounded-2xl overflow-hidden flex flex-col bg-white dark:bg-zinc-800 shadow-xs ring-1 ring-black/15 dark:ring-white/15 shadow-main shadow-inner transition-all duration-300 ease-in-out pt-4 pb-4 pl-4 pr-1 lg:pt-16 lg:pb-16 lg:pl-16 lg:pr-6">
|
||||
<div class="flex-1 w-full h-full overflow-y-auto main-content-scroll lg:pr-6 translate-x-1 lg:translate-x-0 lg:transform-none">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{% block core_scripts %}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<script src="/static/js/main.js" type="module" defer></script>
|
||||
{% endblock core_scripts %}
|
||||
<!-- [核心] Block 2: 留给子页面的脚本扩展插槽 -->
|
||||
{% block page_scripts %}{% endblock page_scripts %}
|
||||
|
||||
{% block modals %}{% endblock modals %}
|
||||
|
||||
<!-- =================================================================== -->
|
||||
<!-- 全局异步任务中心 (Global Async Task Center) -->
|
||||
<!-- =================================================================== -->
|
||||
|
||||
<!-- 组件 A: 全局实时反馈信使 (The Toast/Snackbar Messenger) -->
|
||||
<div id="global-toast-container"
|
||||
class="fixed bottom-4 right-4 z-[9999] w-full max-w-sm space-y-3 pointer-events-none">
|
||||
<!-- Toast 通知将由JS动态插入到这里 -->
|
||||
<!-- 示例Toast结构 (供JS参考):
|
||||
<div class="toast-item">
|
||||
<div class="toast-icon toast-icon-loading">
|
||||
<i class="fas fa-spinner fa-spin"></i>
|
||||
</div>
|
||||
<div class="toast-content">
|
||||
<p class="toast-title">任务已开始</p>
|
||||
<p class="toast-message">正在批量添加API Keys...</p>
|
||||
</div>
|
||||
<button class="toast-close-btn">×</button>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<!-- 组件 B: 全局持久化任务中心 (The Task Hub) -->
|
||||
<div id="global-task-hub" class="fixed top-6 right-6 z-[9998]">
|
||||
<!-- 1. 触发器: 小铃铛图标 -->
|
||||
<button id="task-hub-trigger"
|
||||
class="relative h-10 w-10 flex items-center justify-center rounded-full bg-white/80 dark:bg-zinc-800/80 backdrop-blur-sm shadow-lg ring-1 ring-black/10 dark:ring-white/10 text-zinc-500 dark:text-zinc-400 hover:text-blue-500 dark:hover:text-blue-400 transition-colors duration-200">
|
||||
|
||||
<!-- SVG动画层 -->
|
||||
<svg class="absolute inset-0 h-full w-full" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle id="task-hub-countdown-ring"
|
||||
cx="12" cy="12" r="11.5" stroke="#3b82f6" stroke-width="1"
|
||||
class="origin-center -rotate-90"
|
||||
stroke-linecap="round"
|
||||
style="stroke-dasharray: 72.26; stroke-dashoffset: 72.26;"
|
||||
/>
|
||||
</svg>
|
||||
<i class="fas fa-bell text-lg"></i>
|
||||
<!-- 任务活动指示器 (默认隐藏) -->
|
||||
<span id="task-hub-indicator"
|
||||
class="absolute top-1 right-1 h-2.5 w-2.5 rounded-full bg-blue-500 border-2 border-white dark:border-zinc-800 hidden animate-pulse">
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<!-- 2. 浮动面板 (默认隐藏) -->
|
||||
<div id="task-hub-panel"
|
||||
class="absolute top-full right-0 mt-2 w-80 rounded-xl bg-white/90 dark:bg-zinc-800/90 backdrop-blur-lg shadow-2xl ring-1 ring-black/5 dark:ring-white/10 hidden origin-top-right transition-all"
|
||||
role="dialog" aria-modal="true" aria-labelledby="task-hub-title">
|
||||
<!-- [新增] 面板头部容器,用于定位倒计时条 -->
|
||||
<div class="relative">
|
||||
<!-- 面板头部 -->
|
||||
<div class="flex items-center justify-between p-3 border-b border-black/10 dark:border-white/10">
|
||||
<h3 id="task-hub-title" class="font-semibold text-sm text-zinc-800 dark:text-zinc-200">任务中心</h3>
|
||||
<button id="task-hub-clear-btn" class="text-xs text-zinc-500 hover:text-blue-500">清空已完成</button>
|
||||
</div>
|
||||
|
||||
<!-- [新增] 倒计时进度条 (默认宽度为0) -->
|
||||
<div id="task-hub-countdown-bar"
|
||||
class="absolute bottom-0 left-0 h-0.5 bg-gradient-to-r from-blue-500/30 to-blue-500 w-0 transition-all duration-[5000ms] ease-linear">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 任务列表容器 (可滚动) -->
|
||||
<div id="task-list-container" class="p-2 max-h-96 overflow-y-auto space-y-2">
|
||||
<!-- 任务项将由JS动态插入到这里 -->
|
||||
<!-- 示例任务项 (供JS参考):
|
||||
<div class="task-list-item">
|
||||
<div class="task-item-icon task-item-icon-success">
|
||||
<i class="fas fa-check"></i>
|
||||
</div>
|
||||
<div class="task-item-content">
|
||||
<p class="task-item-title">批量添加 Key</p>
|
||||
<p class="task-item-status">成功</p>
|
||||
</div>
|
||||
<div class="task-item-timestamp">1分钟前</div>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
<!-- 空状态 (默认隐藏) -->
|
||||
<div id="task-list-empty" class="hidden text-center text-xs text-zinc-400 py-10 px-4">
|
||||
<i class="fas fa-inbox fa-2x mb-2"></i>
|
||||
<p>当前没有正在运行或最近完成的任务</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
117
web/templates/chat.html
Normal file
117
web/templates/chat.html
Normal file
@@ -0,0 +1,117 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Web Chat - GEMINI BALANCER{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- [核心] 聊天界面的主容器,使用 flex 布局撑满整个可用空间 -->
|
||||
<div class="w-full h-full flex overflow-hidden rounded-lg border border-border" data-page-id="chat">
|
||||
|
||||
<!-- =================================================================== -->
|
||||
<!-- 1. 左侧栏: 会话列表 -->
|
||||
<!-- =================================================================== -->
|
||||
<aside class="w-[280px] h-full flex flex-col border-r border-border bg-muted/50 shrink-0">
|
||||
<!-- 侧边栏头部 -->
|
||||
<div class="p-4 border-b border-border shrink-0">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-xl font-bold tracking-tight">会话</h2>
|
||||
<button class="btn btn-ghost btn-icon btn-sm" aria-label="新建会话">
|
||||
<i class="fas fa-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="relative mt-4">
|
||||
<i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground"></i>
|
||||
<input class="input h-9 pl-10" placeholder="搜索会话...">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 会话列表 (可滚动) -->
|
||||
<div class="flex-grow overflow-y-auto main-content-scroll p-2">
|
||||
<!-- 示例会话 1: 激活状态 -->
|
||||
<a href="#" class="flex flex-col items-start gap-2 rounded-lg p-3 text-left text-sm transition-all hover:bg-accent bg-accent">
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<div class="font-semibold">关于Python异步编程</div>
|
||||
<div class="ml-auto text-xs text-muted-foreground">3:15 PM</div>
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground line-clamp-2">好的,我明白了。所以 aiohttp 客户端的 session 应该在...</div>
|
||||
</a>
|
||||
<!-- 示例会话 2: 非激活状态 -->
|
||||
<a href="#" class="flex flex-col items-start gap-2 rounded-lg p-3 text-left text-sm transition-all hover:bg-accent">
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<div class="font-semibold">Tailwind v4 迁移指南</div>
|
||||
<div class="ml-auto text-xs text-muted-foreground">1:45 PM</div>
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground line-clamp-2">我们已经成功解决了 JIT 编译器的缓存和 HSL 函数的问题...</div>
|
||||
</a>
|
||||
<!-- 示例会话 3: 非激活状态 -->
|
||||
<a href="#" class="flex flex-col items-start gap-2 rounded-lg p-3 text-left text-sm transition-all hover:bg-accent">
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<div class="font-semibold">数据库性能优化</div>
|
||||
<div class="ml-auto text-xs text-muted-foreground">昨天</div>
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground line-clamp-2">索引的创建确实是关键,特别是在有大量 JOIN 操作的查询中。</div>
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- =================================================================== -->
|
||||
<!-- 2. 右侧主区域: 聊天窗口 -->
|
||||
<!-- =================================================================== -->
|
||||
<main class="flex-1 flex flex-col h-full">
|
||||
<!-- 聊天窗口头部 -->
|
||||
<div class="flex items-center p-4 border-b border-border shrink-0">
|
||||
<h3 class="text-lg font-semibold">关于Python异步编程</h3>
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
<button class="btn btn-ghost btn-icon btn-sm" aria-label="清除会话">
|
||||
<i class="fas fa-eraser"></i>
|
||||
</button>
|
||||
<button class="btn btn-ghost btn-icon btn-sm" aria-label="更多选项">
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 消息区域 (可滚动) -->
|
||||
<div class="flex-grow p-6 overflow-y-auto main-content-scroll">
|
||||
<div class="space-y-6">
|
||||
<!-- 示例消息 1: 用户 -->
|
||||
<div class="flex items-start gap-4">
|
||||
<span class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-secondary text-secondary-foreground">
|
||||
<i class="fas fa-user"></i>
|
||||
</span>
|
||||
<div class="flex-1 space-y-2">
|
||||
<div class="rounded-lg bg-muted p-3">
|
||||
<p class="text-sm text-foreground">你能给我解释一下 aiohttp 客户端的 session 和 connector 管理吗?我总是搞不清楚什么时候应该创建,什么时候应该关闭。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 示例消息 2: 助手 (Gemini) -->
|
||||
<div class="flex items-start gap-4">
|
||||
<span class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground">
|
||||
<i class="fas fa-robot"></i>
|
||||
</span>
|
||||
<div class="flex-1 space-y-2">
|
||||
<div class="rounded-lg bg-primary/10 border border-primary/20 p-3">
|
||||
<p class="text-sm text-foreground">当然可以。这是一个非常经典的问题。简单来说,`ClientSession` 应该在你的应用程序的生命周期内尽可能地保持单例存在...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 输入区域 -->
|
||||
<div class="p-4 border-t border-border shrink-0 bg-background">
|
||||
<div class="relative">
|
||||
<textarea placeholder="输入消息..." class="input pr-20 resize-none" rows="1"></textarea>
|
||||
<div class="absolute top-1/2 right-3 -translate-y-1/2 flex items-center gap-2">
|
||||
<button class="btn btn-ghost btn-icon btn-sm" aria-label="附加文件">
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</button>
|
||||
<button class="btn btn-primary btn-sm">发送</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block page_scripts %}
|
||||
{% endblock page_scripts %}
|
||||
203
web/templates/dashboard.html
Normal file
203
web/templates/dashboard.html
Normal file
@@ -0,0 +1,203 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}监控面板 - Gemini Balancer{% endblock %}
|
||||
|
||||
{% block head_extra %}
|
||||
<link rel="stylesheet" href="/static/css/status-grid.css">
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<!-- [核心] 页面顶栏:标题与全局控制器 -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-3xl font-bold tracking-tight">数据总览</h2>
|
||||
<div class="flex items-center space-x-3">
|
||||
<!-- [最终形态] 完全自定义的下拉选择器组件 -->
|
||||
<div class="relative w-full" data-custom-select-container>
|
||||
|
||||
<!-- 1. 隐藏的真实 <select>,用于表单提交 -->
|
||||
<select name="group-filter" id="globalGroupFilter" class="hidden">
|
||||
<option value="">所有分组</option>
|
||||
<!-- JS将会动态填充更多选项 -->
|
||||
</select>
|
||||
|
||||
<!-- 2. "诱饵" - 我们看到的、可以完全自定义的按钮 -->
|
||||
<button type="button" class="custom-select-trigger h-9 w-full rounded-md border border-zinc-300 bg-zinc-50 py-1 pl-4 pr-8 text-left text-sm shadow-sm transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-inset dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<span class="block truncate">所有分组</span>
|
||||
<span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<svg class="h-4 w-4 text-zinc-700 dark:text-zinc-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m6 8 4 4 4-4"/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<!-- 3. "真正的菜单" - 可以被我们完全样式化的下拉面板 -->
|
||||
<div class="custom-select-panel absolute z-10 mt-1 w-full rounded-lg bg-zinc-50 dark:bg-zinc-900 shadow-xl p-1 hidden">
|
||||
<!-- 选项将会由 JS 动态生成并插入此处 -->
|
||||
<!-- 示例选项 -->
|
||||
<div class="custom-select-option" data-value="">所有分组</div>
|
||||
<div class="custom-select-option" data-value="group1">分组一</div>
|
||||
<div class="custom-select-option" data-value="group2">分组二</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- [核心] 快速处置按钮 (触发模态框) -->
|
||||
<button id="quick-action-btn" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors h-9 px-4 py-2 bg-blue-500 text-white shadow hover:bg-blue-500/90">
|
||||
<i class="fas fa-bolt"></i>
|
||||
<span class="hidden lg:inline">快速处置</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- [组件蓝图] 任何页面都可以复制此结构来创建一个新的滑动标签实例 -->
|
||||
<div class="py-2">
|
||||
<div class="w-full overflow-x-auto scrollbar-hide">
|
||||
<!-- 1. 容器: 必须有 `data-sliding-tabs-container` 属性 -->
|
||||
<div role="tablist" class="relative inline-flex h-10 items-center justify-center inset-shadow-sm/25 rounded-lg bg-zinc-800/50 dark:bg-zinc-950 p-1" data-sliding-tabs-container>
|
||||
|
||||
<!-- 2. 指示器: 必须有 `data-tab-indicator` 属性 -->
|
||||
<div class="absolute left-0 h-[calc(100%-0.5rem)] rounded-md bg-white dark:bg-zinc-700 shadow-sm" data-tab-indicator style="transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);"></div>
|
||||
<!-- 3. 标签项: 必须有 `data-tab-item` 属性。激活项应有 `tab-active` class -->
|
||||
<a href="#" role="tab" class="tab-item tab-active" data-tab-item>数据总览</a>
|
||||
<a href="#" role="tab" class="tab-item" data-tab-item>密钥管理</a>
|
||||
<a href="#" role="tab" class="tab-item" data-tab-item>性能分析</a>
|
||||
<a href="#" role="tab" class="tab-item" data-tab-item>请求日志</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- [核心] 标签页内容区域 -->
|
||||
<div class="mt-8">
|
||||
<!-- 标签A: 数据总览 (默认显示) -->
|
||||
<div id="tab-content-overview" class="space-y-6">
|
||||
|
||||
<!-- 第一行: 四核心指标卡片 -->
|
||||
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-4">
|
||||
<!-- 卡片1: 密钥统计 -->
|
||||
<div class="ds-stats-card bg-card text-card-foreground">
|
||||
<div class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<div class="text-sm font-medium">密钥统计</div>
|
||||
<i class="h-4 w-4 text-zinc-500 fas fa-key"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-2xl font-bold">
|
||||
<span id="stat-valid-keys">0</span> / <span id="stat-total-keys">0</span>
|
||||
</div>
|
||||
<p class="text-xs text-zinc-500 dark:text-zinc-400">有效密钥 / 总密钥数</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 卡片2: 请求总览 -->
|
||||
<div class="ds-stats-card bg-card text-card-foreground">
|
||||
<div class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<div class="text-sm font-medium">请求总览</div>
|
||||
<i class="h-4 w-4 text-zinc-500 fas fa-exchange-alt"></i>
|
||||
</div>
|
||||
<div>
|
||||
<!-- [ID保留] 沿用旧ID: stat-calls-24h -->
|
||||
<div class="text-2xl font-bold" id="stat-calls-24h">0</div>
|
||||
<p class="text-xs text-zinc-500 dark:text-zinc-400">24小时请求数</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 卡片3: Token消耗 -->
|
||||
<div class="ds-stats-card bg-card text-card-foreground">
|
||||
<div class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<div class="text-sm font-medium">Token 消耗</div>
|
||||
<i class="h-4 w-4 text-zinc-500 fas fa-coins"></i>
|
||||
</div>
|
||||
<div>
|
||||
<!-- [ID新增] 新增ID: stat-tokens-24h -->
|
||||
<div class="text-2xl font-bold" id="stat-tokens-24h">0</div>
|
||||
<p class="text-xs text-zinc-500 dark:text-zinc-400">24小时消耗</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 卡片4: 请求成功率 -->
|
||||
<div class="ds-stats-card bg-card text-card-foreground">
|
||||
<div class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<div class="text-sm font-medium">请求成功率</div>
|
||||
<i class="h-4 w-4 text-zinc-500 fas fa-check-circle"></i>
|
||||
</div>
|
||||
<div>
|
||||
<!-- [ID新增] 新增ID: stat-success-rate-24h -->
|
||||
<div class="text-2xl font-bold" id="stat-success-rate-24h">0%</div>
|
||||
<p class="text-xs text-zinc-500 dark:text-zinc-400">24小时成功率</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 第二行: API 状态分布 (通栏) -->
|
||||
<div class="ds-stats-card bg-card text-card-foreground">
|
||||
<h3 class="font-semibold leading-none tracking-tight mb-4">API 状态分布</h3>
|
||||
<!-- [容器保留] 沿用旧ID, JS可无缝对接 -->
|
||||
<div id="poolGridContainer" class="relative p-2 canvas-placeholder" style="height: 60px;">
|
||||
<canvas id="poolGridCanvas" class="absolute top-0 left-0 w-full h-full"></canvas>
|
||||
</div>
|
||||
<!-- [图例保留] 沿用旧ID -->
|
||||
<div id="poolStatusLegend" class="flex flex-wrap justify-center gap-x-4 gap-y-2 text-xs mt-4">
|
||||
<span class="legend-item" data-status-hover="ACTIVE"><i class="fas fa-square text-green-500"></i> ACTIVE (<span id="legend-active">0</span>)</span>
|
||||
<span class="legend-item" data-status-hover="PENDING"><i class="fas fa-square text-gray-400"></i> PENDING (<span id="legend-pending">0</span>)</span>
|
||||
<span class="legend-item" data-status-hover="COOLDOWN"><i class="fas fa-square text-yellow-500"></i> COOLDOWN (<span id="legend-cooldown">0</span>)</span>
|
||||
<span class="legend-item" data-status-hover="DISABLED"><i class="fas fa-square text-orange-500"></i> DISABLED (<span id="legend-disabled">0</span>)</span>
|
||||
<span class="legend-item" data-status-hover="BANNED"><i class="fas fa-square text-red-500"></i> BANNED (<span id="legend-banned">0</span>)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 第三行: 左侧图表 + 右侧排行 -->
|
||||
<div class="grid gap-6 lg:grid-cols-5">
|
||||
<!-- 左侧大卡片: 请求趋势图 (占位) -->
|
||||
<div class="ds-stats-card bg-card text-card-foreground lg:col-span-3">
|
||||
<h3 class="font-semibold leading-none tracking-tight">请求趋势</h3>
|
||||
<p class="text-sm text-zinc-500 dark:text-zinc-400 mb-4">成功与失败请求数</p>
|
||||
<!-- [容器新增] 新增一个清晰的ID给新图表 -->
|
||||
<div class="h-[350px] flex items-center justify-center text-zinc-400">
|
||||
<canvas id="successFailChart"></canvas>
|
||||
<span>图表加载中...</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧小卡片: 模型排行 (占位) -->
|
||||
<div class="ds-stats-card bg-card text-card-foreground lg:col-span-2">
|
||||
<h3 class="font-semibold leading-none tracking-tight">模型排行</h3>
|
||||
<p class="text-sm text-zinc-500 dark:text-zinc-400 mb-4">24小时内调用次数</p>
|
||||
<!-- [容器新增] 新增一个列表容器ID -->
|
||||
<div id="model-ranking-list" class="space-y-4 h-[350px] overflow-y-auto pr-2">
|
||||
<!-- JS将在此处动态填充排行列表 -->
|
||||
<div class="flex items-center text-sm text-zinc-500">排行数据加载中...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 其他标签页的内容区域 (暂时留空) -->
|
||||
<div id="tab-content-keys" class="hidden">密钥管理内容</div>
|
||||
<div id="tab-content-performance" class="hidden">性能分析内容</div>
|
||||
<div id="tab-content-requests" class="hidden">请求日志内容</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- [占位符] 快速处置模态框 -->
|
||||
<div id="quick-action-modal" class="fixed inset-0 bg-black/50 z-50 hidden items-center justify-center">
|
||||
<div class="bg-white dark:bg-zinc-800 rounded-lg shadow-xl w-full max-w-md p-6">
|
||||
<h3 class="text-lg font-medium">快速处置</h3>
|
||||
<p class="text-sm text-zinc-500 mt-2">模态框内容占位符,未来将在此处实现具体功能。</p>
|
||||
<div class="mt-4 flex justify-end">
|
||||
<button id="close-modal-btn" class="px-4 py-2 text-sm rounded-md bg-zinc-200 dark:bg-zinc-700">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block page_scripts %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
});
|
||||
</script>
|
||||
<!-- [保留] 核心UI依赖 -->
|
||||
<script src="/static/js/status-grid.js" defer></script>
|
||||
<!-- [保留] 核心UI主程序 (需要进行适配) -->
|
||||
<script src="/static/js/dashboard.js" defer></script>
|
||||
{% endblock %}
|
||||
29
web/templates/error.html
Normal file
29
web/templates/error.html
Normal file
@@ -0,0 +1,29 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}发生错误 - Gemini Balance{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container mx-auto mt-10 px-4">
|
||||
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-8 rounded-lg shadow-lg" role="alert">
|
||||
<div class="flex">
|
||||
<div class="py-1">
|
||||
<svg class="fill-current h-8 w-8 text-red-500 mr-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
||||
<path d="M10 0C4.486 0 0 4.486 0 10s4.486 10 10 10 10-4.486 10-10S15.514 0 10 0zm0 18c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8zm-1-5h2v2h-2v-2zm0-8h2v6h-2V5z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-2xl font-bold mb-2">服务出现内部错误</p>
|
||||
<p class="text-base">很抱歉,在处理您的请求时遇到了一个问题。</p>
|
||||
{% if error %}
|
||||
<div class="mt-4 bg-red-200 p-3 rounded">
|
||||
<p class="font-mono text-sm">
|
||||
<strong>错误详情:</strong> {{ error }}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
<a href="/" class="mt-6 inline-block bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">
|
||||
返回首页
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
943
web/templates/keys.html
Normal file
943
web/templates/keys.html
Normal file
@@ -0,0 +1,943 @@
|
||||
|
||||
{% extends "base.html" %}
|
||||
{% block title %}API 分组管理 - GEMINI BALANCER{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="w-full h-full flex flex-col pl-0 pr-3 lg:px-0">
|
||||
<!-- [核心] 页面顶栏:标题与全局控制器 -->
|
||||
<div class="flex items-center justify-between mb-6 shrink-0">
|
||||
<h2 class="text-3xl font-bold tracking-tight">API 管理</h2>
|
||||
<button class="lg:hidden text-zinc-500 dark:text-zinc-400">
|
||||
<i class="fas fa-search text-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col lg:flex-row flex-grow gap-x-4 overflow-hidden min-h-0">
|
||||
|
||||
<!-- 左侧分栏: Group 列表 -->
|
||||
<aside class="w-full lg:w-1/4 flex flex-col p-0 relative mb-3 lg:mb-0 lg:min-h-0 shrink-0">
|
||||
<!-- 桌面端搜索框 -->
|
||||
<div class="relative mb-3 mr-4 shrink-0 hidden lg:block">
|
||||
<input type="text" class="pl-8 h-9 bg-transparent border border-gray-300 dark:border-gray-700 dark:text-white w-full rounded-md text-sm transition-colors duration-200 ease-in-out focus:outline-none focus:border-blue-500 dark:focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:ring-inset" placeholder="Search Groups...">
|
||||
<i class="fas fa-search w-4 absolute text-gray-400 top-1/2 transform -translate-y-1/2 left-3"></i>
|
||||
</div>
|
||||
|
||||
<!-- 移动端首屏控件 -->
|
||||
<div class="lg:hidden flex items-center gap-x-2">
|
||||
<div class="mobile-group-selector">
|
||||
<div>
|
||||
<h3 class="font-semibold text-sm">Loading...</h3>
|
||||
<p class="card-sub-text">当前选择</p>
|
||||
</div>
|
||||
<button id="group-menu-toggle" class="text-zinc-500 dark:text-zinc-400">
|
||||
<i class="fas fa-bars text-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="add-group-btn-container-mobile" class="shrink-0">
|
||||
<button class="add-group-btn add-group-btn-mobile flex items-center justify-center rounded-lg border-2 border-dashed transition-all duration-200">
|
||||
<i class="fas fa-plus text-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="group-list-collapsible" class="hidden lg:flex flex-col flex-grow overflow-y-auto pr-1">
|
||||
<div id="desktop-group-cards-list" class="hidden lg:flex flex-col">
|
||||
|
||||
<div class="card-list-content space-y-2"><!-- JS通过 desktopGroupContainer 选择器填充这里 --></div>
|
||||
|
||||
<div id="add-group-btn-container" class="sticky pr-1 bottom-0 mt-2 mr-2 shrink-0 pt-2 bg-white dark:bg-zinc-800">
|
||||
<button class="add-group-btn add-group-btn-desktop flex items-center justify-center rounded-lg border-2 border-dashed transition-all duration-200">
|
||||
<i class="fas fa-plus text-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- [移动端] 列表 -->
|
||||
<div id="mobile-group-cards-list" class="block lg:hidden space-y-2"></div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{# 右侧主内容区: Group 详情与 API 管理 #}
|
||||
<main class="w-full lg:w-3/4 flex flex-grow flex-col gap-y-4 overflow-y-auto lg:min-h-0">
|
||||
<!-- Group Dashboard -->
|
||||
<div id="group-dashboard" class="bg-zinc-100 dark:bg-zinc-900/50 border border-zinc-200 dark:border-zinc-700/60 rounded-lg p-4 shrink-0">
|
||||
<!-- Header -->
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-bold">默认分组 (Default)</h2>
|
||||
<div class="flex items-center gap-x-3 text-zinc-400">
|
||||
<button data-action="clone-group" class="hover:text-blue-500 transition-colors duration-200" title="克隆分组"><i class="fas fa-clone"></i></button>
|
||||
<button data-action="edit-group" class="hover:text-blue-500 transition-colors duration-200" title="编辑分组"><i class="fas fa-cog"></i></button>
|
||||
<button data-action="open-settings" class="hover:text-blue-500 transition-colors duration-200" title="高级请求设置"><i class="fas fa-sliders-h"></i></button>
|
||||
<button data-action="delete-group" class="hover:text-red-500 transition-colors duration-200" title="删除分组"><i class="fas fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Stats -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 text-center">
|
||||
<!-- 密钥统计 -->
|
||||
<div>
|
||||
<p class="text-2xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-100">7 / 10</p>
|
||||
<p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">有效密钥 / 总密钥数</p>
|
||||
<div class="mt-2 h-4"></div> <!-- 健康度图标占位 -->
|
||||
</div>
|
||||
<!-- 请求总览 -->
|
||||
<div>
|
||||
<p class="text-2xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-100">0</p>
|
||||
<p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">24小时请求数</p>
|
||||
<div class="mt-2 h-4"></div> <!-- 健康度图标占位 -->
|
||||
</div>
|
||||
<!-- Token 消耗 -->
|
||||
<div>
|
||||
<p class="text-2xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-100">0</p>
|
||||
<p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">24小时消耗</p>
|
||||
<div class="mt-2 h-4"></div> <!-- 健康度图标占位 -->
|
||||
</div>
|
||||
<!-- 请求成功率 -->
|
||||
<div>
|
||||
<p class="text-2xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-100">0%</p>
|
||||
<p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">24小时成功率</p>
|
||||
<div class="mt-2 h-4"></div> <!-- 健康度图标占位 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- API Management Area -->
|
||||
<div class="bg-zinc-100 border border-zinc-200 dark:border-zinc-700/60 dark:bg-zinc-900/50 rounded-lg p-4 flex-grow flex flex-col lg:min-h-0">
|
||||
<!-- Controls Header (已进行响应式重构 - V3 Final) -->
|
||||
<div class="flex flex-col gap-y-3 mb-4">
|
||||
|
||||
<!--
|
||||
============================================================
|
||||
移动端第一行 / 桌面端完整操作行
|
||||
============================================================
|
||||
- 使用 justify-between 将左右两组推开。
|
||||
-->
|
||||
<div class="flex items-center justify-between">
|
||||
<!-- 左侧主要操作 (始终显示) -->
|
||||
<div class="flex items-center gap-x-2">
|
||||
<button id="add-api-btn" class="px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors">
|
||||
<i class="fas fa-plus mr-1"></i> KEY
|
||||
</button>
|
||||
<button id="delete-api-btn" class="px-3 py-1.5 text-sm bg-red-600/80 text-white rounded-md hover:bg-red-700 transition-colors">
|
||||
<i class="fas fa-minus mr-1"></i> KEY
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 右侧操作组 -->
|
||||
<div class="flex items-center gap-x-2">
|
||||
<!-- 移动端: 快速处置 -->
|
||||
<div class="lg:hidden items-center gap-x-2 relative inline-block custom-select" id="mobile-quick-actions-dropdown">
|
||||
<button class="px-3 py-1.5 text-sm bg-zinc-200 dark:bg-zinc-700 rounded-md hover:bg-zinc-300 dark:hover:bg-zinc-600 transition-colors custom-select-trigger">
|
||||
<i class="fas fa-bolt"></i>
|
||||
</button>
|
||||
<div class="dropdown-panel z-30 w-48 custom-select-panel hidden" id="mobile-quick-actions-panel">
|
||||
<!-- Dropdown content will be injected by JavaScript -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 桌面端操作组 (lg及以上屏幕显示) -->
|
||||
<div class="hidden lg:flex items-center gap-x-2">
|
||||
<div class="relative inline-block batch-action-dropdown custom-select">
|
||||
<button class="px-3 py-1.5 text-sm bg-zinc-200 dark:bg-zinc-700 rounded-md hover:bg-zinc-300 dark:hover:bg-zinc-600 transition-colors flex items-center gap-x-2 batch-action-btn custom-select-trigger">
|
||||
<span>批量操作</span>
|
||||
<i class="fas fa-chevron-down text-xs text-zinc-400"></i>
|
||||
</button>
|
||||
<div class="dropdown-panel batch-action-panel custom-select-panel hidden">
|
||||
<div class="py-1">
|
||||
<button data-batch-action="copy-to-clipboard" class="menu-item">
|
||||
<i class="fas fa-copy menu-item-icon menu-item-icon-neutral"></i>
|
||||
<span>批量复制</span>
|
||||
</button>
|
||||
<button data-batch-action="set-status-active" class="menu-item">
|
||||
<i class="fas fa-check-circle menu-item-icon text-green-500"></i>
|
||||
<span>批量启用</span>
|
||||
</button>
|
||||
<button data-batch-action="set-status-disabled" class="menu-item">
|
||||
<i class="fas fa-ban menu-item-icon text-yellow-500"></i>
|
||||
<span>批量禁用</span>
|
||||
</button>
|
||||
<button data-batch-action="revalidate" class="menu-item">
|
||||
<i class="fas fa-rocket menu-item-icon text-blue-500"></i>
|
||||
<span>批量验证</span>
|
||||
</button>
|
||||
|
||||
<!-- Use the new .menu-divider class -->
|
||||
<div class="menu-divider"></div>
|
||||
|
||||
<!-- Use the base class and the danger modifier -->
|
||||
<button data-batch-action="delete" class="menu-item menu-item-danger">
|
||||
<i class="fas fa-trash-alt menu-item-icon"></i> <!-- Color is inherited from .menu-item-danger -->
|
||||
<span>批量移除</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 桌面端: 快速处置 -->
|
||||
<div class="relative inline-block custom-select" id="desktop-quick-actions-dropdown">
|
||||
<button class="px-3 py-1.5 text-sm bg-zinc-200 dark:bg-zinc-700 rounded-md hover:bg-zinc-300 dark:hover:bg-zinc-600 transition-colors flex items-center gap-x-2 custom-select-trigger">
|
||||
<i class="fas fa-bolt"></i>
|
||||
<span>快速处置</span>
|
||||
</button>
|
||||
<div class="dropdown-panel z-30 w-48 custom-select-panel hidden" id="desktop-quick-actions-panel">
|
||||
<!-- Dropdown content will be injected by JavaScript -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative inline-block text-left">
|
||||
<!-- 1. The Trigger Button -->
|
||||
<button data-toggle="custom-select"
|
||||
data-target="#desktop-multifunction-panel"
|
||||
class="px-3 py-1.5 text-sm bg-zinc-200 dark:bg-zinc-700 rounded-md hover:bg-zinc-300 dark:hover:bg-zinc-600 transition-colors">
|
||||
<i class="fas fa-ellipsis-h"></i>
|
||||
</button>
|
||||
|
||||
<!-- 2. The Dropdown Panel (initially hidden) -->
|
||||
<div id="desktop-multifunction-panel"
|
||||
class="custom-select-panel absolute right-0 z-30 mt-2 min-w-max origin-top-right rounded-md bg-white dark:bg-zinc-800 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none hidden">
|
||||
<!-- JS will inject menu items here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
============================================================
|
||||
移动端第二行 / 桌面端完整过滤行
|
||||
============================================================
|
||||
- display: lg:hidden 表示此行在桌面端将被隐藏,仅用于移动端布局。
|
||||
-->
|
||||
<div class="flex items-center justify-between lg:hidden">
|
||||
<!-- 左侧:状态选择器 -->
|
||||
<div class="custom-select relative status-filter-select">
|
||||
<select class="hidden">
|
||||
<option value="all">所有状态</option>
|
||||
<option value="active">有效</option>
|
||||
<option value="cooldown">冷却</option>
|
||||
<option value="pending">待验证</option>
|
||||
<option value="disabled">禁用</option>
|
||||
<option value="banned">无效</option>
|
||||
</select>
|
||||
<div class="custom-select-trigger flex items-center justify-between w-32 cursor-pointer rounded-md border border-zinc-300 dark:border-zinc-600 bg-transparent px-3 py-1.5">
|
||||
<span>所有状态</span>
|
||||
<i class="fas fa-chevron-down text-xs text-zinc-400"></i>
|
||||
</div>
|
||||
<div class="custom-select-panel hidden absolute z-10 mt-1 w-full rounded-md border border-zinc-200 bg-white shadow-lg dark:border-zinc-600 dark:bg-zinc-700"></div>
|
||||
</div>
|
||||
<!-- 右侧:批量操作 和 多功能按钮 -->
|
||||
<div class="flex items-center gap-x-2">
|
||||
<div class="relative inline-block batch-action-dropdown custom-select">
|
||||
<button class="px-3 py-1.5 text-sm bg-zinc-200 dark:bg-zinc-700 rounded-md hover:bg-zinc-300 dark:hover:bg-zinc-600 transition-colors flex items-center gap-x-2 batch-action-btn custom-select-trigger">
|
||||
<span>批量操作</span>
|
||||
<i class="fas fa-chevron-down text-xs text-zinc-400"></i>
|
||||
</button>
|
||||
<div class="dropdown-panel batch-action-panel custom-select-panel hidden">
|
||||
<div class="py-1">
|
||||
<button data-batch-action="copy-to-clipboard" class="menu-item">
|
||||
<i class="fas fa-copy menu-item-icon menu-item-icon-neutral"></i>
|
||||
<span>批量复制</span>
|
||||
</button>
|
||||
<button data-batch-action="set-status-active" class="menu-item">
|
||||
<i class="fas fa-check-circle menu-item-icon text-green-500"></i>
|
||||
<span>批量启用</span>
|
||||
</button>
|
||||
<button data-batch-action="set-status-disabled" class="menu-item">
|
||||
<i class="fas fa-ban menu-item-icon text-yellow-500"></i>
|
||||
<span>批量禁用</span>
|
||||
</button>
|
||||
<button data-batch-action="revalidate" class="menu-item">
|
||||
<i class="fas fa-rocket menu-item-icon text-blue-500"></i>
|
||||
<span>批量验证</span>
|
||||
</button>
|
||||
|
||||
<!-- Use the new .menu-divider class -->
|
||||
<div class="menu-divider"></div>
|
||||
|
||||
<!-- Use the base class and the danger modifier -->
|
||||
<button data-batch-action="delete" class="menu-item menu-item-danger">
|
||||
<i class="fas fa-trash-alt menu-item-icon"></i> <!-- Color is inherited from .menu-item-danger -->
|
||||
<span>批量移除</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative inline-block text-left">
|
||||
<!-- 1. The Trigger Button -->
|
||||
<button data-toggle="custom-select"
|
||||
data-target="#desktop-multifunction-panel"
|
||||
class="px-3 py-1.5 text-sm bg-zinc-200 dark:bg-zinc-700 rounded-md hover:bg-zinc-300 dark:hover:bg-zinc-600 transition-colors">
|
||||
<i class="fas fa-ellipsis-h"></i>
|
||||
</button>
|
||||
|
||||
<!-- 2. The Dropdown Panel (initially hidden) -->
|
||||
<div id="mobile-multifunction-panel"
|
||||
class="custom-select-panel absolute right-0 z-30 mt-2 min-w-max origin-top-right rounded-md bg-white dark:bg-zinc-800 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none hidden">
|
||||
<!-- JS will inject menu items here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
============================================================
|
||||
移动端第三行 / 合并入桌面端的第二行
|
||||
============================================================
|
||||
- 这个容器在两个断点都存在,但其内部元素的可见性不同。
|
||||
-->
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<!-- 桌面端左侧: 状态选择器 -->
|
||||
<div class="custom-select relative hidden lg:block status-filter-select">
|
||||
<select class="hidden">
|
||||
<option value="all">所有状态</option>
|
||||
<option value="active">有效</option>
|
||||
<option value="cooldown">冷却</option>
|
||||
<option value="pending">待验证</option>
|
||||
<option value="disabled">禁用</option>
|
||||
<option value="banned">无效</option>
|
||||
</select>
|
||||
<div class="custom-select-trigger flex items-center justify-between w-32 cursor-pointer rounded-md border border-zinc-300 dark:border-zinc-600 bg-transparent px-3 py-1.5">
|
||||
<span>所有状态</span>
|
||||
<i class="fas fa-chevron-down text-xs text-zinc-400"></i>
|
||||
</div>
|
||||
<div class="custom-select-panel hidden absolute z-10 mt-1 w-full rounded-md border border-zinc-200 bg-white shadow-lg dark:border-zinc-600 dark:bg-zinc-700"></div>
|
||||
</div>
|
||||
|
||||
<!-- 移动端左侧: 搜索图标 -->
|
||||
<button id="mobile-search-btn" class="px-2 py-1.5 text-sm bg-transparent rounded-md hover:bg-zinc-200 dark:hover:bg-zinc-700 transition-colors lg:hidden">
|
||||
<i class="fas fa-search text-zinc-500"></i>
|
||||
</button>
|
||||
|
||||
<!-- 右侧组 (通用) -->
|
||||
<div class="flex items-center gap-x-3">
|
||||
<!-- 桌面端搜索框 -->
|
||||
<div id="desktop-search-container" class="relative hidden lg:block w-full sm:w-48">
|
||||
<i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-zinc-500 dark:text-zinc-300"></i>
|
||||
<input type="text" id="desktop-search-input" placeholder="模糊查找..." class="w-full pl-9 pr-3 py-1.5 text-sm text-zinc-500 dark:text-zinc-400 border border-zinc-300 dark:border-zinc-600 rounded-md bg-transparent focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
<!-- 通用分页与选择 -->
|
||||
<div class="custom-select relative items-per-page-select"> <!-- [ADD CLASS] -->
|
||||
<select class="hidden">
|
||||
<option value="20" selected>20</option> <!-- [ADD VALUE & SELECTED] -->
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
<div class="custom-select-trigger flex items-center justify-between w-28 cursor-pointer rounded-md border border-zinc-300 dark:border-zinc-600 bg-transparent px-3 py-1.5">
|
||||
<span>20 / 页</span>
|
||||
<i class="fas fa-chevron-down text-xs text-zinc-400"></i>
|
||||
</div>
|
||||
<div class="custom-select-panel hidden absolute z-30 mt-1 w-full rounded-md border border-zinc-200 bg-white shadow-lg dark:border-zinc-600 dark:bg-zinc-700"></div>
|
||||
</div>
|
||||
<div class="flex items-center gap-x-2">
|
||||
<input type="checkbox" id="select-all" class="h-4 w-4 rounded border-zinc-300 text-blue-600 focus:ring-blue-500">
|
||||
<label for="select-all" class="text-zinc-600 dark:text-zinc-300">全选</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- API List Container -->
|
||||
<div id="api-list-container" class="flex-grow overflow-y-auto pr-2 -mr-2 main-content-scroll">
|
||||
<!-- Content will be rendered here by JS -->
|
||||
</div>
|
||||
|
||||
<!-- Pagination Controls -->
|
||||
<div class="pagination-controls flex justify-center items-center mt-4 pt-4 border-t border-zinc-200 dark:border-zinc-700 space-x-2 lg:shrink-0"> <!-- [ADD CLASS] -->
|
||||
<!-- Content will be rendered here by JS -->
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modals %}
|
||||
<!-- Add/Edit Group Modal -->
|
||||
<div id="keygroup-modal" class="modal-overlay hidden">
|
||||
<div class="modal-panel max-w-3xl max-h-[90vh]">
|
||||
<!-- Header -->
|
||||
<div class="modal-header shrink-0">
|
||||
<h2 id="modal-title" class="modal-title">创建新的 Key Group</h2>
|
||||
<button data-modal-close="keygroup-modal" id="modal-close-btn" class="modal-close-btn">
|
||||
<i class="fas fa-times text-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Form Body -->
|
||||
<div class="modal-body flex-grow overflow-y-auto pr-4 -mr-4">
|
||||
<div class="grid grid-cols-2 gap-x-6 gap-y-4">
|
||||
<!-- Name -->
|
||||
<div>
|
||||
<label for="group-name" class="flex items-center modal-label">
|
||||
<span>分组名称<span class="text-red-500">*</span></span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="分组的唯一程序内标识符,例如 'default-group'。创建后不可修改。"></i>
|
||||
</label>
|
||||
<input type="text" id="group-name" class="modal-input">
|
||||
</div>
|
||||
<!-- Display Name -->
|
||||
<div>
|
||||
<label for="group-display-name" class="flex items-center modal-label">
|
||||
<span>显示名称<span class="text-red-500">*</span></span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="显示在UI上的名称,方便识别,例如'默认分组'。"></i>
|
||||
</label>
|
||||
<input type="text" id="group-display-name" class="modal-input">
|
||||
</div>
|
||||
<!-- Description -->
|
||||
<div class="col-span-2">
|
||||
<label for="group-description" class="flex items-center modal-label">
|
||||
<span>描述</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="关于此分组用途的简短说明。"></i>
|
||||
</label>
|
||||
<textarea id="group-description" rows="2" class="modal-input"></textarea>
|
||||
</div>
|
||||
<!-- Allowed Models -->
|
||||
<div class="col-span-2">
|
||||
<label class="flex items-center modal-label">
|
||||
<span>允许模型</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="允许该分组使用的模型ID列表。留空表示允许所有模型。"></i>
|
||||
</label>
|
||||
<div id="allowed-models-container" class="tag-input-container">
|
||||
<span class="tag-item">gemini-pro<button class="tag-delete">×</button></span>
|
||||
<span class="tag-item">gemini-1.5-pro-latest<button class="tag-delete">×</button></span>
|
||||
<input type="text" placeholder="添加模型..." class="tag-input-new">
|
||||
</div>
|
||||
</div>
|
||||
<!-- Allowed Tokens -->
|
||||
<div class="col-span-2">
|
||||
<label class="flex items-center modal-label">
|
||||
<span>专属密钥</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="只有列表中的认证令牌 (Auth Tokens) 才能使用此分组。留空则所有密钥都可用。"></i>
|
||||
</label>
|
||||
<div id="allowed-tokens-container" class="tag-input-container">
|
||||
<input type="text" placeholder="添加专属密钥..." class="tag-input-new">
|
||||
</div>
|
||||
</div>
|
||||
<!-- Polling Strategy -->
|
||||
<div>
|
||||
<label for="group-strategy" class="flex items-center modal-label">
|
||||
<span>轮询模式</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="从此分组中选择可用API Key的策略。"></i>
|
||||
</label>
|
||||
<select id="group-strategy" class="modal-input">
|
||||
<option value="random">随机</option>
|
||||
<option value="sequential">顺序</option>
|
||||
<option value="weighted">加权</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- Channel Type -->
|
||||
<div>
|
||||
<label for="group-channel-type" class="flex items-center modal-label">
|
||||
<span>渠道类型</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="此分组关联的上游渠道类型,由系统确定。"></i>
|
||||
</label>
|
||||
<input type="text" id="group-channel-type" class="modal-input bg-zinc-100 dark:bg-zinc-700/50" value="Gemini" disabled>
|
||||
</div>
|
||||
|
||||
<!-- Max Retries (新增) -->
|
||||
<div>
|
||||
<label for="group-max-retries" class="flex items-center modal-label">
|
||||
<span>重试次数</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="单个请求使用不同Key的最大重试次数。"></i>
|
||||
</label>
|
||||
<input type="number" id="group-max-retries" class="modal-input" placeholder="默认: 3">
|
||||
</div>
|
||||
|
||||
<!-- Failure Threshold-->
|
||||
<div>
|
||||
<label for="group-key-blacklist-threshold" class="flex items-center modal-label">
|
||||
<span>失败阈值</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="连续失败多少次后将密钥列入冷却状态。"></i>
|
||||
</label>
|
||||
<input type="number" id="group-key-blacklist-threshold" class="modal-input" placeholder="默认: 3">
|
||||
</div>
|
||||
|
||||
<!-- Enable Proxy -->
|
||||
<div class="flex items-center">
|
||||
<label for="group-enable-proxy" class="modal-label flex-grow flex items-center">
|
||||
<span>启用代理</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="是否为此分组的请求启用系统配置的代理。"></i>
|
||||
</label>
|
||||
<div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
|
||||
<input type="checkbox" name="group-enable-proxy" id="group-enable-proxy" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer">
|
||||
<label for="group-enable-proxy" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Enable Smart Gateway -->
|
||||
<div class="flex items-center">
|
||||
<label for="group-enable-smart-gateway" class="modal-label flex-grow flex items-center">
|
||||
<span>启用智能网关</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="是否启用智能网关功能,自动处理模型映射和重试。"></i>
|
||||
</label>
|
||||
<div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
|
||||
<input type="checkbox" name="group-enable-smart-gateway" id="group-enable-smart-gateway" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer">
|
||||
<label for="group-enable-smart-gateway" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Allowed Upstreams -->
|
||||
<div class="col-span-2">
|
||||
<label class="flex items-center modal-label">
|
||||
<span>上游地址</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="指定可用的上游API地址。如果留空,将使用系统默认地址。"></i>
|
||||
</label>
|
||||
<div id="allowed-upstreams-container" class="tag-input-container">
|
||||
<input type="text" placeholder="添加上游地址..." class="tag-input-new">
|
||||
</div>
|
||||
</div>
|
||||
<!-- Max Retries (Moved to Advanced Request Settings) -->
|
||||
|
||||
<!-- API Key Auto Validation -->
|
||||
<div class="col-span-2 mt-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<label for="group-enable-key-check" class="modal-label flex-grow flex items-center font-semibold">
|
||||
<span>开启自动验证</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="启用后,系统将定期自动验证该分组下所有密钥的有效性。"></i>
|
||||
</label>
|
||||
<div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
|
||||
<input type="checkbox" name="group-enable-key-check" id="group-enable-key-check" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer" checked>
|
||||
<label for="group-enable-key-check" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Collapsible settings for key check -->
|
||||
<div id="key-check-settings" class="grid grid-cols-2 gap-x-6 gap-y-4 mt-4 ">
|
||||
<!-- Detection Model (从全宽收缩为半宽,并移到最前) -->
|
||||
<div>
|
||||
<label for="group-key-check-model" class="flex items-center modal-label">
|
||||
<span>检测模型</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="用于验证此分组下API Key有效性的模型ID。"></i>
|
||||
</label>
|
||||
<input type="text" id="group-key-check-model" class="modal-input" placeholder="默认: gemini-1.5-flash">
|
||||
</div>
|
||||
<!-- Detection Interval -->
|
||||
<div>
|
||||
<label for="group-key-check-interval-minutes" class="flex items-center modal-label">
|
||||
<span>检测周期 (分钟)</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="每隔多少分钟对分组内的密钥进行一次健康检查。"></i>
|
||||
</label>
|
||||
<input type="number" id="group-key-check-interval-minutes" class="modal-input" placeholder="默认: 60">
|
||||
</div>
|
||||
<!-- Detection Concurrency -->
|
||||
<div>
|
||||
<label for="group-key-check-concurrency" class="flex items-center modal-label">
|
||||
<span>检测并发数</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="同时执行密钥验证的最大并发数。"></i>
|
||||
</label>
|
||||
<input type="number" id="group-key-check-concurrency" class="modal-input" placeholder="默认: 5">
|
||||
</div>
|
||||
<!-- Cooldown Duration -->
|
||||
<div>
|
||||
<label for="group-key-cooldown-minutes" class="flex items-center modal-label">
|
||||
<span>冷却时长 (分钟)</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="密钥进入冷却状态后,需要等待多长时间才能被再次检查。"></i>
|
||||
</label>
|
||||
<input type="number" id="group-key-cooldown-minutes" class="modal-input" placeholder="默认: 10">
|
||||
</div>
|
||||
<!-- Detection Endpoint (保持全宽) -->
|
||||
<div class="col-span-2">
|
||||
<label for="group-key-check-endpoint" class="flex items-center modal-label">
|
||||
<span>检测端点</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="用于密钥验证的上游API地址。如果留空,将使用系统默认地址。"></i>
|
||||
</label>
|
||||
<input type="text" id="group-key-check-endpoint" class="modal-input" placeholder="留空以使用全局默认">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="modal-footer shrink-0">
|
||||
<button data-modal-close="keygroup-modal" id="modal-cancel-btn" class="modal-btn modal-btn-secondary">取消</button>
|
||||
<button id="modal-save-btn" class="modal-btn modal-btn-primary">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add API Modal -->
|
||||
<div id="add-api-modal" class="modal-overlay hidden">
|
||||
<div class="modal-panel">
|
||||
<!-- Header -->
|
||||
<div class="modal-header">
|
||||
<h2 id="add-api-modal-title" class="modal-title">批量添加 API Keys</h2>
|
||||
<button data-modal-close="add-api-modal" class="modal-close-btn">
|
||||
<i class="fas fa-times text-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Body 现在包含两个可切换的视图 -->
|
||||
<div class="modal-body">
|
||||
<!-- 视图 1: 输入视图 (默认显示) -->
|
||||
<div id="add-api-input-view">
|
||||
<label for="api-add-textarea" class="modal-label">API Keys (每行一个)</label>
|
||||
<textarea id="api-add-textarea" rows="15" class="modal-input mt-1 font-mono text-xs"
|
||||
placeholder="sk-abc... AIza... gsk_..."></textarea>
|
||||
</div>
|
||||
|
||||
<!-- 视图 2: 结果/进度视图 (默认隐藏) -->
|
||||
<div id="add-api-result-view" class="hidden">
|
||||
<!-- 这里的内容将由JS动态填充 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div id="add-api-modal-footer" class="modal-footer justify-between">
|
||||
<!-- 左側:新增的驗證開關 -->
|
||||
<div class="flex items-center space-x-4">
|
||||
<input type="checkbox" id="validate-on-import-checkbox" class="form-checkbox h-5 w-5 ml-2 text-blue-600 focus:ring-blue-500 border-gray-300" checked>
|
||||
<label for="validate-on-import-checkbox" class="text-sm font-medium text-zinc-700 dark:text-zinc-300 select-none"> 同步验证</label>
|
||||
<i class="fas fa-question-circle text-gray-400 tooltip-icon"
|
||||
data-tooltip-text="推荐开启。密钥导入后同步调用测试模型对API进行有效性验证,无效Key将被隔离。"></i>
|
||||
</div>
|
||||
|
||||
<!-- 右側:現有的按鈕組 -->
|
||||
<div class="flex items-center space-x-2">
|
||||
<button data-modal-close="add-api-modal" class="modal-btn modal-btn-secondary">取消</button>
|
||||
<button id="add-api-import-btn" class="modal-btn modal-btn-primary">導入</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Delete API Modal -->
|
||||
<div id="delete-api-modal" class="modal-overlay hidden">
|
||||
<div class="modal-panel">
|
||||
<!-- Header -->
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">批量删除 API Keys</h2>
|
||||
<button data-modal-close="delete-api-modal" class="modal-close-btn">
|
||||
<i class="fas fa-times text-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Body -->
|
||||
<div class="modal-body">
|
||||
<label for="api-delete-textarea" class="text-sm font-medium text-zinc-700 dark:text-zinc-300">API Keys (每行一个)</label>
|
||||
<textarea id="api-delete-textarea" rows="15" class="modal-input mt-1 font-mono text-xs" placeholder="sk-abc... AIza... gsk_..."></textarea>
|
||||
</div>
|
||||
<!-- Footer -->
|
||||
<div class="modal-footer">
|
||||
<button data-modal-close="delete-api-modal" class="modal-btn modal-btn-secondary">取消</button>
|
||||
<button class="modal-btn modal-btn-danger">删除</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Group Confirmation Modal -->
|
||||
<div id="delete-group-modal" class="modal-overlay hidden">
|
||||
<div class="modal-panel">
|
||||
<!-- Header -->
|
||||
<div class="modal-header">
|
||||
<h2 id="delete-group-modal-title" class="modal-title">确认删除分组</h2>
|
||||
<button data-modal-close="delete-group-modal" class="modal-close-btn">
|
||||
<i class="fas fa-times text-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Body -->
|
||||
<div class="modal-body space-y-4">
|
||||
<p class="text-sm text-zinc-600 dark:text-zinc-400">
|
||||
这是一个危险且不可逆的操作。请仔细阅读以下警告:
|
||||
</p>
|
||||
<ul class="list-disc list-inside space-y-2 text-sm bg-yellow-100 dark:bg-yellow-900/30 p-3 rounded-lg border border-yellow-300 dark:border-yellow-700">
|
||||
<li>此操作将解除所有 API Key 与该分组的关联。</li>
|
||||
<li class="font-semibold">未与其他分组关联的 Key 将被 <strong class="text-red-500">彻底从数据库中删除</strong>。</li>
|
||||
<li>该操作无法撤销。</li>
|
||||
</ul>
|
||||
<div>
|
||||
<label for="delete-group-confirm-input" class="text-sm font-medium text-zinc-700 dark:text-zinc-300">
|
||||
请输入 <code class="text-red-500 bg-zinc-200 dark:bg-zinc-700 px-1 py-0.5 rounded">删除</code> 来确认操作:
|
||||
</label>
|
||||
<input type="text" id="delete-group-confirm-input" class="modal-input mt-1 font-mono" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
<!-- Footer -->
|
||||
<div class="modal-footer">
|
||||
<button data-modal-close="delete-group-modal" class="modal-btn modal-btn-secondary">取消</button>
|
||||
<button id="delete-group-confirm-btn" class="modal-btn modal-btn-danger" disabled>确认删除</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Clone Group Confirmation Modal -->
|
||||
<div id="clone-group-modal" class="modal-overlay hidden">
|
||||
<div class="modal-panel">
|
||||
<!-- Header -->
|
||||
<div class="modal-header">
|
||||
<h2 id="clone-group-modal-title" class="modal-title">确认克隆分组</h2>
|
||||
<button data-modal-close="clone-group-modal" class="modal-close-btn">
|
||||
<i class="fas fa-times text-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Body -->
|
||||
<div class="modal-body space-y-4">
|
||||
<p class="text-sm text-zinc-600 dark:text-zinc-400">
|
||||
此操作将创建一个当前分组的完整副本,包括:
|
||||
</p>
|
||||
<ul class="list-disc list-inside space-y-2 text-sm bg-blue-100 dark:bg-blue-900/30 p-3 rounded-lg border border-blue-300 dark:border-blue-700">
|
||||
<li>所有相同的 API Key 关联关系。</li>
|
||||
<li>所有允许的模型和上游设置。</li>
|
||||
<li>所有请求配置和运营设置。</li>
|
||||
</ul>
|
||||
<p class="text-sm text-zinc-600 dark:text-zinc-400">
|
||||
新分组的名称将被自动命名为 <code class="bg-zinc-200 dark:bg-zinc-700 px-1 py-0.5 rounded">[原名称]-clone-[时间戳]</code>,您可以在之后对其进行编辑。
|
||||
</p>
|
||||
</div>
|
||||
<!-- Footer -->
|
||||
<div class="modal-footer">
|
||||
<button data-modal-close="clone-group-modal" class="modal-btn modal-btn-secondary">取消</button>
|
||||
<button id="clone-group-confirm-btn" class="modal-btn modal-btn-primary">确认克隆</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========================================================================== -->
|
||||
<!-- NEW: Advanced Request Settings Modal -->
|
||||
<!-- ========================================================================== -->
|
||||
<div id="request-settings-modal" class="modal-overlay hidden">
|
||||
<div class="modal-panel max-w-3xl max-h-[90vh]">
|
||||
<!-- Header -->
|
||||
<div class="modal-header shrink-0">
|
||||
<h2 class="modal-title">高级请求设置</h2>
|
||||
<button data-modal-close="request-settings-modal" class="modal-close-btn">
|
||||
<i class="fas fa-times text-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Form Body -->
|
||||
<div class="modal-body flex-grow overflow-y-auto pr-4 -mr-4 space-y-8"> <!-- 增加了 space-y-8 来拉开分区距离 -->
|
||||
|
||||
<!-- 1. Custom Headers -->
|
||||
<div>
|
||||
<label class="flex items-center modal-label text-lg font-semibold">
|
||||
<i class="fas fa-file-alt w-6 text-center text-zinc-400 mr-2"></i> <!-- ICON -->
|
||||
<span>自定义请求头 (Custom Headers)</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="在这里添加的键值对将被添加到所有出站API请求的Header中。"></i>
|
||||
</label>
|
||||
<div id="CUSTOM_HEADERS_container" class="mt-2 space-y-2">
|
||||
<!-- JS will populate this -->
|
||||
</div>
|
||||
<button id="addCustomHeaderBtn" type="button" class="mt-2 text-sm text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 font-medium">
|
||||
<i class="fas fa-plus mr-1"></i> 添加 Header
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 2. Streaming Optimization (折叠优化) -->
|
||||
<div class="border-t dark:border-zinc-700 pt-6"> <!-- 分隔线 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="flex items-center modal-label text-lg font-semibold">
|
||||
<i class="fas fa-stream w-6 text-center text-zinc-400 mr-2"></i> <!-- ICON -->
|
||||
<span>流式输出优化</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="启用后,可对流式输出进行延迟、分块等优化,改善用户体验。"></i>
|
||||
</label>
|
||||
<div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
|
||||
<input type="checkbox" name="STREAM_OPTIMIZER_ENABLED" id="STREAM_OPTIMIZER_ENABLED" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer">
|
||||
<label for="STREAM_OPTIMIZER_ENABLED" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- [折叠容器] 这个容器将由JS控制显示/隐藏 -->
|
||||
<div id="streaming-settings-panel" class="hidden mt-4">
|
||||
<div class="grid grid-cols-2 gap-x-6 gap-y-4">
|
||||
<div>
|
||||
<label for="STREAM_MIN_DELAY" class="flex items-center modal-label"><span>最小延迟 (ms)</span></label>
|
||||
<input type="number" id="STREAM_MIN_DELAY" class="modal-input" placeholder="例如: 16">
|
||||
</div>
|
||||
<div>
|
||||
<label for="STREAM_MAX_DELAY" class="flex items-center modal-label"><span>最大延迟 (ms)</span></label>
|
||||
<input type="number" id="STREAM_MAX_DELAY" class="modal-input" placeholder="例如: 24">
|
||||
</div>
|
||||
<div>
|
||||
<label for="STREAM_SHORT_TEXT_THRESHOLD" class="flex items-center modal-label"><span>短文本阈值</span></label>
|
||||
<input type="number" id="STREAM_SHORT_TEXT_THRESHOLD" class="modal-input" placeholder="例如: 10">
|
||||
</div>
|
||||
<div>
|
||||
<label for="STREAM_LONG_TEXT_THRESHOLD" class="flex items-center modal-label"><span>长文本阈值</span></label>
|
||||
<input type="number" id="STREAM_LONG_TEXT_THRESHOLD" class="modal-input" placeholder="例如: 50">
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label for="STREAM_CHUNK_SIZE" class="flex items-center modal-label"><span>分块大小</span></label>
|
||||
<input type="number" id="STREAM_CHUNK_SIZE" class="modal-input" placeholder="例如: 5">
|
||||
</div>
|
||||
</div>
|
||||
<!-- [假流式输出同行] -->
|
||||
<div class="border-t dark:border-zinc-700 pt-4 mt-4 flex items-center justify-between gap-x-6">
|
||||
<div class="flex items-center flex-grow translate-y-3">
|
||||
<label for="FAKE_STREAM_ENABLED" class="modal-label flex items-center">
|
||||
<span>启用假流式输出</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="当启用时,将调用非流式接口,并在等待响应期间发送空数据以维持连接。"></i>
|
||||
</label>
|
||||
<div class="relative inline-block w-10 ml-auto mr-2 align-middle select-none">
|
||||
<input type="checkbox" name="FAKE_STREAM_ENABLED" id="FAKE_STREAM_ENABLED" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer">
|
||||
<label for="FAKE_STREAM_ENABLED" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow">
|
||||
<label for="FAKE_STREAM_EMPTY_DATA_INTERVAL_SECONDS" class="flex items-center modal-label"><span>发送间隔 (s)</span></label>
|
||||
<input type="number" id="FAKE_STREAM_EMPTY_DATA_INTERVAL_SECONDS" class="modal-input" placeholder="例如: 5">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. Safety Settings -->
|
||||
<div class="border-t dark:border-zinc-700 pt-6"> <!-- 分隔线 -->
|
||||
<label class="flex items-center modal-label text-lg font-semibold">
|
||||
<i class="fas fa-shield-alt w-6 text-center text-zinc-400 mr-2"></i> <!-- ICON -->
|
||||
<span>安全设置 (Safety Settings)</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="配置模型的安全过滤级别,例如 HARM_CATEGORY_HARASSMENT: BLOCK_NONE。"></i>
|
||||
</label>
|
||||
<div id="SAFETY_SETTINGS_container" class="mt-2 space-y-2">
|
||||
<!-- JS will populate this -->
|
||||
</div>
|
||||
<button id="addSafetySettingBtn" type="button" class="mt-2 text-sm text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 font-medium">
|
||||
<i class="fas fa-plus mr-1"></i> 添加安全设置
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 4. Advanced Model Settings (保持折叠) -->
|
||||
<div class="border-t dark:border-zinc-700 pt-6"> <!-- 分隔线 -->
|
||||
<details>
|
||||
<summary class="cursor-pointer text-lg font-semibold text-zinc-700 dark:text-zinc-300 flex items-center">
|
||||
<i class="fas fa-cogs w-6 text-center text-zinc-400 mr-2"></i> <!-- ICON -->
|
||||
<span>高级模型设置</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="配置模型的高级设置,自定义图像模型/搜索模型/思考设置等,非熟知不建议修改。"></i>
|
||||
<i class="fas fa-chevron-down text-sm ml-auto transition-transform"></i>
|
||||
</summary>
|
||||
<div class="mt-4 space-y-6">
|
||||
<!-- Image Models -->
|
||||
<div>
|
||||
<label class="flex items-center modal-label">
|
||||
<span>图像模型 (Image Models)</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="支持图像处理的模型列表。"></i>
|
||||
</label>
|
||||
<div id="IMAGE_MODELS_container" class="tag-input-container">
|
||||
<input type="text" placeholder="添加模型..." class="tag-input-new">
|
||||
</div>
|
||||
</div>
|
||||
<!-- Search Models -->
|
||||
<div>
|
||||
<label class="flex items-center modal-label">
|
||||
<span>搜索模型 (Search Models)</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="支持搜索功能的模型列表。"></i>
|
||||
</label>
|
||||
<div id="SEARCH_MODELS_container" class="tag-input-container">
|
||||
<input type="text" placeholder="添加模型..." class="tag-input-new">
|
||||
</div>
|
||||
</div>
|
||||
<!-- Filtered Models -->
|
||||
<div>
|
||||
<label class="flex items-center modal-label">
|
||||
<span>过滤模型 (Filtered Models)</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="需要从模型列表中排除的模型。"></i>
|
||||
</label>
|
||||
<div id="FILTERED_MODELS_container" class="tag-input-container">
|
||||
<input type="text" placeholder="添加模型..." class="tag-input-new">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Enable Code Execution -->
|
||||
<div class="flex items-center">
|
||||
<label for="TOOLS_CODE_EXECUTION_ENABLED" class="modal-label flex-grow flex items-center">
|
||||
<span>启用代码执行工具</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="是否为模型启用代码执行工具。"></i>
|
||||
</label>
|
||||
<div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
|
||||
<input type="checkbox" name="TOOLS_CODE_EXECUTION_ENABLED" id="TOOLS_CODE_EXECUTION_ENABLED" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer">
|
||||
<label for="TOOLS_CODE_EXECUTION_ENABLED" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Enable URL Context -->
|
||||
<div class="flex items-center">
|
||||
<label for="URL_CONTEXT_ENABLED" class="modal-label flex-grow flex items-center">
|
||||
<span>启用网址上下文</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="是否启用网址上下文功能。"></i>
|
||||
</label>
|
||||
<div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
|
||||
<input type="checkbox" name="URL_CONTEXT_ENABLED" id="URL_CONTEXT_ENABLED" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer">
|
||||
<label for="URL_CONTEXT_ENABLED" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- URL Context Models -->
|
||||
<div>
|
||||
<label class="flex items-center modal-label">
|
||||
<span>网址上下文模型</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="支持网址上下文功能的模型列表。"></i>
|
||||
</label>
|
||||
<div id="URL_CONTEXT_MODELS_container" class="tag-input-container">
|
||||
<input type="text" placeholder="添加模型..." class="tag-input-new">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Show Search Link -->
|
||||
<div class="flex items-center">
|
||||
<label for="SHOW_SEARCH_LINK" class="modal-label flex-grow flex items-center">
|
||||
<span>显示搜索链接</span>
|
||||
</label>
|
||||
<div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
|
||||
<input type="checkbox" name="SHOW_SEARCH_LINK" id="SHOW_SEARCH_LINK" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer">
|
||||
<label for="SHOW_SEARCH_LINK" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Show Thinking Process -->
|
||||
<div class="flex items-center">
|
||||
<label for="SHOW_THINKING_PROCESS" class="modal-label flex-grow flex items-center">
|
||||
<span>显示思考过程</span>
|
||||
</label>
|
||||
<div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
|
||||
<input type="checkbox" name="SHOW_THINKING_PROCESS" id="SHOW_THINKING_PROCESS" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer">
|
||||
<label for="SHOW_THINKING_PROCESS" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Thinking Models -->
|
||||
<div>
|
||||
<label class="flex items-center modal-label">
|
||||
<span>思考模型 (Thinking Models)</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="用于“思考过程”的模型列表。"></i>
|
||||
</label>
|
||||
<div id="THINKING_MODELS_container" class="tag-input-container">
|
||||
<input type="text" placeholder="添加模型..." class="tag-input-new">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Thinking Budget Map -->
|
||||
<div>
|
||||
<label class="flex items-center modal-label">
|
||||
<span>思考模型预算映射</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="为每个思考模型设置预算(-1为auto),此项与上方模型列表自动关联。"></i>
|
||||
</label>
|
||||
<div id="THINKING_BUDGET_MAP_container" class="mt-2 space-y-2">
|
||||
<!-- JS will populate this -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<!-- 5. Config Overrides (折叠优化) -->
|
||||
<div class="border-t dark:border-zinc-700 pt-6"> <!-- 分隔线 -->
|
||||
<details>
|
||||
<summary class="cursor-pointer text-lg font-semibold text-zinc-700 dark:text-zinc-300 flex items-center">
|
||||
<i class="fas fa-code w-6 text-center text-zinc-400 mr-2"></i> <!-- ICON -->
|
||||
<span>覆盖参数 (JSON)</span>
|
||||
<i class="fas fa-question-circle tooltip-icon" data-tooltip-text="使用JSON格式覆盖默认的请求参数。"></i>
|
||||
<i class="fas fa-chevron-down text-sm ml-auto transition-transform"></i>
|
||||
</summary>
|
||||
<!-- [修复] 将 placeholder 内容放在一行,避免被截断 -->
|
||||
<textarea id="group-config-overrides" rows="6" class="mt-2 modal-input font-mono text-xs" placeholder='{
|
||||
"temperature": 0.8
|
||||
}'></textarea>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="modal-footer shrink-0">
|
||||
<button data-modal-close="request-settings-modal" class="modal-btn modal-btn-secondary">取消</button>
|
||||
<button id="request-settings-save-btn" class="modal-btn modal-btn-primary">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock modals %}
|
||||
|
||||
{% block page_scripts %}
|
||||
{% endblock page_scripts %}
|
||||
184
web/templates/logs.html
Normal file
184
web/templates/logs.html
Normal file
@@ -0,0 +1,184 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}日志管理 - GEMINI BALANCER{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="w-full h-full flex flex-col pl-0 pr-3 lg:px-0" data-page-id="logs">
|
||||
<!-- =================================================================== -->
|
||||
<!-- 1. 页面顶栏:标题与全局控制器 -->
|
||||
<!-- =================================================================== -->
|
||||
<div class="flex items-center justify-between mb-4 shrink-0">
|
||||
<div>
|
||||
<h2 class="text-3xl font-bold tracking-tight">日志管理</h2>
|
||||
<p class="text-sm text-zinc-500 dark:text-zinc-400 mt-1">查看、筛选和分析所有通过系统的请求记录。</p>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<!-- [新] 日志系统参数管理图标 -->
|
||||
<button class="btn btn-icon" aria-label="日志设置">
|
||||
<i class="fas fa-cog text-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- =================================================================== -->
|
||||
<!-- 2. 导航标签 -->
|
||||
<!-- =================================================================== -->
|
||||
<div class="py-2 shrink-0">
|
||||
<div class="w-full overflow-x-auto scrollbar-hide">
|
||||
<div role="tablist" class="relative inline-flex h-10 items-center justify-center inset-shadow-sm/25 rounded-lg bg-zinc-800/50 dark:bg-zinc-950 p-1" data-sliding-tabs-container>
|
||||
<div class="absolute left-0 h-[calc(100%-0.5rem)] rounded-md bg-white dark:bg-zinc-700 shadow-sm" data-tab-indicator style="transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);"></div>
|
||||
<a href="#" role="tab" class="tab-item tab-active" data-tab-item>错误日志</a>
|
||||
<a href="#" role="tab" class="tab-item" data-tab-item>系统日志</a>
|
||||
<a href="#" role="tab" class="tab-item" data-tab-item>保留标签</a>
|
||||
<a href="#" role="tab" class="tab-item" data-tab-item>保留标签</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- =================================================================== -->
|
||||
<!-- 3. 主内容区:过滤器 + 表格 (精确复刻版) -->
|
||||
<!-- =================================================================== -->
|
||||
<div class="flex items-center justify-between shrink-0 py-4">
|
||||
|
||||
<div class="flex flex-1 items-center space-x-2">
|
||||
|
||||
<input class="input h-8 w-[150px] lg:w-[250px]" placeholder="筛选密钥..." value="">
|
||||
|
||||
<button class="btn btn-outline border-dashed h-8 px-3 text-xs">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-2 h-4 w-4">
|
||||
<circle cx="12" cy="12" r="10"></circle><path d="M8 12h8"></path><path d="M12 8v8"></path>
|
||||
</svg>
|
||||
错误类型
|
||||
</button>
|
||||
|
||||
<button class="btn btn-outline border-dashed h-8 px-3 text-xs">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-2 h-4 w-4">
|
||||
<circle cx="12" cy="12" r="10"></circle><path d="M8 12h8"></path><path d="M12 8v8"></path>
|
||||
</svg>
|
||||
错误码
|
||||
</button>
|
||||
|
||||
<button class="btn btn-outline border-dashed h-8 px-3 text-xs">
|
||||
<i class="fas fa-calendar-alt mr-2 h-4 w-4"></i>
|
||||
时间范围
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- [应用] .btn .btn-outline, 并覆盖高度/字体 -->
|
||||
<button class="btn btn-outline hidden lg:flex h-8 px-3 text-xs">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-2 h-4 w-4">
|
||||
<path d="M20 7h-9"></path><path d="M14 17H5"></path><circle cx="17" cy="17" r="3"></circle><circle cx="7" cy="7" r="3"></circle>
|
||||
</svg>
|
||||
批量操作
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 3.2 数据表格容器 (带边框和圆角) -->
|
||||
<div class="rounded-lg border border-zinc-200 dark:border-zinc-700 flex-grow overflow-hidden flex flex-col">
|
||||
<!-- 容器需要 overflow-auto 以便表格内容超出时滚动 -->
|
||||
<div class="relative w-full overflow-auto flex-grow main-content-scroll">
|
||||
<table class="w-full caption-bottom text-sm">
|
||||
<thead class="[&_tr]:border-b border-b-zinc-200 dark:border-b-zinc-800 sticky top-0 bg-zinc-50 dark:bg-zinc-900 z-10">
|
||||
<tr class="transition-colors hover:bg-zinc-100/50 dark:hover:bg-zinc-800/50">
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-zinc-500 dark:text-zinc-400 w-4">
|
||||
<input type="checkbox" class="checkbox">
|
||||
</th>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-zinc-500 dark:text-zinc-400">ID</th>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-zinc-500 dark:text-zinc-400">Gemini 密钥</th>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-zinc-500 dark:text-zinc-400">错误类型</th>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-zinc-500 dark:text-zinc-400">错误码</th>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-zinc-500 dark:text-zinc-400">模型名称</th>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-zinc-500 dark:text-zinc-400">请求时间</th>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-zinc-500 dark:text-zinc-400">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="[&_tr:last-child]:border-0" id="logs-table-body">
|
||||
|
||||
<tr class="border-b border-b-zinc-200 dark:border-b-zinc-700 transition-colors hover:bg-zinc-100/50 dark:hover:bg-zinc-800/50">
|
||||
<td class="p-4 align-middle"><input type="checkbox" class="h-4 w-4 rounded border-zinc-300 text-blue-600 focus:ring-blue-500"></td>
|
||||
<td class="p-4 align-middle font-mono text-zinc-600 dark:text-zinc-300">#12346</td>
|
||||
<td class="p-4 align-middle font-medium font-mono">AIza...s7f1</td>
|
||||
<td class="p-4 align-middle text-zinc-600 dark:text-zinc-300">API Key Invalid</td>
|
||||
<td class="p-4 align-middle"><span class="inline-flex items-center rounded-md bg-destructive/10 px-2 py-1 text-xs font-medium text-destructive">429</span></td>
|
||||
<td class="p-4 align-middle font-mono">gemini-1.5-pro-latest</td>
|
||||
<td class="p-4 align-middle text-zinc-500 dark:text-zinc-400">2024-05-21 10:31:15</td>
|
||||
<td class="p-4 align-middle">
|
||||
<button class="btn btn-ghost btn-icon btn-sm" aria-label="更多操作"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4"><circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle><circle cx="5" cy="12" r="1"></circle></svg></button>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- [示例] 您可以复制粘贴下面这行来增加更多数据行进行测试 -->
|
||||
<tr class="border-b border-b-zinc-200 dark:border-b-zinc-700 transition-colors hover:bg-zinc-100/50 dark:hover:bg-zinc-800/50">
|
||||
<td class="p-4 align-middle"><input type="checkbox" class="h-4 w-4 rounded border-zinc-300 text-blue-600 focus:ring-blue-500"></td>
|
||||
<td class="p-4 align-middle font-mono text-zinc-600 dark:text-zinc-300">#12347</td>
|
||||
<td class="p-4 align-middle font-medium font-mono">AIza...s7f2</td>
|
||||
<td class="p-4 align-middle text-zinc-600 dark:text-zinc-300">Quota Exceeded</td>
|
||||
<td class="p-4 align-middle"><span class="inline-flex items-center rounded-md bg-destructive/10 px-2 py-1 text-xs font-medium text-destructive">429</span></td>
|
||||
<td class="p-4 align-middle font-mono">gemini-1.0-pro</td>
|
||||
<td class="p-4 align-middle text-zinc-500 dark:text-zinc-400">2024-05-21 10:32:15</td>
|
||||
<td class="p-4 align-middle">
|
||||
<button class="btn btn-ghost btn-icon btn-sm" aria-label="更多操作"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4"><circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle><circle cx="5" cy="12" r="1"></circle></svg></button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="border-b border-b-zinc-200 dark:border-b-zinc-700 transition-colors hover:bg-zinc-100/50 dark:hover:bg-zinc-800/50">
|
||||
<td class="p-4 align-middle"><input type="checkbox" class="h-4 w-4 rounded border-zinc-300 text-blue-600 focus:ring-blue-500"></td>
|
||||
<td class="p-4 align-middle font-mono text-zinc-600 dark:text-zinc-300">#12348</td>
|
||||
<td class="p-4 align-middle font-medium font-mono">AIza...s7f3</td>
|
||||
<td class="p-4 align-middle text-zinc-600 dark:text-zinc-300">Server Error</td>
|
||||
<td class="p-4 align-middle"><span class="inline-flex items-center rounded-md bg-yellow-500/20 px-2 py-1 text-xs font-medium text-yellow-700 dark:text-yellow-400">500</span></td>
|
||||
<td class="p-4 align-middle font-mono">gemini-1.5-pro-latest</td>
|
||||
<td class="p-4 align-middle text-zinc-500 dark:text-zinc-400">2024-05-21 10:33:15</td>
|
||||
<td class="p-4 align-middle">
|
||||
<button class="btn btn-ghost btn-icon btn-sm" aria-label="更多操作"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4"><circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle><circle cx="5" cy="12" r="1"></circle></svg></button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 3.3 分页控制器 -->
|
||||
<div class="flex items-center justify-between p-2 shrink-0 border-t border-zinc-200 dark:border-zinc-700">
|
||||
<div class="flex-1 text-sm text-zinc-500 dark:text-zinc-400">
|
||||
已选择 <span class="font-semibold text-zinc-900 dark:text-white">0</span> / <span class="font-semibold text-zinc-900 dark:text-white">100</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-6 lg:space-x-8">
|
||||
<div class="flex items-center space-x-2">
|
||||
<p class="text-sm font-medium">每页行数</p>
|
||||
<button type="button" class="btn btn-secondary h-8 w-[70px] flex justify-between items-center px-2">
|
||||
<span>10</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4 opacity-50"><path d="m6 9 6 6 6-6"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex w-[100px] items-center justify-center text-sm font-medium">
|
||||
第 1 / 10 页
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<button class="btn btn-secondary h-8 w-8 p-0 hidden lg:flex" disabled>
|
||||
<span class="sr-only">Go to first page</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4"><path d="m11 17-5-5 5-5"></path><path d="m18 17-5-5 5-5"></path></svg>
|
||||
</button>
|
||||
<button class="btn btn-secondary h-8 w-8 p-0" disabled>
|
||||
<span class="sr-only">Go to previous page</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4"><path d="m15 18-6-6 6-6"></path></svg>
|
||||
</button>
|
||||
<button class="btn btn-secondary h-8 w-8 p-0">
|
||||
<span class="sr-only">Go to next page</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4"><path d="m9 18 6-6-6-6"></path></svg>
|
||||
</button>
|
||||
<button class="btn btn-secondary h-8 w-8 p-0 hidden lg:flex">
|
||||
<span class="sr-only">Go to last page</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4"><path d="m6 17 5-5-5-5"></path><path d="m13 17 5-5-5-5"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modals %}
|
||||
<!-- 日志详情模态框将在此处定义 -->
|
||||
{% endblock modals %}
|
||||
|
||||
{% block page_scripts %}
|
||||
{% endblock page_scripts %}
|
||||
2048
web/templates/settings.html
Normal file
2048
web/templates/settings.html
Normal file
File diff suppressed because it is too large
Load Diff
102
web/templates/tasks.html
Normal file
102
web/templates/tasks.html
Normal file
@@ -0,0 +1,102 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}计划任务 - GEMINI BALANCER{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="w-full h-full flex flex-col pl-0 pr-3 lg:px-0" data-page-id="tasks">
|
||||
<!-- =================================================================== -->
|
||||
<!-- 1. 页面顶栏:标题与全局控制器 -->
|
||||
<!-- =================================================================== -->
|
||||
<div class="flex items-center justify-between mb-6 shrink-0">
|
||||
<div>
|
||||
<h2 class="text-3xl font-bold tracking-tight">计划任务</h2>
|
||||
<p class="text-sm text-zinc-500 dark:text-zinc-400 mt-1">管理和监控所有周期性系统任务。</p>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<button class="btn btn-primary">
|
||||
<i class="fas fa-plus mr-2 h-4 w-4"></i>
|
||||
新建任务
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- =================================================================== -->
|
||||
<!-- 2. 主内容区:任务列表 -->
|
||||
<!-- =================================================================== -->
|
||||
<div class="rounded-lg border border-border flex-grow overflow-hidden flex flex-col">
|
||||
<!-- 容器需要 overflow-auto 以便表格内容超出时滚动 -->
|
||||
<div class="relative w-full overflow-auto flex-grow main-content-scroll">
|
||||
<table class="w-full caption-bottom text-sm">
|
||||
<thead class="[&_tr]:border-b border-b-border sticky top-0 bg-muted/50 dark:bg-muted/50 z-10">
|
||||
<tr class="transition-colors hover:bg-muted/80 dark:hover:bg-muted/80">
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground w-24">状态</th>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground">任务名称</th>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground">CRON 表达式</th>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground">上次运行</th>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground">下次运行</th>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground w-32">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="[&_tr:last-child]:border-0">
|
||||
<!-- 示例任务 1: 运行中 -->
|
||||
<tr class="border-b border-b-border transition-colors hover:bg-muted/80 dark:hover:bg-muted/80">
|
||||
<td class="p-4 align-middle">
|
||||
<div class="inline-flex items-center gap-2">
|
||||
<span class="relative flex h-3 w-3">
|
||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
|
||||
<span class="relative inline-flex rounded-full h-3 w-3 bg-green-500"></span>
|
||||
</span>
|
||||
<span class="text-foreground">运行中</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="p-4 align-middle font-medium">密钥可用性检查</td>
|
||||
<td class="p-4 align-middle font-mono text-muted-foreground">0 */1 * * *</td>
|
||||
<td class="p-4 align-middle text-muted-foreground">2024-05-21 10:30:00</td>
|
||||
<td class="p-4 align-middle text-muted-foreground">2024-05-21 11:30:00</td>
|
||||
<td class="p-4 align-middle space-x-2">
|
||||
<button class="btn btn-ghost btn-icon btn-sm" aria-label="立即运行"><i class="fas fa-play"></i></button>
|
||||
<button class="btn btn-ghost btn-icon btn-sm" aria-label="编辑任务"><i class="fas fa-pencil-alt"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- 示例任务 2: 已暂停 -->
|
||||
<tr class="border-b border-b-border transition-colors hover:bg-muted/80 dark:hover:bg-muted/80">
|
||||
<td class="p-4 align-middle">
|
||||
<div class="inline-flex items-center gap-2">
|
||||
<span class="relative flex h-3 w-3"><span class="relative inline-flex rounded-full h-3 w-3 bg-yellow-500"></span></span>
|
||||
<span class="text-foreground">已暂停</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="p-4 align-middle font-medium">清理过期日志</td>
|
||||
<td class="p-4 align-middle font-mono text-muted-foreground">0 1 * * *</td>
|
||||
<td class="p-4 align-middle text-muted-foreground">2024-05-20 01:00:00</td>
|
||||
<td class="p-4 align-middle text-muted-foreground">N/A</td>
|
||||
<td class="p-4 align-middle space-x-2">
|
||||
<button class="btn btn-ghost btn-icon btn-sm" aria-label="立即运行"><i class="fas fa-play"></i></button>
|
||||
<button class="btn btn-ghost btn-icon btn-sm" aria-label="编辑任务"><i class="fas fa-pencil-alt"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- 示例任务 3: 失败 -->
|
||||
<tr class="border-b border-b-border transition-colors hover:bg-muted/80 dark:hover:bg-muted/80">
|
||||
<td class="p-4 align-middle">
|
||||
<div class="inline-flex items-center gap-2">
|
||||
<span class="relative flex h-3 w-3"><span class="relative inline-flex rounded-full h-3 w-3 bg-destructive"></span></span>
|
||||
<span class="text-destructive">失败</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="p-4 align-middle font-medium">同步外部数据源</td>
|
||||
<td class="p-4 align-middle font-mono text-muted-foreground">*/30 * * * *</td>
|
||||
<td class="p-4 align-middle text-muted-foreground">2024-05-21 10:00:00 (错误)</td>
|
||||
<td class="p-4 align-middle text-muted-foreground">2024-05-21 10:30:00</td>
|
||||
<td class="p-4 align-middle space-x-2">
|
||||
<button class="btn btn-ghost btn-icon btn-sm" aria-label="查看日志"><i class="fas fa-file-alt"></i></button>
|
||||
<button class="btn btn-ghost btn-icon btn-sm" aria-label="编辑任务"><i class="fas fa-pencil-alt"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block page_scripts %}
|
||||
{% endblock page_scripts %}
|
||||
10
web/templates/test.html
Normal file
10
web/templates/test.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Pongo2 Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello from Test Template!</h1>
|
||||
<p>My name is {{ name }}.</p>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user