mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-06-19 08:52:05 -03:00
feat(settings): hide API key from frontend, use status+edit instead of password field
Backend changes: - Add civitai_api_key to _NO_SYNC_KEYS, return only boolean civitai_api_key_set - Clean up known template placeholder on load to prevent false positive Frontend changes: - Replace type=password with type=text + CSS masking (-webkit-text-security) - Replace pre-filled input with status display (Configured/Not configured) - Add inline edit view with Save/Cancel buttons - Re-add eye toggle via CSS class toggle (not type switching) - Use CSS transitions for smooth status/edit view switching This prevents Chromium/Vivaldi password manager from triggering 'save password' prompts when opening the settings modal.
This commit is contained in:
@@ -335,7 +335,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* API key input specific styles */
|
||||
/* API key input — CSS masking (prevents Chrome password manager triggers) */
|
||||
.api-key-masked {
|
||||
-webkit-text-security: disc;
|
||||
}
|
||||
|
||||
/* API key input specific styles (shared with proxy password) */
|
||||
.api-key-input {
|
||||
width: 100%; /* Take full width of parent */
|
||||
position: relative;
|
||||
@@ -345,7 +350,7 @@
|
||||
|
||||
.api-key-input input {
|
||||
width: 100%;
|
||||
padding: 6px 40px 6px 10px; /* Add left padding */
|
||||
padding: 6px 40px 6px 10px; /* Right padding for eye button */
|
||||
height: 32px;
|
||||
box-sizing: border-box;
|
||||
border-radius: var(--border-radius-xs);
|
||||
@@ -353,6 +358,13 @@
|
||||
background-color: var(--lora-surface);
|
||||
color: var(--text-color);
|
||||
font-size: 0.95em;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.api-key-input input:focus {
|
||||
border-color: var(--lora-accent);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(var(--lora-accent-rgb, 79, 70, 229), 0.1);
|
||||
}
|
||||
|
||||
.api-key-input .toggle-visibility {
|
||||
@@ -364,12 +376,98 @@
|
||||
opacity: 0.6;
|
||||
cursor: pointer;
|
||||
padding: 4px 8px;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.api-key-input .toggle-visibility:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* API key item — stack status/edit views vertically for smooth cross-fade */
|
||||
.api-key-item .setting-control {
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
/* API key status display (shown when not editing) */
|
||||
.api-key-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
transition: opacity 0.2s ease, transform 0.2s ease, max-height 0.25s ease;
|
||||
max-height: 80px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.api-key-status.is-hidden {
|
||||
opacity: 0;
|
||||
max-height: 0;
|
||||
transform: translateY(-4px);
|
||||
pointer-events: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.api-key-status-text {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 0.95em;
|
||||
white-space: nowrap;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
/* Status color modifiers — replace inline styles */
|
||||
.api-key-status--configured .fa-check-circle {
|
||||
color: var(--lora-success);
|
||||
}
|
||||
|
||||
.api-key-status--unconfigured .fa-times-circle {
|
||||
color: var(--lora-error);
|
||||
}
|
||||
|
||||
/* Utility classes for status icon colors (used by JS) */
|
||||
.text-success {
|
||||
color: var(--lora-success);
|
||||
}
|
||||
|
||||
.text-error {
|
||||
color: var(--lora-error);
|
||||
}
|
||||
|
||||
/* API key inline edit container — flex row with input + buttons */
|
||||
.api-key-edit {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
transition: opacity 0.2s ease, transform 0.2s ease, max-height 0.25s ease;
|
||||
max-height: 80px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.api-key-edit.is-hidden {
|
||||
opacity: 0;
|
||||
max-height: 0;
|
||||
transform: translateY(-4px);
|
||||
pointer-events: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.api-key-edit .api-key-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.api-key-edit .primary-btn,
|
||||
.api-key-edit .secondary-btn {
|
||||
height: 32px;
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Text input wrapper styles for consistent input styling */
|
||||
.text-input-wrapper {
|
||||
width: 100%;
|
||||
|
||||
@@ -327,10 +327,15 @@ export class DoctorManager {
|
||||
case 'open-settings':
|
||||
modalManager.showModal('settingsModal');
|
||||
window.setTimeout(() => {
|
||||
const input = document.getElementById('civitaiApiKey');
|
||||
if (input) {
|
||||
input.focus();
|
||||
input.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
// Open the API key editor directly
|
||||
if (typeof settingsManager.editApiKey === 'function') {
|
||||
settingsManager.editApiKey();
|
||||
} else {
|
||||
const input = document.getElementById('civitaiApiKey');
|
||||
if (input) {
|
||||
input.focus();
|
||||
input.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
break;
|
||||
|
||||
@@ -347,9 +347,9 @@ export class SettingsManager {
|
||||
if (this.isOpen) {
|
||||
this.loadSettingsToUI();
|
||||
} else {
|
||||
// Clear sensitive fields on close to prevent browser save-password prompts
|
||||
const apiKeyInput = document.getElementById('civitaiApiKey');
|
||||
if (apiKeyInput) apiKeyInput.value = '';
|
||||
// Reset API key edit mode on close
|
||||
this.cancelEditApiKey(true);
|
||||
// Clear proxy password on close
|
||||
const proxyPasswordInput = document.getElementById('proxyPassword');
|
||||
if (proxyPasswordInput) proxyPasswordInput.value = '';
|
||||
}
|
||||
@@ -825,10 +825,8 @@ export class SettingsManager {
|
||||
usePortableCheckbox.checked = !!state.global.settings.use_portable_settings;
|
||||
}
|
||||
|
||||
const civitaiApiKeyInput = document.getElementById('civitaiApiKey');
|
||||
if (civitaiApiKeyInput) {
|
||||
civitaiApiKeyInput.value = state.global.settings.civitai_api_key || '';
|
||||
}
|
||||
// Update API key status display (do NOT pre-fill the input)
|
||||
this.updateApiKeyStatus();
|
||||
|
||||
const civitaiHostSelect = document.getElementById('civitaiHost');
|
||||
if (civitaiHostSelect) {
|
||||
@@ -2898,16 +2896,97 @@ export class SettingsManager {
|
||||
}
|
||||
}
|
||||
|
||||
// ── CivitAI API Key management ──────────────────────────────
|
||||
|
||||
updateApiKeyStatus() {
|
||||
const hasKey = !!(state.global.settings.civitai_api_key_set ||
|
||||
state.global.settings.civitai_api_key);
|
||||
const statusEl = document.getElementById('civitaiApiKeyStatus');
|
||||
const statusText = document.getElementById('civitaiApiKeyStatusText');
|
||||
const actionBtn = document.getElementById('civitaiApiKeyActionBtn');
|
||||
if (!statusText || !actionBtn) return;
|
||||
|
||||
if (hasKey) {
|
||||
statusText.classList.remove('api-key-status--unconfigured');
|
||||
statusText.classList.add('api-key-status--configured');
|
||||
statusText.innerHTML = '<i class="fas fa-check-circle text-success"></i> '
|
||||
+ translate('settings.civitaiApiKeyConfigured', {}, 'Configured');
|
||||
actionBtn.textContent = translate('common.actions.change', {}, 'Change');
|
||||
} else {
|
||||
statusText.classList.remove('api-key-status--configured');
|
||||
statusText.classList.add('api-key-status--unconfigured');
|
||||
statusText.innerHTML = '<i class="fas fa-times-circle text-error"></i> '
|
||||
+ translate('settings.civitaiApiKeyNotConfigured', {}, 'Not configured');
|
||||
actionBtn.textContent = translate('settings.civitaiApiKeySet', {}, 'Set up');
|
||||
}
|
||||
}
|
||||
|
||||
editApiKey() {
|
||||
const statusEl = document.getElementById('civitaiApiKeyStatus');
|
||||
if (statusEl) statusEl.classList.add('is-hidden');
|
||||
const editContainer = document.getElementById('civitaiApiKeyEdit');
|
||||
if (editContainer) editContainer.classList.remove('is-hidden');
|
||||
// Focus the input
|
||||
const input = document.getElementById('civitaiApiKey');
|
||||
if (input) {
|
||||
input.value = ''; // Never pre-fill the secret
|
||||
setTimeout(() => input.focus(), 50);
|
||||
}
|
||||
}
|
||||
|
||||
cancelEditApiKey(silent) {
|
||||
const editContainer = document.getElementById('civitaiApiKeyEdit');
|
||||
if (editContainer) editContainer.classList.add('is-hidden');
|
||||
const statusContainer = document.getElementById('civitaiApiKeyStatus');
|
||||
if (statusContainer) statusContainer.classList.remove('is-hidden');
|
||||
// Clear any typed value
|
||||
const input = document.getElementById('civitaiApiKey');
|
||||
if (input) input.value = '';
|
||||
if (!silent) {
|
||||
this.updateApiKeyStatus();
|
||||
}
|
||||
}
|
||||
|
||||
async saveApiKey() {
|
||||
const input = document.getElementById('civitaiApiKey');
|
||||
if (!input) return;
|
||||
|
||||
const value = input.value.trim();
|
||||
|
||||
try {
|
||||
await this.saveSetting('civitai_api_key', value);
|
||||
showToast('toast.settings.settingsUpdated',
|
||||
{ setting: 'CivitAI API Key' }, 'success');
|
||||
} catch (error) {
|
||||
showToast('toast.settings.settingSaveFailed',
|
||||
{ message: error.message }, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the in-memory flag so the UI reflects the change
|
||||
state.global.settings.civitai_api_key_set = !!value;
|
||||
this.cancelEditApiKey(true);
|
||||
this.updateApiKeyStatus();
|
||||
}
|
||||
|
||||
toggleInputVisibility(button) {
|
||||
const input = button.parentElement.querySelector('input');
|
||||
if (!input) return;
|
||||
const icon = button.querySelector('i');
|
||||
|
||||
if (input.type === 'password') {
|
||||
if (input.dataset.mask === 'css') {
|
||||
// CSS-masked input (CivitAI API key) — toggle class, not type
|
||||
input.classList.toggle('api-key-masked');
|
||||
if (icon) {
|
||||
icon.className = input.classList.contains('api-key-masked')
|
||||
? 'fas fa-eye'
|
||||
: 'fas fa-eye-slash';
|
||||
}
|
||||
} else if (input.type === 'password') {
|
||||
input.type = 'text';
|
||||
icon.className = 'fas fa-eye-slash';
|
||||
if (icon) icon.className = 'fas fa-eye-slash';
|
||||
} else {
|
||||
input.type = 'password';
|
||||
icon.className = 'fas fa-eye';
|
||||
if (icon) icon.className = 'fas fa-eye';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { DEFAULT_PATH_TEMPLATES, DEFAULT_PRIORITY_TAG_CONFIG } from '../utils/co
|
||||
|
||||
const DEFAULT_SETTINGS_BASE = Object.freeze({
|
||||
civitai_api_key: '',
|
||||
civitai_api_key_set: false,
|
||||
civitai_host: 'civitai.com',
|
||||
download_backend: 'python',
|
||||
aria2c_path: '',
|
||||
|
||||
Reference in New Issue
Block a user