(function() { var state = { workspaceId: null, onImported: null, jobId: null, preview: null, filterQuery: '', filterMethod: '', }; function qs(id) { return document.getElementById(id); } function setStatus(message, isError) { var node = qs('openapi-import-status'); if (!node) return; node.textContent = message || ''; node.classList.toggle('error', !!isError); } function selectedKeys() { return Array.from(document.querySelectorAll('[data-openapi-operation]:checked')) .map(function(input) { return input.value; }); } function operationRows() { return Array.from(document.querySelectorAll('.openapi-import-operation')); } function visibleOperationRows() { return operationRows().filter(function(row) { return !row.hidden; }); } function updateSelection() { var node = qs('openapi-import-selection'); var selectedCount = selectedKeys().length; var totalCount = document.querySelectorAll('[data-openapi-operation]').length; var visibleCount = visibleOperationRows().length; if (node) node.textContent = 'Выбрано: ' + selectedCount + ' из ' + totalCount + ', показано: ' + visibleCount; updateGroupSelectionState(); } function updateGroupSelectionState() { document.querySelectorAll('.openapi-import-group').forEach(function(groupNode) { var rows = Array.from(groupNode.querySelectorAll('.openapi-import-operation')); var visibleRows = rows.filter(function(row) { return !row.hidden; }); var visibleInputs = visibleRows .map(function(row) { return row.querySelector('[data-openapi-operation]'); }) .filter(Boolean); var selectedVisible = visibleInputs.filter(function(input) { return input.checked; }).length; var selectedTotal = rows .map(function(row) { return row.querySelector('[data-openapi-operation]'); }) .filter(function(input) { return input && input.checked; }).length; var checkbox = groupNode.querySelector('[data-openapi-group]'); if (checkbox) { checkbox.indeterminate = selectedVisible > 0 && selectedVisible < visibleInputs.length; checkbox.checked = visibleInputs.length > 0 && selectedVisible === visibleInputs.length; } var counter = groupNode.querySelector('[data-openapi-group-count]'); if (counter) { counter.textContent = 'выбрано ' + selectedTotal + ' из ' + rows.length + ', показано ' + visibleRows.length; } }); } function applyFilters() { var query = state.filterQuery.toLowerCase(); var method = state.filterMethod.toLowerCase(); document.querySelectorAll('.openapi-import-group').forEach(function(groupNode) { var visibleInGroup = 0; groupNode.querySelectorAll('.openapi-import-operation').forEach(function(row) { var rowMethod = String(row.dataset.openapiMethod || '').toLowerCase(); var searchText = String(row.dataset.openapiSearch || '').toLowerCase(); var visible = (!method || rowMethod === method) && (!query || searchText.indexOf(query) >= 0); row.hidden = !visible; if (visible) visibleInGroup += 1; }); groupNode.hidden = visibleInGroup === 0; }); updateSelection(); } function setVisibleSelection(checked) { visibleOperationRows().forEach(function(row) { var input = row.querySelector('[data-openapi-operation]'); if (input) input.checked = checked; }); updateSelection(); } function renderPreview(response) { state.jobId = response.job_id; state.preview = response.preview; state.filterQuery = ''; state.filterMethod = ''; var preview = response.preview; qs('openapi-import-preview-panel').hidden = false; qs('openapi-import-result').hidden = true; qs('openapi-import-result').innerHTML = ''; qs('openapi-import-source').textContent = [ preview.source.title || 'Imported API', preview.source.format + (preview.source.version ? ' ' + preview.source.version : ''), 'методов: ' + preview.groups.reduce(function(sum, group) { return sum + group.operations.length; }, 0), ].join(' · '); var serverSelect = qs('openapi-import-server'); serverSelect.innerHTML = ''; var servers = preview.source.servers && preview.source.servers.length ? preview.source.servers : ['']; servers.forEach(function(server) { var option = document.createElement('option'); option.value = server; option.textContent = server || 'Указать позже'; serverSelect.appendChild(option); }); var searchInput = qs('openapi-import-search'); var methodFilter = qs('openapi-import-method-filter'); if (searchInput) searchInput.value = ''; if (methodFilter) { methodFilter.innerHTML = ''; Array.from(new Set(preview.groups.flatMap(function(group) { return group.operations.map(function(operation) { return operation.method; }); }))).sort().forEach(function(method) { var option = document.createElement('option'); option.value = method; option.textContent = method; methodFilter.appendChild(option); }); } var groupsNode = qs('openapi-import-groups'); groupsNode.innerHTML = ''; if (preview.findings && preview.findings.length) { var documentFindings = document.createElement('div'); documentFindings.className = 'openapi-import-document-findings'; documentFindings.innerHTML = preview.findings.map(function(finding) { return renderFinding(finding); }).join(''); groupsNode.appendChild(documentFindings); } preview.groups.forEach(function(group) { var groupNode = document.createElement('section'); groupNode.className = 'openapi-import-group'; var header = document.createElement('div'); header.className = 'openapi-import-group-header'; header.innerHTML = '' + escapeHtml(group.title) + ' · ' + group.operations.length + '' + '' + ''; groupNode.appendChild(header); group.operations.forEach(function(operation) { var row = document.createElement('label'); row.className = 'openapi-import-operation'; row.dataset.openapiMethod = operation.method; row.dataset.openapiSearch = [ group.title, operation.key, operation.suggested_name, operation.suggested_display_name, operation.path, operation.method, ].join(' '); row.innerHTML = [ '', '
' + escapeHtml(operation.suggested_display_name) + '
', '
' + escapeHtml(operation.suggested_name) + ' · ' + escapeHtml(operation.path) + ' · входов: ' + operation.input_fields + ' · выходов: ' + operation.output_fields + '
', '' + escapeHtml(operation.method) + '', ].join(''); row.appendChild(renderOperationMappingPreview(operation)); if (operation.findings && operation.findings.length) { var findings = document.createElement('div'); findings.className = 'openapi-import-findings'; findings.innerHTML = operation.findings.map(function(finding) { return renderFinding(finding); }).join(''); row.appendChild(findings); } groupNode.appendChild(row); }); var groupCheckbox = groupNode.querySelector('[data-openapi-group]'); groupCheckbox.addEventListener('change', function() { groupNode.querySelectorAll('.openapi-import-operation').forEach(function(row) { if (row.hidden) return; var input = row.querySelector('[data-openapi-operation]'); if (input) input.checked = groupCheckbox.checked; }); updateSelection(); }); groupsNode.appendChild(groupNode); }); groupsNode.querySelectorAll('[data-openapi-operation]').forEach(function(input) { input.addEventListener('change', updateSelection); }); applyFilters(); } function renderOperationMappingPreview(operation) { var details = document.createElement('div'); details.className = 'openapi-import-mapping-preview'; var request = summarizeRequestMapping(operation); [ ['Path', request.path], ['Query', request.query], ['Header', request.headers], ['Body', request.body], ].forEach(function(entry) { appendMappingGroup(details, entry[0], entry[1]); }); appendMappingGroup(details, 'Ответ', summarizeResponseMapping(operation)); return details; } function summarizeRequestMapping(operation) { var result = { path: [], query: [], headers: [], body: [], }; var rules = operation && operation.draft && operation.draft.input_mapping && Array.isArray(operation.draft.input_mapping.rules) ? operation.draft.input_mapping.rules : []; rules.forEach(function(rule) { var target = String(rule.target || ''); if (target.indexOf('$.request.path.') === 0) { result.path.push(target.replace('$.request.path.', '')); } else if (target.indexOf('$.request.query.') === 0) { result.query.push(target.replace('$.request.query.', '')); } else if (target.indexOf('$.request.headers.') === 0) { result.headers.push(target.replace('$.request.headers.', '')); } else if (target.indexOf('$.request.body.') === 0) { result.body.push(target.replace('$.request.body.', '')); } else if (target === '$.request.body') { result.body.push('body'); } }); return result; } function summarizeResponseMapping(operation) { var rules = operation && operation.draft && operation.draft.output_mapping && Array.isArray(operation.draft.output_mapping.rules) ? operation.draft.output_mapping.rules : []; return rules.map(function(rule) { var target = String(rule.target || '').replace('$.output.', '').replace('$.output', 'result'); var source = String(rule.source || '').replace('$.response.body.', '').replace('$.response.body', 'body'); return target + ' ← ' + source; }); } function appendMappingGroup(root, label, values) { if (!values || !values.length) return; var group = document.createElement('div'); group.className = 'openapi-import-mapping-group'; var title = document.createElement('span'); title.className = 'openapi-import-mapping-label'; title.textContent = label; group.appendChild(title); values.slice(0, 8).forEach(function(value) { var chip = document.createElement('code'); chip.className = 'openapi-import-mapping-chip'; chip.textContent = value; group.appendChild(chip); }); if (values.length > 8) { var more = document.createElement('span'); more.className = 'openapi-import-mapping-more'; more.textContent = '+' + (values.length - 8); group.appendChild(more); } root.appendChild(group); } function escapeHtml(value) { return String(value || '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } function renderFinding(finding) { var severity = String(finding && finding.severity || 'warning').toLowerCase(); var label = severity === 'info' ? 'i' : severity === 'error' ? '×' : '!'; return '' + '' + label + ' ' + escapeHtml(finding.message) + ''; } function previewOperationByName(name) { if (!state.preview || !state.preview.groups) return null; for (var groupIndex = 0; groupIndex < state.preview.groups.length; groupIndex += 1) { var operations = state.preview.groups[groupIndex].operations || []; for (var operationIndex = 0; operationIndex < operations.length; operationIndex += 1) { if (operations[operationIndex].suggested_name === name) { return operations[operationIndex]; } } } return null; } function wizardHref(operationId) { return ((window.CrankRoutes && window.CrankRoutes.wizard) || '/wizard/') + '?mode=edit&operationId=' + encodeURIComponent(operationId); } function appendFindingNodes(root, findings) { if (!findings || !findings.length) { var empty = document.createElement('span'); empty.className = 'openapi-import-result-ok'; empty.textContent = 'Критичных замечаний нет'; root.appendChild(empty); return; } findings.slice(0, 4).forEach(function(finding) { var wrapper = document.createElement('div'); wrapper.innerHTML = renderFinding(finding); root.appendChild(wrapper.firstElementChild); }); if (findings.length > 4) { var more = document.createElement('span'); more.className = 'openapi-import-result-more'; more.textContent = '+' + (findings.length - 4) + ' еще'; root.appendChild(more); } } async function preview() { if (!state.workspaceId) { setStatus('Не выбран workspace.', true); return; } var documentText = qs('openapi-import-document').value.trim(); if (!documentText) { setStatus('Вставьте документ OpenAPI/Swagger или выберите файл.', true); return; } setStatus('Разбираю документ...'); qs('openapi-import-preview').disabled = true; try { var response = await window.CrankApi.previewOpenApiImport(state.workspaceId, documentText); renderPreview(response); setStatus('Документ разобран. Выберите методы для импорта.'); } catch (error) { setStatus(error.message || 'Не удалось разобрать документ.', true); } finally { qs('openapi-import-preview').disabled = false; } } async function createDrafts() { var keys = selectedKeys(); if (!keys.length) { setStatus('Выберите хотя бы один метод.', true); return; } if (!state.jobId) { setStatus('Сначала разберите OpenAPI документ.', true); return; } var serverUrl = (qs('openapi-import-server-custom').value || qs('openapi-import-server').value || '').trim(); if (!serverUrl) { setStatus('Укажите base URL для создаваемых операций.', true); return; } setStatus('Создаю черновики...'); qs('openapi-import-create').disabled = true; try { var response = await window.CrankApi.createOpenApiImport(state.workspaceId, state.jobId, { selected_operation_keys: keys, server_url: serverUrl, conflict_mode: qs('openapi-import-conflict-mode').value || 'rename', }); var message = 'Создано: ' + response.created.length; if (response.skipped.length) message += ', пропущено: ' + response.skipped.length; setStatus(message); renderResult(response); if (typeof state.onImported === 'function') { await state.onImported(); } } catch (error) { setStatus(error.message || 'Не удалось создать черновики.', true); } finally { qs('openapi-import-create').disabled = false; } } function renderResult(response) { var node = qs('openapi-import-result'); if (!node) return; node.replaceChildren(); var title = document.createElement('strong'); title.textContent = 'Результат импорта'; node.appendChild(title); if (response.created && response.created.length) { var firstHref = wizardHref(response.created[0].operation_id); var primary = document.createElement('div'); primary.className = 'openapi-import-primary-result'; primary.innerHTML = 'Открыть первый черновик в мастере'; node.appendChild(primary); var table = document.createElement('div'); table.className = 'openapi-import-result-table'; table.setAttribute('role', 'table'); table.innerHTML = [ '
', '
Черновик
', '
Замечания
', '
Действие
', '
', ].join(''); response.created.forEach(function(operation) { var previewOperation = previewOperationByName(operation.name); var row = document.createElement('div'); row.className = 'openapi-import-result-row'; row.setAttribute('role', 'row'); var nameCell = document.createElement('div'); nameCell.setAttribute('role', 'cell'); var name = document.createElement('div'); name.className = 'openapi-import-result-name'; name.textContent = operation.name; var meta = document.createElement('div'); meta.className = 'openapi-import-result-meta'; meta.textContent = previewOperation ? previewOperation.method + ' ' + previewOperation.path + ' · v' + operation.version : 'v' + operation.version; nameCell.appendChild(name); nameCell.appendChild(meta); var findingsCell = document.createElement('div'); findingsCell.className = 'openapi-import-result-findings'; findingsCell.setAttribute('role', 'cell'); appendFindingNodes(findingsCell, previewOperation ? previewOperation.findings : []); var actionCell = document.createElement('div'); actionCell.setAttribute('role', 'cell'); var action = document.createElement('a'); action.className = 'btn-secondary openapi-import-result-action'; action.href = wizardHref(operation.operation_id); action.textContent = previewOperation && previewOperation.findings && previewOperation.findings.length ? 'Исправить в мастере' : 'Открыть в мастере'; actionCell.appendChild(action); row.appendChild(nameCell); row.appendChild(findingsCell); row.appendChild(actionCell); table.appendChild(row); }); node.appendChild(table); } if (response.skipped && response.skipped.length) { var skipped = document.createElement('div'); skipped.className = 'openapi-import-result-skipped'; skipped.textContent = 'Пропущено: ' + response.skipped.map(function(item) { return (item.name || item.operation_key) + ' — ' + item.reason; }).join('; '); node.appendChild(skipped); } if (response.findings && response.findings.length) { var responseFindings = document.createElement('div'); responseFindings.className = 'openapi-import-result-findings'; response.findings.forEach(function(finding) { var wrapper = document.createElement('div'); wrapper.innerHTML = renderFinding(finding); responseFindings.appendChild(wrapper.firstElementChild); }); node.appendChild(responseFindings); } node.hidden = false; } function reset() { state.jobId = null; state.preview = null; state.filterQuery = ''; state.filterMethod = ''; qs('openapi-import-document').value = ''; qs('openapi-import-file').value = ''; qs('openapi-import-file-name').textContent = 'Файл не выбран'; qs('openapi-import-server-custom').value = ''; qs('openapi-import-conflict-mode').value = 'rename'; if (qs('openapi-import-search')) qs('openapi-import-search').value = ''; if (qs('openapi-import-method-filter')) qs('openapi-import-method-filter').innerHTML = ''; qs('openapi-import-preview-panel').hidden = true; qs('openapi-import-groups').innerHTML = ''; qs('openapi-import-result').hidden = true; qs('openapi-import-result').innerHTML = ''; setStatus(''); updateSelection(); } function open(options) { state.workspaceId = options.workspaceId; state.onImported = options.onImported; qs('openapi-import-modal').hidden = false; } function close() { qs('openapi-import-modal').hidden = true; } document.addEventListener('DOMContentLoaded', function() { if (!qs('openapi-import-modal')) return; qs('openapi-import-preview').addEventListener('click', preview); qs('openapi-import-create').addEventListener('click', createDrafts); qs('openapi-import-reset').addEventListener('click', reset); qs('openapi-import-search').addEventListener('input', function(event) { state.filterQuery = event.target.value.trim(); applyFilters(); }); qs('openapi-import-method-filter').addEventListener('change', function(event) { state.filterMethod = event.target.value; applyFilters(); }); qs('openapi-import-select-visible').addEventListener('click', function() { setVisibleSelection(true); }); qs('openapi-import-clear-visible').addEventListener('click', function() { setVisibleSelection(false); }); qs('openapi-import-file').addEventListener('change', async function(event) { var file = event.target.files && event.target.files[0]; if (!file) return; qs('openapi-import-file-name').textContent = file.name; qs('openapi-import-document').value = await file.text(); }); document.querySelectorAll('[data-openapi-close]').forEach(function(node) { node.addEventListener('click', close); }); }); window.CrankOpenApiImport = { open: open, close: close, reset: reset, }; }());