fix: support editing upstream entries in wizard

This commit is contained in:
a.tolmachev
2026-04-06 00:32:48 +03:00
parent 627023ffcc
commit 09604c6481
3 changed files with 91 additions and 14 deletions
+1 -1
View File
@@ -58,7 +58,7 @@
<div class="upstream-preview-name" id="upstream-preview-name"></div>
<div class="upstream-preview-url" id="upstream-preview-url"></div>
<span class="upstream-auth-badge" id="upstream-preview-badge"></span>
<button class="upstream-preview-change" onclick="openUpstreamDropdown()" data-i18n="wizard.step2.change">Change</button>
<button class="upstream-preview-change" onclick="beginEditSelectedUpstream(event)" data-i18n="wizard.step2.change">Change</button>
</div>
<!-- Register new upstream trigger row -->
+6
View File
@@ -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',
+84 -13
View File
@@ -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) {