diff --git a/TASKS.md b/TASKS.md index 3dd8b5d..a116b4b 100644 --- a/TASKS.md +++ b/TASKS.md @@ -2,18 +2,18 @@ ## Current -### `feat/staging-note-block-helper` +### `feat/soap-wizard-foundation` Status: completed DoD: -- authenticated staging notes block can be generated through a helper command -- authenticated pass docs point to the helper -- README and scripts docs expose the helper +- wizard step 1 exposes SOAP as a selectable protocol +- wizard step 3 supports WSDL/XSD upload and backend-driven SOAP service inspection +- SOAP service, port and operation selection is serialized into operation target payload ## Next -- `feat/live-authenticated-staging-entry` +- `feat/websocket-wizard-foundation` ## Backlog diff --git a/apps/ui/html/wizard/step1.html b/apps/ui/html/wizard/step1.html index 3c7e5ce..2947086 100644 --- a/apps/ui/html/wizard/step1.html +++ b/apps/ui/html/wizard/step1.html @@ -12,7 +12,7 @@
- - - + +
diff --git a/apps/ui/html/wizard/step3-soap.html b/apps/ui/html/wizard/step3-soap.html new file mode 100644 index 0000000..f6d4c04 --- /dev/null +++ b/apps/ui/html/wizard/step3-soap.html @@ -0,0 +1,101 @@ +
+
+
+
+ Step 3 of 5 +
+

WSDL and SOAP binding

+

Upload the WSDL/XSD bundle, inspect services and choose the exact service, port and operation that Crank should expose as an MCP tool.

