var currentStep = 1;
var TOTAL_STEPS = 5;
var wizardProtocol = 'rest'; // 'rest' | 'graphql' | 'grpc' | 'websocket' | 'soap'
var wizardMode = 'create'; // 'create' | 'edit'
var wizardEditId = null;
var wizardWorkspaceId = null;
var wizardCurrentOperation = null;
var wizardCurrentVersion = null;
var wizardProtoUpload = null;
var wizardDescriptorSetUpload = null;
var wizardSoapWsdlUpload = null;
var wizardSoapXsdUpload = null;
var wizardTestResponsePreview = null;
var grpcDescriptorServices = [];
var soapServiceCatalog = [];
var wizardProtocolCapabilities = null;
function tKey(key) {
return typeof t === 'function' ? t(key) : key;
}
function tfKey(key, vars) {
return typeof tf === 'function' ? tf(key, vars) : key;
}
function defaultProtocolCapabilities() {
return {
rest: {
supports_execution_modes: ['unary', 'window', 'session', 'async_job'],
supports_transport_behaviors: ['request_response', 'server_stream', 'deferred_result'],
},
graphql: {
supports_execution_modes: ['unary'],
supports_transport_behaviors: ['request_response'],
},
grpc: {
supports_execution_modes: ['unary', 'window', 'session', 'async_job'],
supports_transport_behaviors: ['request_response', 'server_stream'],
},
websocket: {
supports_execution_modes: ['window', 'session', 'async_job'],
supports_transport_behaviors: ['server_stream', 'stateful_session', 'deferred_result'],
},
soap: {
supports_execution_modes: ['unary', 'async_job'],
supports_transport_behaviors: ['request_response', 'server_stream', 'deferred_result'],
},
};
}
function currentProtocolCapabilities() {
var capabilities = wizardProtocolCapabilities || defaultProtocolCapabilities();
return capabilities[wizardProtocol] || capabilities.rest;
}
async function loadProtocolCapabilities() {
if (!wizardWorkspaceId || !window.CrankApi || typeof window.CrankApi.getProtocolCapabilities !== 'function') {
wizardProtocolCapabilities = defaultProtocolCapabilities();
return wizardProtocolCapabilities;
}
try {
var response = await window.CrankApi.getProtocolCapabilities(wizardWorkspaceId);
var mapped = {};
(response.items || []).forEach(function(item) {
mapped[item.protocol] = item;
});
wizardProtocolCapabilities = mapped;
} catch (_error) {
wizardProtocolCapabilities = defaultProtocolCapabilities();
}
return wizardProtocolCapabilities;
}
/* ── Dynamic step loading ── */
function _stepFile(n) {
return (n === 3) ? 'step3-' + wizardProtocol + '.html' : 'step' + n + '.html';
}
function loadStep(n, callback) {
var panelId = (n === 3) ? step3PanelId() : 'step-panel-' + n;
if (document.getElementById(panelId)) { if (callback) callback(); return; }
fetch(_stepFile(n))
.then(function(r) { return r.text(); })
.then(function(html) {
var c = document.getElementById('step-panel-container');
if (c) c.insertAdjacentHTML('beforeend', html);
if (typeof applyLang === 'function') applyLang();
if (callback) callback();
})
.catch(function() { if (callback) callback(); });
}
/* Step 3 panel id by protocol */
function step3PanelId() {
if (wizardProtocol === 'graphql') return 'step-panel-3-graphql';
if (wizardProtocol === 'grpc') return 'step-panel-3-grpc';
if (wizardProtocol === 'websocket') return 'step-panel-3-websocket';
if (wizardProtocol === 'soap') return 'step-panel-3-soap';
return 'step-panel-3-rest';
}
/* Sidebar step-3 label by protocol */
var STEP3_LABELS = {
rest: tKey('wizard.step3.rest.label'),
graphql: tKey('wizard.step3.graphql.label'),
grpc: tKey('wizard.step3.grpc.label'),
websocket: tKey('wizard.step3.websocket.label'),
soap: tKey('wizard.step3.soap.label'),
};
var CHECK_SVG = '';
var ARROW_SVG = '';
var BACK_SVG = '';
function goToStep(n) {
if (n < 1 || n > TOTAL_STEPS) return;
loadStep(n, function() { _doGoToStep(n); });
}
function _doGoToStep(n) {
document.querySelectorAll('.step-number[data-step]').forEach(function(element) {
var step = Number(element.getAttribute('data-step')) || 0;
element.textContent = tfKey('wizard.step_short', { step: step });
});
// 1. hide all panes, show target (step 4 is protocol-specific)
document.querySelectorAll('.step-pane').forEach(function(p) { p.style.display = 'none'; });
var panelId = (n === 3) ? step3PanelId() : 'step-panel-' + n;
var pane = document.getElementById(panelId);
if (pane) pane.style.display = '';
// 2. update sidebar step items
document.querySelectorAll('.step-item').forEach(function(item, i) {
var num = i + 1;
item.classList.remove('active', 'done', 'pending');
var ind = item.querySelector('.step-indicator');
var statusEl = item.querySelector('.step-status-text');
if (num < n) {
item.classList.add('done');
if (ind) ind.innerHTML = CHECK_SVG;
if (statusEl) statusEl.textContent = tKey('wizard.status.completed');
} else if (num === n) {
item.classList.add('active');
if (ind) ind.textContent = num;
if (statusEl) statusEl.textContent = tKey('wizard.status.in_progress');
} else {
item.classList.add('pending');
if (ind) ind.textContent = num;
if (statusEl) statusEl.textContent = tKey('wizard.status.not_started');
}
});
// 3. update progress bar
var pct = Math.round((n / TOTAL_STEPS) * 100);
var fill = document.querySelector('.progress-bar-fill');
if (fill) fill.style.width = pct + '%';
var pctEl = document.querySelector('.progress-pct');
if (pctEl) pctEl.textContent = pct + '%';
// 4. update step counter
var counter = document.querySelector('[data-step-counter]');
if (counter) counter.innerHTML = tfKey('wizard.step_of', { step: n, total: TOTAL_STEPS });
// 5. back button
var backBtn = document.querySelector('.btn-back');
if (backBtn) {
if (n === 1) {
backBtn.disabled = true;
backBtn.style.opacity = '0.35';
backBtn.style.cursor = 'not-allowed';
} else {
backBtn.disabled = false;
backBtn.style.opacity = '';
backBtn.style.cursor = '';
}
}
// 6. continue button
var contBtn = document.querySelector('.btn-continue');
if (contBtn) {
if (n === TOTAL_STEPS) {
var finalLabel = wizardMode === 'edit' ? tKey('wizard.button.save_changes') : tKey('wizard.button.create');
contBtn.innerHTML = finalLabel + ' ' + ARROW_SVG;
contBtn.style.cssText = 'background:#3fb950;border-color:#2ea043;box-shadow:0 1px 4px rgba(63,185,80,0.4);';
} else {
contBtn.innerHTML = tKey('wizard.button.continue') + ' ' + ARROW_SVG;
contBtn.style.cssText = '';
}
}
currentStep = n;
window.scrollTo({ top: 0, behavior: 'smooth' });
}
function loadWizardPanels(steps) {
return steps.reduce(function(chain, step) {
return chain.then(function() {
return new Promise(function(resolve) {
loadStep(step, resolve);
});
});
}, Promise.resolve());
}
function bindProtocolCards() {
document.querySelectorAll('.protocol-card').forEach(function(card) {
card.addEventListener('click', function() {
document.querySelectorAll('.protocol-card').forEach(function(item) {
item.classList.remove('selected');
item.setAttribute('aria-checked', 'false');
});
card.classList.add('selected');
card.setAttribute('aria-checked', 'true');
var proto = card.dataset.protocol
|| (card.classList.contains('rest')
? 'rest'
: card.classList.contains('graphql')
? 'graphql'
: card.classList.contains('grpc')
? 'grpc'
: card.classList.contains('websocket')
? 'websocket'
: card.classList.contains('soap')
? 'soap'
: 'rest');
wizardProtocol = proto;
var s3name = document.getElementById('sidebar-step-3-name');
if (s3name) s3name.textContent = STEP3_LABELS[proto] || tKey('wizard.step3.rest.label');
if (proto === 'graphql') {
var pathInput = document.getElementById('endpoint-path');
if (pathInput && pathInput.value === '/v1/leads') pathInput.value = '/graphql';
}
});
card.addEventListener('keydown', function(event) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
card.click();
}
});
});
}
document.addEventListener('DOMContentLoaded', async function() {
document.querySelector('.btn-continue').addEventListener('click', function() {
if (currentStep < TOTAL_STEPS) {
goToStep(currentStep + 1);
} else {
saveOperation();
}
});
document.querySelector('.btn-back').addEventListener('click', function() {
if (!this.disabled && currentStep > 1) goToStep(currentStep - 1);
});
document.querySelectorAll('.step-item').forEach(function(item, i) {
item.addEventListener('click', function() { goToStep(i + 1); });
});
var backToCatalog = document.getElementById('back-to-catalog');
if (backToCatalog) {
backToCatalog.addEventListener('click', function() {
window.location.href = (window.CrankRoutes && window.CrankRoutes.home) || '/';
});
}
var closeBtn = document.querySelector('.progress-close');
if (closeBtn) {
closeBtn.addEventListener('click', function() {
window.location.href = (window.CrankRoutes && window.CrankRoutes.home) || '/';
});
}
var saveDraftBtn = document.querySelector('.btn-save-draft');
if (saveDraftBtn) {
saveDraftBtn.addEventListener('click', function() {
saveOperation(true);
});
}
await (window.whenWorkspacesReady ? window.whenWorkspacesReady() : Promise.resolve());
var workspace = window.getCurrentWorkspace ? window.getCurrentWorkspace() : null;
wizardWorkspaceId = workspace ? workspace.id : null;
await loadProtocolCapabilities();
await loadWizardPanels([1, 2, 3, 4, 5]);
await loadWizardAuthResources();
bindProtocolCards();
bindWizardLiveActions();
bindStreamingConfigControls();
updateUpstreamAuthUi();
var quickSecretModal = document.getElementById('quick-secret-modal');
var quickSecretClose = document.getElementById('quick-secret-close-btn');
var quickSecretCancel = document.getElementById('quick-secret-cancel-btn');
var quickSecretSubmit = document.getElementById('quick-secret-submit-btn');
if (quickSecretClose) quickSecretClose.addEventListener('click', closeQuickSecretModal);
if (quickSecretCancel) quickSecretCancel.addEventListener('click', closeQuickSecretModal);
if (quickSecretModal) {
quickSecretModal.addEventListener('click', function(event) {
if (event.target === quickSecretModal) closeQuickSecretModal();
});
}
if (quickSecretSubmit) {
quickSecretSubmit.addEventListener('click', async function() {
var button = quickSecretSubmit;
var original = button.textContent;
button.disabled = true;
button.textContent = tKey('wizard.step2.quick_secret_submit');
try {
await submitQuickSecret();
} catch (error) {
if (window.CrankUi) {
window.CrankUi.error(
error.message || tKey('wizard.toast.quick_secret_error_title'),
tKey('wizard.toast.quick_secret_error_title')
);
}
} finally {
button.disabled = false;
button.textContent = original;
}
});
}
var params = new URLSearchParams(window.location.search);
if (params.get('mode') === 'edit' && params.get('operationId')) {
wizardMode = 'edit';
wizardEditId = params.get('operationId');
document.title = 'Crank — ' + tKey('wizard.progress.edit');
await loadOperationForEdit();
}
updateWizardProtocolVisibility();
_doGoToStep(1);
});
/* ── HTTP method picker (Step 4 REST) ── */
var METHOD_CALLOUTS = {
GET: null,
DELETE: null,
POST: { icon: 'info', text: { en: 'POST selected — body encoding required. In step 4 you will define the JSON schema for the body payload. Crank will automatically serialize MCP tool arguments into the format you specify.', ru: 'Выбран POST — требуется кодирование тела запроса. На шаге 4 вы зададите JSON-схему для body payload. Crank автоматически сериализует аргументы MCP-инструмента в выбранный формат.' } },
PUT: { icon: 'info', text: { en: 'PUT selected — full-resource replacement. The request body must contain a complete resource representation. Define the body schema in step 4.', ru: 'Выбран PUT — полная замена ресурса. Request body должен содержать полное представление ресурса. Задайте body schema на шаге 4.' } },
PATCH: { icon: 'info', text: { en: 'PATCH selected — partial update. Only include the fields you want to modify in the body schema defined in step 4.', ru: 'Выбран PATCH — частичное обновление. В body schema на шаге 4 включайте только те поля, которые хотите изменить.' } },
};
function selectMethod(btn) {
document.querySelectorAll('.method-card').forEach(function(b) { b.classList.remove('active'); });
btn.classList.add('active');
var method = btn.dataset.method;
var callout = document.getElementById('method-callout-rest');
if (!callout) return;
var info = METHOD_CALLOUTS[method];
if (info) {
var lang = localStorage.getItem('crank_lang') || 'en';
var text = typeof info.text === 'string' ? info.text : (info.text[lang] || info.text.en);
callout.innerHTML =
'' +
'' + text + '';
callout.style.display = 'flex';
} else {
callout.style.display = 'none';
}
}
/* ── GraphQL operation type picker (Step 4 GraphQL) ── */
function selectGqlType(btn) {
document.querySelectorAll('.gql-type-card').forEach(function(b) { b.classList.remove('active'); });
btn.classList.add('active');
// Update query editor placeholder if it's still the default
var editor = document.getElementById('gql-query-editor');
if (!editor) return;
var type = btn.dataset.gqlType;
var defaults = {
query: 'query GetContact($id: ID!) {\n contact(id: $id) {\n id\n firstName\n lastName\n email\n company {\n id\n name\n }\n createdAt\n }\n}',
mutation: 'mutation CreateContact(\n $firstName: String!\n $lastName: String!\n $email: String!\n $company: String\n) {\n createContact(input: {\n firstName: $firstName\n lastName: $lastName\n email: $email\n company: $company\n }) {\n id\n firstName\n lastName\n createdAt\n }\n}'
};
if (defaults[type]) editor.value = defaults[type];
}
/* ── Upstream selector (Step 2) ── */
var upstreams = [
{ id: 'open-meteo', name: 'Open Meteo', url: 'https://api.open-meteo.com', staticHeaders: '{}', authProfileId: null, authProfileName: '', authKind: null },
{ id: 'countries-graphql', name: 'Countries GraphQL', url: 'https://countries.trevorblades.com', staticHeaders: '{}', authProfileId: null, authProfileName: '', authKind: null },
{ id: 'grpcb', name: 'grpcb.in', url: 'https://grpcb.in:443', staticHeaders: '{}', authProfileId: null, authProfileName: '', authKind: null }
];
var wizardSecrets = [];
var wizardAuthProfiles = [];
var selectedUpstreamId = null;
var dropdownOpen = false;
var editingUpstreamId = null;
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,
};
}
async function loadWizardAuthResources() {
if (!wizardWorkspaceId || !window.CrankApi) {
wizardSecrets = [];
wizardAuthProfiles = [];
renderAuthProfileOptions();
return;
}
try {
var results = await Promise.all([
window.CrankApi.listSecrets(wizardWorkspaceId),
window.CrankApi.listAuthProfiles(wizardWorkspaceId),
]);
wizardSecrets = (results[0] && results[0].items) || [];
wizardAuthProfiles = (results[1] && results[1].items) || [];
} catch (_error) {
wizardSecrets = [];
wizardAuthProfiles = [];
}
upstreams.forEach(syncUpstreamAuthMetadata);
renderAuthProfileOptions();
}
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.style.display = (kind === 'bearer' || kind === 'api_key_header') ? '' : 'none';
}
if (queryGroup) {
queryGroup.style.display = kind === 'api_key_query' ? '' : 'none';
}
if (basicRow) {
basicRow.style.display = kind === 'basic' ? '' : 'none';
}
if (singleSecretGroup) {
singleSecretGroup.style.display = kind === 'basic' ? 'none' : '';
}
}
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.style.display = mode === 'existing' ? '' : 'none';
if (createGroup) createGroup.style.display = mode === 'create' ? '' : 'none';
updateAuthProfileCreateUi();
}
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 = '
' + tfKey('agents.drawer.ops_no_match', { query: escapeHtml(filter) }) + '
';
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.style.display = '';
dropdownOpen = true;
renderDropdownList('');
if (search) { search.value = ''; search.focus(); }
// Close new-upstream form if open
var form = document.getElementById('upstream-new-form');
if (form) form.style.display = 'none';
}
function closeUpstreamDropdown() {
var combobox = document.getElementById('upstream-combobox');
var dropdown = document.getElementById('upstream-dropdown');
if (!combobox || !dropdown) return;
combobox.classList.remove('open');
dropdown.style.display = 'none';
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;
// Update combobox trigger value
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 =
'' + escapeHtml(u.name) + '
' +
'' + escapeHtml(u.url) + '
';
}
}
// Show preview strip
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.style.display = 'flex';
}
// Hide new-upstream form and reset trigger
var form = document.getElementById('upstream-new-form');
if (form) form.style.display = 'none';
var trigger = document.getElementById('upstream-new-trigger');
if (trigger) trigger.classList.remove('active');
// Update reflection panel upstream info strip
var reflectName = document.getElementById('grpc-reflect-upstream-name');
var reflectUrl = document.getElementById('grpc-reflect-upstream-url');
if (reflectName) reflectName.textContent = u.name;
if (reflectUrl) reflectUrl.textContent = u.url;
}
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';
if (isOpen) {
// toggle off — restore previous state
if (form) form.style.display = 'none';
if (trigger) trigger.classList.remove('active');
return;
}
// Clear combobox trigger & hide preview
var val = document.getElementById('upstream-combobox-value');
if (val) val.innerHTML = '' + tKey('wizard.step2.upstream_placeholder') + '';
var preview = document.getElementById('upstream-preview');
if (preview) preview.style.display = 'none';
selectedUpstreamId = null;
// Mark trigger active and show form
if (trigger) trigger.classList.add('active');
if (form) {
form.style.display = '';
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.style.display = 'none';
if (form) {
form.style.display = '';
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 nextRecord = {
id: editingUpstreamId || ('custom-' + Date.now()),
name: name,
url: url,
staticHeaders: staticHeaders,
authProfileId: authProfileId,
authProfileName: authProfileName,
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.style.display = 'none';
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.style.display = 'none';
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')
);
}
}
function escapeHtml(str) {
return String(str).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"');
}
// Close dropdown on outside click
document.addEventListener('click', function() {
if (dropdownOpen) closeUpstreamDropdown();
});
/* ══════════════════════════════════════════════
gRPC — source selector
══════════════════════════════════════════════ */
var grpcSource = 'proto'; // 'proto' | 'reflection' | 'manual'
function selectGrpcSource(source) {
grpcSource = source;
document.querySelectorAll('.grpc-source-btn').forEach(function(btn) {
btn.classList.toggle('active', btn.dataset.source === source);
});
document.getElementById('grpc-src-proto').style.display = source === 'proto' ? '' : 'none';
document.getElementById('grpc-src-reflection').style.display = source === 'reflection' ? '' : 'none';
document.getElementById('grpc-src-manual').style.display = source === 'manual' ? '' : 'none';
// hide shared detail when switching sources
var detail = document.getElementById('grpc-method-detail');
if (detail) detail.style.display = 'none';
selectedRpcMethod = null;
}
/* ══════════════════════════════════════════════
gRPC — Proto file parser & UI
══════════════════════════════════════════════ */
var protoParsed = null; // { package, services, messages, streamingCount }
var selectedRpcMethod = null; // { service, method, request, response }
/* Extract top-level blocks with proper brace matching */
function extractBlocks(text, keyword) {
var blocks = [];
var re = new RegExp(keyword + '\\s+(\\w+)\\s*\\{', 'g');
var match;
while ((match = re.exec(text)) !== null) {
var name = match[1];
var start = match.index + match[0].length;
var depth = 1;
var i = start;
while (i < text.length && depth > 0) {
if (text[i] === '{') depth++;
else if (text[i] === '}') depth--;
i++;
}
blocks.push({ name: name, body: text.slice(start, i - 1), start: match.index });
}
return blocks;
}
/* Strip single-line and block comments */
function stripProtoComments(text) {
// block comments
text = text.replace(/\/\*[\s\S]*?\*\//g, '');
// line comments
text = text.replace(/\/\/[^\n]*/g, '');
return text;
}
/* Parse a proto text → { package, services, messages, streamingCount } */
function parseProto(text) {
text = stripProtoComments(text);
var pkg = '';
var pkgMatch = text.match(/\bpackage\s+([\w.]+)\s*;/);
if (pkgMatch) pkg = pkgMatch[1];
// messages: map name → [{ repeated, type, name }]
var messages = {};
extractBlocks(text, 'message').forEach(function(blk) {
var fields = [];
var fieldRe = /\b(repeated\s+)?([\w.]+)\s+(\w+)\s*=\s*\d+/g;
var fm;
while ((fm = fieldRe.exec(blk.body)) !== null) {
// skip reserved / map keywords
if (['option','reserved','extensions','oneof'].indexOf(fm[2]) !== -1) continue;
fields.push({ repeated: !!fm[1], type: fm[2], name: fm[3] });
}
messages[blk.name] = fields;
});
var streamingCount = 0;
var services = [];
extractBlocks(text, 'service').forEach(function(svc) {
var methods = [];
// rpc Name ( [stream] ReqType ) returns ( [stream] ResType ) { }
var rpcRe = /\brpc\s+(\w+)\s*\(\s*(stream\s+)?([\w.]+)\s*\)\s*returns\s*\(\s*(stream\s+)?([\w.]+)\s*\)/g;
var rm;
while ((rm = rpcRe.exec(svc.body)) !== null) {
var streamIn = !!rm[2];
var streamOut = !!rm[4];
if (streamIn || streamOut) {
streamingCount++;
} else {
// Strip package prefix from type names for display
var reqType = rm[3].split('.').pop();
var resType = rm[5].split('.').pop();
methods.push({ name: rm[1], request: reqType, response: resType });
}
}
if (methods.length > 0) {
services.push({ name: svc.name, methods: methods });
}
});
return { package: pkg, services: services, messages: messages, streamingCount: streamingCount };
}
/* Generic renderer: fills a service/method list into given element IDs */
function renderServiceResults(parsed, ids) {
protoParsed = parsed;
selectedRpcMethod = null;
var notice = document.getElementById(ids.notice);
var countEl = document.getElementById(ids.count);
if (notice) {
notice.style.display = parsed.streamingCount > 0 ? '' : 'none';
if (countEl && parsed.streamingCount > 0) countEl.textContent = parsed.streamingCount;
}
var totalMethods = parsed.services.reduce(function(n, s) { return n + s.methods.length; }, 0);
var summaryEl = document.getElementById(ids.summary);
if (summaryEl) {
var servicesLabel = window.tPlural('wizard.grpc.services_label', parsed.services.length, {
count: parsed.services.length,
});
var methodsLabel = window.tPlural('wizard.grpc.methods_label', totalMethods, {
count: totalMethods,
});
summaryEl.textContent = tfKey('wizard.grpc.services_found', {
services_label: servicesLabel,
methods_label: methodsLabel,
});
}
var listEl = document.getElementById(ids.list);
if (!listEl) return;
listEl.innerHTML = '';
if (parsed.services.length === 0) {
var emptyDiv = document.createElement('div');
emptyDiv.className = 'proto-empty';
emptyDiv.textContent = tKey('wizard.grpc.no_methods');
listEl.appendChild(emptyDiv);
} else {
var svcTmpl = document.getElementById('tmpl-proto-service-group');
var methodTmpl = document.getElementById('tmpl-proto-method-card');
parsed.services.forEach(function(svc) {
var svcNode = svcTmpl.content.cloneNode(true);
svcNode.querySelector('.proto-service-name').textContent = svc.name;
var grid = svcNode.querySelector('.proto-method-grid');
svc.methods.forEach(function(m) {
var mNode = methodTmpl.content.cloneNode(true);
var btn = mNode.querySelector('.proto-method-card');
btn.dataset.service = svc.name;
btn.dataset.method = m.name;
btn.dataset.req = m.request;
btn.dataset.res = m.response;
mNode.querySelector('.proto-method-name').textContent = m.name;
mNode.querySelector('.proto-req-type').textContent = m.request;
mNode.querySelector('.proto-res-type').textContent = m.response;
grid.appendChild(mNode);
});
listEl.appendChild(svcNode);
});
}
document.getElementById(ids.view).style.display = '';
document.getElementById('grpc-method-detail').style.display = 'none';
}
function renderParsedProto(parsed) {
var notice = document.getElementById('proto-streaming-text');
if (notice && parsed && parsed.streamingCount) {
notice.textContent = window.tPlural('wizard.step3.grpc.streaming_hidden', parsed.streamingCount, {
count: parsed.streamingCount,
});
}
renderServiceResults(parsed, {
notice: 'proto-streaming-notice', count: 'proto-streaming-count',
summary: 'proto-services-summary', list: 'proto-services-list', view: 'proto-parsed-view',
});
}
function renderReflectionResult(parsed) {
var reflectText = document.querySelector('#reflect-streaming-notice .info-callout-text');
if (reflectText && parsed && parsed.streamingCount) {
reflectText.textContent = window.tPlural('wizard.step3.grpc.streaming_hidden', parsed.streamingCount, {
count: parsed.streamingCount,
});
}
renderServiceResults(parsed, {
notice: 'reflect-streaming-notice', count: 'reflect-streaming-count',
summary: 'reflect-services-summary', list: 'reflect-services-list', view: 'reflect-parsed-view',
});
}
/* Select a method card (shared between proto + reflection) */
function selectRpcMethod(btn) {
document.querySelectorAll('.proto-method-card').forEach(function(b) { b.classList.remove('active'); });
btn.classList.add('active');
var svcName = btn.dataset.service;
var methodName = btn.dataset.method;
var reqType = btn.dataset.req;
var resType = btn.dataset.res;
selectedRpcMethod = { service: svcName, method: methodName, request: reqType, response: resType };
var pkg = protoParsed && protoParsed.package ? protoParsed.package + '.' : '';
var grpcPath = '/' + pkg + svcName + '/' + methodName;
var pathEl = document.getElementById('grpc-path-value');
if (pathEl) pathEl.textContent = grpcPath;
var sub = document.getElementById('grpc-method-subtitle');
if (sub) sub.textContent = 'rpc ' + methodName + ' (' + reqType + ') returns (' + resType + ')';
var reqTypeEl = document.getElementById('grpc-req-type');
var resTypeEl = document.getElementById('grpc-res-type');
if (reqTypeEl) reqTypeEl.textContent = reqType;
if (resTypeEl) resTypeEl.textContent = resType;
var sigGrid = document.getElementById('grpc-detail-sig');
if (sigGrid) sigGrid.style.display = '';
renderFieldList('grpc-req-fields', reqType);
renderFieldList('grpc-res-fields', resType);
document.getElementById('grpc-method-detail').style.display = '';
}
function renderFieldList(elId, typeName) {
var el = document.getElementById(elId);
if (!el) return;
var fields = protoParsed && protoParsed.messages[typeName];
el.innerHTML = '';
if (!fields || fields.length === 0) {
var emptyEl = document.createElement('div');
emptyEl.className = 'proto-field-empty';
emptyEl.innerHTML = tKey('wizard.grpc.fields_unresolved') + '
' + tKey('wizard.grpc.fields_unresolved_body') + '';
el.appendChild(emptyEl);
return;
}
var fieldTmpl = document.getElementById('tmpl-proto-field-row');
fields.forEach(function(f) {
var node = fieldTmpl.content.cloneNode(true);
node.querySelector('.proto-field-name').textContent = f.name;
node.querySelector('.proto-field-type').textContent = (f.repeated ? 'repeated ' : '') + f.type;
el.appendChild(node);
});
}
/* File handling */
function handleProtoFile(event) {
var file = event.target.files[0];
if (!file) return;
var reader = new FileReader();
reader.onload = function(e) {
wizardProtoUpload = { fileName: file.name, content: e.target.result };
showProtoFileInfo(file.name, file.size);
var parsed = parseProto(e.target.result);
renderParsedProto(parsed);
};
reader.readAsText(file);
}
function handleProtoDrop(event) {
event.preventDefault();
var dropzone = document.getElementById('proto-dropzone');
if (dropzone) dropzone.classList.remove('drag-over');
var file = event.dataTransfer.files[0];
if (!file || !file.name.endsWith('.proto')) return;
var reader = new FileReader();
reader.onload = function(e) {
wizardProtoUpload = { fileName: file.name, content: e.target.result };
showProtoFileInfo(file.name, file.size);
renderParsedProto(parseProto(e.target.result));
};
reader.readAsText(file);
}
function showProtoFileInfo(name, size) {
var dropzone = document.getElementById('proto-dropzone');
var info = document.getElementById('proto-file-info');
var nameEl = document.getElementById('proto-file-name');
var sizeEl = document.getElementById('proto-file-size');
if (dropzone) dropzone.style.display = 'none';
if (info) info.style.display = '';
if (nameEl) nameEl.textContent = name;
if (sizeEl) sizeEl.textContent = (size / 1024).toFixed(1) + ' KB';
// hide paste area
document.getElementById('proto-paste-area').style.display = 'none';
document.getElementById('proto-paste-btn').textContent = 'Paste content';
}
function resetProto() {
var dropzone = document.getElementById('proto-dropzone');
var info = document.getElementById('proto-file-info');
var parsedView = document.getElementById('proto-parsed-view');
if (dropzone) dropzone.style.display = '';
if (info) info.style.display = 'none';
if (parsedView) parsedView.style.display = 'none';
var fileInput = document.getElementById('proto-file-input');
if (fileInput) fileInput.value = '';
protoParsed = null;
selectedRpcMethod = null;
wizardProtoUpload = null;
}
function toggleProtoPaste() {
var area = document.getElementById('proto-paste-area');
var btn = document.getElementById('proto-paste-btn');
if (!area) return;
var open = area.style.display !== 'none';
area.style.display = open ? 'none' : '';
if (btn) btn.textContent = open ? 'Paste content' : 'Cancel paste';
if (!open) {
var ta = document.getElementById('proto-paste-input');
if (ta) setTimeout(function() { ta.focus(); }, 50);
}
}
function cancelProtoPaste() {
var area = document.getElementById('proto-paste-area');
var btn = document.getElementById('proto-paste-btn');
if (area) area.style.display = 'none';
if (btn) btn.textContent = 'Paste content';
}
function parseProtoPasted() {
var ta = document.getElementById('proto-paste-input');
if (!ta || !ta.value.trim()) return;
var parsed = parseProto(ta.value);
wizardProtoUpload = { fileName: 'pasted.proto', content: ta.value };
document.getElementById('proto-paste-area').style.display = 'none';
var dropzone = document.getElementById('proto-dropzone');
if (dropzone) dropzone.style.display = 'none';
var info = document.getElementById('proto-file-info');
var nameEl = document.getElementById('proto-file-name');
var sizeEl = document.getElementById('proto-file-size');
if (info) info.style.display = '';
if (nameEl) nameEl.textContent = 'pasted.proto';
if (sizeEl) sizeEl.textContent = (ta.value.length / 1024).toFixed(1) + ' KB';
renderParsedProto(parsed);
}
/* ══════════════════════════════════════════════
gRPC — Server reflection (simulated)
══════════════════════════════════════════════ */
var REFLECTION_MOCK = {
package: 'acme.v1',
streamingCount: 2,
services: [
{ name: 'ContactService', methods: [
{ name: 'GetContact', request: 'GetContactRequest', response: 'Contact' },
{ name: 'CreateContact', request: 'CreateContactRequest', response: 'Contact' },
{ name: 'UpdateContact', request: 'UpdateContactRequest', response: 'Contact' },
{ name: 'DeleteContact', request: 'DeleteContactRequest', response: 'DeleteContactResponse' },
]},
{ name: 'CompanyService', methods: [
{ name: 'GetCompany', request: 'GetCompanyRequest', response: 'Company' },
{ name: 'ListCompanies', request: 'ListCompaniesRequest', response: 'ListCompaniesResponse' },
]},
],
messages: {
'GetContactRequest': [{ type: 'string', name: 'id' }],
'Contact': [{ type: 'string', name: 'id' }, { type: 'string', name: 'first_name' }, { type: 'string', name: 'last_name' }, { type: 'string', name: 'email' }, { type: 'string', name: 'company_id' }, { type: 'string', name: 'created_at' }],
'CreateContactRequest': [{ type: 'string', name: 'first_name' }, { type: 'string', name: 'last_name' }, { type: 'string', name: 'email' }, { type: 'string', name: 'company_id' }],
'UpdateContactRequest': [{ type: 'string', name: 'id' }, { type: 'string', name: 'first_name' }, { type: 'string', name: 'last_name' }, { type: 'string', name: 'email' }],
'DeleteContactRequest': [{ type: 'string', name: 'id' }],
'DeleteContactResponse': [{ type: 'bool', name: 'success' }],
'GetCompanyRequest': [{ type: 'string', name: 'id' }],
'Company': [{ type: 'string', name: 'id' }, { type: 'string', name: 'name' }, { type: 'string', name: 'domain' }, { type: 'int32', name: 'employee_count' }],
'ListCompaniesRequest': [{ type: 'int32', name: 'page' }, { type: 'int32', name: 'page_size' }, { type: 'string', name: 'query' }],
'ListCompaniesResponse': [{ type: 'Company', name: 'companies', repeated: true }, { type: 'int32', name: 'total' }],
},
};
function startReflection() {
goToStep(5);
showWizardLiveStatus(
tKey('wizard.grpc.discovery_title'),
tKey('wizard.grpc.discovery_body')
);
}
/* ══════════════════════════════════════════════
gRPC — Manual entry
══════════════════════════════════════════════ */
function grpcManualRouteInput(val) {
var detail = document.getElementById('grpc-method-detail');
var sigGrid = document.getElementById('grpc-detail-sig');
if (!val || !val.startsWith('/') || val.indexOf('/', 1) === -1) {
if (detail) detail.style.display = 'none';
return;
}
// Parse service + method from route: /[pkg.]Service/Method
var parts = val.replace(/^\//, '').split('/');
var servicePart = parts[0] || '';
var methodName = parts[1] || '';
var serviceName = servicePart.split('.').pop();
var pathEl = document.getElementById('grpc-path-value');
if (pathEl) pathEl.textContent = val;
var sub = document.getElementById('grpc-method-subtitle');
if (sub) sub.textContent = serviceName + '/' + methodName;
// hide sig grid in manual mode (no field data)
if (sigGrid) sigGrid.style.display = 'none';
grpcManualTypeInput();
if (detail) detail.style.display = '';
}
function grpcManualTypeInput() {
var reqInput = document.getElementById('grpc-manual-req-type');
var resInput = document.getElementById('grpc-manual-res-type');
var reqTypeEl = document.getElementById('grpc-req-type');
var resTypeEl = document.getElementById('grpc-res-type');
if (reqTypeEl) reqTypeEl.textContent = (reqInput && reqInput.value.trim()) || '—';
if (resTypeEl) resTypeEl.textContent = (resInput && resInput.value.trim()) || '—';
}
function textValue(id) {
var element = document.getElementById(id);
return element ? element.value.trim() : '';
}
function setValue(id, value) {
var element = document.getElementById(id);
if (element) element.value = value || '';
}
function parseStructuredText(text) {
if (!text.trim()) return {};
try {
return JSON.parse(text);
} catch (_error) {
if (!window.jsyaml) throw new Error(tKey('wizard.error.parser_unavailable'));
return window.jsyaml.load(text) || {};
}
}
function inferSchemaKind(typeName) {
if (typeName === 'object') return 'object';
if (typeName === 'array') return 'array';
if (typeName === 'string') return 'string';
if (typeName === 'integer') return 'integer';
if (typeName === 'number') return 'number';
if (typeName === 'boolean') return 'boolean';
if (typeName === 'null') return 'null';
return 'string';
}
function convertJsonSchemaToCrankSchema(node, requiredNames) {
var schemaNode = node || {};
var typeName = Array.isArray(schemaNode.type)
? schemaNode.type.find(function(value) { return value !== 'null'; }) || 'string'
: schemaNode.type || (schemaNode.properties ? 'object' : 'string');
var nullable = Array.isArray(schemaNode.type) && schemaNode.type.indexOf('null') >= 0;
var kind = schemaNode.enum ? 'enum' : inferSchemaKind(typeName);
var schema = {
type: kind,
description: schemaNode.description || null,
required: false,
nullable: nullable,
default_value: Object.prototype.hasOwnProperty.call(schemaNode, 'default') ? schemaNode.default : null,
fields: {},
items: null,
enum_values: schemaNode.enum || [],
variants: [],
};
if (kind === 'object') {
var requiredSet = new Set(schemaNode.required || requiredNames || []);
Object.keys(schemaNode.properties || {}).forEach(function(fieldName) {
var field = convertJsonSchemaToCrankSchema(schemaNode.properties[fieldName], []);
field.required = requiredSet.has(fieldName);
schema.fields[fieldName] = field;
});
}
if (kind === 'array' && schemaNode.items) {
schema.items = convertJsonSchemaToCrankSchema(schemaNode.items, []);
}
return schema;
}
function mappingRootForProtocol(protocol) {
if (protocol === 'graphql') return '$.request.variables';
if (protocol === 'grpc') return '$.request.grpc';
var activeMethod = document.querySelector('.method-card.active');
var method = activeMethod ? activeMethod.dataset.method : 'POST';
return method === 'GET' || method === 'DELETE' ? '$.request.query' : '$.request.body';
}
function normalizeInputSource(path) {
if (typeof path !== 'string') return '$.mcp';
if (path.indexOf('$.input.') === 0) return '$.mcp.' + path.slice('$.input.'.length);
if (path === '$.input') return '$.mcp';
return path;
}
function normalizeOutputSource(path) {
if (typeof path !== 'string') return '$.response.data';
return path;
}
function buildMappingSet(rawValue, mode) {
if (rawValue && Array.isArray(rawValue.rules)) {
return { rules: rawValue.rules };
}
var rules = [];
if (!rawValue || typeof rawValue !== 'object') {
return { rules: rules };
}
var root = mappingRootForProtocol(wizardProtocol);
Object.keys(rawValue).forEach(function(key) {
var source = rawValue[key];
if (typeof source !== 'string') return;
if (mode === 'input') {
rules.push({
source: normalizeInputSource(source),
target: root + '.' + key,
required: false,
});
return;
}
rules.push({
source: normalizeOutputSource(source),
target: '$.output.' + key,
required: false,
});
});
return { rules: rules };
}
function parseExecutionConfig(text) {
if (window.CrankStreamingForm && typeof window.CrankStreamingForm.validateStreamingConfig === 'function') {
var validation = window.CrankStreamingForm.validateStreamingConfig([currentProtocolCapabilities()]);
if (!validation.valid) {
throw new Error(validation.errors[0]);
}
}
var value = parseStructuredText(text);
var retry = value.retry || value.retry_policy || null;
var headers = value.headers && typeof value.headers === 'object' ? value.headers : {};
var selectedUpstream = currentUpstream();
if (selectedUpstream && selectedUpstream.authMode === 'create') {
throw new Error(tKey('wizard.error.save_upstream_first'));
}
var authProfileRef = selectedUpstream && selectedUpstream.authProfileId
? selectedUpstream.authProfileId
: (value.auth && value.auth.profile ? value.auth.profile : null);
var config = {
timeout_ms: Number(value.timeout_ms || 10000),
retry_policy: retry && retry.max_attempts ? { max_attempts: Number(retry.max_attempts) } : null,
auth_profile_ref: authProfileRef,
headers: headers,
protocol_options: null,
};
if (wizardProtocol === 'grpc') {
var useTls = false;
if (value.protocol_options && value.protocol_options.grpc) {
useTls = !!value.protocol_options.grpc.use_tls;
} else if (value.tls) {
useTls = !!value.tls.verify;
}
config.protocol_options = { grpc: { use_tls: useTls } };
} else if (wizardProtocol === 'websocket') {
var websocketProtocolOptions = value.protocol_options && value.protocol_options.websocket
? value.protocol_options.websocket
: {};
var heartbeatInterval = websocketProtocolOptions.heartbeat_interval_ms != null
? Number(websocketProtocolOptions.heartbeat_interval_ms)
: numericValueOrNull('websocket-heartbeat-interval-ms');
var reconnectAttempts = websocketProtocolOptions.reconnect_max_attempts != null
? Number(websocketProtocolOptions.reconnect_max_attempts)
: numericValueOrNull('websocket-reconnect-max-attempts');
var reconnectBackoff = websocketProtocolOptions.reconnect_backoff_ms != null
? Number(websocketProtocolOptions.reconnect_backoff_ms)
: numericValueOrNull('websocket-reconnect-backoff-ms');
var runtimeOptions = {};
if (Number.isFinite(heartbeatInterval) && heartbeatInterval > 0) {
runtimeOptions.heartbeat_interval_ms = heartbeatInterval;
}
if (Number.isFinite(reconnectAttempts) && reconnectAttempts >= 0) {
runtimeOptions.reconnect_max_attempts = reconnectAttempts;
}
if (Number.isFinite(reconnectBackoff) && reconnectBackoff >= 0) {
runtimeOptions.reconnect_backoff_ms = reconnectBackoff;
}
if (Object.keys(runtimeOptions).length) {
config.protocol_options = { websocket: runtimeOptions };
}
}
config.streaming = collectStreamingConfig();
return config;
}
function currentUpstream() {
var selected = upstreams.find(function(item) { return item.id === selectedUpstreamId; });
if (selected) return selected;
var customName = textValue('new-upstream-name');
var customUrl = textValue('new-upstream-url');
if (!customUrl && wizardProtocol === 'soap') {
customUrl = textValue('soap-endpoint-override');
}
if (!customUrl) return null;
var authMode = textValue('new-upstream-auth-mode') || 'none';
var authProfileId = authMode === 'existing' ? textValue('new-upstream-auth-profile') : null;
var authProfile = authProfileById(authProfileId);
return {
id: 'custom',
name: customName || 'custom-upstream',
url: customUrl,
staticHeaders: textValue('new-upstream-static-headers') || '{\n}',
authMode: authMode,
authProfileId: authProfileId || null,
authProfileName: authProfile ? authProfile.name : '',
authKind: authProfile ? authProfile.kind : null,
};
}
function joinUrl(baseUrl, path) {
if (!baseUrl) return path || '';
if (!path) return baseUrl;
return baseUrl.replace(/\/+$/, '') + '/' + path.replace(/^\/+/, '');
}
function selectedGraphqlOperationType() {
var active = document.querySelector('.gql-type-card.active');
return active ? active.dataset.gqlType : 'query';
}
function graphqlTopLevelField(queryText) {
var match = queryText.replace(/#[^\n]*/g, '').match(/\{\s*([A-Za-z_][A-Za-z0-9_]*)/);
return match ? match[1] : null;
}
function graphqlOperationName(queryText) {
var match = queryText.match(/\b(query|mutation)\s+([A-Za-z_][A-Za-z0-9_]*)/);
return match ? match[2] : textValue('tool-name');
}
function buildTarget() {
var upstream = currentUpstream();
if (!upstream || !upstream.url) {
throw new Error(tKey('wizard.error.select_upstream'));
}
var pathTemplate = textValue('endpoint-path') || '/';
if (wizardProtocol === 'rest') {
var activeMethod = document.querySelector('.method-card.active');
return {
kind: 'rest',
base_url: upstream.url,
method: activeMethod ? activeMethod.dataset.method : 'POST',
path_template: pathTemplate,
static_headers: parseHeaderMap(upstream.staticHeaders),
};
}
if (wizardProtocol === 'graphql') {
var queryTemplate = textValue('gql-query-editor');
var topField = graphqlTopLevelField(queryTemplate);
return {
kind: 'graphql',
endpoint: joinUrl(upstream.url, pathTemplate),
operation_type: selectedGraphqlOperationType(),
operation_name: graphqlOperationName(queryTemplate),
query_template: queryTemplate,
response_path: topField ? '$.response.body.data.' + topField : '$.response.body.data',
};
}
if (wizardProtocol === 'soap') {
var endpointOverride = textValue('soap-endpoint-override') || upstream.url;
var existingWsdlRef = wizardCurrentVersion && wizardCurrentVersion.target && wizardCurrentVersion.target.kind === 'soap'
? wizardCurrentVersion.target.wsdl_ref
: 'sample_wsdl_pending';
return {
kind: 'soap',
wsdl_ref: existingWsdlRef,
service_name: textValue('soap-service-name') || 'Service',
port_name: textValue('soap-port-name') || 'Port',
operation_name: textValue('soap-operation-name') || 'Operation',
endpoint_override: endpointOverride || null,
soap_version: textValue('soap-version') || 'soap_11',
soap_action: textValue('soap-action') || null,
binding_style: textValue('soap-binding-style') || 'document_literal',
headers: [],
fault_contract: null,
metadata: { input_part_names: [], output_part_names: [], namespaces: [] },
};
}
if (wizardProtocol === 'websocket') {
return {
kind: 'websocket',
url: joinUrl(upstream.url, textValue('websocket-path')),
subprotocols: textareaLines('websocket-subprotocols'),
subscribe_message_template: stringValueOrNull('websocket-subscribe-template')
? parseStructuredText(textValue('websocket-subscribe-template'))
: null,
unsubscribe_message_template: stringValueOrNull('websocket-unsubscribe-template')
? parseStructuredText(textValue('websocket-unsubscribe-template'))
: null,
static_headers: parseHeaderMap(upstream.staticHeaders),
};
}
var route = textValue('grpc-manual-route') || (document.getElementById('grpc-path-value') ? document.getElementById('grpc-path-value').textContent.trim() : '');
var descriptorRef = wizardCurrentVersion && wizardCurrentVersion.target && wizardCurrentVersion.target.kind === 'grpc'
? wizardCurrentVersion.target.descriptor_ref
: 'desc_pending';
var descriptorSetB64 = wizardCurrentVersion && wizardCurrentVersion.target && wizardCurrentVersion.target.kind === 'grpc'
? (wizardCurrentVersion.target.descriptor_set_b64 || '')
: '';
var parsedRoute = parseGrpcRoute(route);
return {
kind: 'grpc',
server_addr: upstream.url,
package: parsedRoute.package,
service: parsedRoute.service,
method: parsedRoute.method,
descriptor_ref: descriptorRef,
descriptor_set_b64: descriptorSetB64,
};
}
function parseGrpcRoute(route) {
var normalized = route.replace(/^\/+/, '');
var parts = normalized.split('/');
if (parts.length !== 2) {
return {
package: '',
service: '',
method: '',
};
}
var servicePart = parts[0];
var method = parts[1];
var serviceSegments = servicePart.split('.');
var service = serviceSegments.pop() || '';
var packageName = serviceSegments.join('.');
return {
package: packageName,
service: service,
method: method,
};
}
function parseHeaderMap(text) {
if (!text || !text.trim()) return {};
var value = parseStructuredText(text);
return value && typeof value === 'object' ? value : {};
}
function numericValueOrNull(id) {
var value = textValue(id);
if (!value) return null;
var parsed = Number(value);
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
}
function stringValueOrNull(id) {
var value = textValue(id);
return value ? value : null;
}
function textareaLines(id) {
var value = textValue(id);
if (!value) return [];
return value
.split(/\r?\n/)
.map(function(line) { return line.trim(); })
.filter(Boolean);
}
function selectedStreamingMode() {
var element = document.getElementById('streaming-mode');
return element ? element.value : 'unary';
}
function transportOptionsForMode(mode) {
var capabilities = currentProtocolCapabilities();
var behaviors = (capabilities.supports_transport_behaviors || []).slice();
if (mode === 'unary') return behaviors.filter(function(value) { return value === 'request_response'; });
if (mode === 'window') return behaviors.filter(function(value) {
return value === 'request_response' || value === 'server_stream';
});
if (mode === 'session') return behaviors.filter(function(value) {
return value === 'server_stream' || value === 'stateful_session';
});
if (mode === 'async_job') return behaviors.filter(function(value) {
return value === 'deferred_result';
});
return behaviors;
}
function modeOptionsForProtocol() {
var capabilities = currentProtocolCapabilities();
return (capabilities.supports_execution_modes || ['unary']).filter(function(mode) {
return transportOptionsForMode(mode).length > 0 || mode === 'unary';
});
}
function updateStreamingTransportOptions(preferredValue) {
var element = document.getElementById('streaming-transport-behavior');
if (!element) return;
var options = transportOptionsForMode(selectedStreamingMode());
element.innerHTML = '';
options.forEach(function(value) {
var option = document.createElement('option');
option.value = value;
option.textContent = tKey('wizard.streaming.transport.' + value);
element.appendChild(option);
});
if (preferredValue && options.indexOf(preferredValue) >= 0) {
element.value = preferredValue;
} else if (options.length) {
element.value = options[0];
}
}
function updateStreamingModeOptions(preferredValue) {
var element = document.getElementById('streaming-mode');
if (!element) return;
var options = modeOptionsForProtocol();
element.innerHTML = '';
options.forEach(function(value) {
var option = document.createElement('option');
option.value = value;
option.textContent = tKey('wizard.streaming.mode.' + value);
element.appendChild(option);
});
if (preferredValue && options.indexOf(preferredValue) >= 0) {
element.value = preferredValue;
} else {
element.value = options.indexOf('unary') >= 0 ? 'unary' : (options[0] || 'unary');
}
updateStreamingTransportOptions();
}
function ensureStreamingToolFamilyDefaults(mode) {
var base = textValue('tool-name') || 'stream_tool';
if (mode === 'session') {
if (!textValue('streaming-start-tool-name')) setValue('streaming-start-tool-name', base + '_start');
if (!textValue('streaming-poll-tool-name')) setValue('streaming-poll-tool-name', base + '_poll');
if (!textValue('streaming-stop-tool-name')) setValue('streaming-stop-tool-name', base + '_stop');
}
if (mode === 'async_job') {
if (!textValue('streaming-start-tool-name')) setValue('streaming-start-tool-name', base + '_start');
if (!textValue('streaming-status-tool-name')) setValue('streaming-status-tool-name', base + '_status');
if (!textValue('streaming-result-tool-name')) setValue('streaming-result-tool-name', base + '_result');
if (!textValue('streaming-cancel-tool-name')) setValue('streaming-cancel-tool-name', base + '_cancel');
}
}
function updateStreamingConfigVisibility() {
var mode = selectedStreamingMode();
var card = document.getElementById('wizard-streaming-config-card');
var help = document.getElementById('streaming-mode-help');
var toolFamily = document.getElementById('streaming-tool-family-block');
var asyncRow = document.getElementById('streaming-async-tool-family-row');
if (!card) return;
var isUnary = mode === 'unary';
var isWindow = mode === 'window';
var isSession = mode === 'session';
var isAsyncJob = mode === 'async_job';
[
'streaming-window-duration-ms',
'streaming-poll-interval-ms',
'streaming-upstream-timeout-ms',
'streaming-idle-timeout-ms',
'streaming-session-lifetime-ms',
'streaming-max-items',
'streaming-max-bytes',
'streaming-max-field-length',
'streaming-sampling-rate',
'streaming-items-path',
'streaming-summary-path',
'streaming-done-path',
'streaming-cursor-path',
'streaming-status-path',
'streaming-redacted-paths',
'streaming-truncate-item-fields',
'streaming-drop-duplicates'
].forEach(function(id) {
var node = document.getElementById(id);
if (!node) return;
var group = node.closest('.form-group') || node.closest('.checkbox-pill');
if (group) {
group.style.display = isUnary ? 'none' : '';
}
});
if (help) {
help.textContent = isUnary
? tKey('wizard.streaming.help.unary')
: (isSession
? tKey('wizard.streaming.help.session')
: (isAsyncJob
? tKey('wizard.streaming.help.async_job')
: tKey('wizard.streaming.help.window')));
}
if (toolFamily) toolFamily.style.display = (isSession || isAsyncJob) ? '' : 'none';
if (asyncRow) asyncRow.style.display = isAsyncJob ? '' : 'none';
ensureStreamingToolFamilyDefaults(mode);
}
function bindStreamingConfigControls() {
updateStreamingModeOptions();
updateStreamingConfigVisibility();
var mode = document.getElementById('streaming-mode');
if (mode) {
mode.addEventListener('change', function() {
updateStreamingTransportOptions();
updateStreamingConfigVisibility();
});
}
}
function collectStreamingConfig() {
var mode = selectedStreamingMode();
if (mode === 'unary') return null;
var config = {
mode: mode,
transport_behavior: stringValueOrNull('streaming-transport-behavior') || 'request_response',
window_duration_ms: numericValueOrNull('streaming-window-duration-ms'),
poll_interval_ms: numericValueOrNull('streaming-poll-interval-ms'),
upstream_timeout_ms: numericValueOrNull('streaming-upstream-timeout-ms'),
idle_timeout_ms: numericValueOrNull('streaming-idle-timeout-ms'),
max_session_lifetime_ms: numericValueOrNull('streaming-session-lifetime-ms'),
max_items: numericValueOrNull('streaming-max-items'),
max_bytes: numericValueOrNull('streaming-max-bytes'),
aggregation_mode: stringValueOrNull('streaming-aggregation-mode') || 'summary_plus_samples',
summary_path: stringValueOrNull('streaming-summary-path'),
items_path: stringValueOrNull('streaming-items-path'),
cursor_path: stringValueOrNull('streaming-cursor-path'),
status_path: stringValueOrNull('streaming-status-path'),
done_path: stringValueOrNull('streaming-done-path'),
redacted_paths: textareaLines('streaming-redacted-paths'),
truncate_item_fields: !!document.getElementById('streaming-truncate-item-fields').checked,
max_field_length: numericValueOrNull('streaming-max-field-length'),
drop_duplicates: !!document.getElementById('streaming-drop-duplicates').checked,
sampling_rate: (function() {
var value = textValue('streaming-sampling-rate');
if (!value) return null;
var parsed = Number(value);
return Number.isFinite(parsed) ? parsed : null;
}()),
tool_family: {},
};
if (mode === 'session') {
config.tool_family.start_tool_name = stringValueOrNull('streaming-start-tool-name');
config.tool_family.poll_tool_name = stringValueOrNull('streaming-poll-tool-name');
config.tool_family.stop_tool_name = stringValueOrNull('streaming-stop-tool-name');
} else if (mode === 'async_job') {
config.tool_family.start_tool_name = stringValueOrNull('streaming-start-tool-name');
config.tool_family.status_tool_name = stringValueOrNull('streaming-status-tool-name');
config.tool_family.result_tool_name = stringValueOrNull('streaming-result-tool-name');
config.tool_family.cancel_tool_name = stringValueOrNull('streaming-cancel-tool-name');
}
Object.keys(config).forEach(function(key) {
if (config[key] === null) delete config[key];
});
if (!config.redacted_paths.length) delete config.redacted_paths;
return config;
}
function applyStreamingConfig(streaming) {
updateStreamingModeOptions(streaming && streaming.mode ? streaming.mode : 'unary');
if (!streaming) {
updateStreamingConfigVisibility();
return;
}
setValue('streaming-window-duration-ms', streaming.window_duration_ms || '');
setValue('streaming-poll-interval-ms', streaming.poll_interval_ms || '');
setValue('streaming-upstream-timeout-ms', streaming.upstream_timeout_ms || '');
setValue('streaming-idle-timeout-ms', streaming.idle_timeout_ms || '');
setValue('streaming-session-lifetime-ms', streaming.max_session_lifetime_ms || '');
setValue('streaming-max-items', streaming.max_items || '');
setValue('streaming-max-bytes', streaming.max_bytes || '');
setValue('streaming-max-field-length', streaming.max_field_length || '');
setValue('streaming-sampling-rate', streaming.sampling_rate || '');
setValue('streaming-items-path', streaming.items_path || '');
setValue('streaming-summary-path', streaming.summary_path || '');
setValue('streaming-done-path', streaming.done_path || '');
setValue('streaming-cursor-path', streaming.cursor_path || '');
setValue('streaming-status-path', streaming.status_path || '');
setValue('streaming-redacted-paths', (streaming.redacted_paths || []).join('\n'));
document.getElementById('streaming-truncate-item-fields').checked = !!streaming.truncate_item_fields;
document.getElementById('streaming-drop-duplicates').checked = !!streaming.drop_duplicates;
updateStreamingTransportOptions(streaming.transport_behavior);
setValue('streaming-start-tool-name', streaming.tool_family && streaming.tool_family.start_tool_name ? streaming.tool_family.start_tool_name : '');
setValue('streaming-poll-tool-name', streaming.tool_family && streaming.tool_family.poll_tool_name ? streaming.tool_family.poll_tool_name : '');
setValue('streaming-stop-tool-name', streaming.tool_family && streaming.tool_family.stop_tool_name ? streaming.tool_family.stop_tool_name : '');
setValue('streaming-status-tool-name', streaming.tool_family && streaming.tool_family.status_tool_name ? streaming.tool_family.status_tool_name : '');
setValue('streaming-result-tool-name', streaming.tool_family && streaming.tool_family.result_tool_name ? streaming.tool_family.result_tool_name : '');
setValue('streaming-cancel-tool-name', streaming.tool_family && streaming.tool_family.cancel_tool_name ? streaming.tool_family.cancel_tool_name : '');
var aggregation = document.getElementById('streaming-aggregation-mode');
if (aggregation && streaming.aggregation_mode) aggregation.value = streaming.aggregation_mode;
updateStreamingConfigVisibility();
}
function buildToolDescription() {
return {
title: textValue('tool-title') || textValue('tool-display-name') || textValue('tool-name'),
description: textValue('tool-description'),
tags: [],
examples: [],
};
}
function collectWizardPayload() {
var name = textValue('tool-name');
if (!name) throw new Error(tKey('wizard.error.tool_name'));
var inputSchemaValue = parseStructuredText(textValue('tool-input-schema'));
var outputSchemaValue = parseStructuredText(textValue('tool-output-schema'));
var inputMappingValue = parseStructuredText(textValue('tool-input-mapping'));
var outputMappingValue = parseStructuredText(textValue('tool-output-mapping'));
return {
name: name,
display_name: textValue('tool-display-name') || name,
category: 'general',
protocol: wizardProtocol,
target: buildTarget(),
input_schema: convertJsonSchemaToCrankSchema(inputSchemaValue, []),
output_schema: convertJsonSchemaToCrankSchema(outputSchemaValue, []),
input_mapping: buildMappingSet(inputMappingValue, 'input'),
output_mapping: buildMappingSet(outputMappingValue, 'output'),
execution_config: parseExecutionConfig(textValue('tool-exec-config')),
tool_description: buildToolDescription(),
};
}
async function saveOperation(stayOnPage) {
try {
await persistCurrentDraft(stayOnPage);
} catch (error) {
if (window.CrankUi) {
window.CrankUi.error(error.message || tKey('wizard.error.save'), tKey('wizard.error.save_title'));
}
}
}
async function loadOperationForEdit() {
if (!wizardWorkspaceId || !wizardEditId) return;
var detail = await window.CrankApi.getOperation(wizardWorkspaceId, wizardEditId);
wizardProtocol = detail.protocol || 'rest';
await loadWizardPanels([3]);
var draftVersion = await window.CrankApi.getOperationVersion(
wizardWorkspaceId,
wizardEditId,
detail.draft_version_ref.version
);
wizardCurrentOperation = detail;
wizardCurrentVersion = draftVersion;
prefillWizardFromEdit(detail, draftVersion);
}
function setEditModePresentation() {
var progressLabel = document.querySelector('.progress-label');
if (progressLabel) progressLabel.textContent = tKey('wizard.progress.edit');
var sidebarBrand = document.querySelector('.step-sidebar-brand');
if (sidebarBrand) sidebarBrand.innerHTML = tKey('wizard.sidebar.brand.edit');
}
function selectProtocol(protocol) {
wizardProtocol = protocol || 'rest';
document.querySelectorAll('.protocol-card').forEach(function(card) {
var proto = card.dataset.protocol
|| (card.classList.contains('rest')
? 'rest'
: card.classList.contains('graphql')
? 'graphql'
: card.classList.contains('grpc')
? 'grpc'
: card.classList.contains('websocket')
? 'websocket'
: card.classList.contains('soap')
? 'soap'
: 'rest');
var selected = proto === wizardProtocol;
card.classList.toggle('selected', selected);
card.setAttribute('aria-checked', selected ? 'true' : 'false');
});
var s3name = document.getElementById('sidebar-step-3-name');
if (s3name) s3name.textContent = STEP3_LABELS[wizardProtocol] || tKey('wizard.step3.rest.label');
updateWizardProtocolVisibility();
}
function prefillUpstream(baseUrl, headers, authProfileRef) {
var known = upstreams.find(function(item) {
return item.url === baseUrl && (item.authProfileId || null) === (authProfileRef || null);
});
if (known) {
pickUpstream(known.id);
return;
}
selectedUpstreamId = null;
var trigger = document.getElementById('upstream-new-trigger');
var form = document.getElementById('upstream-new-form');
if (trigger) trigger.classList.add('active');
if (form) form.style.display = '';
setValue('new-upstream-name', 'imported-upstream');
setValue('new-upstream-url', baseUrl);
setValue('new-upstream-static-headers', headers && Object.keys(headers).length ? JSON.stringify(headers, null, 2) : '{\n}');
setValue('new-upstream-auth-mode', authProfileRef ? 'existing' : 'none');
setValue('new-upstream-auth-profile', authProfileRef || '');
updateUpstreamAuthUi();
var valueEl = document.getElementById('upstream-combobox-value');
if (valueEl) {
valueEl.innerHTML =
'imported-upstream
' +
'' + escapeHtml(baseUrl) + '
';
}
}
function crankSchemaToJsonSchema(schema) {
var result = {};
var typeName = schema.type === 'enum' ? 'string' : schema.type;
result.type = schema.nullable ? [typeName, 'null'] : typeName;
if (schema.description) result.description = schema.description;
if (schema.default_value !== null && schema.default_value !== undefined) result.default = schema.default_value;
if (schema.enum_values && schema.enum_values.length) result.enum = schema.enum_values.slice();
if (schema.type === 'object') {
result.type = schema.nullable ? ['object', 'null'] : 'object';
result.properties = {};
var required = [];
Object.keys(schema.fields || {}).forEach(function(fieldName) {
var field = schema.fields[fieldName];
result.properties[fieldName] = crankSchemaToJsonSchema(field);
if (field.required) required.push(fieldName);
});
if (required.length) result.required = required;
result.additionalProperties = false;
}
if (schema.type === 'array' && schema.items) {
result.items = crankSchemaToJsonSchema(schema.items);
}
return result;
}
function mappingSetToEditorValue(mappingSet, mode, protocol) {
var rules = mappingSet && mappingSet.rules ? mappingSet.rules : [];
var object = {};
rules.forEach(function(rule) {
if (mode === 'input') {
var key = rule.target.replace(mappingRootForProtocol(protocol) + '.', '');
object[key] = rule.source.replace('$.mcp.', '$.input.');
return;
}
var outputKey = rule.target.replace('$.output.', '');
object[outputKey] = rule.source;
});
return window.jsyaml ? window.jsyaml.dump(object, { lineWidth: -1 }) : JSON.stringify(object, null, 2);
}
function executionConfigToEditorValue(config) {
var value = {
timeout_ms: config.timeout_ms,
};
if (config.retry_policy) {
value.retry = { max_attempts: config.retry_policy.max_attempts };
}
if (config.auth_profile_ref) {
value.auth = { profile: config.auth_profile_ref };
}
if (config.headers && Object.keys(config.headers).length) {
value.headers = config.headers;
}
if (config.protocol_options && config.protocol_options.grpc) {
value.tls = { verify: !!config.protocol_options.grpc.use_tls };
}
if (config.protocol_options && config.protocol_options.websocket) {
value.protocol_options = value.protocol_options || {};
value.protocol_options.websocket = {};
if (config.protocol_options.websocket.heartbeat_interval_ms != null) {
value.protocol_options.websocket.heartbeat_interval_ms = config.protocol_options.websocket.heartbeat_interval_ms;
}
if (config.protocol_options.websocket.reconnect_max_attempts != null) {
value.protocol_options.websocket.reconnect_max_attempts = config.protocol_options.websocket.reconnect_max_attempts;
}
if (config.protocol_options.websocket.reconnect_backoff_ms != null) {
value.protocol_options.websocket.reconnect_backoff_ms = config.protocol_options.websocket.reconnect_backoff_ms;
}
if (!Object.keys(value.protocol_options.websocket).length) {
delete value.protocol_options.websocket;
}
if (!Object.keys(value.protocol_options).length) {
delete value.protocol_options;
}
}
return window.jsyaml ? window.jsyaml.dump(value, { lineWidth: -1 }) : JSON.stringify(value, null, 2);
}
function prefillWizardFromEdit(detail, versionDocument) {
if (!detail || !versionDocument) return;
setEditModePresentation();
selectProtocol(detail.protocol);
var target = versionDocument.target || {};
if (target.kind === 'rest') {
prefillUpstream(target.base_url, target.static_headers, versionDocument.execution_config ? versionDocument.execution_config.auth_profile_ref : null);
setValue('endpoint-path', target.path_template);
document.querySelectorAll('.method-card').forEach(function(card) {
card.classList.toggle('active', card.dataset.method === target.method);
});
} else if (target.kind === 'graphql') {
var endpoint = target.endpoint || '';
var parsedEndpoint = new URL(endpoint, window.location.origin);
prefillUpstream(parsedEndpoint.origin, {}, versionDocument.execution_config ? versionDocument.execution_config.auth_profile_ref : null);
setValue('endpoint-path', parsedEndpoint.pathname + parsedEndpoint.search);
setValue('gql-query-editor', target.query_template);
document.querySelectorAll('.gql-type-card').forEach(function(card) {
card.classList.toggle('active', card.dataset.gqlType === target.operation_type);
});
} else if (target.kind === 'grpc') {
prefillUpstream(target.server_addr, {}, versionDocument.execution_config ? versionDocument.execution_config.auth_profile_ref : null);
var route = '/' + (target.package ? target.package + '.' : '') + target.service + '/' + target.method;
setValue('grpc-manual-route', route);
setValue('grpc-manual-req-type', '');
setValue('grpc-manual-res-type', '');
grpcManualRouteInput(route);
grpcManualTypeInput();
} else if (target.kind === 'soap') {
prefillUpstream(
target.endpoint_override || '',
{},
versionDocument.execution_config ? versionDocument.execution_config.auth_profile_ref : null
);
setValue('soap-service-name', target.service_name || '');
setValue('soap-port-name', target.port_name || '');
setValue('soap-operation-name', target.operation_name || '');
setValue('soap-endpoint-override', target.endpoint_override || '');
setValue('soap-action', target.soap_action || '');
setValue('soap-version', target.soap_version || 'soap_11');
setValue('soap-binding-style', target.binding_style || 'document_literal');
} else if (target.kind === 'websocket') {
prefillUpstream(
target.url,
target.static_headers || {},
versionDocument.execution_config ? versionDocument.execution_config.auth_profile_ref : null
);
setValue('websocket-path', '');
setValue('websocket-subprotocols', (target.subprotocols || []).join('\n'));
setValue(
'websocket-subscribe-template',
target.subscribe_message_template ? JSON.stringify(target.subscribe_message_template, null, 2) : ''
);
setValue(
'websocket-unsubscribe-template',
target.unsubscribe_message_template ? JSON.stringify(target.unsubscribe_message_template, null, 2) : ''
);
var websocketOptions = versionDocument.execution_config
&& versionDocument.execution_config.protocol_options
&& versionDocument.execution_config.protocol_options.websocket
? versionDocument.execution_config.protocol_options.websocket
: null;
setValue('websocket-heartbeat-interval-ms', websocketOptions && websocketOptions.heartbeat_interval_ms ? websocketOptions.heartbeat_interval_ms : '');
setValue('websocket-reconnect-max-attempts', websocketOptions && websocketOptions.reconnect_max_attempts != null ? websocketOptions.reconnect_max_attempts : '');
setValue('websocket-reconnect-backoff-ms', websocketOptions && websocketOptions.reconnect_backoff_ms ? websocketOptions.reconnect_backoff_ms : '');
}
setValue('tool-name', detail.name);
setValue('tool-display-name', detail.display_name);
setValue('tool-title', versionDocument.tool_description ? versionDocument.tool_description.title : detail.display_name);
setValue('tool-description', versionDocument.tool_description ? versionDocument.tool_description.description : '');
setValue('tool-input-schema', JSON.stringify(crankSchemaToJsonSchema(versionDocument.input_schema), null, 2));
setValue('tool-output-schema', JSON.stringify(crankSchemaToJsonSchema(versionDocument.output_schema), null, 2));
setValue('tool-input-mapping', mappingSetToEditorValue(versionDocument.input_mapping, 'input', detail.protocol));
setValue('tool-output-mapping', mappingSetToEditorValue(versionDocument.output_mapping, 'output', detail.protocol));
setValue('tool-exec-config', executionConfigToEditorValue(versionDocument.execution_config));
applyStreamingConfig(versionDocument.execution_config ? versionDocument.execution_config.streaming : null);
var toolNameInput = document.getElementById('tool-name');
if (toolNameInput) toolNameInput.disabled = true;
updateWizardProtocolVisibility();
}
function bindWizardLiveActions() {
bindLiveAction('wizard-upload-input-sample', tKey('wizard.busy.input_sample'), uploadInputSampleFromWizard);
bindLiveAction('wizard-upload-output-sample', tKey('wizard.busy.output_sample'), uploadOutputSampleFromWizard);
bindLiveAction('wizard-generate-draft', tKey('wizard.busy.generate'), generateDraftFromWizard);
bindLiveAction('wizard-run-test', tKey('wizard.busy.test'), runWizardTest);
bindClick('wizard-copy-test-response', copyTestResponseToOutputSample);
bindLiveAction('wizard-export-yaml', tKey('wizard.busy.export_yaml'), exportWizardYaml);
bindLiveAction('wizard-import-yaml', tKey('wizard.busy.import_yaml'), importWizardYaml);
bindLiveAction('wizard-publish-operation', tKey('wizard.busy.publish'), publishWizardOperation);
bindClick('wizard-select-descriptor-set', function() {
var input = document.getElementById('wizard-descriptor-set-input');
if (input) input.click();
});
bindLiveAction('wizard-upload-descriptor-set', tKey('wizard.busy.descriptor'), uploadDescriptorSetAndDiscover);
bindClick('wizard-select-soap-wsdl', function() {
var input = document.getElementById('wizard-soap-wsdl-input');
if (input) input.click();
});
bindClick('wizard-select-soap-xsd', function() {
var input = document.getElementById('wizard-soap-xsd-input');
if (input) input.click();
});
bindLiveAction('wizard-upload-soap-contracts', tKey('wizard.busy.wsdl'), uploadSoapArtifactsAndInspect);
bindClick('wizard-import-yaml-file-trigger', function() {
var input = document.getElementById('wizard-import-yaml-file');
if (input) input.click();
});
var descriptorInput = document.getElementById('wizard-descriptor-set-input');
if (descriptorInput) descriptorInput.addEventListener('change', handleDescriptorSetSelection);
var soapWsdlInput = document.getElementById('wizard-soap-wsdl-input');
if (soapWsdlInput) soapWsdlInput.addEventListener('change', handleSoapWsdlSelection);
var soapXsdInput = document.getElementById('wizard-soap-xsd-input');
if (soapXsdInput) soapXsdInput.addEventListener('change', handleSoapXsdSelection);
var yamlInput = document.getElementById('wizard-import-yaml-file');
if (yamlInput) yamlInput.addEventListener('change', handleImportYamlFileSelection);
}
function bindClick(id, handler) {
var element = document.getElementById(id);
if (!element) return;
element.addEventListener('click', function(event) {
event.preventDefault();
handler();
});
}
function bindLiveAction(id, busyLabel, handler) {
var element = document.getElementById(id);
if (!element) return;
element.addEventListener('click', function(event) {
event.preventDefault();
runWizardLiveAction(element, busyLabel, handler);
});
}
async function runWizardLiveAction(button, busyLabel, handler) {
if (!button || button.dataset.busy === 'true') {
return;
}
var originalLabel = button.textContent;
button.dataset.busy = 'true';
button.disabled = true;
button.classList.add('is-busy');
button.textContent = busyLabel;
try {
await handler();
} catch (error) {
showWizardLiveStatus(
tKey('wizard.live.failed_title'),
error && error.message ? error.message : tKey('wizard.live.failed_body'),
true
);
if (window.CrankUi) {
window.CrankUi.error(
error && error.message ? error.message : tKey('wizard.live.failed_body'),
tKey('wizard.live.failed_title')
);
}
} finally {
button.dataset.busy = 'false';
button.disabled = false;
button.classList.remove('is-busy');
button.textContent = originalLabel;
}
}
function updateWizardProtocolVisibility() {
var grpcTools = document.getElementById('wizard-grpc-live-tools');
if (grpcTools) grpcTools.style.display = wizardProtocol === 'grpc' ? '' : 'none';
updateStreamingModeOptions();
updateStreamingConfigVisibility();
}
function showWizardLiveStatus(title, text, isError) {
var root = document.getElementById('wizard-live-status');
var titleEl = document.getElementById('wizard-live-status-title');
var textEl = document.getElementById('wizard-live-status-text');
if (!root || !titleEl || !textEl) return;
titleEl.textContent = title;
textEl.textContent = text;
root.style.display = '';
root.classList.toggle('is-error', !!isError);
root.classList.toggle('is-success', !isError);
}
function currentDraftVersion() {
if (wizardCurrentOperation && wizardCurrentOperation.draft_version_ref) {
return wizardCurrentOperation.draft_version_ref.version;
}
if (wizardCurrentOperation && wizardCurrentOperation.current_draft_version) {
return wizardCurrentOperation.current_draft_version;
}
if (wizardCurrentVersion && wizardCurrentVersion.version) {
return wizardCurrentVersion.version;
}
return 1;
}
function updateOperationPayload(payload) {
return {
display_name: payload.display_name,
category: payload.category,
target: payload.target,
input_schema: payload.input_schema,
output_schema: payload.output_schema,
input_mapping: payload.input_mapping,
output_mapping: payload.output_mapping,
execution_config: payload.execution_config,
tool_description: payload.tool_description,
};
}
async function refreshCurrentOperationState() {
if (!wizardWorkspaceId || !wizardEditId) return null;
var detail = await window.CrankApi.getOperation(wizardWorkspaceId, wizardEditId);
var versionNumber = detail.draft_version_ref
? detail.draft_version_ref.version
: detail.current_draft_version;
var versionDocument = await window.CrankApi.getOperationVersion(
wizardWorkspaceId,
wizardEditId,
versionNumber
);
wizardCurrentOperation = detail;
wizardCurrentVersion = versionDocument;
updateWizardProtocolVisibility();
return {
detail: detail,
version: versionDocument,
};
}
async function uploadPendingGrpcArtifacts(payload) {
if (wizardProtocol !== 'grpc' || !wizardEditId) return;
var descriptorResponse = null;
if (wizardProtoUpload) {
descriptorResponse = await window.CrankApi.uploadProtoFile(
wizardWorkspaceId,
wizardEditId,
new TextEncoder().encode(wizardProtoUpload.content),
wizardProtoUpload.fileName
);
wizardProtoUpload = null;
}
if (wizardDescriptorSetUpload) {
descriptorResponse = await window.CrankApi.uploadDescriptorSet(
wizardWorkspaceId,
wizardEditId,
wizardDescriptorSetUpload.bytes,
wizardDescriptorSetUpload.fileName
);
wizardDescriptorSetUpload = null;
var descriptorName = document.getElementById('wizard-descriptor-set-name');
if (descriptorName) descriptorName.textContent = tKey('wizard.descriptor.uploaded');
}
if (!descriptorResponse) return;
payload.target.descriptor_ref = descriptorResponse.descriptor_id;
payload.target.descriptor_set_b64 = '';
await window.CrankApi.updateOperation(
wizardWorkspaceId,
wizardEditId,
updateOperationPayload(payload)
);
}
async function uploadPendingSoapArtifacts(payload) {
if (wizardProtocol !== 'soap' || !wizardEditId) return;
var wsdlResponse = null;
if (wizardSoapWsdlUpload) {
wsdlResponse = await window.CrankApi.uploadWsdlFile(
wizardWorkspaceId,
wizardEditId,
wizardSoapWsdlUpload.bytes,
wizardSoapWsdlUpload.fileName
);
wizardSoapWsdlUpload = null;
var wsdlName = document.getElementById('wizard-soap-wsdl-name');
if (wsdlName) wsdlName.textContent = tKey('wizard.step3.soap.wsdl_uploaded');
}
if (wizardSoapXsdUpload) {
await window.CrankApi.uploadXsdFile(
wizardWorkspaceId,
wizardEditId,
wizardSoapXsdUpload.bytes,
wizardSoapXsdUpload.fileName
);
wizardSoapXsdUpload = null;
var xsdName = document.getElementById('wizard-soap-xsd-name');
if (xsdName) xsdName.textContent = tKey('wizard.step3.soap.xsd_uploaded');
}
if (!wsdlResponse) return;
payload.target.wsdl_ref = wsdlResponse.descriptor_id;
await window.CrankApi.updateOperation(
wizardWorkspaceId,
wizardEditId,
updateOperationPayload(payload)
);
}
async function persistCurrentDraft(stayOnPage) {
if (!wizardWorkspaceId) {
throw new Error(tKey('wizard.error.no_workspace'));
}
var payload = collectWizardPayload();
var response;
var createdNow = false;
if (wizardMode === 'edit' && wizardEditId) {
response = await window.CrankApi.updateOperation(
wizardWorkspaceId,
wizardEditId,
updateOperationPayload(payload)
);
} else {
response = await window.CrankApi.createOperation(wizardWorkspaceId, payload);
createdNow = true;
wizardEditId = response.operation_id;
wizardMode = 'edit';
history.replaceState(
{},
'',
window.location.pathname + '?mode=edit&operationId=' + encodeURIComponent(wizardEditId)
);
setEditModePresentation();
_doGoToStep(currentStep);
var toolNameInput = document.getElementById('tool-name');
if (toolNameInput) toolNameInput.disabled = true;
}
await uploadPendingGrpcArtifacts(payload);
await uploadPendingSoapArtifacts(payload);
await refreshCurrentOperationState();
if (stayOnPage) {
var saveDraftBtn = document.querySelector('.btn-save-draft');
if (saveDraftBtn) {
var label = saveDraftBtn.textContent;
saveDraftBtn.textContent = tKey('wizard.save.saved');
setTimeout(function() {
saveDraftBtn.textContent = label;
}, 1400);
}
}
showWizardLiveStatus(
createdNow ? tKey('wizard.save.created') : tKey('wizard.save.updated'),
tKey('wizard.save.body')
);
return response;
}
function safeStringify(value) {
if (value === null || value === undefined) return '';
if (typeof value === 'string') return value;
return JSON.stringify(value, null, 2);
}
function setTextareaValue(id, value) {
var element = document.getElementById(id);
if (!element) return;
element.value = safeStringify(value);
}
function describeWizardTestResult(result) {
if (result.stream_session && result.stream_session.session_id) {
return {
title: tKey('wizard.test.session_started'),
body: tfKey('wizard.test.session_started_body', {
session_id: result.stream_session.session_id,
poll_after_ms: result.stream_session.poll_after_ms
}),
isError: false,
};
}
if (result.async_job && result.async_job.job_id) {
return {
title: tKey('wizard.test.async_job_started'),
body: tfKey('wizard.test.async_job_started_body', {
job_id: result.async_job.job_id
}),
isError: false,
};
}
if (result.window) {
var bodyParts = [
tKey('wizard.test.window_completed_body')
];
if (result.window.truncated) {
bodyParts.push(tKey('wizard.test.window_truncated_note'));
}
if (result.window.has_more) {
bodyParts.push(tKey('wizard.test.window_more_note'));
}
if (!result.window.window_complete) {
bodyParts.push(tKey('wizard.test.window_incomplete_note'));
}
return {
title: result.ok ? tKey('wizard.test.window_completed') : tKey('wizard.test.failed'),
body: bodyParts.join(' '),
isError: !result.ok,
};
}
return {
title: result.ok ? tKey('wizard.test.completed') : tKey('wizard.test.failed'),
body: result.ok ? tKey('wizard.test.completed_body') : tKey('wizard.test.failed_body'),
isError: !result.ok,
};
}
async function uploadInputSampleFromWizard() {
await persistCurrentDraft(true);
await window.CrankApi.uploadInputSample(
wizardWorkspaceId,
wizardEditId,
parseStructuredText(textValue('wizard-input-sample'))
);
showWizardLiveStatus(tKey('wizard.sample.input_saved'), tKey('wizard.sample.input_saved_body'));
}
async function uploadOutputSampleFromWizard() {
await persistCurrentDraft(true);
await window.CrankApi.uploadOutputSample(
wizardWorkspaceId,
wizardEditId,
parseStructuredText(textValue('wizard-output-sample'))
);
showWizardLiveStatus(tKey('wizard.sample.output_saved'), tKey('wizard.sample.output_saved_body'));
}
async function generateDraftFromWizard() {
await persistCurrentDraft(true);
var generated = await window.CrankApi.generateDraft(wizardWorkspaceId, wizardEditId, {});
setValue('tool-input-schema', JSON.stringify(crankSchemaToJsonSchema(generated.input_schema), null, 2));
setValue('tool-output-schema', JSON.stringify(crankSchemaToJsonSchema(generated.output_schema), null, 2));
setValue('tool-input-mapping', mappingSetToEditorValue(generated.input_mapping, 'input', wizardProtocol));
setValue('tool-output-mapping', mappingSetToEditorValue(generated.output_mapping, 'output', wizardProtocol));
showWizardLiveStatus(tKey('wizard.sample.generated'), tKey('wizard.sample.generated_body'));
}
async function runWizardTest() {
await persistCurrentDraft(true);
var result = await window.CrankApi.runOperationTest(wizardWorkspaceId, wizardEditId, {
version: currentDraftVersion(),
input: parseStructuredText(textValue('wizard-test-input')),
});
wizardTestResponsePreview = result.response_preview;
setTextareaValue('wizard-test-request-preview', result.request_preview);
setTextareaValue('wizard-test-response-preview', result.response_preview);
setTextareaValue('wizard-test-errors', result.errors && result.errors.length ? result.errors : []);
var status = describeWizardTestResult(result);
showWizardLiveStatus(
status.title,
status.body,
status.isError
);
}
function copyTestResponseToOutputSample() {
if (!wizardTestResponsePreview) {
showWizardLiveStatus(tKey('wizard.test.no_response'), tKey('wizard.test.no_response_body'), true);
return;
}
setTextareaValue('wizard-output-sample', wizardTestResponsePreview);
showWizardLiveStatus(tKey('wizard.test.response_copied'), tKey('wizard.test.response_copied_body'));
}
function downloadTextFile(fileName, content, mimeType) {
var blob = new Blob([content], { type: mimeType || 'text/plain;charset=utf-8' });
var url = URL.createObjectURL(blob);
var link = document.createElement('a');
link.href = url;
link.download = fileName;
link.click();
URL.revokeObjectURL(url);
}
async function exportWizardYaml() {
await persistCurrentDraft(true);
var yaml = await window.CrankApi.exportOperation(wizardWorkspaceId, wizardEditId, {
mode: 'portable',
version: currentDraftVersion(),
});
downloadTextFile((textValue('tool-name') || 'operation') + '.yaml', yaml, 'application/yaml');
showWizardLiveStatus(tKey('wizard.yaml.exported'), tKey('wizard.yaml.exported_body'));
}
async function importWizardYaml() {
if (!wizardWorkspaceId) {
throw new Error(tKey('wizard.error.no_workspace'));
}
var yamlDocument = textValue('wizard-import-yaml-text');
if (!yamlDocument) {
showWizardLiveStatus(tKey('wizard.yaml.none'), tKey('wizard.yaml.none_body'), true);
return;
}
var imported = await window.CrankApi.importOperation(wizardWorkspaceId, yamlDocument, 'upsert');
showWizardLiveStatus(tKey('wizard.yaml.imported'), tKey('wizard.yaml.imported_body'));
window.location.href = window.location.pathname + '?mode=edit&operationId=' + encodeURIComponent(imported.operation_id);
}
async function publishWizardOperation() {
await persistCurrentDraft(true);
var published = await window.CrankApi.publishOperation(
wizardWorkspaceId,
wizardEditId,
currentDraftVersion()
);
await refreshCurrentOperationState();
showWizardLiveStatus(
tKey('wizard.publish.done'),
tfKey('wizard.publish.done_body', { version: published.published_version })
);
}
function handleImportYamlFileSelection(event) {
var file = event.target.files && event.target.files[0];
if (!file) return;
var reader = new FileReader();
reader.onload = function(loadEvent) {
setValue('wizard-import-yaml-text', loadEvent.target.result || '');
showWizardLiveStatus(tKey('wizard.yaml.loaded'), tKey('wizard.yaml.loaded_body'));
};
reader.readAsText(file);
}
function handleDescriptorSetSelection(event) {
var file = event.target.files && event.target.files[0];
if (!file) return;
var reader = new FileReader();
reader.onload = function(loadEvent) {
wizardDescriptorSetUpload = {
fileName: file.name,
bytes: new Uint8Array(loadEvent.target.result),
};
var fileNameEl = document.getElementById('wizard-descriptor-set-name');
if (fileNameEl) fileNameEl.textContent = file.name;
showWizardLiveStatus(tKey('wizard.descriptor.selected'), tKey('wizard.descriptor.selected_body'));
};
reader.readAsArrayBuffer(file);
}
function handleSoapWsdlSelection(event) {
var file = event.target.files && event.target.files[0];
if (!file) return;
var reader = new FileReader();
reader.onload = function(loadEvent) {
wizardSoapWsdlUpload = {
fileName: file.name,
bytes: new Uint8Array(loadEvent.target.result),
};
var fileNameEl = document.getElementById('wizard-soap-wsdl-name');
if (fileNameEl) fileNameEl.textContent = file.name;
showWizardLiveStatus(tKey('wizard.step3.soap.wsdl_selected'), tKey('wizard.step3.soap.wsdl_selected_body'));
};
reader.readAsArrayBuffer(file);
}
function handleSoapXsdSelection(event) {
var file = event.target.files && event.target.files[0];
if (!file) return;
var reader = new FileReader();
reader.onload = function(loadEvent) {
wizardSoapXsdUpload = {
fileName: file.name,
bytes: new Uint8Array(loadEvent.target.result),
};
var fileNameEl = document.getElementById('wizard-soap-xsd-name');
if (fileNameEl) fileNameEl.textContent = file.name;
showWizardLiveStatus(tKey('wizard.step3.soap.xsd_selected'), tKey('wizard.step3.soap.xsd_selected_body'));
};
reader.readAsArrayBuffer(file);
}
function renderDescriptorServiceList(services) {
grpcDescriptorServices = services || [];
var summaryEl = document.getElementById('wizard-descriptor-services-summary');
var listEl = document.getElementById('wizard-descriptor-services-list');
if (!summaryEl || !listEl) return;
var methodCount = grpcDescriptorServices.reduce(function(total, service) {
return total + ((service.methods || []).length);
}, 0);
summaryEl.textContent = grpcDescriptorServices.length
? tfKey('wizard.grpc.services_discovered', { services: grpcDescriptorServices.length, methods: methodCount })
: tKey('wizard.grpc.services_none');
listEl.innerHTML = '';
grpcDescriptorServices.forEach(function(service, serviceIndex) {
var group = document.createElement('div');
group.className = 'config-card';
group.style.marginBottom = '0';
var body = document.createElement('div');
body.className = 'config-card-body';
body.style.padding = '16px';
body.style.gap = '10px';
var title = document.createElement('div');
title.className = 'config-card-title';
title.textContent = (service.package ? service.package + '.' : '') + service.service;
body.appendChild(title);
(service.methods || []).forEach(function(method, methodIndex) {
var button = document.createElement('button');
button.type = 'button';
button.className = 'btn-ghost-sm';
button.style.width = '100%';
button.style.justifyContent = 'space-between';
button.textContent = method.name;
button.addEventListener('click', function() {
applyDiscoveredGrpcMethod(serviceIndex, methodIndex);
});
body.appendChild(button);
});
group.appendChild(body);
listEl.appendChild(group);
});
}
function applyDiscoveredGrpcMethod(serviceIndex, methodIndex) {
var service = grpcDescriptorServices[serviceIndex];
var method = service && service.methods ? service.methods[methodIndex] : null;
if (!service || !method) return;
var route = '/' + (service.package ? service.package + '.' : '') + service.service + '/' + method.name;
selectGrpcSource('manual');
setValue('grpc-manual-route', route);
setValue('grpc-manual-req-type', method.name + 'Request');
setValue('grpc-manual-res-type', method.name + 'Response');
grpcManualRouteInput(route);
grpcManualTypeInput();
setValue('tool-input-schema', JSON.stringify(crankSchemaToJsonSchema(method.input_schema), null, 2));
setValue('tool-output-schema', JSON.stringify(crankSchemaToJsonSchema(method.output_schema), null, 2));
showWizardLiveStatus(tKey('wizard.descriptor.applied'), tKey('wizard.descriptor.applied_body'));
}
async function uploadDescriptorSetAndDiscover() {
var hasSavedDescriptor = !!(
wizardCurrentVersion
&& wizardCurrentVersion.target
&& wizardCurrentVersion.target.kind === 'grpc'
&& wizardCurrentVersion.target.descriptor_ref
);
if (!wizardDescriptorSetUpload && !hasSavedDescriptor) {
showWizardLiveStatus(tKey('wizard.descriptor.no_file'), tKey('wizard.descriptor.no_file_body'), true);
return;
}
await persistCurrentDraft(true);
var services = await window.CrankApi.listGrpcServices(
wizardWorkspaceId,
wizardEditId,
currentDraftVersion()
);
renderDescriptorServiceList(services.services || []);
showWizardLiveStatus(tKey('wizard.descriptor.discovered'), tKey('wizard.descriptor.discovered_body'));
}
function renderSoapServiceCatalog(services) {
soapServiceCatalog = services || [];
var summaryEl = document.getElementById('wizard-soap-services-summary');
var listEl = document.getElementById('wizard-soap-services-list');
if (!summaryEl || !listEl) return;
var portCount = 0;
var operationCount = 0;
soapServiceCatalog.forEach(function(service) {
portCount += (service.ports || []).length;
(service.ports || []).forEach(function(port) {
operationCount += (port.operations || []).length;
});
});
summaryEl.textContent = soapServiceCatalog.length
? tfKey('wizard.step3.soap.services_discovered', {
services: soapServiceCatalog.length,
ports: portCount,
operations: operationCount
})
: tKey('wizard.step3.soap.services_none');
listEl.innerHTML = '';
soapServiceCatalog.forEach(function(service, serviceIndex) {
var group = document.createElement('div');
group.className = 'config-card';
group.style.marginBottom = '0';
var body = document.createElement('div');
body.className = 'config-card-body';
body.style.padding = '16px';
body.style.gap = '10px';
var title = document.createElement('div');
title.className = 'config-card-title';
title.textContent = service.service_name;
body.appendChild(title);
(service.ports || []).forEach(function(port, portIndex) {
var portLabel = document.createElement('div');
portLabel.className = 'form-hint';
portLabel.textContent = port.port_name + ' · ' + port.soap_version;
body.appendChild(portLabel);
(port.operations || []).forEach(function(operation, operationIndex) {
var button = document.createElement('button');
button.type = 'button';
button.className = 'btn-ghost-sm';
button.style.width = '100%';
button.style.justifyContent = 'space-between';
button.textContent = operation.operation_name;
button.addEventListener('click', function() {
applyDiscoveredSoapOperation(serviceIndex, portIndex, operationIndex);
});
body.appendChild(button);
});
});
group.appendChild(body);
listEl.appendChild(group);
});
}
function applyDiscoveredSoapOperation(serviceIndex, portIndex, operationIndex) {
var service = soapServiceCatalog[serviceIndex];
var port = service && service.ports ? service.ports[portIndex] : null;
var operation = port && port.operations ? port.operations[operationIndex] : null;
if (!service || !port || !operation) return;
setValue('soap-service-name', service.service_name);
setValue('soap-port-name', port.port_name);
setValue('soap-operation-name', operation.operation_name);
setValue('soap-version', port.soap_version || 'soap_11');
setValue('soap-action', operation.soap_action || '');
setValue('soap-binding-style', operation.binding_style || 'document_literal');
if (!textValue('soap-endpoint-override') && port.endpoint) {
setValue('soap-endpoint-override', port.endpoint);
}
showWizardLiveStatus(tKey('wizard.step3.soap.applied'), tKey('wizard.step3.soap.applied_body'));
}
async function uploadSoapArtifactsAndInspect() {
var hasSavedWsdl = !!(
wizardCurrentVersion
&& wizardCurrentVersion.target
&& wizardCurrentVersion.target.kind === 'soap'
&& wizardCurrentVersion.target.wsdl_ref
&& wizardCurrentVersion.target.wsdl_ref !== 'sample_wsdl_pending'
);
if (!wizardSoapWsdlUpload && !hasSavedWsdl) {
showWizardLiveStatus(tKey('wizard.step3.soap.no_wsdl_error'), tKey('wizard.step3.soap.no_wsdl_error_body'), true);
return;
}
await persistCurrentDraft(true);
var services = await window.CrankApi.listSoapServices(
wizardWorkspaceId,
wizardEditId,
currentDraftVersion()
);
renderSoapServiceCatalog(services.services || []);
showWizardLiveStatus(tKey('wizard.step3.soap.discovered'), tKey('wizard.step3.soap.discovered_body'));
}