Files
crank/apps/ui/js/wizard-upstreams.js
T
github-ops d072d142ca
Deploy / deploy (push) Successful in 1m36s
CI / Rust Checks (push) Successful in 4m50s
CI / UI Checks (push) Successful in 5s
CI / Deployment Manifests (push) Successful in 2s
CI / Frontend E2E (push) Failing after 5m5s
Polish community UI copy and cleanup
2026-06-19 21:15:02 +00:00

605 lines
20 KiB
JavaScript

/* ── 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')
);
}
}