Files
gemini-banlancer/frontend/js/components/apiKeyManager.js
2025-11-20 12:24:05 +08:00

313 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// frontend/js/components/apiKeyManager.js
//import { apiFetch } from "../main.js"; // Assuming apiFetch is exported from main.js
import { apiFetch, apiFetchJson } from '../services/api.js';
import { modalManager } from "./ui.js";
/**
* Manages all API operations related to keys.
* This class provides a centralized interface for actions such as
* fetching, verifying, resetting, and deleting keys.
*/
class ApiKeyManager {
constructor() {
// The constructor can be used to initialize any properties,
// though for this static-like service class, it might be empty.
}
// [新增] 开始一个向指定分组添加Keys的异步任务
/**
* Starts a task to add multiple API keys to a specific group.
* @param {number} groupId - The ID of the group.
* @param {string} keysText - A string of keys, separated by newlines.
* @returns {Promise<object>} A promise that resolves to the initial task status object.
*/
async addKeysToGroup(groupId, keysText, validate) {
// 后端期望的 Body 结构
const payload = {
key_group_id: groupId,
keys: keysText,
validate_on_import: validate
};
// POST 请求不应被缓存,使用原始的 apiFetch 并设置 noCache
const response = await apiFetch(`/admin/keygroups/${groupId}/apikeys/bulk`, {
method: 'POST',
body: JSON.stringify(payload),
noCache: true
});
return response.json();
}
// [新增] 查询一个指定任务的当前状态
/**
* Gets the current status of a background task.
* @param {string} taskId - The ID of the task.
* @returns {Promise<object>} A promise that resolves to the task status object.
*/
getTaskStatus(taskId, options = {}) {
return apiFetchJson(`/admin/tasks/${taskId}`, options);
}
/**
* Fetches a paginated and filtered list of keys.
* @param {string} type - The type of keys to fetch ('valid' or 'invalid').
* @param {number} [page=1] - The page number to retrieve.
* @param {number} [limit=10] - The number of keys per page.
* @param {string} [searchTerm=''] - A search term to filter keys.
* @param {number|null} [failCountThreshold=null] - A threshold for filtering by failure count.
* @returns {Promise<object>} A promise that resolves to the API response data.
*/
async fetchKeys(type, page = 1, limit = 10, searchTerm = '', failCountThreshold = null) {
const params = new URLSearchParams({
page: page,
limit: limit,
status: type,
});
if (searchTerm) params.append('search', searchTerm);
if (failCountThreshold !== null) params.append('fail_count_threshold', failCountThreshold);
return await apiFetch(`/api/keys?${params.toString()}`);
}
/**
* Starts a task to unlink multiple API keys from a specific group.
* @param {number} groupId - The ID of the group.
* @param {string} keysText - A string of keys, separated by newlines.
* @returns {Promise<object>} A promise that resolves to the initial task status object.
*/
async unlinkKeysFromGroup(groupId, keysInput) {
let keysAsText;
if (Array.isArray(keysInput)) {
keysAsText = keysInput.join('\n');
} else {
keysAsText = keysInput;
}
const payload = {
key_group_id: groupId,
keys: keysAsText
};
const response = await apiFetch(`/admin/keygroups/${groupId}/apikeys/bulk`, {
method: 'DELETE',
body: JSON.stringify(payload),
noCache: true
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({ message: response.statusText }));
throw new Error(errorData.message || `Request failed with status ${response.status}`);
}
return response.json();
}
/**
* 更新一个Key在特定分组中的状态 (e.g., 'ACTIVE', 'DISABLED').
* @param {number} groupId - The ID of the group.
* @param {number} keyId - The ID of the API key (api_keys.id).
* @param {string} newStatus - The new operational status ('ACTIVE', 'DISABLED', etc.).
* @returns {Promise<object>} A promise that resolves to the updated mapping object.
*/
async updateKeyStatusInGroup(groupId, keyId, newStatus) {
const endpoint = `/admin/keygroups/${groupId}/apikeys/${keyId}`;
const payload = { status: newStatus };
return await apiFetchJson(endpoint, {
method: 'PUT',
body: JSON.stringify(payload),
noCache: true
});
}
/**
* [MODIFIED] Fetches a paginated and filtered list of API key details for a specific group.
* @param {number} groupId - The ID of the group.
* @param {object} [params={}] - An object containing pagination and filter parameters.
* @param {number} [params.page=1] - The page number to fetch.
* @param {number} [params.limit=20] - The number of items per page.
* @param {string} [params.status] - An optional status to filter the keys by.
* @returns {Promise<object>} A promise that resolves to a pagination object.
*/
async getKeysForGroup(groupId, params = {}) {
// Step 1: Create a URLSearchParams object. This is the modern, safe way to build query strings.
const query = new URLSearchParams({
page: params.page || 1, // Default to page 1 if not provided
limit: params.limit || 20, // Default to 20 per page if not provided
});
// Step 2: Conditionally add the 'status' parameter IF it exists in the params object.
if (params.status) {
query.append('status', params.status);
}
if (params.keyword && params.keyword.trim() !== '') {
query.append('keyword', params.keyword.trim());
}
// Step 3: Construct the final URL by converting the query object to a string.
const url = `/admin/keygroups/${groupId}/apikeys?${query.toString()}`;
// The rest of the logic remains the same.
const responseData = await apiFetchJson(url, { noCache: true });
if (!responseData.success || typeof responseData.data !== 'object' || !Array.isArray(responseData.data.items)) {
throw new Error(responseData.message || 'Failed to fetch paginated keys for the group.');
}
return responseData.data;
}
/**
* 启动一个重新验证一个或多个Key的异步任务。
* @param {number} groupId - The ID of the group context for validation.
* @param {string[]} keyValues - An array of API key strings to revalidate.
* @returns {Promise<object>} A promise that resolves to the initial task status object.
*/
async revalidateKeys(groupId, keyValues) {
const payload = {
keys: keyValues.join('\n')
};
const url = `/admin/keygroups/${groupId}/apikeys/test`;
const responseData = await apiFetchJson(url, {
method: 'POST',
body: JSON.stringify(payload),
noCache: true,
});
if (!responseData.success || !responseData.data) {
throw new Error(responseData.message || "Failed to start revalidation task.");
}
return responseData.data;
}
/**
* Starts a generic bulk action task for an entire group based on filters.
* This single function replaces the need for separate cleanup, revalidate, and restore functions.
* @param {number} groupId The group ID.
* @param {object} payload The body of the request, defining the action and filters.
* @returns {Promise<object>} The initial task response with a task_id.
*/
async startGroupBulkActionTask(groupId, payload) {
// This assumes a new, unified endpoint on the backend.
const url = `/admin/keygroups/${groupId}/bulk-actions`;
const responseData = await apiFetchJson(url, {
method: 'POST',
body: JSON.stringify(payload)
});
if (!responseData.success || !responseData.data) {
throw new Error(responseData.message || "未能启动分组批量任务。");
}
return responseData.data;
}
/**
* [NEW] Fetches all keys for a group, filtered by status, for export purposes using the dedicated export API.
* @param {number} groupId The ID of the group.
* @param {string[]} statuses An array of statuses to filter by (e.g., ['active', 'cooldown']). Use ['all'] for everything.
* @returns {Promise<string[]>} A promise that resolves to an array of API key strings.
*/
async exportKeysForGroup(groupId, statuses = ['all']) {
const params = new URLSearchParams();
statuses.forEach(status => params.append('status', status));
// This now points to our new, clean, non-paginated API endpoint
const url = `/admin/keygroups/${groupId}/apikeys/export?${params.toString()}`;
const responseData = await apiFetchJson(url, { noCache: true });
if (!responseData.success || !Array.isArray(responseData.data)) {
throw new Error(responseData.message || '未能获取用于导出的Key列表。');
}
return responseData.data;
}
/** 以下为GB预置函数未做对齐
* Verifies a single API key.
* @param {string} key - The API key to verify.
* @returns {Promise<object>} A promise that resolves to the API response data.
*/
async verifyKey(key) {
return await apiFetch(`/gemini/v1beta/verify-key/${key}`, { method: "POST" });
}
/**
* Verifies a batch of selected API keys.
* @param {string[]} keys - An array of API keys to verify.
* @returns {Promise<object>} A promise that resolves to the API response data.
*/
async verifySelectedKeys(keys) {
return await apiFetch(`/gemini/v1beta/verify-selected-keys`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ keys }),
});
}
/**
* Resets the failure count for a single API key.
* @param {string} key - The API key whose failure count is to be reset.
* @returns {Promise<object>} A promise that resolves to the API response data.
*/
async resetFailCount(key) {
return await apiFetch(`/gemini/v1beta/reset-fail-count/${key}`, { method: "POST" });
}
/**
* Resets the failure count for a batch of selected API keys.
* @param {string[]} keys - An array of API keys to reset.
* @returns {Promise<object>} A promise that resolves to the API response data.
*/
async resetSelectedFailCounts(keys) {
return await apiFetch(`/gemini/v1beta/reset-selected-fail-counts`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ keys }),
});
}
/**
* Deletes a single API key.
* @param {string} key - The API key to delete.
* @returns {Promise<object>} A promise that resolves to the API response data.
*/
async deleteKey(key) {
return await apiFetch(`/api/config/keys/${key}`, { method: "DELETE" });
}
/**
* Deletes a batch of selected API keys.
* @param {string[]} keys - An array of API keys to delete.
* @returns {Promise<object>} A promise that resolves to the API response data.
*/
async deleteSelectedKeys(keys) {
return await apiFetch("/api/config/keys/delete-selected", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ keys }),
});
}
/**
* Fetches all keys, both valid and invalid.
* @returns {Promise<object>} A promise that resolves to an object containing 'valid_keys' and 'invalid_keys' arrays.
*/
async fetchAllKeys() {
return await apiFetch('/api/keys/all');
}
/**
* Fetches usage details for a specific key over the last 24 hours.
* @param {string} key - The API key to get details for.
* @returns {Promise<object>} A promise that resolves to the API response data.
*/
async getKeyUsageDetails(key) {
return await apiFetch(`/api/key-usage-details/${key}`);
}
/**
* Fetches API call statistics for a given period.
* @param {string} period - The time period for the stats (e.g., '1m', '1h', '24h').
* @returns {Promise<object>} A promise that resolves to the API response data.
*/
async getStatsDetails(period) {
return await apiFetch(`/api/stats/details?period=${period}`);
}
}
export const apiKeyManager = new ApiKeyManager();