Files
sless/doc/decisions/funcs_console_editor.md
T
Naeel c762047234 fix(builder/go1.23): add require sless/fn/handler to server/go.mod at build time
go.work replace rule requires explicit require directive in server/go.mod.
Patch appended at kaniko build time - no base image rebuild needed.
2026-03-22 17:28:40 +03:00

11 KiB
Raw Blame History

План: веб-редактор функций в funcs-console

Создано: 2026-03-22


Что добавляем

Три фичи в https://sless.kube5s.ru/funcs/<namespace>:

  1. Создание функции — кнопка «+ Новая функция», форма, сохранение
  2. Редактирование кода — встроенный редактор (CodeMirror), сохранение = загрузка нового кода
  3. Запуск функции — кнопка «▶ Запустить», поле ввода JSON event, вывод ответа

Текущее состояние (что уже есть)

Компонент Есть
Листинг функций
Просмотр кода (read-only)
Enable/disable триггера
Invoke
Редактирование
Создание

Архитектура

Проблема аутентификации

/funcs/<ns> не требует токена пользователя — использует SLESS_SERVICE_TOKEN. Создание/редактирование — операции записи, должны быть защищены.

Решение: токен передаётся через форму логина:

  • При открытии /funcs/<ns> без токена → кнопка «Войти», поле ввода токена
  • Токен сохраняется в localStorage / sessionStorage
  • Все write-запросы от frontend идут с Authorization: Bearer <token> к proxy в funcs-service
  • funcs-service proxy проксирует токен пользователя к оператору (не serviceToken)

Почему так: оператор уже проверяет JWT структуру. Токен не верифицируется по подписи — это существующее ограничение (trusted perimeter).


Новые маршруты в funcs-service (Go, main.go)

POST   /funcs/{ns}/api/services              — создать сервис (proxy → оператор)
POST   /funcs/{ns}/api/services/{name}/code  — загрузить код (принимает файлы, делает zip → оператор)
POST   /funcs/{ns}/api/services/{name}/invoke — вызвать функцию (proxy → /fn/{ns}/{name})
DELETE /funcs/{ns}/api/services/{name}        — удалить сервис (proxy → оператор)

Все /api/ маршруты проксируют токен из Authorization header к оператору.


Изменения в Go (main.go)

1. Новый handler: proxyServiceCreate

// POST /funcs/{ns}/api/services
// Принимает JSON {name, runtime, entrypoint, memory_mb, env_vars}
// Проксирует токен из Authorization header → оператор

2. Новый handler: proxyCodeUpload

// POST /funcs/{ns}/api/services/{name}/code
// Принимает multipart: несколько файлов (name + content)
// Создаёт zip в памяти → POST /v1/namespaces/{ns}/services/{name}/upload
// Проксирует токен из Authorization header

Почему zip в памяти: браузер не может создать zip напрямую без JSZip. Альтернатива: использовать JSZip на фронте → отправить binary zip → проще.

Выбор: JSZip на фронте — проще proxy (просто forward binary), меньше Go кода.

3. Новый handler: proxyInvoke

// POST /funcs/{ns}/api/services/{name}/invoke
// Тело: JSON event от пользователя
// Проксирует → POST /fn/{ns}/{name} (публичный endpoint, без токена)
// Возвращает ответ функции

4. Расширение роутера в handleFuncsNS

case "api":
    handleAPI(w, r, operatorURL, externalURL, ns, parts[2:])

Изменения в HTML/JS (index.html)

Зависимости (CDN, добавить в <head>)

<!-- CodeMirror 6 — легковесный редактор -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.js"></script>
<!-- JSZip — создание zip в браузере -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>

Почему CodeMirror а не Monaco: Monaco тяжёлый (~3MB), подключается через AMD loader. CodeMirror 6 — лёгкий, простой CDN, достаточен для подсветки Python/JS/Go.

Фича 1: Авторизация

[header]  sless / sless-mu01  [⚙ Токен: ________] [Войти]  [↻ обновить]
  • Если токен в localStorage → подставляем в заголовок сразу
  • Иначе — поле ввода видно
  • Токен валидируется структурно на JS (3 части, exp > now)

Фича 2: Кнопка «+ Сервис»

[header]  ...  [+ Сервис]  [↻ обновить]

Клик → модальное окно:

Имя:         [____________]
Runtime:     [python3.11 ▾]
Entrypoint:  [handler.handle]
Memory (MB): [128]
Env vars:    [KEY=VALUE, по одной строке]
             [+ ещё одна строка]

              [Отмена]  [Создать]

После создания (201) → открывается редактор кода для этой функции.

Фича 3: Редактор кода

На каждой карточке функции — кнопка «✎ Редактировать» (рядом с expand).

При клике:

  1. Загружается текущий код через /funcs/{ns}/source/{fn}?kind=service
  2. Открывается inline-редактор под карточкой (или modal — обсудить)
  3. CodeMirror с подсветкой по runtime

Интерфейс редактора:

┌─ handler.py ──────────────────────────── [+ файл] [✕] ─┐
│  def handle(event):                                      │
│      return {"ok": True}                                 │
│                                                          │
│                                                          │
└──────────────────────────────────────────────────────────┘
┌─ requirements.txt ─────────────────────── [✕] ──────────┐
│  psycopg2-binary==2.9.9                                  │
└──────────────────────────────────────────────────────────┘
[Сохранить и пересобрать]   [Отмена]

Сохранение:

  1. JSZip.file(name, content) для каждого открытого файла
  2. zip.generateAsync({type:"blob"}) → FormData → POST /funcs/{ns}/api/services/{name}/code
  3. Proxy → оператор upload → kaniko re-build
  4. После 200 → карточка показывает "Building..."

Фича 4: Запуск функции

На каждой карточке сервиса (kind=service, phase=Ready) — кнопка «▶ Запустить».

Клик → inline панель под карточкой:

Event JSON:
┌──────────────────────────────────────────────────────────┐
│  {"name": "world"}                                       │
└──────────────────────────────────────────────────────────┘
[▶ Отправить]

Ответ (200, 34ms):
┌──────────────────────────────────────────────────────────┐
│  {"hello": "world"}                                      │
└──────────────────────────────────────────────────────────┘

Запрос идёт напрямую с браузера на /fn/{ns}/{name} (публичный endpoint, без токена). Ответ показывается с highlight.js.


Порядок реализации

# Шаг Файл Сложность
1 Добавить /api/ роуты в handleFuncsNS main.go низкая
2 proxyServiceCreate handler main.go низкая
3 proxyCodeUploadForward handler (forward binary zip) main.go низкая
4 proxyInvoke handler main.go минимальная
5 Форма авторизации (localStorage токен) index.html низкая
6 Кнопка + Сервис + модальное окно создания index.html средняя
7 Встроенный редактор CodeMirror + JSZip upload index.html средняя
8 Панель invoke index.html низкая
9 Пересобрать образ funcs-service + деплой Makefile/deploy ~5 мин
10 Smoke-test: создать → редактировать → запустить браузер ~5 мин

Порядок важен: сначала backend proxy (1-4), потом UI (5-8).


Что НЕ делаем (scope)

  • Удаление функции через UI — не заявлено, пропускаем
  • Редактирование job-style функций (FunctionJob) — только sless_service
  • История версий кода — S3 уже хранит последнюю, версионирование не реализовано
  • Управление триггерами (создание/удаление) — уже есть enable/disable, этого достаточно
  • Real-time логи — отдельная задача

Связанные файлы

  • services/funcs/main.go
  • services/funcs/index.html
  • services/funcs/Dockerfile
  • deployments/k8s/funcs-service.yaml