feat: add soap wizard foundation
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<!-- Protocol selection cards -->
|
||||
<div class="protocol-grid">
|
||||
|
||||
<div class="protocol-card rest selected" role="radio" aria-checked="true" tabindex="0">
|
||||
<div class="protocol-card rest selected" data-protocol="rest" role="radio" aria-checked="true" tabindex="0">
|
||||
<div class="protocol-card-check">
|
||||
<svg viewBox="0 0 12 12"><polyline points="1.5,6 4.5,9 10.5,3"/></svg>
|
||||
</div>
|
||||
@@ -34,7 +34,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="protocol-card graphql" role="radio" aria-checked="false" tabindex="0">
|
||||
<div class="protocol-card graphql" data-protocol="graphql" role="radio" aria-checked="false" tabindex="0">
|
||||
<div class="protocol-card-check">
|
||||
<svg viewBox="0 0 12 12"><polyline points="1.5,6 4.5,9 10.5,3"/></svg>
|
||||
</div>
|
||||
@@ -57,7 +57,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="protocol-card grpc" role="radio" aria-checked="false" tabindex="0">
|
||||
<div class="protocol-card grpc" data-protocol="grpc" role="radio" aria-checked="false" tabindex="0">
|
||||
<div class="protocol-card-check">
|
||||
<svg viewBox="0 0 12 12"><polyline points="1.5,6 4.5,9 10.5,3"/></svg>
|
||||
</div>
|
||||
@@ -76,6 +76,26 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="protocol-card soap" data-protocol="soap" role="radio" aria-checked="false" tabindex="0">
|
||||
<div class="protocol-card-check">
|
||||
<svg viewBox="0 0 12 12"><polyline points="1.5,6 4.5,9 10.5,3"/></svg>
|
||||
</div>
|
||||
<div class="protocol-icon-wrap">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#f59e0b" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 5h16v14H4z"/>
|
||||
<path d="M8 5V3h8v2"/>
|
||||
<path d="M7 10h10M7 14h6"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="protocol-card-name" data-i18n="wizard.step1.soap_name">SOAP</div>
|
||||
<div class="protocol-card-tagline" data-i18n="wizard.step1.soap_tagline">Enterprise WSDL/XSD service contracts normalized into MCP tools with structured XML mapping.</div>
|
||||
<div class="protocol-card-tags">
|
||||
<span class="protocol-tag">WSDL</span>
|
||||
<span class="protocol-tag">XML</span>
|
||||
<span class="protocol-tag" data-i18n="wizard.step1.unary">Unary</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /protocol-grid -->
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
<div id="step-panel-3-soap" class="step-pane">
|
||||
<div class="step-panel-header">
|
||||
<div class="step-panel-eyebrow">
|
||||
<div class="step-panel-eyebrow-line"></div>
|
||||
<span data-i18n-html="wizard.step_of">Step 3 of 5</span>
|
||||
</div>
|
||||
<h1 class="step-panel-title" data-i18n="wizard.step3.soap.title">WSDL and SOAP binding</h1>
|
||||
<p class="step-panel-subtitle" data-i18n="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.</p>
|
||||
</div>
|
||||
|
||||
<div class="config-card" style="margin-bottom: 20px;">
|
||||
<div class="config-card-header">
|
||||
<div class="config-card-header-icon">
|
||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="var(--text-secondary)" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3.5 2.5h6l3 3v8a1 1 0 0 1-1 1h-8a1 1 0 0 1-1-1v-10a1 1 0 0 1 1-1z"/>
|
||||
<path d="M9.5 2.5v3h3"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="config-card-title" data-i18n="wizard.step3.soap.wsdl_title">Contract files</div>
|
||||
<div class="config-card-subtitle" data-i18n="wizard.step3.soap.wsdl_subtitle">WSDL is required. Supporting XSD files are optional and can be uploaded before discovery.</div>
|
||||
</div>
|
||||
<span class="config-card-optional" style="color:var(--error,#f87171);" data-i18n="wizard.required">Required</span>
|
||||
</div>
|
||||
<div class="config-card-body" style="gap: 16px;">
|
||||
<div class="form-row">
|
||||
<input id="wizard-soap-wsdl-input" type="file" accept=".wsdl,.xml,text/xml,application/wsdl+xml" style="display:none">
|
||||
<button id="wizard-select-soap-wsdl" class="btn-ghost-sm" type="button" data-i18n="wizard.step3.soap.choose_wsdl">Choose WSDL</button>
|
||||
<span id="wizard-soap-wsdl-name" style="color: var(--text-muted); font-size: 12px;" data-i18n="wizard.step3.soap.no_wsdl">No WSDL selected</span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<input id="wizard-soap-xsd-input" type="file" accept=".xsd,.xml,text/xml,application/xml" style="display:none">
|
||||
<button id="wizard-select-soap-xsd" class="btn-ghost-sm" type="button" data-i18n="wizard.step3.soap.choose_xsd">Choose XSD</button>
|
||||
<span id="wizard-soap-xsd-name" style="color: var(--text-muted); font-size: 12px;" data-i18n="wizard.step3.soap.no_xsd">No XSD selected</span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<button id="wizard-upload-soap-contracts" class="btn-primary-sm" type="button" data-i18n="wizard.step3.soap.upload_discover">Upload and inspect services</button>
|
||||
</div>
|
||||
<div class="form-hint" id="wizard-soap-services-summary"></div>
|
||||
<div id="wizard-soap-services-list" style="display:grid; gap: 10px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="config-card" style="margin-bottom: 20px;">
|
||||
<div class="config-card-header">
|
||||
<div class="config-card-header-icon">
|
||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="var(--text-secondary)" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M2.5 5.5h11M5.5 2.5v3M10.5 2.5v3M4 8.5h8v5H4z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="config-card-title" data-i18n="wizard.step3.soap.binding_title">Selected binding</div>
|
||||
<div class="config-card-subtitle" data-i18n="wizard.step3.soap.binding_subtitle">The chosen service, port and operation become the target binding for this operation draft.</div>
|
||||
</div>
|
||||
<span class="config-card-optional" data-i18n="wizard.optional">Optional</span>
|
||||
</div>
|
||||
<div class="config-card-body">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label" data-i18n="wizard.step3.soap.service_name">Service</label>
|
||||
<input id="soap-service-name" class="form-input input-mono" type="text" placeholder="LeadService">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" data-i18n="wizard.step3.soap.port_name">Port</label>
|
||||
<input id="soap-port-name" class="form-input input-mono" type="text" placeholder="LeadPort">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label" data-i18n="wizard.step3.soap.operation_name">Operation</label>
|
||||
<input id="soap-operation-name" class="form-input input-mono" type="text" placeholder="CreateLead">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" data-i18n="wizard.step3.soap.version">SOAP version</label>
|
||||
<select id="soap-version" class="form-select">
|
||||
<option value="soap_11">SOAP 1.1</option>
|
||||
<option value="soap_12">SOAP 1.2</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label" data-i18n="wizard.step3.soap.action">SOAPAction</label>
|
||||
<input id="soap-action" class="form-input input-mono" type="text" placeholder="urn:createLead">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" data-i18n="wizard.step3.soap.binding_style">Binding style</label>
|
||||
<select id="soap-binding-style" class="form-select">
|
||||
<option value="document_literal" data-i18n="wizard.step3.soap.binding.document_literal">Document literal</option>
|
||||
<option value="rpc_literal" data-i18n="wizard.step3.soap.binding.rpc_literal">RPC literal</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" data-i18n="wizard.step3.soap.endpoint_override">Endpoint override</label>
|
||||
<input id="soap-endpoint-override" class="form-input input-mono" type="text" placeholder="https://soap.example.com/lead">
|
||||
<div class="form-hint" data-i18n="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.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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 || {});
|
||||
},
|
||||
|
||||
@@ -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 сохранен',
|
||||
|
||||
+243
-1
@@ -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 = '<svg width="10" height="10"><use href="' + (window.APP_BASE||'') + 'icons/general/check.svg#icon"/></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'));
|
||||
}
|
||||
|
||||
@@ -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 должен:
|
||||
|
||||
Reference in New Issue
Block a user