feat: добавлена пометка типа операции (Build/Push) в истории сборок Dockerfile
- Добавлена колонка 'Тип' во все таблицы истории сборок - Для push операций отображается registry вместо платформ - Сохранение пользователя при создании push лога - Исправлена ошибка с logger в push_docker_image endpoint - Улучшено отображение истории сборок с визуальными индикаторами
This commit is contained in:
190
app/static/js/editor.js
Normal file
190
app/static/js/editor.js
Normal file
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* Редактор кода с подсветкой синтаксиса
|
||||
* Использует 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
|
||||
};
|
||||
Reference in New Issue
Block a user