+
+ +
+
+
+ + + + +
+
+
Contract files
+
WSDL is required. Supporting XSD files are optional and can be uploaded before discovery.
+
+ Required +
+
+
+ + + No WSDL selected +
+
+ + + No XSD selected +
+
+ +
+
+
+
+
+ +
+
+
+ + + +
+
+
Selected binding
+
The chosen service, port and operation become the target binding for this operation draft.
+
+ Optional +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
Defaults to the upstream base URL from step 2. Override only when the binding address differs from the selected upstream.
+
+
+
+
diff --git a/apps/ui/js/api.js b/apps/ui/js/api.js index 760439c..48041d8 100644 --- a/apps/ui/js/api.js +++ b/apps/ui/js/api.js @@ -257,12 +257,32 @@ fileName ); }, + uploadWsdlFile: function(workspaceId, operationId, content, fileName) { + return postBytes( + '/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId) + '/descriptors/wsdl', + content, + fileName + ); + }, + uploadXsdFile: function(workspaceId, operationId, content, fileName) { + return postBytes( + '/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId) + '/descriptors/xsd', + content, + fileName + ); + }, listGrpcServices: function(workspaceId, operationId, version) { return get( '/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId) + '/grpc/services' + query({ version: version }) ); }, + listSoapServices: function(workspaceId, operationId, version) { + return get( + '/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId) + '/soap/services' + + query({ version: version }) + ); + }, generateDraft: function(workspaceId, operationId, payload) { return post('/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId) + '/drafts/generate', payload || {}); }, diff --git a/apps/ui/js/i18n.js b/apps/ui/js/i18n.js index 7a97dc7..d62f1ec 100644 --- a/apps/ui/js/i18n.js +++ b/apps/ui/js/i18n.js @@ -605,6 +605,7 @@ var TRANSLATIONS = { 'wizard.step3.rest.label': 'Request config', 'wizard.step3.graphql.label': 'GQL operation', 'wizard.step3.grpc.label': 'RPC method', + 'wizard.step3.soap.label': 'WSDL binding', 'wizard.step3.rest.title': 'HTTP method & format', 'wizard.step3.rest.subtitle': 'Choose the HTTP verb this operation sends and configure content negotiation headers. Crank will serialize MCP tool arguments into the appropriate request body format.', 'wizard.step3.rest.method_title': 'HTTP method', @@ -666,6 +667,27 @@ var TRANSLATIONS = { 'wizard.step3.grpc.route_label': 'gRPC route', 'wizard.step3.grpc.request_label': 'Request', 'wizard.step3.grpc.response_label': 'Response', + 'wizard.step3.soap.title': 'WSDL and SOAP binding', + 'wizard.step3.soap.subtitle': 'Upload the WSDL/XSD bundle, inspect services and choose the exact service, port and operation that Crank should expose as an MCP tool.', + 'wizard.step3.soap.wsdl_title': 'Contract files', + 'wizard.step3.soap.wsdl_subtitle': 'WSDL is required. Supporting XSD files are optional and can be uploaded before discovery.', + 'wizard.step3.soap.choose_wsdl': 'Choose WSDL', + 'wizard.step3.soap.choose_xsd': 'Choose XSD', + 'wizard.step3.soap.no_wsdl': 'No WSDL selected', + 'wizard.step3.soap.no_xsd': 'No XSD selected', + 'wizard.step3.soap.upload_discover': 'Upload and inspect services', + 'wizard.step3.soap.binding_title': 'Selected binding', + 'wizard.step3.soap.binding_subtitle': 'The chosen service, port and operation become the target binding for this operation draft.', + 'wizard.step3.soap.service_name': 'Service', + 'wizard.step3.soap.port_name': 'Port', + 'wizard.step3.soap.operation_name': 'Operation', + 'wizard.step3.soap.version': 'SOAP version', + 'wizard.step3.soap.action': 'SOAPAction', + 'wizard.step3.soap.binding_style': 'Binding style', + 'wizard.step3.soap.binding.document_literal': 'Document literal', + 'wizard.step3.soap.binding.rpc_literal': 'RPC literal', + 'wizard.step3.soap.endpoint_override': 'Endpoint override', + 'wizard.step3.soap.endpoint_override_hint': 'Defaults to the upstream base URL from step 2. Override only when the binding address differs from the selected upstream.', 'wizard.step4.title': 'Tool config', 'wizard.step4.subtitle': 'Name your tool, write the LLM-facing description, and define the input/output schemas. The description is the primary signal the model uses when deciding whether to call this tool.', 'wizard.step4.identity_title': 'Tool identity', @@ -766,6 +788,21 @@ var TRANSLATIONS = { 'wizard.busy.import_yaml': 'Importing YAML…', 'wizard.busy.publish': 'Publishing operation…', 'wizard.busy.descriptor': 'Uploading descriptor set…', + 'wizard.busy.wsdl': 'Inspecting WSDL…', + 'wizard.step3.soap.wsdl_selected': 'WSDL selected', + 'wizard.step3.soap.wsdl_selected_body': 'The WSDL file is staged and will be uploaded with the current draft.', + 'wizard.step3.soap.xsd_selected': 'XSD selected', + 'wizard.step3.soap.xsd_selected_body': 'The XSD file is staged and will be uploaded with the current draft.', + 'wizard.step3.soap.wsdl_uploaded': 'WSDL uploaded', + 'wizard.step3.soap.xsd_uploaded': 'XSD uploaded', + 'wizard.step3.soap.services_discovered': '{services} service(s), {ports} port(s) and {operations} operation(s) discovered from the uploaded contract.', + 'wizard.step3.soap.services_none': 'No SOAP services discovered from the uploaded WSDL yet.', + 'wizard.step3.soap.applied': 'SOAP binding applied', + 'wizard.step3.soap.applied_body': 'The selected service, port and operation were applied to the current draft.', + 'wizard.step3.soap.no_wsdl_error': 'WSDL is required', + 'wizard.step3.soap.no_wsdl_error_body': 'Choose a WSDL file or reuse an already uploaded draft contract before inspection.', + 'wizard.step3.soap.discovered': 'SOAP services discovered', + 'wizard.step3.soap.discovered_body': 'The uploaded WSDL was inspected through the backend and the available bindings are ready to apply.', 'wizard.sample.input_saved': 'Input sample saved', 'wizard.sample.input_saved_body': 'The input sample is now stored against the current draft version.', 'wizard.sample.output_saved': 'Output sample saved', @@ -1532,6 +1569,7 @@ var TRANSLATIONS = { 'wizard.step3.rest.label': 'Конфиг запроса', 'wizard.step3.graphql.label': 'GQL операция', 'wizard.step3.grpc.label': 'RPC метод', + 'wizard.step3.soap.label': 'WSDL binding', 'wizard.step3.rest.title': 'HTTP метод и формат', 'wizard.step3.rest.subtitle': 'Выберите HTTP-метод, который отправляет эта операция, и настройте content negotiation headers. Crank сериализует аргументы MCP-инструмента в нужный формат request body.', 'wizard.step3.rest.method_title': 'HTTP метод', @@ -1593,6 +1631,27 @@ var TRANSLATIONS = { 'wizard.step3.grpc.route_label': 'gRPC route', 'wizard.step3.grpc.request_label': 'Запрос', 'wizard.step3.grpc.response_label': 'Ответ', + 'wizard.step3.soap.title': 'WSDL и SOAP binding', + 'wizard.step3.soap.subtitle': 'Загрузите WSDL/XSD bundle, получите список сервисов и выберите точный service, port и operation, который Crank должен опубликовать как MCP tool.', + 'wizard.step3.soap.wsdl_title': 'Файлы контракта', + 'wizard.step3.soap.wsdl_subtitle': 'WSDL обязателен. Supporting XSD files можно загрузить дополнительно до discovery.', + 'wizard.step3.soap.choose_wsdl': 'Выбрать WSDL', + 'wizard.step3.soap.choose_xsd': 'Выбрать XSD', + 'wizard.step3.soap.no_wsdl': 'WSDL не выбран', + 'wizard.step3.soap.no_xsd': 'XSD не выбран', + 'wizard.step3.soap.upload_discover': 'Загрузить и получить сервисы', + 'wizard.step3.soap.binding_title': 'Выбранный binding', + 'wizard.step3.soap.binding_subtitle': 'Выбранные service, port и operation станут target binding для текущего draft.', + 'wizard.step3.soap.service_name': 'Service', + 'wizard.step3.soap.port_name': 'Port', + 'wizard.step3.soap.operation_name': 'Operation', + 'wizard.step3.soap.version': 'SOAP версия', + 'wizard.step3.soap.action': 'SOAPAction', + 'wizard.step3.soap.binding_style': 'Binding style', + 'wizard.step3.soap.binding.document_literal': 'Document literal', + 'wizard.step3.soap.binding.rpc_literal': 'RPC literal', + 'wizard.step3.soap.endpoint_override': 'Endpoint override', + 'wizard.step3.soap.endpoint_override_hint': 'По умолчанию берется upstream base URL из шага 2. Переопределяйте только если binding address отличается от выбранного upstream.', 'wizard.step4.title': 'Конфиг инструмента', 'wizard.step4.subtitle': 'Задайте имя инструмента, описание для LLM и определите входную/выходную схему. Описание — главный сигнал, по которому модель решает, вызывать ли этот инструмент.', 'wizard.step4.identity_title': 'Идентичность инструмента', @@ -1693,6 +1752,21 @@ var TRANSLATIONS = { 'wizard.busy.import_yaml': 'Импорт YAML…', 'wizard.busy.publish': 'Публикация операции…', 'wizard.busy.descriptor': 'Загрузка descriptor set…', + 'wizard.busy.wsdl': 'Inspection WSDL…', + 'wizard.step3.soap.wsdl_selected': 'WSDL выбран', + 'wizard.step3.soap.wsdl_selected_body': 'WSDL staged и будет загружен вместе с текущим draft.', + 'wizard.step3.soap.xsd_selected': 'XSD выбран', + 'wizard.step3.soap.xsd_selected_body': 'XSD staged и будет загружен вместе с текущим draft.', + 'wizard.step3.soap.wsdl_uploaded': 'WSDL загружен', + 'wizard.step3.soap.xsd_uploaded': 'XSD загружен', + 'wizard.step3.soap.services_discovered': 'Из загруженного контракта найдено {services} service(s), {ports} port(s) и {operations} operation(s).', + 'wizard.step3.soap.services_none': 'Из загруженного WSDL пока не найдено ни одного SOAP service.', + 'wizard.step3.soap.applied': 'SOAP binding применен', + 'wizard.step3.soap.applied_body': 'Выбранные service, port и operation применены к текущему draft.', + 'wizard.step3.soap.no_wsdl_error': 'Нужен WSDL', + 'wizard.step3.soap.no_wsdl_error_body': 'Выберите WSDL или используйте уже загруженный контракт текущего draft перед inspection.', + 'wizard.step3.soap.discovered': 'SOAP services получены', + 'wizard.step3.soap.discovered_body': 'Загруженный WSDL был inspected через backend, и доступные bindings готовы к применению.', 'wizard.sample.input_saved': 'Входной sample сохранен', 'wizard.sample.input_saved_body': 'Входной sample теперь сохранен в текущей версии черновика.', 'wizard.sample.output_saved': 'Выходной sample сохранен', diff --git a/apps/ui/js/wizard.js b/apps/ui/js/wizard.js index 4b5e5f0..d738bb6 100644 --- a/apps/ui/js/wizard.js +++ b/apps/ui/js/wizard.js @@ -8,8 +8,11 @@ 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; @@ -91,6 +94,7 @@ function loadStep(n, callback) { function step3PanelId() { if (wizardProtocol === 'graphql') return 'step-panel-3-graphql'; if (wizardProtocol === 'grpc') return 'step-panel-3-grpc'; + if (wizardProtocol === 'soap') return 'step-panel-3-soap'; return 'step-panel-3-rest'; } @@ -99,6 +103,7 @@ var STEP3_LABELS = { rest: tKey('wizard.step3.rest.label'), graphql: tKey('wizard.step3.graphql.label'), grpc: tKey('wizard.step3.grpc.label'), + soap: tKey('wizard.step3.soap.label'), }; var CHECK_SVG = ''; @@ -212,6 +217,8 @@ function bindProtocolCards() { ? 'graphql' : card.classList.contains('grpc') ? 'grpc' + : card.classList.contains('soap') + ? 'soap' : 'rest'); wizardProtocol = proto; @@ -1586,6 +1593,28 @@ function buildTarget() { }; } + 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: [] }, + }; + } + 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 @@ -1963,7 +1992,15 @@ 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' : 'grpc'); + || (card.classList.contains('rest') + ? 'rest' + : card.classList.contains('graphql') + ? 'graphql' + : card.classList.contains('grpc') + ? 'grpc' + : card.classList.contains('soap') + ? 'soap' + : 'rest'); var selected = proto === wizardProtocol; card.classList.toggle('selected', selected); card.setAttribute('aria-checked', selected ? 'true' : 'false'); @@ -2091,6 +2128,19 @@ function prefillWizardFromEdit(detail, versionDocument) { 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'); } setValue('tool-name', detail.name); @@ -2123,6 +2173,15 @@ function bindWizardLiveActions() { 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(); @@ -2130,6 +2189,10 @@ function bindWizardLiveActions() { 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); @@ -2290,6 +2353,44 @@ async function uploadPendingGrpcArtifacts(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')); @@ -2322,6 +2423,7 @@ async function persistCurrentDraft(stayOnPage) { } await uploadPendingGrpcArtifacts(payload); + await uploadPendingSoapArtifacts(payload); await refreshCurrentOperationState(); if (stayOnPage) { @@ -2487,6 +2589,38 @@ function handleDescriptorSetSelection(event) { 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'); @@ -2574,3 +2708,111 @@ async function uploadDescriptorSetAndDiscover() { 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')); +} diff --git a/docs/protocols/soap.md b/docs/protocols/soap.md index b5762ba..560a536 100644 --- a/docs/protocols/soap.md +++ b/docs/protocols/soap.md @@ -118,6 +118,15 @@ Foundation routes: - `POST /operations/{operation_id}/descriptors/xsd` - `GET /operations/{operation_id}/soap/services` +Current UI foundation: + +- protocol selection card on wizard step 1; +- dedicated SOAP step 3 panel; +- WSDL upload; +- optional XSD upload; +- backend-driven inspection of `service -> port -> operation`; +- applying selected binding metadata into the operation draft target. + ## 8. Поведение runtime При выполнении SOAP operation runtime должен: