Files
gemini-banlancer/frontend/js/pages/keys/keyGroupModal.js
2025-11-20 12:24:05 +08:00

222 lines
9.9 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/pages/keys/keyGroupModal.js
import { modalManager } from '../../components/ui.js';
const MAX_GROUP_NAME_LENGTH = 32;
export default class KeyGroupModal {
constructor({ onSave, tagInputInstances }) {
this.modalId = 'keygroup-modal';
this.onSave = onSave;
this.tagInputs = tagInputInstances;
this.editingGroupId = null;
const modal = document.getElementById(this.modalId);
if (!modal) {
throw new Error(`Modal with id "${this.modalId}" not found.`);
}
this.elements = {
modal: modal,
title: document.getElementById('modal-title'),
saveBtn: document.getElementById('modal-save-btn'),
// 表单字段
nameInput: document.getElementById('group-name'),
nameHelper: document.getElementById('group-name-helper'),
displayNameInput: document.getElementById('group-display-name'),
descriptionInput: document.getElementById('group-description'),
strategySelect: document.getElementById('group-strategy'),
maxRetriesInput: document.getElementById('group-max-retries'),
failureThresholdInput: document.getElementById('group-key-blacklist-threshold'),
enableProxyToggle: document.getElementById('group-enable-proxy'),
enableSmartGatewayToggle: document.getElementById('group-enable-smart-gateway'),
// 自动验证设置
enableKeyCheckToggle: document.getElementById('group-enable-key-check'),
keyCheckSettingsPanel: document.getElementById('key-check-settings'),
keyCheckModelInput: document.getElementById('group-key-check-model'),
keyCheckIntervalInput: document.getElementById('group-key-check-interval-minutes'),
keyCheckConcurrencyInput: document.getElementById('group-key-check-concurrency'),
keyCooldownInput: document.getElementById('group-key-cooldown-minutes'),
keyCheckEndpointInput: document.getElementById('group-key-check-endpoint'),
};
this._initEventListeners();
}
open(groupData = null) {
this._populateForm(groupData);
modalManager.show(this.modalId);
}
close() {
modalManager.hide(this.modalId);
}
_initEventListeners() {
if (this.elements.saveBtn) {
this.elements.saveBtn.addEventListener('click', this._handleSave.bind(this));
}
if (this.elements.nameInput) {
this.elements.nameInput.addEventListener('input', this._sanitizeGroupName.bind(this));
}
// 自动验证开关控制面板显隐
if (this.elements.enableKeyCheckToggle) {
this.elements.enableKeyCheckToggle.addEventListener('change', (e) => {
this.elements.keyCheckSettingsPanel.classList.toggle('hidden', !e.target.checked);
});
}
const closeAction = () => this.close();
const closeTriggers = this.elements.modal.querySelectorAll(`[data-modal-close="${this.modalId}"]`);
closeTriggers.forEach(trigger => trigger.addEventListener('click', closeAction));
this.elements.modal.addEventListener('click', (event) => {
if (event.target === this.elements.modal) closeAction();
});
}
// 实时净化 group name 的哨兵函数
_sanitizeGroupName(event) {
const input = event.target;
let value = input.value;
// 1. Convert to lowercase.
value = value.toLowerCase();
// 2. Remove all illegal characters.
value = value.replace(/[^a-z0-9-]/g, '');
// 3. Enforce the length limit by truncating.
if (value.length > MAX_GROUP_NAME_LENGTH) {
value = value.substring(0, MAX_GROUP_NAME_LENGTH);
}
if (input.value !== value) {
input.value = value;
}
}
async _handleSave() {
// [MODIFICATION] The save button's disabled state is now reset in a finally block for robustness.
this._sanitizeGroupName({ target: this.elements.nameInput });
const data = this._collectFormData();
if (!data.name || !data.display_name) {
alert('分组名称和显示名称是必填项。');
return;
}
// 最终提交前的正则验证
const groupNameRegex = /^[a-z0-9-]+$/;
if (!groupNameRegex.test(data.name) || data.name.length > MAX_GROUP_NAME_LENGTH) {
alert('分组名称格式无效。仅限使用小写字母、数字和连字符(-)且长度不超过32个字符。');
return;
}
if (this.onSave) {
this.elements.saveBtn.disabled = true;
this.elements.saveBtn.textContent = '保存中...';
try {
await this.onSave(data);
this.close();
} catch (error) {
console.error("Failed to save key group:", error);
} finally {
this.elements.saveBtn.disabled = false;
this.elements.saveBtn.textContent = '保存';
}
}
}
_populateForm(data) {
if (data) { // 编辑模式
this.editingGroupId = data.id;
this.elements.title.textContent = '编辑 Key Group';
this.elements.nameInput.value = data.name || '';
this.elements.nameInput.disabled = false;
this.elements.displayNameInput.value = data.display_name || '';
this.elements.descriptionInput.value = data.description || '';
this.elements.strategySelect.value = data.polling_strategy || 'random';
this.elements.enableProxyToggle.checked = data.enable_proxy || false;
const settings = data.settings && data.settings.SettingsJSON ? data.settings.SettingsJSON : {};
this.elements.maxRetriesInput.value = settings.max_retries ?? '';
this.elements.failureThresholdInput.value = settings.key_blacklist_threshold ?? '';
this.elements.enableSmartGatewayToggle.checked = settings.enable_smart_gateway || false;
const isKeyCheckEnabled = settings.enable_key_check || false;
this.elements.enableKeyCheckToggle.checked = isKeyCheckEnabled;
this.elements.keyCheckSettingsPanel.classList.toggle('hidden', !isKeyCheckEnabled);
this.elements.keyCheckModelInput.value = settings.key_check_model || '';
this.elements.keyCheckIntervalInput.value = settings.key_check_interval_minutes ?? '';
this.elements.keyCheckConcurrencyInput.value = settings.key_check_concurrency ?? '';
this.elements.keyCooldownInput.value = settings.key_cooldown_minutes ?? '';
this.elements.keyCheckEndpointInput.value = settings.key_check_endpoint || '';
this.tagInputs.models.setValues(data.allowed_models || []);
this.tagInputs.upstreams.setValues(data.allowed_upstreams || []);
this.tagInputs.tokens.setValues(data.allowed_tokens || []);
} else { // 创建模式
this.editingGroupId = null;
this.elements.title.textContent = '创建新的 Key Group';
this._resetForm();
}
}
_collectFormData() {
const parseIntOrNull = (value) => {
const trimmed = value.trim();
return trimmed === '' ? null : parseInt(trimmed, 10);
};
const formData = {
name: this.elements.nameInput.value.trim(),
display_name: this.elements.displayNameInput.value.trim(),
description: this.elements.descriptionInput.value.trim(),
polling_strategy: this.elements.strategySelect.value,
max_retries: parseIntOrNull(this.elements.maxRetriesInput.value),
key_blacklist_threshold: parseIntOrNull(this.elements.failureThresholdInput.value),
enable_proxy: this.elements.enableProxyToggle.checked,
enable_smart_gateway: this.elements.enableSmartGatewayToggle.checked,
enable_key_check: this.elements.enableKeyCheckToggle.checked,
key_check_model: this.elements.keyCheckModelInput.value.trim() || null,
key_check_interval_minutes: parseIntOrNull(this.elements.keyCheckIntervalInput.value),
key_check_concurrency: parseIntOrNull(this.elements.keyCheckConcurrencyInput.value),
key_cooldown_minutes: parseIntOrNull(this.elements.keyCooldownInput.value),
key_check_endpoint: this.elements.keyCheckEndpointInput.value.trim() || null,
allowed_models: this.tagInputs.models.getValues(),
allowed_upstreams: this.tagInputs.upstreams.getValues(),
allowed_tokens: this.tagInputs.tokens.getValues(),
};
if (this.editingGroupId) {
formData.id = this.editingGroupId;
}
return formData;
}
/**
* [核心修正] 完整且健壮的表单重置方法
*/
_resetForm() {
this.elements.nameInput.value = '';
this.elements.nameInput.disabled = false;
this.elements.displayNameInput.value = '';
this.elements.descriptionInput.value = '';
this.elements.strategySelect.value = 'random';
this.elements.maxRetriesInput.value = '';
this.elements.failureThresholdInput.value = '';
this.elements.enableProxyToggle.checked = false;
this.elements.enableSmartGatewayToggle.checked = false;
this.elements.enableKeyCheckToggle.checked = false;
this.elements.keyCheckSettingsPanel.classList.add('hidden');
this.elements.keyCheckModelInput.value = '';
this.elements.keyCheckIntervalInput.value = '';
this.elements.keyCheckConcurrencyInput.value = '';
this.elements.keyCooldownInput.value = '';
this.elements.keyCheckEndpointInput.value = '';
this.tagInputs.models.setValues([]);
this.tagInputs.upstreams.setValues([]);
this.tagInputs.tokens.setValues([]);
}
}