// 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([]); } }