go.work replace rule requires explicit require directive in server/go.mod. Patch appended at kaniko build time - no base image rebuild needed.
11 KiB
План: веб-редактор функций в funcs-console
Создано: 2026-03-22
Что добавляем
Три фичи в https://sless.kube5s.ru/funcs/<namespace>:
- Создание функции — кнопка «+ Новая функция», форма, сохранение
- Редактирование кода — встроенный редактор (CodeMirror), сохранение = загрузка нового кода
- Запуск функции — кнопка «▶ Запустить», поле ввода 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).
При клике:
- Загружается текущий код через
/funcs/{ns}/source/{fn}?kind=service - Открывается inline-редактор под карточкой (или modal — обсудить)
- CodeMirror с подсветкой по runtime
Интерфейс редактора:
┌─ handler.py ──────────────────────────── [+ файл] [✕] ─┐
│ def handle(event): │
│ return {"ok": True} │
│ │
│ │
└──────────────────────────────────────────────────────────┘
┌─ requirements.txt ─────────────────────── [✕] ──────────┐
│ psycopg2-binary==2.9.9 │
└──────────────────────────────────────────────────────────┘
[Сохранить и пересобрать] [Отмена]
Сохранение:
- JSZip.file(name, content) для каждого открытого файла
- zip.generateAsync({type:"blob"}) → FormData → POST
/funcs/{ns}/api/services/{name}/code - Proxy → оператор upload → kaniko re-build
- После 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.goservices/funcs/index.htmlservices/funcs/Dockerfiledeployments/k8s/funcs-service.yaml