feat: добавлена пометка типа операции (Build/Push) в истории сборок Dockerfile

- Добавлена колонка 'Тип' во все таблицы истории сборок
- Для push операций отображается registry вместо платформ
- Сохранение пользователя при создании push лога
- Исправлена ошибка с logger в push_docker_image endpoint
- Улучшено отображение истории сборок с визуальными индикаторами
This commit is contained in:
Сергей Антропов
2026-02-15 22:59:02 +03:00
parent 23e1a6037b
commit 1fbf9185a2
232 changed files with 38075 additions and 5 deletions

190
app/static/js/editor.js Normal file
View 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
};