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,'&').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 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')); }