- Добавлена колонка 'Тип' во все таблицы истории сборок - Для push операций отображается registry вместо платформ - Сохранение пользователя при создании push лога - Исправлена ошибка с logger в push_docker_image endpoint - Улучшено отображение истории сборок с визуальными индикаторами
519 lines
21 KiB
HTML
519 lines
21 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}Редактировать {{ preset.name }} - DevOpsLab{% endblock %}
|
||
{% block page_title %}Редактирование preset'а: {{ preset.name }}{% endblock %}
|
||
|
||
{% block header_actions %}
|
||
<a href="/presets/{{ preset.name }}?category={{ preset.category }}" class="btn btn-secondary btn-sm">
|
||
<i class="fas fa-arrow-left me-2"></i>
|
||
Назад
|
||
</a>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<div x-data="presetEditor()">
|
||
<form
|
||
hx-post="/api/v1/presets/{{ preset.name }}/update?category={{ preset.category }}"
|
||
hx-target="#result"
|
||
hx-swap="innerHTML"
|
||
@submit.prevent="submitForm"
|
||
class="card"
|
||
>
|
||
<!-- Базовая информация -->
|
||
<div class="card-header">
|
||
<h5 class="mb-0">Базовая информация</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="mb-3">
|
||
<label class="form-label">Имя preset'а</label>
|
||
<input
|
||
type="text"
|
||
value="{{ preset.name }}"
|
||
class="form-control"
|
||
readonly
|
||
disabled
|
||
>
|
||
<div class="form-text">Имя preset'а нельзя изменить</div>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label">Описание</label>
|
||
<textarea
|
||
name="description"
|
||
x-model="formData.description"
|
||
rows="2"
|
||
class="form-control"
|
||
placeholder="Описание preset'а..."
|
||
>{{ preset.data.description if preset.data and preset.data.description else '' }}</textarea>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label">Категория</label>
|
||
<input
|
||
type="text"
|
||
value="{{ preset.category }}"
|
||
class="form-control"
|
||
readonly
|
||
disabled
|
||
>
|
||
<div class="form-text">Категорию нельзя изменить</div>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label">Docker Network</label>
|
||
<input
|
||
type="text"
|
||
name="docker_network"
|
||
x-model="formData.docker_network"
|
||
class="form-control"
|
||
placeholder="labnet"
|
||
>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Хосты -->
|
||
<div class="card-header">
|
||
<h5 class="mb-0">Хосты</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="space-y-2 mb-3" x-ref="hostsContainer">
|
||
<template x-for="(host, index) in formData.hosts" :key="index">
|
||
<div class="card mb-2">
|
||
<div class="card-body">
|
||
<div class="row g-3 align-items-end">
|
||
<div class="col-12 col-md-2">
|
||
<label class="form-label small">Имя хоста</label>
|
||
<input
|
||
type="text"
|
||
x-model="host.name"
|
||
placeholder="u1"
|
||
class="form-control"
|
||
required
|
||
>
|
||
</div>
|
||
<div class="col-12 col-md-3">
|
||
<label class="form-label small">Семейство образа</label>
|
||
<select
|
||
x-model="host.family"
|
||
class="form-select"
|
||
>
|
||
<option value="ubuntu20">Ubuntu 20</option>
|
||
<option value="ubuntu22">Ubuntu 22</option>
|
||
<option value="ubuntu24">Ubuntu 24</option>
|
||
<option value="debian9">Debian 9</option>
|
||
<option value="debian10">Debian 10</option>
|
||
<option value="debian11">Debian 11</option>
|
||
<option value="debian12">Debian 12</option>
|
||
<option value="centos7">CentOS 7</option>
|
||
<option value="centos8">CentOS 8</option>
|
||
<option value="centos9">CentOS 9</option>
|
||
<option value="alma">AlmaLinux</option>
|
||
<option value="rocky">Rocky Linux</option>
|
||
<option value="rhel">RHEL</option>
|
||
<option value="redos">RedOS</option>
|
||
<option value="astra">Astra Linux</option>
|
||
<option value="alt9">Alt Linux 9</option>
|
||
<option value="alt10">Alt Linux 10</option>
|
||
</select>
|
||
</div>
|
||
<div class="col-12 col-md-3">
|
||
<label class="form-label small">Группы (через запятую)</label>
|
||
<input
|
||
type="text"
|
||
x-model="host.groups_str"
|
||
placeholder="test, web"
|
||
class="form-control"
|
||
@input="updateHostGroups(index)"
|
||
>
|
||
</div>
|
||
<div class="col-12 col-md-2">
|
||
<label class="form-label small">Тип (опционально)</label>
|
||
<select
|
||
x-model="host.type"
|
||
class="form-select"
|
||
>
|
||
<option value="">Обычный</option>
|
||
<option value="dind">DinD</option>
|
||
<option value="dood">DOoD</option>
|
||
</select>
|
||
</div>
|
||
<div class="col-12 col-md-2">
|
||
<button
|
||
type="button"
|
||
@click="removeHost(index)"
|
||
class="btn btn-danger btn-sm w-100"
|
||
title="Удалить"
|
||
>
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
|
||
<button
|
||
type="button"
|
||
@click="addHost"
|
||
class="btn btn-outline-secondary"
|
||
>
|
||
<i class="fas fa-plus me-2"></i>
|
||
Добавить хост
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Образы -->
|
||
<div class="card-header">
|
||
<h5 class="mb-0">Docker образы</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="mb-3">
|
||
<div class="space-y-2" x-ref="imagesContainer">
|
||
<template x-for="(image, key) in formData.images" :key="key">
|
||
<div class="row g-2 mb-2 align-items-center">
|
||
<div class="col-12 col-md-4">
|
||
<input
|
||
type="text"
|
||
:value="key"
|
||
class="form-control"
|
||
readonly
|
||
disabled
|
||
>
|
||
</div>
|
||
<div class="col-12 col-md-6">
|
||
<input
|
||
type="text"
|
||
x-model="formData.images[key]"
|
||
class="form-control"
|
||
placeholder="inecs/ansible-lab:ubuntu22-latest"
|
||
>
|
||
</div>
|
||
<div class="col-12 col-md-2">
|
||
<button
|
||
type="button"
|
||
@click="removeImage(key)"
|
||
class="btn btn-danger btn-sm w-100"
|
||
title="Удалить"
|
||
>
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row g-2 mb-3">
|
||
<div class="col-12 col-md-4">
|
||
<input
|
||
type="text"
|
||
x-model="newImageKey"
|
||
class="form-control"
|
||
placeholder="Новый ключ (ubuntu25)"
|
||
>
|
||
</div>
|
||
<div class="col-12 col-md-6">
|
||
<input
|
||
type="text"
|
||
x-model="newImageValue"
|
||
class="form-control"
|
||
placeholder="inecs/ansible-lab:ubuntu25-latest"
|
||
>
|
||
</div>
|
||
<div class="col-12 col-md-2">
|
||
<button
|
||
type="button"
|
||
@click="addImage"
|
||
class="btn btn-outline-primary w-100"
|
||
>
|
||
<i class="fas fa-plus"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Systemd Defaults -->
|
||
<div class="card-header">
|
||
<h5 class="mb-0">Systemd Defaults</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="row g-3">
|
||
<div class="col-12 col-md-6">
|
||
<div class="form-check form-switch">
|
||
<input
|
||
class="form-check-input"
|
||
type="checkbox"
|
||
x-model="formData.systemd_defaults.privileged"
|
||
id="privileged"
|
||
>
|
||
<label class="form-check-label" for="privileged">
|
||
Privileged
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="col-12 col-md-6">
|
||
<label class="form-label">Command</label>
|
||
<input
|
||
type="text"
|
||
x-model="formData.systemd_defaults.command"
|
||
class="form-control"
|
||
placeholder="/sbin/init"
|
||
>
|
||
</div>
|
||
<div class="col-12">
|
||
<label class="form-label">Volumes (по одному на строку)</label>
|
||
<textarea
|
||
x-model="formData.systemd_defaults.volumes_str"
|
||
@input="updateVolumes"
|
||
rows="3"
|
||
class="form-control"
|
||
placeholder="/sys/fs/cgroup:/sys/fs/cgroup:rw"
|
||
></textarea>
|
||
</div>
|
||
<div class="col-12">
|
||
<label class="form-label">Tmpfs (через запятую)</label>
|
||
<input
|
||
type="text"
|
||
x-model="formData.systemd_defaults.tmpfs_str"
|
||
@input="updateTmpfs"
|
||
class="form-control"
|
||
placeholder="/run, /run/lock"
|
||
>
|
||
</div>
|
||
<div class="col-12">
|
||
<label class="form-label">Capabilities (через запятую)</label>
|
||
<input
|
||
type="text"
|
||
x-model="formData.systemd_defaults.capabilities_str"
|
||
@input="updateCapabilities"
|
||
class="form-control"
|
||
placeholder="SYS_ADMIN"
|
||
>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{% if preset.category == 'k8s' %}
|
||
<!-- Kind Clusters (только для k8s) -->
|
||
<div class="card-header">
|
||
<h5 class="mb-0">Kind Clusters</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="mb-3">
|
||
<div class="space-y-2" x-ref="clustersContainer">
|
||
<template x-for="(cluster, index) in formData.kind_clusters" :key="index">
|
||
<div class="row g-2 mb-2 align-items-center">
|
||
<div class="col-12 col-md-10">
|
||
<input
|
||
type="text"
|
||
x-model="formData.kind_clusters[index]"
|
||
class="form-control"
|
||
placeholder="cluster-name"
|
||
>
|
||
</div>
|
||
<div class="col-12 col-md-2">
|
||
<button
|
||
type="button"
|
||
@click="removeCluster(index)"
|
||
class="btn btn-danger btn-sm w-100"
|
||
title="Удалить"
|
||
>
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
|
||
<button
|
||
type="button"
|
||
@click="addCluster"
|
||
class="btn btn-outline-secondary"
|
||
>
|
||
<i class="fas fa-plus me-2"></i>
|
||
Добавить кластер
|
||
</button>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Скрытые поля для отправки -->
|
||
<input
|
||
type="hidden"
|
||
name="hosts"
|
||
:value="JSON.stringify(formData.hosts.map(h => ({
|
||
name: h.name,
|
||
family: h.family,
|
||
groups: h.groups,
|
||
type: h.type || undefined,
|
||
supported_platforms: h.supported_platforms || undefined
|
||
})))"
|
||
>
|
||
<input
|
||
type="hidden"
|
||
name="images"
|
||
:value="JSON.stringify(formData.images)"
|
||
>
|
||
<input
|
||
type="hidden"
|
||
name="systemd_defaults"
|
||
:value="JSON.stringify({
|
||
privileged: formData.systemd_defaults.privileged,
|
||
command: formData.systemd_defaults.command,
|
||
volumes: formData.systemd_defaults.volumes,
|
||
tmpfs: formData.systemd_defaults.tmpfs,
|
||
capabilities: formData.systemd_defaults.capabilities
|
||
})"
|
||
>
|
||
{% if preset.category == 'k8s' %}
|
||
<input
|
||
type="hidden"
|
||
name="kind_clusters"
|
||
:value="JSON.stringify(formData.kind_clusters)"
|
||
>
|
||
{% endif %}
|
||
|
||
<!-- Результат -->
|
||
<div id="result" class="card-body border-top"></div>
|
||
|
||
<!-- Кнопки -->
|
||
<div class="card-footer">
|
||
<div class="d-flex gap-2">
|
||
<button type="submit" class="btn btn-primary">
|
||
<i class="fas fa-save me-2"></i>
|
||
Сохранить изменения
|
||
</button>
|
||
<a href="/presets/{{ preset.name }}?category={{ preset.category }}" class="btn btn-secondary">
|
||
<i class="fas fa-times me-2"></i>
|
||
Отмена
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<script>
|
||
function presetEditor() {
|
||
// Парсинг данных preset'а из шаблона
|
||
const presetData = {% if preset.data %}{{ preset.data | tojson }}{% else %}{}{% endif %};
|
||
|
||
// Инициализация хостов
|
||
const hosts = presetData.hosts || [];
|
||
const formattedHosts = hosts.map(host => ({
|
||
name: host.name || '',
|
||
family: host.family || 'ubuntu22',
|
||
groups: host.groups || [],
|
||
groups_str: Array.isArray(host.groups) ? host.groups.join(', ') : (host.groups || ''),
|
||
type: host.type || '',
|
||
supported_platforms: host.supported_platforms || []
|
||
}));
|
||
|
||
// Инициализация образов
|
||
const images = presetData.images || {};
|
||
|
||
// Инициализация systemd defaults
|
||
const systemdDefaults = presetData.systemd_defaults || {};
|
||
const volumes = systemdDefaults.volumes || [];
|
||
const tmpfs = systemdDefaults.tmpfs || [];
|
||
const capabilities = systemdDefaults.capabilities || [];
|
||
|
||
// Извлечение описания из комментария в content
|
||
let description = '';
|
||
{% if preset.content %}
|
||
const contentLines = {{ preset.content | tojson }}.split('\n');
|
||
for (const line of contentLines) {
|
||
if (line.trim().startsWith('#description:')) {
|
||
description = line.split('#description:')[1].trim();
|
||
break;
|
||
}
|
||
}
|
||
{% endif %}
|
||
|
||
return {
|
||
formData: {
|
||
description: description,
|
||
docker_network: presetData.docker_network || 'labnet',
|
||
hosts: formattedHosts.length > 0 ? formattedHosts : [{
|
||
name: 'u1',
|
||
family: 'ubuntu22',
|
||
groups: ['test'],
|
||
groups_str: 'test',
|
||
type: ''
|
||
}],
|
||
images: images,
|
||
systemd_defaults: {
|
||
privileged: systemdDefaults.privileged !== undefined ? systemdDefaults.privileged : true,
|
||
command: systemdDefaults.command || '/sbin/init',
|
||
volumes: volumes,
|
||
volumes_str: volumes.join('\n'),
|
||
tmpfs: tmpfs,
|
||
tmpfs_str: tmpfs.join(', '),
|
||
capabilities: capabilities,
|
||
capabilities_str: capabilities.join(', ')
|
||
},
|
||
kind_clusters: presetData.kind_clusters || []
|
||
},
|
||
newImageKey: '',
|
||
newImageValue: '',
|
||
addHost() {
|
||
this.formData.hosts.push({
|
||
name: `u${this.formData.hosts.length + 1}`,
|
||
family: 'ubuntu22',
|
||
groups: ['test'],
|
||
groups_str: 'test',
|
||
type: ''
|
||
});
|
||
},
|
||
removeHost(index) {
|
||
this.formData.hosts.splice(index, 1);
|
||
},
|
||
updateHostGroups(index) {
|
||
const host = this.formData.hosts[index];
|
||
host.groups = host.groups_str.split(',').map(g => g.trim()).filter(g => g);
|
||
},
|
||
addImage() {
|
||
if (this.newImageKey && this.newImageValue) {
|
||
this.formData.images[this.newImageKey] = this.newImageValue;
|
||
this.newImageKey = '';
|
||
this.newImageValue = '';
|
||
}
|
||
},
|
||
removeImage(key) {
|
||
delete this.formData.images[key];
|
||
},
|
||
updateVolumes() {
|
||
this.formData.systemd_defaults.volumes =
|
||
this.formData.systemd_defaults.volumes_str
|
||
.split('\n')
|
||
.map(v => v.trim())
|
||
.filter(v => v);
|
||
},
|
||
updateTmpfs() {
|
||
this.formData.systemd_defaults.tmpfs =
|
||
this.formData.systemd_defaults.tmpfs_str
|
||
.split(',')
|
||
.map(t => t.trim())
|
||
.filter(t => t);
|
||
},
|
||
updateCapabilities() {
|
||
this.formData.systemd_defaults.capabilities =
|
||
this.formData.systemd_defaults.capabilities_str
|
||
.split(',')
|
||
.map(c => c.trim())
|
||
.filter(c => c);
|
||
},
|
||
addCluster() {
|
||
this.formData.kind_clusters.push('');
|
||
},
|
||
removeCluster(index) {
|
||
this.formData.kind_clusters.splice(index, 1);
|
||
},
|
||
submitForm(event) {
|
||
// HTMX обработает отправку
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
{% endblock %}
|