Files
crank/apps/ui/js/wizard.js
T
2026-05-02 19:50:34 +00:00

1302 lines
49 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
function tKey(key) {
return typeof t === 'function' ? t(key) : key;
}
function tfKey(key, vars) {
return typeof tf === 'function' ? tf(key, vars) : key;
}
var TOTAL_STEPS = window.CrankWizardShell.TOTAL_STEPS;
var loadProtocolCapabilities = window.CrankWizardState.loadProtocolCapabilities;
var currentProtocolCapabilities = window.CrankWizardState.currentProtocolCapabilities;
var loadStep = window.CrankWizardShell.loadStep;
var goToStep = window.CrankWizardShell.goToStep;
var _doGoToStep = window.CrankWizardShell.doGoToStep;
var loadWizardPanels = window.CrankWizardShell.loadWizardPanels;
var bindProtocolCards = window.CrankWizardShell.bindProtocolCards;
var step3PanelId = window.CrankWizardShell.step3PanelId;
var bindStreamingConfigControls = window.CrankStreamingForm.bindStreamingConfigControls;
var collectStreamingConfig = window.CrankStreamingForm.collectStreamingConfig;
var applyStreamingConfig = window.CrankStreamingForm.applyStreamingConfig;
var currentStep = window.currentStep;
var wizardProtocol = window.wizardProtocol;
var wizardMode = window.wizardMode;
var wizardEditId = window.wizardEditId;
var wizardWorkspaceId = window.wizardWorkspaceId;
var wizardCurrentOperation = window.wizardCurrentOperation;
var wizardCurrentVersion = window.wizardCurrentVersion;
var wizardProtoUpload = window.wizardProtoUpload;
var wizardDescriptorSetUpload = window.wizardDescriptorSetUpload;
var wizardSoapWsdlUpload = window.wizardSoapWsdlUpload;
var wizardSoapXsdUpload = window.wizardSoapXsdUpload;
var wizardTestResponsePreview = window.wizardTestResponsePreview;
var grpcDescriptorServices = window.grpcDescriptorServices;
var soapServiceCatalog = window.soapServiceCatalog;
var wizardProtocolCapabilities = window.wizardProtocolCapabilities;
var wizardSecrets = window.wizardSecrets;
var wizardAuthProfiles = window.wizardAuthProfiles;
var selectedUpstreamId = window.selectedUpstreamId;
var editingUpstreamId = window.editingUpstreamId;
var protoParsed = window.protoParsed;
var selectedRpcMethod = window.selectedRpcMethod;
async function initWizardPage() {
renderSidebarBrand('create');
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);
}
if (window.CrankDiagnostics && typeof window.CrankDiagnostics.bootstrap === 'function') {
window.CrankDiagnostics.bootstrap('wizard', initWizardPage);
} else {
document.addEventListener('DOMContentLoaded', function() {
void initWizardPage();
});
}
/* ── HTTP method picker (Step 4 REST) ── */
var METHOD_CALLOUTS = {
GET: null,
DELETE: null,
POST: { icon: 'info', text: { en: { title: 'POST selected — body encoding required.', body: '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: { title: 'Выбран POST — требуется кодирование тела запроса.', body: 'На шаге 4 вы зададите JSON-схему для body payload. Crank автоматически сериализует аргументы MCP-инструмента в выбранный формат.' } } },
PUT: { icon: 'info', text: { en: { title: 'PUT selected — full-resource replacement.', body: 'The request body must contain a complete resource representation. Define the body schema in step 4.' }, ru: { title: 'Выбран PUT — полная замена ресурса.', body: 'Request body должен содержать полное представление ресурса. Задайте body schema на шаге 4.' } } },
PATCH: { icon: 'info', text: { en: { title: 'PATCH selected — partial update.', body: 'Only include the fields you want to modify in the body schema defined in step 4.' }, ru: { title: 'Выбран PATCH — частичное обновление.', body: 'В body schema на шаге 4 включайте только те поля, которые хотите изменить.' } } },
};
function renderSidebarBrand(mode) {
var sidebarBrand = document.querySelector('.step-sidebar-brand');
if (!sidebarBrand) return;
var language = localStorage.getItem('crank_lang') || 'en';
var parts;
if (mode === 'edit') {
parts = language === 'ru'
? { prefix: 'Редактировать ', suffix: 'операцию' }
: { prefix: 'Edit ', suffix: 'operation' };
} else {
parts = language === 'ru'
? { prefix: 'Создать ', suffix: 'операцию' }
: { prefix: 'Create ', suffix: 'operation' };
}
sidebarBrand.dataset.wizardBrand = mode;
sidebarBrand.textContent = parts.prefix;
var accent = document.createElement('span');
accent.textContent = parts.suffix;
sidebarBrand.appendChild(accent);
}
window.addEventListener('crank:langchange', function() {
renderSidebarBrand(wizardMode === 'edit' ? 'edit' : 'create');
});
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 = '';
var icon = buildIconSvg(
(window.APP_BASE || '') + 'icons/general/info-circle.svg#icon',
15,
15
);
icon.style.flexShrink = '0';
icon.style.color = 'var(--accent)';
callout.appendChild(icon);
var content = document.createElement('span');
var title = document.createElement('strong');
title.textContent = text.title;
content.appendChild(title);
content.appendChild(document.createTextNode(' ' + text.body));
callout.appendChild(content);
callout.hidden = false;
} else {
callout.hidden = true;
}
}
/* ── 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];
}
function escapeHtml(str) {
return String(str).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
// Close dropdown on outside click
document.addEventListener('click', function() {
if (dropdownOpen) closeUpstreamDropdown();
});
/* ══════════════════════════════════════════════
gRPC — source selector
══════════════════════════════════════════════ */
var grpcSource = 'proto'; // 'proto' | 'reflection' | 'manual'
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 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');
renderSidebarBrand('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) {
var step3Labels = window.CrankWizardState.step3Labels();
s3name.textContent = step3Labels[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.hidden = false;
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 = '';
valueEl.appendChild(buildUpstreamTriggerContent('imported-upstream', 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.hidden = wizardProtocol !== 'grpc';
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.hidden = false;
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'));
}