313 lines
13 KiB
JavaScript
313 lines
13 KiB
JavaScript
// 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();
|