feat(web-console): URL строкой ниже, статус сборки после Save (v0.1.9)
This commit is contained in:
@@ -27,7 +27,7 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: funcs
|
- name: funcs
|
||||||
image: naeel/sless-funcs-service:v0.1.8
|
image: naeel/sless-funcs-service:v0.1.9
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8090
|
- containerPort: 8090
|
||||||
env:
|
env:
|
||||||
|
|||||||
+66
-44
@@ -188,15 +188,21 @@
|
|||||||
color: #8b949e;
|
color: #8b949e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fn-url-row {
|
||||||
|
padding: 0 14px 9px 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
.fn-url {
|
.fn-url {
|
||||||
font-size: 0.76rem;
|
font-size: 0.76rem;
|
||||||
color: #58a6ff;
|
color: #58a6ff;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
max-width: 280px;
|
white-space: nowrap;
|
||||||
flex-shrink: 1;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fn-url:hover {
|
.fn-url:hover {
|
||||||
@@ -620,35 +626,47 @@
|
|||||||
// Для Building/Pending — таймер секунд + автополинг до Ready/Failed
|
// Для Building/Pending — таймер секунд + автополинг до Ready/Failed
|
||||||
if (fn.phase === 'Building' || fn.phase === 'Pending') {
|
if (fn.phase === 'Building' || fn.phase === 'Pending') {
|
||||||
var timerEl = $txt(hdr, 'span', 'fn-timer', '0с');
|
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');
|
var a = document.createElement('a');
|
||||||
a.className = 'fn-url';
|
a.className = 'fn-url';
|
||||||
a.href = fn.url;
|
a.href = url; a.target = '_blank'; a.rel = 'noopener noreferrer';
|
||||||
a.target = '_blank';
|
a.textContent = url;
|
||||||
a.rel = 'noopener noreferrer';
|
|
||||||
a.textContent = fn.url;
|
|
||||||
a.addEventListener('click', function (e) { e.stopPropagation(); });
|
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++) {
|
for (var t = 0; t < triggers.length; t++) {
|
||||||
var tr = triggers[t];
|
var tr = triggers[t];
|
||||||
if (tr.type === 'http') {
|
if (tr.type === 'http') {
|
||||||
if (!fn.url) {
|
if (!fn.url) {
|
||||||
var url = extURL ? extURL + '/fn/' + ns + '/' + fn.name : (tr.url || '');
|
var url = extURL ? extURL + '/fn/' + ns + '/' + fn.name : (tr.url || '');
|
||||||
if (url) {
|
if (url) setCardUrl(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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
hdr.appendChild(makeTriggerBtn(tr, ns));
|
hdr.appendChild(makeTriggerBtn(tr, ns));
|
||||||
} else if (tr.type === 'cron') {
|
} 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');
|
var editBtn = $el('button', 'action-btn');
|
||||||
editBtn.textContent = '✎';
|
editBtn.textContent = '✎';
|
||||||
@@ -714,6 +729,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
card.appendChild(hdr);
|
card.appendChild(hdr);
|
||||||
|
// urlRow вставляется динамически (ensureUrlRow) — до body
|
||||||
card.appendChild(body);
|
card.appendChild(body);
|
||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
@@ -841,9 +857,16 @@
|
|||||||
deps_filename: depsFilename || ''
|
deps_filename: depsFilename || ''
|
||||||
})
|
})
|
||||||
}).then(function (r) {
|
}).then(function (r) {
|
||||||
|
saveBtn.disabled = false;
|
||||||
if (r.ok) {
|
if (r.ok) {
|
||||||
status.textContent = '✓ Сохранено — сборка запущена';
|
// Ищем phaseBadge текущей карточки через DOM
|
||||||
status.className = 'edit-status ok';
|
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 {
|
} else {
|
||||||
return r.text().then(function (t) {
|
return r.text().then(function (t) {
|
||||||
status.textContent = '✗ ' + t;
|
status.textContent = '✗ ' + t;
|
||||||
@@ -851,9 +874,10 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
|
saveBtn.disabled = false;
|
||||||
status.textContent = '✗ ' + err;
|
status.textContent = '✗ ' + err;
|
||||||
status.className = 'edit-status err';
|
status.className = 'edit-status err';
|
||||||
}).finally(function () { saveBtn.disabled = false; });
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
cancelBtn.addEventListener('click', function () { body.style.display = 'none'; });
|
cancelBtn.addEventListener('click', function () { body.style.display = 'none'; });
|
||||||
@@ -892,11 +916,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// startBuildPoll — опрашивает /fn-status каждые 5с пока phase=Building/Pending.
|
// startBuildPoll — опрашивает /fn-status каждые 5с пока phase=Building/Pending.
|
||||||
// Показывает таймер в секундах, обновляет badge, добавляет URL когда сервис готов.
|
// Показывает таймер-строку в statusEl (если передан), обновляет phaseBadge,
|
||||||
function startBuildPoll(hdr, phaseBadge, timerEl, fn, ns) {
|
// добавляет URL через onUrl-коллбэк когда сервис появляется.
|
||||||
|
function startBuildPoll(phaseBadge, timerEl, fn, ns, statusEl, onUrl) {
|
||||||
var buildStart = Date.now();
|
var buildStart = Date.now();
|
||||||
var timerItv = setInterval(function () {
|
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);
|
}, 1000);
|
||||||
var pollItv = setInterval(function () {
|
var pollItv = setInterval(function () {
|
||||||
fetch('/funcs/' + ns + '/fn-status/' + fn.name + '?kind=' + (fn.kind || '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;
|
if (!d.phase || d.phase === 'Building' || d.phase === 'Pending') return;
|
||||||
clearInterval(timerItv);
|
clearInterval(timerItv);
|
||||||
clearInterval(pollItv);
|
clearInterval(pollItv);
|
||||||
|
if (phaseBadge) {
|
||||||
phaseBadge.className = 'badge badge-' + d.phase;
|
phaseBadge.className = 'badge badge-' + d.phase;
|
||||||
phaseBadge.textContent = 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);
|
}, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user