feat(web-console): URL строкой ниже, статус сборки после Save (v0.1.9)
This commit is contained in:
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user