feat(web-console): URL строкой ниже, статус сборки после Save (v0.1.9)

This commit is contained in:
Naeel
2026-03-23 11:01:01 +03:00
parent 470039f6d6
commit 6e73729c46
2 changed files with 69 additions and 47 deletions
+1 -1
View File
@@ -27,7 +27,7 @@ spec:
spec:
containers:
- name: funcs
image: naeel/sless-funcs-service:v0.1.8
image: naeel/sless-funcs-service:v0.1.9
ports:
- containerPort: 8090
env:
+66 -44
View File
@@ -188,15 +188,21 @@
color: #8b949e;
}
.fn-url-row {
padding: 0 14px 9px 14px;
display: flex;
align-items: center;
gap: 6px;
}
.fn-url {
font-size: 0.76rem;
color: #58a6ff;
text-decoration: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 280px;
flex-shrink: 1;
white-space: nowrap;
max-width: 100%;
}
.fn-url:hover {
@@ -620,35 +626,47 @@
// Для Building/Pending — таймер секунд + автополинг до Ready/Failed
if (fn.phase === 'Building' || fn.phase === 'Pending') {
var timerEl = $txt(hdr, 'span', 'fn-timer', '0с');
startBuildPoll(hdr, phaseBadge, timerEl, fn, ns);
startBuildPoll(phaseBadge, timerEl, fn, ns, null, function(url) {
if (!fn.url) { fn.url = url; setCardUrl(url); }
});
}
if (fn.url) {
if (fn.message) $txt(hdr, 'span', 'fn-msg', fn.message);
hdr.appendChild($el('span', 'spacer'));
// urlRow — отдельная строка под заголовком для URL (не умещается в одну строку)
var urlRow = null;
var cardUrlAnchor = null;
function ensureUrlRow() {
if (!urlRow) {
urlRow = $el('div', 'fn-url-row');
// вставляем после hdr, до body
card.insertBefore(urlRow, body);
}
return urlRow;
}
function setCardUrl(url) {
if (cardUrlAnchor) return; // уже есть
var row = ensureUrlRow();
var a = document.createElement('a');
a.className = 'fn-url';
a.href = fn.url;
a.target = '_blank';
a.rel = 'noopener noreferrer';
a.textContent = fn.url;
a.href = url; a.target = '_blank'; a.rel = 'noopener noreferrer';
a.textContent = url;
a.addEventListener('click', function (e) { e.stopPropagation(); });
hdr.appendChild(a);
row.appendChild(a);
cardUrlAnchor = a;
}
if (fn.url) setCardUrl(fn.url);
for (var t = 0; t < triggers.length; t++) {
var tr = triggers[t];
if (tr.type === 'http') {
if (!fn.url) {
var url = extURL ? extURL + '/fn/' + ns + '/' + fn.name : (tr.url || '');
if (url) {
var a = document.createElement('a');
a.className = 'fn-url';
a.href = url;
a.target = '_blank';
a.rel = 'noopener noreferrer';
a.textContent = url;
a.addEventListener('click', function (e) { e.stopPropagation(); });
hdr.appendChild(a);
}
if (url) setCardUrl(url);
}
hdr.appendChild(makeTriggerBtn(tr, ns));
} else if (tr.type === 'cron') {
@@ -657,9 +675,6 @@
}
}
if (fn.message) $txt(hdr, 'span', 'fn-msg', fn.message);
hdr.appendChild($el('span', 'spacer'));
// Кнопка редактирования кода
var editBtn = $el('button', 'action-btn');
editBtn.textContent = '✎';
@@ -714,6 +729,7 @@
});
card.appendChild(hdr);
// urlRow вставляется динамически (ensureUrlRow) — до body
card.appendChild(body);
return card;
}
@@ -841,9 +857,16 @@
deps_filename: depsFilename || ''
})
}).then(function (r) {
saveBtn.disabled = false;
if (r.ok) {
status.textContent = '✓ Сохранено — сборка запущена';
status.className = 'edit-status ok';
// Ищем phaseBadge текущей карточки через DOM
var cardEl = body.closest ? body.closest('.fn-card') : null;
var pb = cardEl ? cardEl.querySelector('.badge-Ready, .badge-Building, .badge-Pending, .badge-Failed, .badge-Error') : null;
if (pb) { pb.className = 'badge badge-Building'; pb.textContent = 'Building'; }
status.textContent = '⏳ Сборка… 0с';
status.className = 'edit-status';
// Запускаем поллинг: обновляет статус в строке редактора и badge карточки
startBuildPoll(pb, null, { name: fnName, kind: fnKind || 'function' }, ns, status, null);
} else {
return r.text().then(function (t) {
status.textContent = '✗ ' + t;
@@ -851,9 +874,10 @@
});
}
}).catch(function (err) {
saveBtn.disabled = false;
status.textContent = '✗ ' + err;
status.className = 'edit-status err';
}).finally(function () { saveBtn.disabled = false; });
});
});
cancelBtn.addEventListener('click', function () { body.style.display = 'none'; });
@@ -892,11 +916,14 @@
}
// startBuildPoll — опрашивает /fn-status каждые 5с пока phase=Building/Pending.
// Показывает таймер в секундах, обновляет badge, добавляет URL когда сервис готов.
function startBuildPoll(hdr, phaseBadge, timerEl, fn, ns) {
// Показывает таймер-строку в statusEl (если передан), обновляет phaseBadge,
// добавляет URL через onUrl-коллбэк когда сервис появляется.
function startBuildPoll(phaseBadge, timerEl, fn, ns, statusEl, onUrl) {
var buildStart = Date.now();
var timerItv = setInterval(function () {
timerEl.textContent = Math.floor((Date.now() - buildStart) / 1000) + 'с';
var sec = Math.floor((Date.now() - buildStart) / 1000);
if (timerEl && timerEl.parentNode) timerEl.textContent = sec + 'с';
if (statusEl) statusEl.textContent = '⏳ Сборка… ' + sec + 'с';
}, 1000);
var pollItv = setInterval(function () {
fetch('/funcs/' + ns + '/fn-status/' + fn.name + '?kind=' + (fn.kind || 'function'))
@@ -905,25 +932,20 @@
if (!d.phase || d.phase === 'Building' || d.phase === 'Pending') return;
clearInterval(timerItv);
clearInterval(pollItv);
if (phaseBadge) {
phaseBadge.className = 'badge badge-' + d.phase;
phaseBadge.textContent = d.phase;
// Убираем таймер с экрана — он нужен только во время сборки
if (timerEl.parentNode) timerEl.parentNode.removeChild(timerEl);
// Для сервиса — добавляем URL в заголовок карточки при появлении
if (d.url && !fn.url) {
fn.url = d.url;
var urlEl = document.createElement('a');
urlEl.className = 'fn-url';
urlEl.href = d.url;
urlEl.target = '_blank';
urlEl.rel = 'noopener noreferrer';
urlEl.textContent = d.url;
urlEl.addEventListener('click', function (e) { e.stopPropagation(); });
var spacer = hdr.querySelector('.spacer');
if (spacer) hdr.insertBefore(urlEl, spacer);
}
if (timerEl && timerEl.parentNode) timerEl.parentNode.removeChild(timerEl);
if (statusEl) {
statusEl.textContent = d.phase === 'Ready'
? '✓ Сборка завершена — функция готова'
: '✗ Сборка завершилась со статусом: ' + d.phase;
statusEl.className = 'edit-status ' + (d.phase === 'Ready' ? 'ok' : 'err');
}
if (d.url && onUrl) onUrl(d.url);
})
.catch(function () {}); // тихо — нет смысла шуметь
.catch(function () {});
}, 5000);
}