This commit is contained in:
XOF
2025-11-20 12:24:05 +08:00
commit f28bdc751f
164 changed files with 64248 additions and 0 deletions

View File

@@ -0,0 +1,221 @@
// 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([]);
}
}