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-name" id="upstream-preview-name"></div>
<div class="upstream-preview-url" id="upstream-preview-url"></div> <div class="upstream-preview-url" id="upstream-preview-url"></div>
<span class="upstream-auth-badge" id="upstream-preview-badge"></span> <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> </div>
<!-- Register new upstream trigger row --> <!-- Register new upstream trigger row -->
+6
View File
@@ -458,6 +458,9 @@ var TRANSLATIONS = {
'wizard.step2.upstream_placeholder': 'Select an upstream…', 'wizard.step2.upstream_placeholder': 'Select an upstream…',
'wizard.step2.search_placeholder': 'Search by name or URL…', 'wizard.step2.search_placeholder': 'Search by name or URL…',
'wizard.step2.change': 'Change', '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.register_new': 'Register new upstream',
'wizard.step2.name': 'Name', 'wizard.step2.name': 'Name',
'wizard.step2.base_url': 'Base URL', 'wizard.step2.base_url': 'Base URL',
@@ -1237,6 +1240,9 @@ var TRANSLATIONS = {
'wizard.step2.upstream_placeholder': 'Выберите upstream…', 'wizard.step2.upstream_placeholder': 'Выберите upstream…',
'wizard.step2.search_placeholder': 'Поиск по имени или URL…', 'wizard.step2.search_placeholder': 'Поиск по имени или URL…',
'wizard.step2.change': 'Изменить', '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.register_new': 'Зарегистрировать новый upstream',
'wizard.step2.name': 'Имя', 'wizard.step2.name': 'Имя',
'wizard.step2.base_url': 'Base URL', 'wizard.step2.base_url': 'Base URL',
+84 -13
View File
@@ -288,15 +288,42 @@ function selectGqlType(btn) {
/* ── Upstream selector (Step 3) ── */ /* ── Upstream selector (Step 3) ── */
var upstreams = [ var upstreams = [
{ id: 'acme-api', name: 'acme-api', url: 'https://api.acme.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', auth: 'apikey', authLabel: 'API key' }, { 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', auth: 'none', authLabel: 'No auth' }, { id: 'internal-crm', name: 'internal-crm', url: 'https://crm.internal.acme.io', authHeaders: '{\n}' },
{ id: 'sendgrid', name: 'sendgrid-mail', url: 'https://api.sendgrid.com', auth: 'apikey', authLabel: 'API key' }, { 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', auth: 'bearer', authLabel: 'Bearer token' }, { id: 'twilio', name: 'twilio-sms', url: 'https://api.twilio.com', authHeaders: '{\n \"Authorization\": \"Bearer ${secrets.TWILIO_API_KEY}\"\n}' },
]; ];
var selectedUpstreamId = null; var selectedUpstreamId = null;
var dropdownOpen = false; 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) { function renderDropdownList(filter) {
var list = document.getElementById('upstream-dropdown-list'); var list = document.getElementById('upstream-dropdown-list');
@@ -315,6 +342,7 @@ function renderDropdownList(filter) {
list.innerHTML = ''; list.innerHTML = '';
filtered.forEach(function(u) { filtered.forEach(function(u) {
var sel = u.id === selectedUpstreamId; var sel = u.id === selectedUpstreamId;
var meta = upstreamMeta(u);
var node = tmpl.content.cloneNode(true); var node = tmpl.content.cloneNode(true);
var item = node.querySelector('.upstream-dropdown-item'); var item = node.querySelector('.upstream-dropdown-item');
if (sel) item.classList.add('selected'); 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-name').textContent = u.name;
node.querySelector('.upstream-dropdown-item-url').textContent = u.url; node.querySelector('.upstream-dropdown-item-url').textContent = u.url;
var badge = node.querySelector('.upstream-auth-badge'); var badge = node.querySelector('.upstream-auth-badge');
badge.textContent = u.authLabel; badge.textContent = meta.label;
badge.className = 'upstream-auth-badge auth-' + u.auth; badge.className = 'upstream-auth-badge auth-' + meta.kind;
list.appendChild(node); list.appendChild(node);
}); });
} }
@@ -364,6 +392,7 @@ function filterUpstreams(val) {
function pickUpstream(id) { function pickUpstream(id) {
selectedUpstreamId = id; selectedUpstreamId = id;
editingUpstreamId = null;
closeUpstreamDropdown(); closeUpstreamDropdown();
var u = upstreams.find(function(x) { return x.id === id; }); 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 pUrl = document.getElementById('upstream-preview-url');
var pBadge = document.getElementById('upstream-preview-badge'); var pBadge = document.getElementById('upstream-preview-badge');
if (preview) { if (preview) {
var meta = upstreamMeta(u);
pName.textContent = u.name; pName.textContent = u.name;
pUrl.textContent = u.url; pUrl.textContent = u.url;
pBadge.textContent = u.authLabel; pBadge.textContent = meta.label;
pBadge.className = 'upstream-auth-badge auth-' + u.auth; pBadge.className = 'upstream-auth-badge auth-' + meta.kind;
preview.style.display = 'flex'; preview.style.display = 'flex';
} }
@@ -414,6 +444,7 @@ function pickUpstream(id) {
function startNewUpstream() { function startNewUpstream() {
closeUpstreamDropdown(); closeUpstreamDropdown();
editingUpstreamId = null;
var trigger = document.getElementById('upstream-new-trigger'); var trigger = document.getElementById('upstream-new-trigger');
var form = document.getElementById('upstream-new-form'); var form = document.getElementById('upstream-new-form');
var isOpen = form && form.style.display !== 'none'; var isOpen = form && form.style.display !== 'none';
@@ -436,11 +467,35 @@ function startNewUpstream() {
if (trigger) trigger.classList.add('active'); if (trigger) trigger.classList.add('active');
if (form) { if (form) {
form.style.display = ''; form.style.display = '';
setValue('new-upstream-name', '');
setValue('new-upstream-url', '');
setValue('new-upstream-auth-headers', '{\n}');
var first = form.querySelector('input'); var first = form.querySelector('input');
if (first) setTimeout(function() { first.focus(); }, 50); 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) { function saveNewUpstream(e) {
e.stopPropagation(); e.stopPropagation();
var nameEl = document.getElementById('new-upstream-name'); var nameEl = document.getElementById('new-upstream-name');
@@ -450,18 +505,33 @@ function saveNewUpstream(e) {
if (!name) { if (nameEl) nameEl.focus(); return; } if (!name) { if (nameEl) nameEl.focus(); return; }
if (!url) { if (urlEl) urlEl.focus(); return; } if (!url) { if (urlEl) urlEl.focus(); return; }
// Add to list var authHeadersEl = document.getElementById('new-upstream-auth-headers');
var newId = 'custom-' + Date.now(); var authHeaders = authHeadersEl ? authHeadersEl.value : '{\n}';
upstreams.push({ id: newId, name: name, url: url, auth: 'bearer', authLabel: 'Bearer token' }); 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 // Reset form
if (nameEl) nameEl.value = ''; if (nameEl) nameEl.value = '';
if (urlEl) urlEl.value = ''; if (urlEl) urlEl.value = '';
if (authHeadersEl) authHeadersEl.value = '{\n}';
var form = document.getElementById('upstream-new-form'); var form = document.getElementById('upstream-new-form');
if (form) form.style.display = 'none'; if (form) form.style.display = 'none';
editingUpstreamId = null;
// Select the new upstream // Select the new upstream
pickUpstream(newId); pickUpstream(nextRecord.id);
} }
function cancelNewUpstream(e) { function cancelNewUpstream(e) {
@@ -470,6 +540,7 @@ function cancelNewUpstream(e) {
if (form) form.style.display = 'none'; if (form) form.style.display = 'none';
var trigger = document.getElementById('upstream-new-trigger'); var trigger = document.getElementById('upstream-new-trigger');
if (trigger) trigger.classList.remove('active'); if (trigger) trigger.classList.remove('active');
editingUpstreamId = null;
} }
function escapeHtml(str) { function escapeHtml(str) {