222 lines
9.9 KiB
JavaScript
222 lines
9.9 KiB
JavaScript
// 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([]);
|
||
}
|
||
}
|