/* ── Upstream selector (Step 2) ── */ var upstreams = []; var dropdownOpen = false; function createDivWithClass(className, text) { var element = document.createElement('div'); element.className = className; element.textContent = text; return element; } function authProfileById(authProfileId) { return wizardAuthProfiles.find(function(item) { return item.id === authProfileId; }) || null; } function syncUpstreamAuthMetadata(upstream) { if (!upstream) return upstream; if (!upstream.authProfileId) { upstream.authProfileName = ''; upstream.authKind = null; return upstream; } var profile = authProfileById(upstream.authProfileId); if (profile) { upstream.authProfileName = profile.name; upstream.authKind = profile.kind; } return upstream; } function inferUpstreamAuth(upstream) { syncUpstreamAuthMetadata(upstream); if (!upstream || !upstream.authProfileId || !upstream.authKind) { return { kind: 'none', label: tKey('wizard.step2.auth_none') }; } if (upstream.authKind === 'bearer') { return { kind: 'bearer', label: tKey('wizard.step2.auth_bearer') }; } if (upstream.authKind === 'basic') { return { kind: 'basic', label: tKey('wizard.step2.auth_basic') }; } return { kind: 'apikey', label: tKey('wizard.step2.auth_apikey') }; } function upstreamMeta(upstream) { var auth = inferUpstreamAuth(upstream); return { kind: auth.kind, label: auth.label, }; } function staticHeadersToEditorValue(value) { if (!value || (typeof value === 'object' && Object.keys(value).length === 0)) { return '{\n}'; } if (typeof value === 'string') { return value; } return JSON.stringify(value, null, 2); } function mapWorkspaceUpstream(record) { return syncUpstreamAuthMetadata({ id: record.id, name: record.name, url: record.base_url, staticHeaders: staticHeadersToEditorValue(record.static_headers), authProfileId: record.auth_profile_id || null, authProfileName: '', authKind: null, persisted: true, }); } async function loadWizardAuthResources() { if (!wizardWorkspaceId || !window.CrankApi) { wizardSecrets = []; wizardAuthProfiles = []; upstreams = []; renderAuthProfileOptions(); return; } try { var results = await Promise.all([ window.CrankApi.listSecrets(wizardWorkspaceId), window.CrankApi.listAuthProfiles(wizardWorkspaceId), window.CrankApi.listUpstreams(wizardWorkspaceId), ]); wizardSecrets = (results[0] && results[0].items) || []; wizardAuthProfiles = (results[1] && results[1].items) || []; upstreams = ((results[2] && results[2].items) || []).map(mapWorkspaceUpstream); } catch (_error) { wizardSecrets = []; wizardAuthProfiles = []; upstreams = []; } upstreams.forEach(syncUpstreamAuthMetadata); renderAuthProfileOptions(); } async function refreshWizardAuthResources() { var previousUpstream = selectedUpstreamId; await loadWizardAuthResources(); renderDropdownList(''); if (previousUpstream && upstreams.some(function(item) { return item.id === previousUpstream; })) { pickUpstream(previousUpstream); } } function renderAuthProfileOptions() { var profileSelect = document.getElementById('new-upstream-auth-profile'); var secretSelect = document.getElementById('new-auth-profile-secret-id'); var usernameSelect = document.getElementById('new-auth-profile-username-secret'); var passwordSelect = document.getElementById('new-auth-profile-password-secret'); var profileHint = document.getElementById('new-upstream-auth-profile-hint'); if (profileSelect) { var previousProfile = profileSelect.value; profileSelect.innerHTML = ''; if (!wizardAuthProfiles.length) { var emptyProfile = document.createElement('option'); emptyProfile.value = ''; emptyProfile.textContent = tKey('wizard.step2.auth_profile_empty'); profileSelect.appendChild(emptyProfile); if (profileHint) profileHint.textContent = tKey('wizard.step2.auth_profile_empty'); } else { var placeholder = document.createElement('option'); placeholder.value = ''; placeholder.textContent = tKey('wizard.step2.auth_profile'); profileSelect.appendChild(placeholder); wizardAuthProfiles.forEach(function(profile) { var option = document.createElement('option'); option.value = profile.id; option.textContent = profile.name + ' · ' + profile.kind; profileSelect.appendChild(option); }); if (previousProfile) { profileSelect.value = previousProfile; } if (profileHint) profileHint.textContent = tKey('wizard.step2.auth_profile_hint'); } } [secretSelect, usernameSelect, passwordSelect].forEach(function(select) { if (!select) return; var previous = select.value; select.innerHTML = ''; var placeholder = document.createElement('option'); placeholder.value = ''; placeholder.textContent = tKey('wizard.step2.secret_value'); select.appendChild(placeholder); wizardSecrets.forEach(function(secret) { var option = document.createElement('option'); option.value = secret.id; option.textContent = secret.name + ' · ' + secret.kind; select.appendChild(option); }); if (previous) { select.value = previous; } }); } function updateAuthProfileCreateUi() { var kind = textValue('new-auth-profile-kind') || 'bearer'; var headerGroup = document.getElementById('auth-profile-header-name-group'); var queryGroup = document.getElementById('auth-profile-query-param-group'); var basicRow = document.getElementById('auth-profile-basic-secret-row'); var singleSecretGroup = document.getElementById('auth-profile-single-secret-group'); if (headerGroup) { headerGroup.hidden = !(kind === 'bearer' || kind === 'api_key_header'); } if (queryGroup) { queryGroup.hidden = kind !== 'api_key_query'; } if (basicRow) { basicRow.hidden = kind !== 'basic'; } if (singleSecretGroup) { singleSecretGroup.hidden = kind === 'basic'; } } function updateUpstreamAuthUi() { var mode = textValue('new-upstream-auth-mode') || 'none'; var existingGroup = document.getElementById('upstream-auth-existing-group'); var createGroup = document.getElementById('upstream-auth-create-group'); if (existingGroup) existingGroup.hidden = mode !== 'existing'; if (createGroup) createGroup.hidden = mode !== 'create'; updateAuthProfileCreateUi(); if (mode === 'existing' || mode === 'create') { void refreshWizardAuthResources(); } } function refreshWizardAuthResourcesOnFocus() { var mode = textValue('new-upstream-auth-mode') || 'none'; var form = document.getElementById('upstream-new-form'); if (form && !form.hidden && (mode === 'existing' || mode === 'create')) { void refreshWizardAuthResources(); } } function buildUpstreamTriggerContent(name, url) { var fragment = document.createDocumentFragment(); fragment.appendChild(createDivWithClass('upstream-trigger-name', name)); fragment.appendChild(createDivWithClass('upstream-trigger-url', url)); return fragment; } function setUpstreamComboboxPlaceholder(container) { if (!container) return; container.innerHTML = ''; var placeholder = document.createElement('span'); placeholder.className = 'upstream-combobox-placeholder'; placeholder.textContent = tKey('wizard.step2.upstream_placeholder'); container.appendChild(placeholder); } function renderDropdownList(filter) { var list = document.getElementById('upstream-dropdown-list'); if (!list) return; var q = (filter || '').toLowerCase(); var filtered = upstreams.filter(function(u) { return !q || u.name.toLowerCase().includes(q) || u.url.toLowerCase().includes(q); }); if (filtered.length === 0) { list.innerHTML = ''; var empty = document.createElement('div'); empty.className = 'upstream-dropdown-empty'; empty.textContent = tfKey('agents.drawer.ops_no_match', { query: filter || '' }); list.appendChild(empty); return; } var tmpl = document.getElementById('tmpl-upstream-item'); list.innerHTML = ''; filtered.forEach(function(u) { var sel = u.id === selectedUpstreamId; var meta = upstreamMeta(u); var node = tmpl.content.cloneNode(true); var item = node.querySelector('.upstream-dropdown-item'); if (sel) item.classList.add('selected'); item.addEventListener('click', function() { pickUpstream(u.id); }); node.querySelector('.upstream-dropdown-item-name').textContent = u.name; node.querySelector('.upstream-dropdown-item-url').textContent = u.url; var badge = node.querySelector('.upstream-auth-badge'); badge.textContent = meta.label; badge.className = 'upstream-auth-badge auth-' + meta.kind; list.appendChild(node); }); } function openUpstreamDropdown() { var combobox = document.getElementById('upstream-combobox'); var dropdown = document.getElementById('upstream-dropdown'); var search = document.getElementById('upstream-search'); if (!combobox || !dropdown) return; combobox.classList.add('open'); dropdown.hidden = false; dropdownOpen = true; renderDropdownList(''); if (search) { search.value = ''; search.focus(); } var form = document.getElementById('upstream-new-form'); if (form) form.hidden = true; } function closeUpstreamDropdown() { var combobox = document.getElementById('upstream-combobox'); var dropdown = document.getElementById('upstream-dropdown'); if (!combobox || !dropdown) return; combobox.classList.remove('open'); dropdown.hidden = true; dropdownOpen = false; } function toggleUpstreamDropdown(e) { e.stopPropagation(); if (dropdownOpen) { closeUpstreamDropdown(); } else { openUpstreamDropdown(); } } function filterUpstreams(val) { renderDropdownList(val); } function pickUpstream(id) { selectedUpstreamId = id; editingUpstreamId = null; closeUpstreamDropdown(); var u = upstreams.find(function(x) { return x.id === id; }); if (!u) return; var val = document.getElementById('upstream-combobox-value'); if (val) { var trigTmpl = document.getElementById('tmpl-upstream-trigger-value'); if (trigTmpl) { val.innerHTML = ''; var trigNode = trigTmpl.content.cloneNode(true); trigNode.querySelector('.upstream-trigger-name').textContent = u.name; trigNode.querySelector('.upstream-trigger-url').textContent = u.url; val.appendChild(trigNode); } else { val.innerHTML = ''; val.appendChild(buildUpstreamTriggerContent(u.name, u.url)); } } var preview = document.getElementById('upstream-preview'); var pName = document.getElementById('upstream-preview-name'); var pUrl = document.getElementById('upstream-preview-url'); var pBadge = document.getElementById('upstream-preview-badge'); if (preview) { var meta = upstreamMeta(u); pName.textContent = u.name; pUrl.textContent = u.url; pBadge.textContent = meta.label; pBadge.className = 'upstream-auth-badge auth-' + meta.kind; preview.hidden = false; } var form = document.getElementById('upstream-new-form'); if (form) form.hidden = true; var trigger = document.getElementById('upstream-new-trigger'); if (trigger) trigger.classList.remove('active'); } function startNewUpstream() { closeUpstreamDropdown(); editingUpstreamId = null; var trigger = document.getElementById('upstream-new-trigger'); var form = document.getElementById('upstream-new-form'); var isOpen = form && !form.hidden; if (isOpen) { if (form) form.hidden = true; if (trigger) trigger.classList.remove('active'); return; } var val = document.getElementById('upstream-combobox-value'); if (val) setUpstreamComboboxPlaceholder(val); var preview = document.getElementById('upstream-preview'); if (preview) preview.hidden = true; selectedUpstreamId = null; if (trigger) trigger.classList.add('active'); if (form) { form.hidden = false; setValue('new-upstream-name', ''); setValue('new-upstream-url', ''); setValue('new-upstream-static-headers', '{\n}'); setValue('new-upstream-auth-mode', 'none'); setValue('new-upstream-auth-profile', ''); setValue('new-auth-profile-name', ''); setValue('new-auth-profile-kind', 'bearer'); setValue('new-auth-profile-header-name', 'Authorization'); setValue('new-auth-profile-query-param', 'api_key'); setValue('new-auth-profile-secret-id', ''); setValue('new-auth-profile-username-secret', ''); setValue('new-auth-profile-password-secret', ''); updateUpstreamAuthUi(); var first = form.querySelector('input'); if (first) setTimeout(function() { first.focus(); }, 50); } } function beginEditSelectedUpstream(e) { if (e) e.stopPropagation(); var upstream = upstreams.find(function(item) { return item.id === selectedUpstreamId; }); if (!upstream) return; closeUpstreamDropdown(); editingUpstreamId = upstream.id; var trigger = document.getElementById('upstream-new-trigger'); var form = document.getElementById('upstream-new-form'); var preview = document.getElementById('upstream-preview'); if (trigger) trigger.classList.add('active'); if (preview) preview.hidden = true; if (form) { form.hidden = false; setValue('new-upstream-name', upstream.name); setValue('new-upstream-url', upstream.url); setValue('new-upstream-static-headers', upstream.staticHeaders || '{\n}'); setValue('new-upstream-auth-profile', upstream.authProfileId || ''); setValue('new-upstream-auth-mode', upstream.authProfileId ? 'existing' : 'none'); updateUpstreamAuthUi(); } } function buildAuthProfileConfig(kind) { if (kind === 'bearer') { var bearerHeader = textValue('new-auth-profile-header-name') || 'Authorization'; var bearerSecretId = textValue('new-auth-profile-secret-id'); if (!bearerHeader) throw new Error(tKey('wizard.error.header_name_required')); if (!bearerSecretId) throw new Error(tKey('wizard.error.secret_required')); return { bearer: { header_name: bearerHeader, secret_id: bearerSecretId, } }; } if (kind === 'basic') { var usernameSecretId = textValue('new-auth-profile-username-secret'); var passwordSecretId = textValue('new-auth-profile-password-secret'); if (!usernameSecretId || !passwordSecretId) throw new Error(tKey('wizard.error.basic_secrets_required')); return { basic: { username_secret_id: usernameSecretId, password_secret_id: passwordSecretId, } }; } if (kind === 'api_key_header') { var headerName = textValue('new-auth-profile-header-name'); var headerSecretId = textValue('new-auth-profile-secret-id'); if (!headerName) throw new Error(tKey('wizard.error.header_name_required')); if (!headerSecretId) throw new Error(tKey('wizard.error.secret_required')); return { api_key_header: { header_name: headerName, secret_id: headerSecretId, } }; } var queryParam = textValue('new-auth-profile-query-param'); var querySecretId = textValue('new-auth-profile-secret-id'); if (!queryParam) throw new Error(tKey('wizard.error.query_param_required')); if (!querySecretId) throw new Error(tKey('wizard.error.secret_required')); return { api_key_query: { param_name: queryParam, secret_id: querySecretId, } }; } async function createAuthProfileFromForm() { var name = textValue('new-auth-profile-name'); var kind = textValue('new-auth-profile-kind') || 'bearer'; if (!name) throw new Error(tKey('wizard.error.auth_profile_name')); var profile = await window.CrankApi.createAuthProfile(wizardWorkspaceId, { name: name, kind: kind, config: buildAuthProfileConfig(kind), }); wizardAuthProfiles.push(profile); renderAuthProfileOptions(); if (window.CrankUi) { window.CrankUi.success( tKey('wizard.toast.auth_profile_created_body'), tKey('wizard.toast.auth_profile_created_title') ); } return profile; } async function saveNewUpstream(e) { e.stopPropagation(); try { var nameEl = document.getElementById('new-upstream-name'); var urlEl = document.getElementById('new-upstream-url'); var name = nameEl ? nameEl.value.trim() : ''; var url = urlEl ? urlEl.value.trim() : ''; if (!name) { if (nameEl) nameEl.focus(); return; } if (!url) { if (urlEl) urlEl.focus(); return; } var staticHeadersEl = document.getElementById('new-upstream-static-headers'); var staticHeaders = staticHeadersEl ? staticHeadersEl.value : '{\n}'; parseHeaderMap(staticHeaders); var authMode = textValue('new-upstream-auth-mode') || 'none'; var authProfileId = null; var authProfileName = ''; var authKind = null; if (authMode === 'existing') { authProfileId = textValue('new-upstream-auth-profile'); if (!authProfileId) throw new Error(tKey('wizard.error.auth_profile_required')); var existingProfile = authProfileById(authProfileId); authProfileName = existingProfile ? existingProfile.name : ''; authKind = existingProfile ? existingProfile.kind : null; } else if (authMode === 'create') { var createdProfile = await createAuthProfileFromForm(); authProfileId = createdProfile.id; authProfileName = createdProfile.name; authKind = createdProfile.kind; setValue('new-upstream-auth-profile', createdProfile.id); setValue('new-upstream-auth-mode', 'existing'); } var payload = { name: name, base_url: url, static_headers: parseHeaderMap(staticHeaders), auth_profile_id: authProfileId, }; var saved = editingUpstreamId ? await window.CrankApi.updateUpstream(wizardWorkspaceId, editingUpstreamId, payload) : await window.CrankApi.createUpstream(wizardWorkspaceId, payload); var nextRecord = mapWorkspaceUpstream(saved); nextRecord.authProfileName = authProfileName; nextRecord.authKind = authKind; if (editingUpstreamId) { upstreams = upstreams.map(function(item) { return item.id === editingUpstreamId ? nextRecord : item; }); } else { upstreams.push(nextRecord); } if (nameEl) nameEl.value = ''; if (urlEl) urlEl.value = ''; if (staticHeadersEl) staticHeadersEl.value = '{\n}'; var form = document.getElementById('upstream-new-form'); if (form) form.hidden = true; editingUpstreamId = null; pickUpstream(nextRecord.id); } catch (error) { if (window.CrankUi) { window.CrankUi.error( error.message || tKey('wizard.toast.auth_profile_error_title'), tKey('wizard.toast.auth_profile_error_title') ); } } } function cancelNewUpstream(e) { e.stopPropagation(); var form = document.getElementById('upstream-new-form'); if (form) form.hidden = true; var trigger = document.getElementById('upstream-new-trigger'); if (trigger) trigger.classList.remove('active'); editingUpstreamId = null; } function openQuickSecretModal(e) { if (e) e.preventDefault(); var modal = document.getElementById('quick-secret-modal'); if (!modal) return; setValue('quick-secret-name', ''); setValue('quick-secret-value', ''); setValue('quick-secret-kind', 'token'); modal.classList.add('open'); setTimeout(function() { var input = document.getElementById('quick-secret-name'); if (input) input.focus(); }, 30); } function closeQuickSecretModal() { var modal = document.getElementById('quick-secret-modal'); if (modal) modal.classList.remove('open'); } async function submitQuickSecret() { var name = textValue('quick-secret-name'); var value = textValue('quick-secret-value'); var kind = textValue('quick-secret-kind') || 'token'; if (!name) throw new Error(tKey('wizard.error.secret_name_required')); if (!value) throw new Error(tKey('wizard.error.secret_value_required')); var secret = await window.CrankApi.createSecret(wizardWorkspaceId, { name: name, kind: kind, value: value, }); wizardSecrets.push(secret); renderAuthProfileOptions(); var primarySelect = document.getElementById('new-auth-profile-secret-id'); var usernameSelect = document.getElementById('new-auth-profile-username-secret'); var passwordSelect = document.getElementById('new-auth-profile-password-secret'); if (primarySelect && primarySelect.offsetParent !== null) { primarySelect.value = secret.id; } else if (usernameSelect && !usernameSelect.value) { usernameSelect.value = secret.id; } else if (passwordSelect) { passwordSelect.value = secret.id; } closeQuickSecretModal(); if (window.CrankUi) { window.CrankUi.success( tKey('wizard.toast.quick_secret_created_body'), tKey('wizard.toast.quick_secret_created_title') ); } }