// 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} 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} 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} 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} 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} 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} 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} 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} 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} 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} 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} 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} 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} 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} 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} 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} 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} 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} 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();