ui: archive upload mode + lint stub, fix stress warmup output, add descriptions
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
# Создание функции — текущий flow (май 2026)
|
||||
|
||||
## UI (браузер)
|
||||
|
||||
### Форма создания (`create-modal`)
|
||||
|
||||
Поля в `index.html`:
|
||||
- `c-lang` — select (python / nodejs / go / ruby / php)
|
||||
- `c-name` — имя функции
|
||||
- `c-entry` — entrypoint (заполняется автоматически по языку из `LANG_TEMPLATES`)
|
||||
- `c-route` — HTTP-путь
|
||||
- `c-methods` — методы, через запятую
|
||||
- `c-timeout` — таймаут в секундах
|
||||
- `c-schedule-enabled` + `c-cron` — cron-расписание (опционально)
|
||||
- `c-code` — **textarea** с исходным кодом функции (единственный способ передать код)
|
||||
|
||||
Кнопки рядом с `c-code`:
|
||||
- `aiCheck(...)` — линтер (POST `/console/api/ai/check`)
|
||||
- `aiExplain(...)` — объяснение через LLM (POST `/console/api/ai/ask`)
|
||||
|
||||
При нажатии «Создать» вызывается `submitCreate()` (`js/functions.js`):
|
||||
|
||||
```js
|
||||
await requestJSON(API_BASE + '/functions', 'POST', {
|
||||
name, language, entrypoint, route, methods, timeout,
|
||||
code: document.getElementById('c-code').value // <-- всегда текст
|
||||
});
|
||||
```
|
||||
|
||||
### Форма редактирования (`edit-modal`)
|
||||
|
||||
Аналогична. `submitEdit()` шлёт:
|
||||
|
||||
```js
|
||||
await requestJSON(API_BASE + '/functions/' + name + '/code', 'PUT', {
|
||||
code: document.getElementById('e-code').value,
|
||||
timeout: ...
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Backend
|
||||
|
||||
### POST /console/api/functions → `handleCreateFunction`
|
||||
|
||||
Структура запроса (`model.CreateFunctionRequest`):
|
||||
```json
|
||||
{ "name": "fn", "language": "python", "code": "def main(): ...",
|
||||
"entrypoint": "main", "route": "/ns/fn", "methods": ["GET"],
|
||||
"timeout": 60, "ttl": "" }
|
||||
```
|
||||
|
||||
Обязательные: `name`, `language` (или `environment`), `code`.
|
||||
|
||||
**Путь для интерпретируемых языков** (python / nodejs / ruby / php):
|
||||
```
|
||||
code (строка)
|
||||
→ buildDeployArchive(lang, code)
|
||||
python/ruby/php: []byte(code) (raw, не zip)
|
||||
nodejs: BuildJSDeployZip(code) → zip с package.json + main.js
|
||||
→ buildDeploySpec(ctx, bytes)
|
||||
→ uploadToStoragesvc() → multipart POST → storagesvc → archiveURL
|
||||
→ fallback: base64 literal (если storagesvc недоступен)
|
||||
→ Package.spec.deployment = { type: "url", url: "..." }
|
||||
или = { type: "literal", literal: "base64..." }
|
||||
```
|
||||
|
||||
**Путь для Go**:
|
||||
```
|
||||
code
|
||||
→ BuildGoSourceZip(code) → zip с исходником
|
||||
→ uploadToStoragesvc → archiveURL
|
||||
→ Package.spec.source = { ... } (source package, компилируется builder job)
|
||||
Package.spec.buildcommand = "build"
|
||||
```
|
||||
|
||||
После создания Package → создаётся Function → HTTPTrigger.
|
||||
При ошибке на любом шаге — откат (best-effort DELETE предыдущих объектов).
|
||||
|
||||
### PUT /console/api/functions/{name}/code → `handleUpdateFunctionCode`
|
||||
|
||||
```json
|
||||
{ "code": "...", "timeout": 60 }
|
||||
```
|
||||
|
||||
Обязателен: `code`.
|
||||
|
||||
Алгоритм:
|
||||
1. Читает текущую Function → берёт `fission-console/language` из аннотации
|
||||
2. `buildDeployArchive(lang, code)` → байты
|
||||
3. Создаёт **новый Package** с именем `{name}-pkg-{timestamp_base36}`
|
||||
4. Обновляет Function.spec.package.packageref на новый пакет
|
||||
5. Удаляет старый Package (best-effort)
|
||||
|
||||
> Почему новый Package, а не обновление старого:
|
||||
> executor кэширует function-service по `functionUid` — не видит изменений в том же Package.
|
||||
> Новое имя гарантирует cache miss.
|
||||
|
||||
---
|
||||
|
||||
## Линтер (ai/check) и LLM (ai/ask)
|
||||
|
||||
### POST /console/api/ai/check
|
||||
|
||||
```json
|
||||
{ "language": "python", "code": "def main(): ..." }
|
||||
```
|
||||
|
||||
Работает **только со строкой кода**. Запускает:
|
||||
- python: `python3 -m py_compile <tmpfile.py>`
|
||||
- nodejs: `node --check <tmpfile.js>`
|
||||
- ruby: `ruby -c <tmpfile.rb>`
|
||||
- php: `php -l <tmpfile.php>`
|
||||
- go: `go/parser.ParseFile(...)` прямо в процессе (без tmpfile)
|
||||
|
||||
Временный файл создаётся в `os.TempDir()`, удаляется через `defer`.
|
||||
|
||||
### POST /console/api/ai/ask
|
||||
|
||||
Отправляет код в LLM (объяснение, анализ). Детали в `ai_prompts.go`.
|
||||
|
||||
---
|
||||
|
||||
## Что сейчас НЕ поддерживается
|
||||
|
||||
- **Загрузка архива (.zip) из файла** — ни в UI, ни в API
|
||||
- Архив можно только скачать (через `extractPackageSourceCode` → `fetchPackageArchive`)
|
||||
|
||||
---
|
||||
|
||||
## Planned: загрузка архива
|
||||
|
||||
### API
|
||||
|
||||
Добавить в `CreateFunctionRequest` и `UpdateCodeRequest` поле `archive_b64 string`.
|
||||
Логика в handler: если `archive_b64 != ""` — пропустить `buildDeployArchive`,
|
||||
сразу `uploadToStoragesvc(base64.Decode(archive_b64))`.
|
||||
|
||||
Валидация:
|
||||
- magic bytes `PK\x03\x04` (zip)
|
||||
- максимум 10MB (против 1MB для кода)
|
||||
|
||||
### UI
|
||||
|
||||
Раздельные режимы в форме — кнопки-переключатели «✏️ Код» / «📦 Архив»:
|
||||
- режим «Код» — textarea (как сейчас)
|
||||
- режим «Архив» — `<input type="file" accept=".zip">`, textarea скрыта
|
||||
|
||||
В `submitCreate` / `submitEdit` — `FileReader.readAsDataURL(file)` → base64 → поле `archive_b64`.
|
||||
|
||||
### Линтер и LLM для архива
|
||||
|
||||
Проблема: линтер (`ai/check`) и LLM (`ai/ask`) принимают `code` — строку.
|
||||
Архив — zip из нескольких файлов. Варианты:
|
||||
|
||||
**Вариант A — на бэке, при загрузке**
|
||||
После распаковки архива (уже есть `decodeArchiveBytesToSource` в `package.go`):
|
||||
- Извлечь главный файл (entrypoint)
|
||||
- Прогнать через линтер
|
||||
- Результат вернуть в том же ответе на create/update (поле `lint_result`)
|
||||
|
||||
Плюсы: не меняет контракт линтера, нет доп. round-trip.
|
||||
Минусы: линтится только один файл из архива.
|
||||
|
||||
**Вариант B — на фронте, до отправки**
|
||||
`FileReader.readAsText(file)` — не работает для zip напрямую.
|
||||
Можно использовать `JSZip` (npm/CDN) — распаковать в браузере → взять главный файл → передать в `ai/check`.
|
||||
|
||||
Плюсы: линтинг до загрузки, быстрая обратная связь.
|
||||
Минусы: добавляется зависимость (JSZip ~100KB).
|
||||
|
||||
**Вариант C — гибрид (рекомендован)**
|
||||
1. Фронт: при выборе архива — использовать JSZip, найти entrypoint-файл по языку,
|
||||
показать его в readonly-textarea, кнопки линтера и LLM работают с этим текстом.
|
||||
2. Отправка: `archive_b64` (весь zip) идёт на бэк как есть.
|
||||
3. Бэк (опционально): после сохранения прогнать `decodeArchiveBytesToSource` → линтер,
|
||||
вернуть предупреждение если нашёл ошибки.
|
||||
|
||||
Так пользователь видит и код и результат линтера до деплоя, и полный архив уходит без изменений.
|
||||
Reference in New Issue
Block a user