From 09604c6481f3162a5e2801bd6781011ded5c7b97 Mon Sep 17 00:00:00 2001 From: "a.tolmachev" Date: Mon, 6 Apr 2026 00:32:48 +0300 Subject: [PATCH] fix: support editing upstream entries in wizard --- apps/ui/html/wizard/step2.html | 2 +- apps/ui/js/i18n.js | 6 +++ apps/ui/js/wizard.js | 97 +++++++++++++++++++++++++++++----- 3 files changed, 91 insertions(+), 14 deletions(-) diff --git a/apps/ui/html/wizard/step2.html b/apps/ui/html/wizard/step2.html index 84bce0f..5f2c137 100644 --- a/apps/ui/html/wizard/step2.html +++ b/apps/ui/html/wizard/step2.html @@ -58,7 +58,7 @@
- + diff --git a/apps/ui/js/i18n.js b/apps/ui/js/i18n.js index a0bca7c..7d9f7e3 100644 --- a/apps/ui/js/i18n.js +++ b/apps/ui/js/i18n.js @@ -458,6 +458,9 @@ var TRANSLATIONS = { 'wizard.step2.upstream_placeholder': 'Select an upstream…', 'wizard.step2.search_placeholder': 'Search by name or URL…', 'wizard.step2.change': 'Change', + 'wizard.step2.auth_none': 'No auth', + 'wizard.step2.auth_bearer': 'Bearer token', + 'wizard.step2.auth_apikey': 'API key', 'wizard.step2.register_new': 'Register new upstream', 'wizard.step2.name': 'Name', 'wizard.step2.base_url': 'Base URL', @@ -1237,6 +1240,9 @@ var TRANSLATIONS = { 'wizard.step2.upstream_placeholder': 'Выберите upstream…', 'wizard.step2.search_placeholder': 'Поиск по имени или URL…', 'wizard.step2.change': 'Изменить', + 'wizard.step2.auth_none': 'Без авторизации', + 'wizard.step2.auth_bearer': 'Bearer token', + 'wizard.step2.auth_apikey': 'API key', 'wizard.step2.register_new': 'Зарегистрировать новый upstream', 'wizard.step2.name': 'Имя', 'wizard.step2.base_url': 'Base URL', diff --git a/apps/ui/js/wizard.js b/apps/ui/js/wizard.js index 92b3803..eb95b9b 100644 --- a/apps/ui/js/wizard.js +++ b/apps/ui/js/wizard.js @@ -288,15 +288,42 @@ function selectGqlType(btn) { /* ── Upstream selector (Step 3) ── */ var upstreams = [ - { id: 'acme-api', name: 'acme-api', url: 'https://api.acme.com', auth: 'bearer', authLabel: 'Bearer token' }, - { id: 'stripe', name: 'stripe-payments', url: 'https://api.stripe.com', auth: 'apikey', authLabel: 'API key' }, - { id: 'internal-crm', name: 'internal-crm', url: 'https://crm.internal.acme.io', auth: 'none', authLabel: 'No auth' }, - { id: 'sendgrid', name: 'sendgrid-mail', url: 'https://api.sendgrid.com', auth: 'apikey', authLabel: 'API key' }, - { id: 'twilio', name: 'twilio-sms', url: 'https://api.twilio.com', auth: 'bearer', authLabel: 'Bearer token' }, + { id: 'acme-api', name: 'acme-api', url: 'https://api.acme.com', authHeaders: '{\n \"Authorization\": \"Bearer ${secrets.ACME_API_TOKEN}\"\n}' }, + { id: 'stripe', name: 'stripe-payments', url: 'https://api.stripe.com', authHeaders: '{\n \"Authorization\": \"Bearer ${secrets.STRIPE_API_KEY}\"\n}' }, + { id: 'internal-crm', name: 'internal-crm', url: 'https://crm.internal.acme.io', authHeaders: '{\n}' }, + { id: 'sendgrid', name: 'sendgrid-mail', url: 'https://api.sendgrid.com', authHeaders: '{\n \"Authorization\": \"Bearer ${secrets.SENDGRID_API_KEY}\"\n}' }, + { id: 'twilio', name: 'twilio-sms', url: 'https://api.twilio.com', authHeaders: '{\n \"Authorization\": \"Bearer ${secrets.TWILIO_API_KEY}\"\n}' }, ]; var selectedUpstreamId = null; var dropdownOpen = false; +var editingUpstreamId = null; + +function inferUpstreamAuth(headersText) { + var parsed = parseStructuredText(headersText || '{}'); + var keys = parsed && typeof parsed === 'object' ? Object.keys(parsed) : []; + if (!keys.length) { + return { kind: 'none', label: tKey('wizard.step2.auth_none') }; + } + + var hasAuthorization = keys.some(function(key) { + return String(key).toLowerCase() === 'authorization'; + }); + + if (hasAuthorization) { + return { kind: 'bearer', label: tKey('wizard.step2.auth_bearer') }; + } + + return { kind: 'apikey', label: tKey('wizard.step2.auth_apikey') }; +} + +function upstreamMeta(upstream) { + var auth = inferUpstreamAuth(upstream && upstream.authHeaders); + return { + kind: auth.kind, + label: auth.label, + }; +} function renderDropdownList(filter) { var list = document.getElementById('upstream-dropdown-list'); @@ -315,6 +342,7 @@ function renderDropdownList(filter) { 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'); @@ -322,8 +350,8 @@ function renderDropdownList(filter) { 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 = u.authLabel; - badge.className = 'upstream-auth-badge auth-' + u.auth; + badge.textContent = meta.label; + badge.className = 'upstream-auth-badge auth-' + meta.kind; list.appendChild(node); }); } @@ -364,6 +392,7 @@ function filterUpstreams(val) { function pickUpstream(id) { selectedUpstreamId = id; + editingUpstreamId = null; closeUpstreamDropdown(); var u = upstreams.find(function(x) { return x.id === id; }); @@ -392,10 +421,11 @@ function pickUpstream(id) { 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 = u.authLabel; - pBadge.className = 'upstream-auth-badge auth-' + u.auth; + pBadge.textContent = meta.label; + pBadge.className = 'upstream-auth-badge auth-' + meta.kind; preview.style.display = 'flex'; } @@ -414,6 +444,7 @@ function pickUpstream(id) { function startNewUpstream() { closeUpstreamDropdown(); + editingUpstreamId = null; var trigger = document.getElementById('upstream-new-trigger'); var form = document.getElementById('upstream-new-form'); var isOpen = form && form.style.display !== 'none'; @@ -436,11 +467,35 @@ function startNewUpstream() { if (trigger) trigger.classList.add('active'); if (form) { form.style.display = ''; + setValue('new-upstream-name', ''); + setValue('new-upstream-url', ''); + setValue('new-upstream-auth-headers', '{\n}'); 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.style.display = 'none'; + if (form) { + form.style.display = ''; + setValue('new-upstream-name', upstream.name); + setValue('new-upstream-url', upstream.url); + setValue('new-upstream-auth-headers', upstream.authHeaders || '{\n}'); + } +} + function saveNewUpstream(e) { e.stopPropagation(); var nameEl = document.getElementById('new-upstream-name'); @@ -450,18 +505,33 @@ function saveNewUpstream(e) { if (!name) { if (nameEl) nameEl.focus(); return; } if (!url) { if (urlEl) urlEl.focus(); return; } - // Add to list - var newId = 'custom-' + Date.now(); - upstreams.push({ id: newId, name: name, url: url, auth: 'bearer', authLabel: 'Bearer token' }); + var authHeadersEl = document.getElementById('new-upstream-auth-headers'); + var authHeaders = authHeadersEl ? authHeadersEl.value : '{\n}'; + var nextRecord = { + id: editingUpstreamId || ('custom-' + Date.now()), + name: name, + url: url, + authHeaders: authHeaders + }; + + if (editingUpstreamId) { + upstreams = upstreams.map(function(item) { + return item.id === editingUpstreamId ? nextRecord : item; + }); + } else { + upstreams.push(nextRecord); + } // Reset form if (nameEl) nameEl.value = ''; if (urlEl) urlEl.value = ''; + if (authHeadersEl) authHeadersEl.value = '{\n}'; var form = document.getElementById('upstream-new-form'); if (form) form.style.display = 'none'; + editingUpstreamId = null; // Select the new upstream - pickUpstream(newId); + pickUpstream(nextRecord.id); } function cancelNewUpstream(e) { @@ -470,6 +540,7 @@ function cancelNewUpstream(e) { if (form) form.style.display = 'none'; var trigger = document.getElementById('upstream-new-trigger'); if (trigger) trigger.classList.remove('active'); + editingUpstreamId = null; } function escapeHtml(str) {