Выбор образа при создании машины карточками: название системы, размер, дата загрузки и поиск, когда образов много

Этот коммит содержится в:
2026-09-27 22:42:23 +09:00
родитель c04e5285e7
Коммит c19d8db6e7
10 изменённых файлов: 117 добавлений и 21 удалений
+19
Просмотреть файл
@@ -333,6 +333,25 @@ hr { border: 0; border-top: 1px solid var(--border); margin: 20px 0; }
.option input:checked + .option-body .option-check { border: 5px solid var(--brand); }
.option-title { font-weight: 650; font-size: 14px; }
.option-text { color: var(--text-2); font-size: 12.5px; }
/* Выбор образа в форме создания машины */
.image-picker { display: grid; gap: 10px; }
.image-list { display: grid; gap: 8px; max-height: 360px; overflow-y: auto; padding: 2px; }
.image-option { position: relative; display: block; cursor: pointer; }
.image-option[hidden] { display: none; }
.image-option input { position: absolute; opacity: 0; pointer-events: none; }
.image-option-body { display: flex; align-items: center; gap: 12px; padding: 10px 14px; border: 1.5px solid var(--border); border-radius: var(--radius); background: var(--surface); transition: border-color .15s, background .15s; }
.image-option:hover .image-option-body { border-color: var(--border-strong); }
.image-option input:checked + .image-option-body { border-color: var(--brand); background: color-mix(in srgb, var(--brand-soft) 55%, var(--surface)); box-shadow: 0 0 0 4px var(--focus); }
.image-option input:focus-visible + .image-option-body { box-shadow: 0 0 0 4px var(--focus); }
.image-option input:checked + .image-option-body .option-check { border: 5px solid var(--brand); }
.image-option .grow { flex: 1; display: grid; gap: 2px; min-width: 0; }
.image-title { font-weight: 650; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.image-meta { color: var(--text-3); font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 560px) {
.image-option .list-icon { display: none; }
.image-title, .image-meta { white-space: normal; overflow-wrap: anywhere; }
}
/* Расписание копий: день недели и список машин видны, только когда нужны (без :has — видны всегда). */
.schedule-form:has(input[name=frequency][value=daily]:checked) [data-when=weekly],
.schedule-form:has(input[name=machines][value=all]:checked) [data-when=chosen] { display: none; }
+16 -8
Просмотреть файл
@@ -435,7 +435,8 @@
const field = (name) => form.elements[name];
const value = (name) => { const el = field(name); if (!el) return ''; if (el instanceof RadioNodeList) return el.value; return el.value; };
const setSummary = (key, text) => $$(`[data-summary="${key}"]`).forEach((el) => { el.textContent = text || '—'; });
const optionText = (select) => select && select.selectedIndex >= 0 ? select.options[select.selectedIndex].dataset.label || select.options[select.selectedIndex].text : '';
// Образы выбираются карточками (радиокнопки): берём отмеченную.
const picked = (name) => $(`input[name="${name}"]:checked`, form);
function sync() {
const source = value('source_type');
@@ -454,9 +455,7 @@
// Default login of the chosen cloud image (ubuntu, debian, …) until the user types a name.
const login = field('ci_user');
if (login && login.dataset.autoLogin === '1') {
const image = field('disk_image_path');
const option = image && image.selectedIndex >= 0 ? image.options[image.selectedIndex] : null;
login.value = option?.dataset.login || 'admin';
login.value = picked('disk_image_path')?.dataset.login || 'admin';
}
const preset = value('preset');
const custom = preset === 'custom';
@@ -469,7 +468,7 @@
$$('[data-custom-resources] input', form).forEach((input) => { input.readOnly = !custom; });
const memory = Number(value('memory')) || 0;
setSummary('name', value('name'));
setSummary('source', source === 'disk_image' ? optionText(field('disk_image_path')) : optionText(field('iso_path')));
setSummary('source', picked(source === 'disk_image' ? 'disk_image_path' : 'iso_path')?.dataset.label || '');
setSummary('cpu', `${value('vcpus')} ${Number(value('vcpus')) === 1 ? 'ядро' : 'ядра'}`);
setSummary('memory', memory >= 1024 ? `${+(memory / 1024).toFixed(1)} ГБ` : `${memory} МБ`);
setSummary('disk', `${value('disk_size')} ГБ`);
@@ -480,9 +479,7 @@
}
// Установщик Windows панель узнаёт по ISO (data-os) и сама выбирает тип, пока пользователь не выбрал его руками.
const guessOs = () => {
const select = field('iso_path');
const option = select && select.selectedIndex >= 0 ? select.options[select.selectedIndex] : null;
const guess = option?.dataset.os || '';
const guess = picked('iso_path')?.dataset.os || '';
const hint = $('[data-os-hint]', form);
if (form.dataset.osManual) return;
const radio = $(`input[name=os_type][value="${guess || 'linux'}"]`, form);
@@ -495,6 +492,17 @@
if (event.target.name === 'iso_path') guessOs();
sync();
});
// Поиск по образам, когда их много.
$$('[data-image-picker]', form).forEach((picker) => {
const search = $('[data-image-search]', picker);
if (!search) return;
search.addEventListener('input', () => {
const query = search.value.trim().toLowerCase();
let shown = 0;
$$('.image-option', picker).forEach((item) => { item.hidden = Boolean(query) && !item.dataset.search.includes(query); shown += item.hidden ? 0 : 1; });
$('[data-image-empty]', picker).hidden = shown > 0;
});
});
guessOs();
sync();
}