feat: clone function — POST /functions/:name/clone (v1.3.80)
- Backend: function_clone.go — copies archive from storagesvc, creates new Package/Function/HTTPTrigger preserving all settings (env vars, timeout, entrypoint, executor type, podspec) - Backend: route handlers.go — /functions/:name/clone POST - Frontend: 'Клон' button in edit modal, inline name input form - Frontend: JS toggleCloneArea/submitClone with validation - Pre-fills clone name as '<original>-copy', validates format
This commit is contained in:
@@ -153,6 +153,8 @@ async function openEdit(name) {
|
||||
try {
|
||||
var errEl = document.getElementById('e-error-msg');
|
||||
if (errEl) errEl.style.display = 'none';
|
||||
var cloneArea = document.getElementById('e-clone-area');
|
||||
if (cloneArea) cloneArea.style.display = 'none';
|
||||
const fn = await getJSON(API_BASE + '/functions/' + encodeURIComponent(name));
|
||||
S.currentEdit = fn;
|
||||
document.getElementById('e-title').textContent = 'Редактирование: ' + name;
|
||||
@@ -399,3 +401,69 @@ function collectEnvVars(prefix) {
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// toggleCloneArea показывает/скрывает форму клонирования
|
||||
function toggleCloneArea(show) {
|
||||
var area = document.getElementById('e-clone-area');
|
||||
var errEl = document.getElementById('e-clone-error');
|
||||
if (!area) return;
|
||||
area.style.display = show ? 'block' : 'none';
|
||||
if (show) {
|
||||
var nameInput = document.getElementById('e-clone-name');
|
||||
if (nameInput) {
|
||||
// Предзаполняем именем оригинала + '-copy'
|
||||
var srcName = (S.currentEdit && S.currentEdit.name) || '';
|
||||
nameInput.value = srcName ? srcName + '-copy' : '';
|
||||
nameInput.focus();
|
||||
nameInput.select();
|
||||
}
|
||||
if (errEl) errEl.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// submitClone отправляет запрос на клонирование функции
|
||||
async function submitClone() {
|
||||
var nameInput = document.getElementById('e-clone-name');
|
||||
var errEl = document.getElementById('e-clone-error');
|
||||
var btn = document.querySelector('#e-clone-area .btn:not(.ghost)');
|
||||
|
||||
var newName = nameInput ? nameInput.value.trim() : '';
|
||||
if (!newName) {
|
||||
if (errEl) { errEl.textContent = 'Введите имя новой функции'; errEl.style.display = 'block'; }
|
||||
return;
|
||||
}
|
||||
if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(newName) || newName.length > 57) {
|
||||
if (errEl) { errEl.textContent = 'Имя: строчные буквы, цифры, дефис; не начинается/заканчивается дефисом; до 57 символов'; errEl.style.display = 'block'; }
|
||||
return;
|
||||
}
|
||||
|
||||
var srcName = S.currentEdit && S.currentEdit.name;
|
||||
if (!srcName) {
|
||||
if (errEl) { errEl.textContent = 'Нет функции для клонирования'; errEl.style.display = 'block'; }
|
||||
return;
|
||||
}
|
||||
|
||||
if (btn) { btn.disabled = true; btn.textContent = '...'; }
|
||||
if (errEl) errEl.style.display = 'none';
|
||||
|
||||
try {
|
||||
var resp = await apiFetch('/functions/' + encodeURIComponent(srcName) + '/clone', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({new_name: newName})
|
||||
});
|
||||
var data = await resp.json();
|
||||
if (!resp.ok) {
|
||||
if (errEl) { errEl.textContent = data.error || ('Ошибка ' + resp.status); errEl.style.display = 'block'; }
|
||||
return;
|
||||
}
|
||||
// Успех — закрываем модалку, обновляем список
|
||||
closeEdit();
|
||||
setStatus('Функция «' + newName + '» создана как копия «' + srcName + '»', false);
|
||||
await loadFunctions();
|
||||
} catch(e) {
|
||||
if (errEl) { errEl.textContent = 'Сетевая ошибка: ' + e.message; errEl.style.display = 'block'; }
|
||||
} finally {
|
||||
if (btn) { btn.disabled = false; btn.textContent = 'Клонировать'; }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user