- Добавлена колонка 'Тип' во все таблицы истории сборок - Для push операций отображается registry вместо платформ - Сохранение пользователя при создании push лога - Исправлена ошибка с logger в push_docker_image endpoint - Улучшено отображение истории сборок с визуальными индикаторами
191 lines
6.2 KiB
JavaScript
191 lines
6.2 KiB
JavaScript
/**
|
||
* Редактор кода с подсветкой синтаксиса
|
||
* Использует CodeMirror 6
|
||
* Автор: Сергей Антропов
|
||
* Сайт: https://devops.org.ru
|
||
*/
|
||
|
||
// Инициализация CodeMirror редактора
|
||
function initCodeEditor(textareaId, language = 'yaml', options = {}) {
|
||
const textarea = document.getElementById(textareaId);
|
||
if (!textarea) {
|
||
console.error(`Textarea with id "${textareaId}" not found`);
|
||
return null;
|
||
}
|
||
|
||
// Проверяем, что CodeMirror загружен
|
||
if (typeof CodeMirror === 'undefined') {
|
||
console.error('CodeMirror is not loaded. Please include CodeMirror library.');
|
||
return null;
|
||
}
|
||
|
||
const defaultOptions = {
|
||
lineNumbers: true,
|
||
mode: language,
|
||
theme: 'default',
|
||
indentUnit: 2,
|
||
indentWithTabs: false,
|
||
lineWrapping: true,
|
||
matchBrackets: true,
|
||
autoCloseBrackets: true,
|
||
foldGutter: true,
|
||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||
...options
|
||
};
|
||
|
||
const editor = CodeMirror.fromTextArea(textarea, defaultOptions);
|
||
|
||
// Сохраняем значение обратно в textarea при изменении
|
||
editor.on('change', function(cm) {
|
||
cm.save();
|
||
});
|
||
|
||
return editor;
|
||
}
|
||
|
||
// Валидация YAML
|
||
function validateYAML(content) {
|
||
try {
|
||
// Используем js-yaml если доступен, иначе простую проверку
|
||
if (typeof jsyaml !== 'undefined') {
|
||
jsyaml.load(content);
|
||
return { valid: true, errors: [] };
|
||
} else {
|
||
// Простая проверка синтаксиса
|
||
const lines = content.split('\n');
|
||
const errors = [];
|
||
let indentStack = [0];
|
||
|
||
for (let i = 0; i < lines.length; i++) {
|
||
const line = lines[i];
|
||
const trimmed = line.trim();
|
||
|
||
// Пропускаем пустые строки и комментарии
|
||
if (!trimmed || trimmed.startsWith('#')) {
|
||
continue;
|
||
}
|
||
|
||
// Проверка отступов
|
||
const indent = line.length - line.trimStart().length;
|
||
const lastIndent = indentStack[indentStack.length - 1];
|
||
|
||
if (indent > lastIndent + 2) {
|
||
errors.push({
|
||
line: i + 1,
|
||
message: `Неправильный отступ на строке ${i + 1}`
|
||
});
|
||
}
|
||
|
||
// Проверка ключей без значений
|
||
if (trimmed.endsWith(':') && !trimmed.includes('{') && !trimmed.includes('[')) {
|
||
// Это нормально для YAML
|
||
}
|
||
}
|
||
|
||
return { valid: errors.length === 0, errors: errors };
|
||
}
|
||
} catch (e) {
|
||
return {
|
||
valid: false,
|
||
errors: [{
|
||
line: 1,
|
||
message: e.message
|
||
}]
|
||
};
|
||
}
|
||
}
|
||
|
||
// Валидация Ansible playbook
|
||
function validateAnsiblePlaybook(content) {
|
||
const yamlValidation = validateYAML(content);
|
||
if (!yamlValidation.valid) {
|
||
return yamlValidation;
|
||
}
|
||
|
||
try {
|
||
if (typeof jsyaml !== 'undefined') {
|
||
const parsed = jsyaml.load(content);
|
||
|
||
if (!Array.isArray(parsed)) {
|
||
return {
|
||
valid: false,
|
||
errors: [{
|
||
line: 1,
|
||
message: 'Playbook должен быть списком (массивом)'
|
||
}]
|
||
};
|
||
}
|
||
|
||
const errors = [];
|
||
parsed.forEach((play, index) => {
|
||
if (!play.hosts && !play.tasks && !play.roles) {
|
||
errors.push({
|
||
line: index + 1,
|
||
message: `Play ${index + 1}: должен содержать hosts, tasks или roles`
|
||
});
|
||
}
|
||
});
|
||
|
||
return { valid: errors.length === 0, errors: errors };
|
||
}
|
||
} catch (e) {
|
||
return {
|
||
valid: false,
|
||
errors: [{
|
||
line: 1,
|
||
message: e.message
|
||
}]
|
||
};
|
||
}
|
||
|
||
return { valid: true, errors: [] };
|
||
}
|
||
|
||
// Показ ошибок валидации в редакторе
|
||
function showValidationErrors(editor, errors) {
|
||
if (!editor || !editor._validationMarkers) {
|
||
return;
|
||
}
|
||
|
||
// Очищаем предыдущие маркеры
|
||
editor._validationMarkers.forEach(marker => {
|
||
if (marker && typeof marker.clear === 'function') {
|
||
marker.clear();
|
||
} else if (marker && typeof marker.remove === 'function') {
|
||
marker.remove();
|
||
}
|
||
});
|
||
|
||
editor._validationMarkers = [];
|
||
|
||
// Очищаем все gutter маркеры
|
||
editor.clearGutter('CodeMirror-linenumbers');
|
||
|
||
errors.forEach(error => {
|
||
const line = Math.max(0, (error.line || 1) - 1); // CodeMirror использует 0-based индексы
|
||
try {
|
||
// Добавляем класс для подсветки строки с ошибкой
|
||
editor.addLineClass(line, 'background', 'cm-error-line');
|
||
|
||
// Добавляем маркер в gutter
|
||
const marker = document.createElement('span');
|
||
marker.className = 'cm-error-marker';
|
||
marker.textContent = '⚠';
|
||
marker.title = error.message || 'Ошибка';
|
||
editor.setGutterMarker(line, 'CodeMirror-linenumbers', marker);
|
||
|
||
editor._validationMarkers.push({ line: line, marker: marker });
|
||
} catch (e) {
|
||
console.warn('Error adding validation marker:', e);
|
||
}
|
||
});
|
||
}
|
||
|
||
// Экспорт функций
|
||
window.CodeEditor = {
|
||
init: initCodeEditor,
|
||
validateYAML: validateYAML,
|
||
validateAnsible: validateAnsiblePlaybook,
|
||
showErrors: showValidationErrors
|
||
};
|