Initial commit
This commit is contained in:
312
frontend/js/components/apiKeyManager.js
Normal file
312
frontend/js/components/apiKeyManager.js
Normal file
@@ -0,0 +1,312 @@
|
||||
// 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();
|
||||
Reference in New Issue
Block a user