mirror of
https://github.com/snailyp/gemini-balance.git
synced 2026-07-03 22:04:18 +08:00
feat(config_editor): 新增批量删除 API 密钥及令牌生成功能
- 实现 API 密钥的批量删除功能: - 在配置编辑器中添加“删除密钥”按钮和批量删除模态框。 - 用户可以在模态框中粘贴密钥列表进行批量删除。 - JavaScript 逻辑负责提取、匹配并移除列表中的密钥。 - 为 ALLOWED_TOKENS 字段添加内联随机令牌生成按钮,方便快速生成。 - 优化配置编辑器中数组项(如 API Key, Allowed Token)的 UI 布局和样式。
This commit is contained in:
@@ -57,6 +57,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const confirmAddApiKeyBtn = document.getElementById('confirmAddApiKeyBtn');
|
||||
const apiKeyBulkInput = document.getElementById('apiKeyBulkInput');
|
||||
const apiKeySearchInput = document.getElementById('apiKeySearchInput');
|
||||
const bulkDeleteApiKeyBtn = document.getElementById('bulkDeleteApiKeyBtn'); // 新增
|
||||
const bulkDeleteApiKeyModal = document.getElementById('bulkDeleteApiKeyModal'); // 新增
|
||||
const closeBulkDeleteModalBtn = document.getElementById('closeBulkDeleteModalBtn'); // 新增
|
||||
const cancelBulkDeleteApiKeyBtn = document.getElementById('cancelBulkDeleteApiKeyBtn'); // 新增
|
||||
const confirmBulkDeleteApiKeyBtn = document.getElementById('confirmBulkDeleteApiKeyBtn'); // 新增
|
||||
const bulkDeleteApiKeyInput = document.getElementById('bulkDeleteApiKeyInput'); // 新增
|
||||
|
||||
// --- 新增:重置确认模态框相关 ---
|
||||
const resetConfirmModal = document.getElementById('resetConfirmModal');
|
||||
@@ -99,9 +105,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
if (event.target == apiKeyModal) {
|
||||
apiKeyModal.classList.remove('show');
|
||||
}
|
||||
if (event.target == resetConfirmModal) { // 新增对重置模态框的处理
|
||||
if (event.target == resetConfirmModal) {
|
||||
resetConfirmModal.classList.remove('show');
|
||||
}
|
||||
if (event.target == bulkDeleteApiKeyModal) { // 新增对批量删除模态框的处理
|
||||
bulkDeleteApiKeyModal.classList.remove('show');
|
||||
}
|
||||
});
|
||||
|
||||
// 确认添加 API Key
|
||||
@@ -113,6 +122,41 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
if (apiKeySearchInput) {
|
||||
apiKeySearchInput.addEventListener('input', handleApiKeySearch);
|
||||
}
|
||||
|
||||
// --- 新增:批量删除 API Key 相关事件 ---
|
||||
// 打开批量删除模态框
|
||||
if (bulkDeleteApiKeyBtn) {
|
||||
bulkDeleteApiKeyBtn.addEventListener('click', () => {
|
||||
if (bulkDeleteApiKeyModal) {
|
||||
bulkDeleteApiKeyModal.classList.add('show');
|
||||
}
|
||||
if (bulkDeleteApiKeyInput) bulkDeleteApiKeyInput.value = ''; // 清空输入框
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭批量删除模态框 (X 按钮)
|
||||
if (closeBulkDeleteModalBtn) {
|
||||
closeBulkDeleteModalBtn.addEventListener('click', () => {
|
||||
if (bulkDeleteApiKeyModal) {
|
||||
bulkDeleteApiKeyModal.classList.remove('show');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭批量删除模态框 (取消按钮)
|
||||
if (cancelBulkDeleteApiKeyBtn) {
|
||||
cancelBulkDeleteApiKeyBtn.addEventListener('click', () => {
|
||||
if (bulkDeleteApiKeyModal) {
|
||||
bulkDeleteApiKeyModal.classList.remove('show');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 确认批量删除 API Key
|
||||
if (confirmBulkDeleteApiKeyBtn) {
|
||||
confirmBulkDeleteApiKeyBtn.addEventListener('click', handleBulkDeleteApiKeys);
|
||||
}
|
||||
// --- 结束:批量删除 API Key 相关 ---
|
||||
// --- 结束:API Key 相关 ---
|
||||
|
||||
// --- 新增:重置确认模态框事件监听 (移到 DOMContentLoaded 内部) ---
|
||||
@@ -141,6 +185,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
// --- 结束:重置相关 ---
|
||||
|
||||
// 移除了静态生成令牌按钮的事件监听器,现在按钮是动态生成的
|
||||
|
||||
}); // <-- DOMContentLoaded 结束括号
|
||||
|
||||
// 初始化配置
|
||||
@@ -304,6 +350,58 @@ function handleApiKeySearch() {
|
||||
});
|
||||
}
|
||||
|
||||
// --- 新增:处理批量删除 API Key 的逻辑 ---
|
||||
function handleBulkDeleteApiKeys() {
|
||||
const bulkDeleteTextarea = document.getElementById('bulkDeleteApiKeyInput'); // Use the textarea ID
|
||||
const apiKeyContainer = document.getElementById('API_KEYS_container');
|
||||
const bulkDeleteModal = document.getElementById('bulkDeleteApiKeyModal');
|
||||
|
||||
if (!bulkDeleteTextarea || !apiKeyContainer || !bulkDeleteModal) return;
|
||||
|
||||
const bulkText = bulkDeleteTextarea.value;
|
||||
if (!bulkText.trim()) {
|
||||
showNotification('请粘贴需要删除的 API 密钥', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the same regex as for adding keys to extract keys to delete
|
||||
const keyRegex = /AIzaSy\S{33}/g;
|
||||
const keysToDelete = new Set(bulkText.match(keyRegex) || []); // Create a Set for efficient lookup
|
||||
|
||||
if (keysToDelete.size === 0) {
|
||||
showNotification('未在输入内容中提取到有效的 API 密钥格式', 'warning');
|
||||
// Optionally clear the textarea or keep it as is
|
||||
// bulkDeleteTextarea.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const keyItems = apiKeyContainer.querySelectorAll('.array-item');
|
||||
let deleteCount = 0;
|
||||
|
||||
keyItems.forEach(item => {
|
||||
const input = item.querySelector('.array-input');
|
||||
// Check if the input exists and its value is in the set of keys to delete
|
||||
if (input && keysToDelete.has(input.value)) {
|
||||
item.remove(); // Remove the entire array item element
|
||||
deleteCount++;
|
||||
}
|
||||
});
|
||||
|
||||
// Close the modal
|
||||
bulkDeleteModal.classList.remove('show');
|
||||
|
||||
// Provide feedback
|
||||
if (deleteCount > 0) {
|
||||
showNotification(`成功删除了 ${deleteCount} 个匹配的密钥`, 'success');
|
||||
} else {
|
||||
// This message implies keys were extracted but not found in the current list
|
||||
showNotification('列表中未找到您输入的任何密钥进行删除', 'info');
|
||||
}
|
||||
|
||||
// Clear the textarea after processing
|
||||
bulkDeleteTextarea.value = '';
|
||||
}
|
||||
|
||||
// 切换标签
|
||||
function switchTab(tabId) {
|
||||
// 更新标签按钮状态
|
||||
@@ -358,29 +456,58 @@ function addArrayItemWithValue(key, value) {
|
||||
if (!container) return;
|
||||
|
||||
const arrayItem = document.createElement('div');
|
||||
arrayItem.className = 'array-item flex justify-between items-center mb-2'; // 使用 Flexbox 布局,垂直居中,底部增加间距
|
||||
|
||||
// 主容器使用 Flexbox
|
||||
arrayItem.className = 'array-item flex items-center mb-2 gap-2'; // 添加 gap-2 来分隔元素
|
||||
|
||||
// 创建一个包装器 div 来包含输入框和生成按钮
|
||||
const inputWrapper = document.createElement('div');
|
||||
// 这个包装器占据主要空间,并使用 Flexbox
|
||||
inputWrapper.className = 'flex items-center flex-grow border border-gray-300 rounded-md focus-within:border-primary-500 focus-within:ring focus-within:ring-primary-200 focus-within:ring-opacity-50';
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'text';
|
||||
input.name = `${key}[]`;
|
||||
input.value = value;
|
||||
input.className = 'array-input flex-grow px-3 py-2 rounded-md border border-gray-300 focus:border-primary-500 focus:ring focus:ring-primary-200 focus:ring-opacity-50 mr-2'; // 输入框占据大部分空间,添加样式和右边距
|
||||
|
||||
// 输入框占据包装器内的主要空间,移除边框和圆角,因为包装器已有
|
||||
input.className = 'array-input flex-grow px-3 py-2 border-none rounded-l-md focus:outline-none'; // 移除右侧圆角
|
||||
|
||||
inputWrapper.appendChild(input); // 将输入框添加到包装器
|
||||
|
||||
// 只为 ALLOWED_TOKENS 添加生成按钮
|
||||
if (key === 'ALLOWED_TOKENS') {
|
||||
const generateBtn = document.createElement('button');
|
||||
generateBtn.type = 'button';
|
||||
// 按钮样式,放在输入框右侧,有背景和内边距,调整颜色
|
||||
generateBtn.className = 'generate-btn px-2 py-2 text-gray-500 hover:text-primary-600 focus:outline-none rounded-r-md bg-gray-100 hover:bg-gray-200 transition-colors'; // 添加背景和右侧圆角
|
||||
generateBtn.innerHTML = '<i class="fas fa-dice"></i>';
|
||||
generateBtn.title = '生成随机令牌';
|
||||
generateBtn.addEventListener('click', function() {
|
||||
const newToken = generateRandomToken();
|
||||
input.value = newToken;
|
||||
showNotification('已生成新令牌', 'success');
|
||||
});
|
||||
inputWrapper.appendChild(generateBtn); // 将生成按钮添加到包装器
|
||||
} else {
|
||||
// 如果不是 ALLOWED_TOKENS,确保输入框有右侧圆角
|
||||
input.classList.add('rounded-r-md');
|
||||
}
|
||||
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.type = 'button';
|
||||
removeBtn.className = 'remove-btn text-gray-400 hover:text-red-500 focus:outline-none transition-colors duration-150 ml-2'; // 新的 Tailwind 样式
|
||||
removeBtn.innerHTML = '<i class="fas fa-trash-alt"></i>'; // 改用垃圾桶图标
|
||||
removeBtn.title = '删除'; // 添加悬停提示
|
||||
// 删除按钮样式,保持不变
|
||||
removeBtn.className = 'remove-btn text-gray-400 hover:text-red-500 focus:outline-none transition-colors duration-150';
|
||||
removeBtn.innerHTML = '<i class="fas fa-trash-alt"></i>';
|
||||
removeBtn.title = '删除';
|
||||
removeBtn.addEventListener('click', function() {
|
||||
arrayItem.remove();
|
||||
});
|
||||
|
||||
arrayItem.appendChild(input);
|
||||
|
||||
// 将包装器(包含输入框和可能的生成按钮)和删除按钮添加到主容器
|
||||
arrayItem.appendChild(inputWrapper);
|
||||
arrayItem.appendChild(removeBtn);
|
||||
|
||||
// 插入到添加按钮之前
|
||||
const controls = container.querySelector('.array-controls');
|
||||
container.insertBefore(arrayItem, controls);
|
||||
|
||||
// 插入到容器末尾
|
||||
container.appendChild(arrayItem);
|
||||
}
|
||||
|
||||
// 收集表单数据
|
||||
@@ -605,3 +732,16 @@ function toggleScrollButtons() {
|
||||
scrollButtons.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// --- 新增:生成随机令牌函数 ---
|
||||
function generateRandomToken() {
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_';
|
||||
const length = 48;
|
||||
let result = 'sk-';
|
||||
const charactersLength = characters.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// --- 结束:生成随机令牌函数 ---
|
||||
|
||||
@@ -108,7 +108,10 @@
|
||||
<div class="array-container bg-white rounded-lg border border-gray-200 p-4 mb-2" id="API_KEYS_container">
|
||||
<!-- 数组项将在这里动态添加 -->
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<div class="flex justify-end gap-2">
|
||||
<button type="button" class="bg-danger-600 hover:bg-danger-700 text-white px-4 py-2 rounded-lg font-medium transition-all duration-200 flex items-center gap-2" id="bulkDeleteApiKeyBtn">
|
||||
<i class="fas fa-trash-alt"></i> 删除密钥
|
||||
</button>
|
||||
<button type="button" class="bg-primary-600 hover:bg-primary-700 text-white px-4 py-2 rounded-lg font-medium transition-all duration-200 flex items-center gap-2" id="addApiKeyBtn">
|
||||
<i class="fas fa-plus"></i> 添加密钥
|
||||
</button>
|
||||
@@ -425,7 +428,24 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Bulk Delete API Key Modal -->
|
||||
<div id="bulkDeleteApiKeyModal" class="modal">
|
||||
<div class="w-full max-w-lg mx-auto bg-white rounded-2xl shadow-2xl overflow-hidden animate-fade-in">
|
||||
<div class="p-6">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-bold text-gray-800">批量删除 API 密钥</h2>
|
||||
<button id="closeBulkDeleteModalBtn" class="text-gray-400 hover:text-gray-600 text-xl">×</button>
|
||||
</div>
|
||||
<p class="text-gray-600 mb-4">每行粘贴一个或多个密钥,将自动提取有效密钥并从列表中删除。</p>
|
||||
<textarea id="bulkDeleteApiKeyInput" rows="10" placeholder="在此处粘贴要删除的 API 密钥..." class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:border-danger-500 focus:ring focus:ring-danger-200 focus:ring-opacity-50 font-mono text-sm"></textarea>
|
||||
<div class="flex justify-end gap-3 mt-6">
|
||||
<button type="button" id="confirmBulkDeleteApiKeyBtn" class="bg-danger-600 hover:bg-danger-700 text-white px-6 py-2 rounded-lg font-medium transition">确认删除</button>
|
||||
<button type="button" id="cancelBulkDeleteApiKeyBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-700 px-6 py-2 rounded-lg font-medium transition">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Reset Confirmation Modal -->
|
||||
<div id="resetConfirmModal" class="modal">
|
||||
<div class="w-full max-w-md mx-auto bg-white rounded-2xl shadow-2xl overflow-hidden animate-fade-in">
|
||||
|
||||
Reference in New Issue
Block a user