(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,
};
}());