feat(funcs-console): показывать sless_service вместе с функциями — единый листинг
This commit is contained in:
@@ -176,6 +176,18 @@
|
||||
color: #f85149;
|
||||
}
|
||||
|
||||
.badge-kind-service {
|
||||
background: #0d2538;
|
||||
border: 1px solid #1158a7;
|
||||
color: #58a6ff;
|
||||
}
|
||||
|
||||
.badge-kind-function {
|
||||
background: #1a2538;
|
||||
border: 1px solid #30363d;
|
||||
color: #8b949e;
|
||||
}
|
||||
|
||||
.fn-url {
|
||||
font-size: 0.76rem;
|
||||
color: #58a6ff;
|
||||
@@ -328,7 +340,10 @@
|
||||
var byFn = data.triggersByFn || {};
|
||||
|
||||
document.getElementById('ns-label').textContent = ns;
|
||||
document.getElementById('summary').textContent = 'Функций: ' + fns.length;
|
||||
var svcsCount = fns.filter(function(f){ return f.kind === 'service'; }).length;
|
||||
var fnsCount = fns.filter(function(f){ return f.kind !== 'service'; }).length;
|
||||
document.getElementById('summary').textContent =
|
||||
'Функций: ' + fnsCount + ' | Сервисов: ' + svcsCount + ' | Всего: ' + fns.length;
|
||||
|
||||
var list = document.getElementById('fn-list');
|
||||
if (fns.length === 0) {
|
||||
@@ -348,11 +363,29 @@
|
||||
|
||||
$txt(hdr, 'span', 'fn-name', fn.name);
|
||||
$txt(hdr, 'span', 'badge badge-runtime', fn.runtime);
|
||||
$txt(hdr, 'span', 'badge badge-' + fn.phase, fn.phase);
|
||||
$txt(hdr, 'span', 'badge badge-' + fn.phase, fn.phase || 'Pending');
|
||||
if (fn.kind) {
|
||||
var kindLabel = fn.kind === 'service' ? 'always-on' : 'job';
|
||||
$txt(hdr, 'span', 'badge badge-kind-' + fn.kind, kindLabel);
|
||||
}
|
||||
|
||||
// sless_service имеет прямой URL из статуса деплоя
|
||||
if (fn.url) {
|
||||
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.addEventListener('click', function (e) { e.stopPropagation(); });
|
||||
hdr.appendChild(a);
|
||||
}
|
||||
|
||||
for (var t = 0; t < triggers.length; t++) {
|
||||
var tr = triggers[t];
|
||||
if (tr.type === 'http') {
|
||||
// Для job-style функций URL берём из триггера; для сервисов уже показан fn.url
|
||||
if (!fn.url) {
|
||||
var url = extURL ? extURL + '/fn/' + ns + '/' + fn.name : (tr.url || '');
|
||||
if (url) {
|
||||
var a = document.createElement('a');
|
||||
@@ -364,6 +397,7 @@
|
||||
a.addEventListener('click', function (e) { e.stopPropagation(); });
|
||||
hdr.appendChild(a);
|
||||
}
|
||||
}
|
||||
hdr.appendChild(makeTriggerBtn(tr, ns));
|
||||
} else if (tr.type === 'cron') {
|
||||
$txt(hdr, 'span', 'fn-cron', 'cron: ' + tr.schedule);
|
||||
|
||||
+51
-4
@@ -1,4 +1,4 @@
|
||||
// Изменено: 2026-04-25 (добавлена HTML web-консоль, proxy endpoints для source/triggers)
|
||||
// Изменено: 2026-03-21 (добавлен вывод sless_service вместе с функциями — пользователь видит оба типа как «функции»)
|
||||
// main.go — глобальный HTTP сервис листинга функций пользователя.
|
||||
// Развёрнут ОДИН РАЗ в namespace sless; работает для ВСЕХ пользователей.
|
||||
// Не связан с terraform — деплоится манифестом deployments/k8s/funcs-service.yaml.
|
||||
@@ -39,7 +39,9 @@ import (
|
||||
//go:embed index.html
|
||||
var htmlPageTemplate string
|
||||
|
||||
// fnResponse — ответ /v1/namespaces/{ns}/functions (подмножество полей оператора)
|
||||
// fnResponse — унифицированная запись функции для листинга.
|
||||
// Kind="function" — job-style (запускается per-request).
|
||||
// Kind="service" — always-on Deployment (всегда готов к запросам, имеет прямой URL).
|
||||
type fnResponse struct {
|
||||
Name string `json:"name"`
|
||||
Runtime string `json:"runtime"`
|
||||
@@ -47,6 +49,19 @@ type fnResponse struct {
|
||||
Message string `json:"message"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
LastBuiltAt string `json:"last_built_at"`
|
||||
Kind string `json:"kind"` // "function" | "service"
|
||||
URL string `json:"url,omitempty"` // прямой URL для sless_service
|
||||
}
|
||||
|
||||
// svcResponse — ответ /v1/namespaces/{ns}/services (подмножество полей оператора).
|
||||
type svcResponse struct {
|
||||
Name string `json:"name"`
|
||||
Runtime string `json:"runtime"`
|
||||
Phase string `json:"phase"`
|
||||
Message string `json:"message"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
LastBuiltAt string `json:"last_built_at"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// trResponse — ответ /v1/namespaces/{ns}/triggers
|
||||
@@ -182,13 +197,35 @@ func handleFuncsNS(operatorURL, externalURL, serviceToken string, exclude map[st
|
||||
}
|
||||
}
|
||||
|
||||
// fetchAndRender загружает данные из оператора и отдаёт ответ: HTML (браузер) или plain text (curl).
|
||||
// fetchAndRender загружает functions + services из оператора и отдаёт ответ.
|
||||
// Оба типа объединяются в единый список — пользователь видит их как «функции».
|
||||
func fetchAndRender(w http.ResponseWriter, r *http.Request, operatorURL, externalURL, authHeader, ns string, exclude map[string]bool) {
|
||||
fns, err := apiGet[[]fnResponse](operatorURL+"/v1/namespaces/"+ns+"/functions", authHeader)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("operator error (functions): %s\n", err), http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
for i := range fns {
|
||||
fns[i].Kind = "function"
|
||||
}
|
||||
|
||||
// sless_service — always-on Deployment + URL. Объединяем с функциями.
|
||||
svcs, svcErr := apiGet[[]svcResponse](operatorURL+"/v1/namespaces/"+ns+"/services", authHeader)
|
||||
if svcErr == nil {
|
||||
for _, svc := range svcs {
|
||||
fns = append(fns, fnResponse{
|
||||
Name: svc.Name,
|
||||
Runtime: svc.Runtime,
|
||||
Phase: svc.Phase,
|
||||
Message: svc.Message,
|
||||
CreatedAt: svc.CreatedAt,
|
||||
LastBuiltAt: svc.LastBuiltAt,
|
||||
Kind: "service",
|
||||
URL: svc.URL,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
trs, err := apiGet[[]trResponse](operatorURL+"/v1/namespaces/"+ns+"/triggers", authHeader)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("operator error (triggers): %s\n", err), http.StatusBadGateway)
|
||||
@@ -279,7 +316,10 @@ func servePlainText(w http.ResponseWriter, ns, externalURL string, fns []fnRespo
|
||||
} else {
|
||||
sb.WriteString(" active: нет\n")
|
||||
}
|
||||
if len(it.httpT) > 0 {
|
||||
// Для sless_service — прямой URL из статуса деплоя
|
||||
if fn.URL != "" {
|
||||
sb.WriteString(fmt.Sprintf(" url: %s\n", fn.URL))
|
||||
} else if len(it.httpT) > 0 {
|
||||
url := it.httpT[0].URL
|
||||
if externalURL != "" {
|
||||
url = externalURL + "/fn/" + ns + "/" + fn.Name
|
||||
@@ -415,6 +455,13 @@ func isValidK8sName(s string) bool {
|
||||
}
|
||||
|
||||
func buildComment(fn fnResponse, httpT, cronT []trResponse) string {
|
||||
if fn.Kind == "service" {
|
||||
active := "неактивна"
|
||||
if fn.Phase == "Ready" {
|
||||
active = "активна"
|
||||
}
|
||||
return fmt.Sprintf("always-on сервис (%s) — %s, %s", fn.Runtime, fn.Phase, active)
|
||||
}
|
||||
if len(httpT) > 0 {
|
||||
active := "активна"
|
||||
if !httpT[0].Active {
|
||||
|
||||
Reference in New Issue
Block a user