Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
865d33a0e1 | ||
|
|
1d81ca34e1 | ||
|
|
8374254020 | ||
|
|
ae913f7ad6 | ||
|
|
68296d8fe0 | ||
|
|
949b0b52d6 | ||
|
|
6a8c3a27e2 | ||
|
|
75532d8d8e | ||
|
|
10b2b4a8e0 | ||
|
|
17f1c5d46f |
@@ -28,16 +28,16 @@
|
||||
1. Не трогать рабочий код без явного указания.
|
||||
|
||||
2. Файлы редактируются локально:
|
||||
~/IoT
|
||||
~/fission-console
|
||||
|
||||
После ЛЮБЫХ изменений ОБЯЗАТЕЛЬНО синхронизировать на ВМ командой:
|
||||
rsync -az \
|
||||
-e "ssh -i ~/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=10" \
|
||||
~/IoT/ \
|
||||
naeel@5.172.178.213:~/terra/IoT/
|
||||
~/fission-console/ \
|
||||
naeel@5.172.178.213:~/terra/fission/
|
||||
|
||||
|
||||
3. Git (add/commit/push) выполнять ЛОКАЛЬНО в ~/IoT
|
||||
3. Git (add/commit/push) выполнять ЛОКАЛЬНО в ~/fission-console
|
||||
4. Docker, kubectl и другие инфраструктурные команды — только через SSH на ВМ:
|
||||
ssh -i ~/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=10 naeel@5.172.178.213 'КОМАНДА'
|
||||
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
|
||||
## Файловая система (актуально)
|
||||
|
||||
1. Все файлы редактируются локально: `~/IoT`
|
||||
1. Все файлы редактируются локально: `~/fission-console`
|
||||
2. После любых изменений — обязательно rsync на ВМ:
|
||||
rsync -az \
|
||||
-e "ssh -i ~/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=10" \
|
||||
~/IoT/ \
|
||||
naeel@5.172.178.213:~/terra/IoT/
|
||||
~/fission-console/ \
|
||||
naeel@5.172.178.213:~/terra/fission/
|
||||
|
||||
3. Git (add/commit/push) выполнять ЛОКАЛЬНО в ~/IoT
|
||||
3. Git (add/commit/push) выполнять ЛОКАЛЬНО в ~/fission-console
|
||||
4. Docker, kubectl и другие инфраструктурные команды — только через SSH на ВМ
|
||||
5. Перед запуском любой команды на ВМ обязательно убедиться, что синхронизация (rsync) выполнена
|
||||
6. SCP, sshfs, remote_dev и маунты больше НЕ используются
|
||||
@@ -50,7 +50,7 @@ ssh -i ~/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=
|
||||
## Git
|
||||
|
||||
⛔⛔⛔ АБСОЛЮТНОЕ ПРАВИЛО:
|
||||
- Git — ТОЛЬКО ЛОКАЛЬНО в `~/fission`. НИКОГДА через SSH на VM.
|
||||
- Git — ТОЛЬКО ЛОКАЛЬНО в `~/fission-console`. НИКОГДА через SSH на VM.
|
||||
- Разрешены ТОЛЬКО две операции: `git commit` и `git push`.
|
||||
- ЗАПРЕЩЕНО: git pull, git fetch, git rebase, git merge, git reset, git stash, git checkout — что угодно кроме commit и push.
|
||||
- Если push отклонён — СТОП, доложить пользователю. Не лезть в pull/merge/rebase самостоятельно.
|
||||
@@ -86,7 +86,7 @@ LOG="test-results/$(date +%Y-%m-%d_%H-%M).log"
|
||||
ssh -i ~/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no naeel@5.172.178.213 \
|
||||
"bash ~/terra/fission/scripts/run_all.sh 2>&1 | tee ~/terra/fission/${LOG}"
|
||||
rsync -az -e "ssh -i ~/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no" \
|
||||
naeel@5.172.178.213:~/terra/IoT/test-results/ ~/IoT/test-results/
|
||||
naeel@5.172.178.213:~/terra/fission/test-results/ ~/fission-console/test-results/
|
||||
```
|
||||
|
||||
**Никогда не разбираться с результатами по памяти / буферу / чату. Только лог.**
|
||||
|
||||
@@ -16,3 +16,10 @@ examples/*/dist/
|
||||
terraform-provider-fission
|
||||
terraform-provider-fission_*
|
||||
console/fission-console
|
||||
|
||||
# Secrets / tokens
|
||||
token.txt
|
||||
|
||||
# Test logs
|
||||
test-results/
|
||||
scripts/test_layer1.sh
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
# Fix: 401 "key is of invalid type" при invoke функций
|
||||
|
||||
## Статус: ГОТОВО К ПРАВКЕ (не исправлено)
|
||||
|
||||
## Root Cause (установлен 2026-05-18)
|
||||
|
||||
Secret `router` в k8s namespace `fission` был ротирован.
|
||||
Router pod перезапустился — подхватил новый пароль.
|
||||
Console pod НЕ перезапускался (5d8h) — имеет старый пароль в env var.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| `FISSION_AUTH_PASSWORD` в console pod (env) | `ToAjBTs0Sb8mLC1J9NN3` (СТАРЫЙ) |
|
||||
| `password` в k8s secret `router` (текущий) | `slxGj3G3FAD7l5ms2tJ9` |
|
||||
|
||||
### Цепочка отказа
|
||||
|
||||
1. `getRouterToken()` → `POST /auth/login` со старым паролем → **401 Unauthorized**
|
||||
2. Fallback: `return s.readSAToken()` → Kubernetes SA token, `alg: RS256`
|
||||
3. Console отправляет `Authorization: Bearer <RS256-SA-token>` в router
|
||||
4. Router keyfunc: `return []byte(JWT_SIGNING_KEY), nil` — возвращает `[]byte` для **любого** алгоритма
|
||||
5. `jwt.Parse` получает RS256 JWT но ключ `[]byte` (ожидается `*rsa.PublicKey`) → **"key is of invalid type"**
|
||||
|
||||
### Подтверждено
|
||||
|
||||
```bash
|
||||
# Старый пароль → 401:
|
||||
kubectl exec -n fission fission-console-874c786c6-j6fvd -- wget -qO- \
|
||||
--post-data='{"username":"admin","password":"ToAjBTs0Sb8mLC1J9NN3"}' \
|
||||
--header='Content-Type: application/json' \
|
||||
http://router.fission.svc.cluster.local/auth/login
|
||||
# → HTTP/1.1 401 Unauthorized
|
||||
|
||||
# Новый пароль → OK:
|
||||
# password=slxGj3G3FAD7l5ms2tJ9 → {"accesstoken":"eyJhbGciOiJIUzI1NiI..."}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Что нужно исправить
|
||||
|
||||
### Файл 1: `console/internal/api/server.go` — функция `getRouterToken()`
|
||||
|
||||
**Проблема A:** читает пароль из env var один раз при старте → устаревает при ротации secret.
|
||||
**Проблема B:** при ошибке login молча возвращает SA token вместо ошибки.
|
||||
|
||||
**Нужно:**
|
||||
- Читать username/password из **файла** при каждом вызове `getRouterToken()`, а не из `os.Getenv` при старте.
|
||||
Путь файла: `/etc/fission-router-secret/username` и `/etc/fission-router-secret/password`
|
||||
- При ошибке login — возвращать `("", error)`, не `readSAToken()`.
|
||||
Caller (`handleInvokeFunction`) должен вернуть 503 с понятным сообщением.
|
||||
|
||||
### Файл 2: `console/deploy/console.yaml`
|
||||
|
||||
**Нужно:** заменить `env` → `secretKeyRef` на volume mount.
|
||||
|
||||
```yaml
|
||||
# Убрать из env:
|
||||
- name: FISSION_AUTH_USERNAME
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: router
|
||||
key: username
|
||||
- name: FISSION_AUTH_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: router
|
||||
key: password
|
||||
|
||||
# Добавить volume:
|
||||
volumes:
|
||||
- name: router-secret
|
||||
secret:
|
||||
secretName: router
|
||||
items:
|
||||
- key: username
|
||||
path: username
|
||||
- key: password
|
||||
path: password
|
||||
|
||||
# Добавить volumeMount:
|
||||
volumeMounts:
|
||||
- name: router-secret
|
||||
mountPath: /etc/fission-router-secret
|
||||
readOnly: true
|
||||
```
|
||||
|
||||
Kubernetes обновляет смонтированные secret-файлы автоматически в течение ~60с после изменения secret.
|
||||
|
||||
---
|
||||
|
||||
## Порядок работы
|
||||
|
||||
1. Прочитать текущий код `getRouterToken()` в `console/internal/api/server.go`
|
||||
2. Найти где инициализируются `s.authUser` / `s.authPass` (вероятно в `NewServer()` или аналоге)
|
||||
3. Убрать сохранение в struct, читать из файла на каждый вызов `getRouterToken()`
|
||||
4. Изменить возврат при ошибке login: `return "", fmt.Errorf("router login failed: %w", err)` вместо `readSAToken()`
|
||||
5. Найти всех callers `getRouterToken()` — обработать ошибку (вернуть 503)
|
||||
6. Обновить `console/deploy/console.yaml` (volume mount вместо env)
|
||||
7. Увеличить тег образа (согласно правилам: vX.Y.Z → vX.Y.Z+1)
|
||||
8. rsync → build → push → apply
|
||||
|
||||
## Текущий тег образа
|
||||
|
||||
Проверить: `grep "naeel/fission-console" console/deploy/console.yaml`
|
||||
Последний известный: `v1.3.92`
|
||||
|
||||
## SSH / rsync
|
||||
|
||||
```bash
|
||||
# rsync:
|
||||
rsync -az -e "ssh -i ~/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=10" \
|
||||
~/fission-console/ naeel@5.172.178.213:~/terra/fission/
|
||||
|
||||
# SSH:
|
||||
ssh -i ~/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=10 naeel@5.172.178.213 'КОМАНДА'
|
||||
```
|
||||
@@ -58,7 +58,7 @@ spec:
|
||||
serviceAccountName: fission-console
|
||||
containers:
|
||||
- name: console
|
||||
image: naeel/fission-console:v1.3.88
|
||||
image: naeel/fission-console:v1.3.92
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8090
|
||||
|
||||
@@ -35,17 +35,26 @@ import (
|
||||
// buildDeployArchive упаковывает исходный код в байты для deployment Package.
|
||||
// Для nodejs — ESM-обёртка (package.json + main.js).
|
||||
// Для php/ruby — zip с одним файлом скрипта.
|
||||
// Для остальных (python) — raw bytes кода.
|
||||
func buildDeployArchive(lang, code string) ([]byte, error) {
|
||||
// Для остальных (python) — raw bytes кода (или zip если есть deps).
|
||||
// deps — содержимое файла зависимостей (requirements.txt, Gemfile, composer.json).
|
||||
// Если deps пустой — поведение как раньше.
|
||||
func buildDeployArchive(lang, code, deps string) ([]byte, error) {
|
||||
switch lang {
|
||||
case "nodejs":
|
||||
// TODO: поддержка package.json с deps для nodejs — пока игнорируем deps
|
||||
return runtime.BuildJSDeployZip(code)
|
||||
case "php":
|
||||
if deps != "" {
|
||||
return runtime.BuildScriptZipWithDeps(code, "main.php", deps, "composer.json")
|
||||
}
|
||||
return runtime.BuildScriptZip(code, "main.php")
|
||||
case "ruby":
|
||||
if deps != "" {
|
||||
return runtime.BuildScriptZipWithDeps(code, "handler.rb", deps, "Gemfile")
|
||||
}
|
||||
return runtime.BuildScriptZip(code, "handler.rb")
|
||||
default:
|
||||
return []byte(code), nil
|
||||
default: // python
|
||||
return runtime.BuildPythonZip(code, deps)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +182,7 @@ func (s *Server) handleCreateFunction(w http.ResponseWriter, r *http.Request) {
|
||||
"buildcommand": "build",
|
||||
}
|
||||
} else {
|
||||
deployBytes, archiveErr := buildDeployArchive(req.Language, req.Code)
|
||||
deployBytes, archiveErr := buildDeployArchive(req.Language, req.Code, req.Deps)
|
||||
if archiveErr != nil {
|
||||
writeJSONError(w, http.StatusInternalServerError, fmt.Sprintf("build %s archive: %v", req.Language, archiveErr))
|
||||
return
|
||||
@@ -205,6 +214,9 @@ func (s *Server) handleCreateFunction(w http.ResponseWriter, r *http.Request) {
|
||||
now := time.Now().UTC()
|
||||
fnAnnotations[functionCreatedAtAnnotation] = now.Format(time.RFC3339)
|
||||
fnAnnotations[functionUpdatedAtAnnotation] = now.Format(time.RFC3339)
|
||||
if req.Deps != "" {
|
||||
fnAnnotations["fission-console/deps"] = req.Deps
|
||||
}
|
||||
if req.TTL != "" {
|
||||
expiresAt, ttlErr := parseTTL(req.TTL)
|
||||
if ttlErr != nil {
|
||||
@@ -329,7 +341,7 @@ func (s *Server) handleUpdateFunctionCode(w http.ResponseWriter, r *http.Request
|
||||
|
||||
// Определяем язык из аннотации — нужен для правильной упаковки
|
||||
lang, _, _ := unstructured.NestedString(fn.Object, "metadata", "annotations", "fission-console/language")
|
||||
deployBytes, archiveErr := buildDeployArchive(lang, req.Code)
|
||||
deployBytes, archiveErr := buildDeployArchive(lang, req.Code, req.Deps)
|
||||
if archiveErr != nil {
|
||||
writeJSONError(w, http.StatusInternalServerError, fmt.Sprintf("build %s archive: %v", lang, archiveErr))
|
||||
return
|
||||
@@ -393,6 +405,11 @@ func (s *Server) handleUpdateFunctionCode(w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
fnAnnotations[functionCreatedAtAnnotation] = createdAt.UTC().Format(time.RFC3339)
|
||||
fnAnnotations[functionUpdatedAtAnnotation] = now.Format(time.RFC3339)
|
||||
if req.Deps != "" {
|
||||
fnAnnotations["fission-console/deps"] = req.Deps
|
||||
} else {
|
||||
delete(fnAnnotations, "fission-console/deps")
|
||||
}
|
||||
fn.SetAnnotations(fnAnnotations)
|
||||
if err := unstructured.SetNestedField(fn.Object, map[string]any{
|
||||
"name": newPkgName,
|
||||
|
||||
@@ -83,6 +83,7 @@ func (s *Server) handleGetFunction(w http.ResponseWriter, r *http.Request, name
|
||||
// Читаем source-type аннотацию (code / archive)
|
||||
sourceType := "code"
|
||||
archiveFilename := ""
|
||||
deps := ""
|
||||
if ann := fn.GetAnnotations(); ann != nil {
|
||||
if v := ann[fissionSourceTypeAnnotation]; v != "" {
|
||||
sourceType = v
|
||||
@@ -90,6 +91,9 @@ func (s *Server) handleGetFunction(w http.ResponseWriter, r *http.Request, name
|
||||
if v := ann["fission-console/archive-filename"]; v != "" {
|
||||
archiveFilename = v
|
||||
}
|
||||
if v := ann["fission-console/deps"]; v != "" {
|
||||
deps = v
|
||||
}
|
||||
}
|
||||
|
||||
writeAnyJSON(w, http.StatusOK, map[string]any{
|
||||
@@ -102,6 +106,7 @@ func (s *Server) handleGetFunction(w http.ResponseWriter, r *http.Request, name
|
||||
"created_at": functionTimestampResponse(fn)["created_at"],
|
||||
"updated_at": functionTimestampResponse(fn)["updated_at"],
|
||||
"code": code,
|
||||
"deps": deps,
|
||||
"source_type": sourceType,
|
||||
"archive_filename": archiveFilename,
|
||||
"route": route,
|
||||
|
||||
@@ -28,9 +28,20 @@ func EnsureEnvironment(ctx context.Context, dyn dynamic.Interface, ns, lang stri
|
||||
envName := "console-" + lang + "-env"
|
||||
|
||||
// Проверяем существование — Get быстрее чем Create+IsAlreadyExists
|
||||
_, getErr := dyn.Resource(EnvironmentGVR).Namespace(ns).Get(ctx, envName, metav1.GetOptions{})
|
||||
existing, getErr := dyn.Resource(EnvironmentGVR).Namespace(ns).Get(ctx, envName, metav1.GetOptions{})
|
||||
if getErr == nil {
|
||||
return envName, nil // уже существует — быстрый путь
|
||||
// Уже существует — проверяем образ. Если устарел — обновляем.
|
||||
currentImage, _, _ := unstructured.NestedString(existing.Object, "spec", "runtime", "image")
|
||||
if currentImage != langDef.Image {
|
||||
if patchErr := unstructured.SetNestedField(existing.Object, langDef.Image, "spec", "runtime", "image"); patchErr == nil {
|
||||
if _, updateErr := dyn.Resource(EnvironmentGVR).Namespace(ns).Update(ctx, existing, metav1.UpdateOptions{}); updateErr != nil {
|
||||
log.Printf("ensureEnvironment: update image %s/%s: %v", ns, envName, updateErr)
|
||||
} else {
|
||||
log.Printf("ensureEnvironment: updated image %s/%s: %s → %s", ns, envName, currentImage, langDef.Image)
|
||||
}
|
||||
}
|
||||
}
|
||||
return envName, nil
|
||||
}
|
||||
if !apierrors.IsNotFound(getErr) {
|
||||
return "", fmt.Errorf("check environment %q: %w", envName, getErr)
|
||||
|
||||
@@ -5,11 +5,14 @@ package model
|
||||
|
||||
// CreateFunctionRequest — тело POST /console/api/functions.
|
||||
// TTL пустой → функция живёт вечно; "1d", "24h" — протухнет через указанное время.
|
||||
// Deps — содержимое файла зависимостей: requirements.txt (python), package.json deps (nodejs),
|
||||
// Gemfile (ruby), composer.json (php). Если задан — код упаковывается в zip вместе с deps-файлом.
|
||||
type CreateFunctionRequest struct {
|
||||
Name string `json:"name"`
|
||||
Language string `json:"language"`
|
||||
Environment string `json:"environment"`
|
||||
Code string `json:"code"`
|
||||
Deps string `json:"deps"` // содержимое файла зависимостей (опционально)
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
Route string `json:"route"`
|
||||
Methods []string `json:"methods"`
|
||||
@@ -51,6 +54,7 @@ type CreateKWTriggerRequest struct {
|
||||
// UpdateCodeRequest — тело PUT /console/api/functions/:name/code.
|
||||
type UpdateCodeRequest struct {
|
||||
Code string `json:"code"`
|
||||
Deps string `json:"deps"` // содержимое файла зависимостей (опционально)
|
||||
Timeout int64 `json:"timeout"`
|
||||
}
|
||||
|
||||
@@ -66,7 +70,7 @@ type LangEnvDef struct {
|
||||
// LangEnvMap сопоставляет идентификатор языка (string) с описанием среды выполнения.
|
||||
// Ключ используется в createFunctionRequest.Language и как суффикс имени Environment.
|
||||
var LangEnvMap = map[string]LangEnvDef{
|
||||
"python": {Image: "naeel/fission-python-env:v1.0"},
|
||||
"python": {Image: "naeel/fission-python-env:v1.1"},
|
||||
"nodejs": {Image: "ghcr.io/fission/node-env"},
|
||||
"go": {Image: "ghcr.io/fission/go-env", BuilderImage: "naeel/go-builder-fast:v1"},
|
||||
"php": {Image: "ghcr.io/fission/php-env"},
|
||||
|
||||
@@ -25,3 +25,28 @@ func buildZip(fileName string, content []byte) ([]byte, error) {
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// buildZipTwo создаёт zip-архив с двумя файлами.
|
||||
// Используется когда пользователь указал файл зависимостей (requirements.txt и т.д.).
|
||||
func buildZipTwo(file1, file2 string, content1, content2 []byte) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
zw := zip.NewWriter(&buf)
|
||||
|
||||
for _, f := range []struct {
|
||||
name string
|
||||
content []byte
|
||||
}{{file1, content1}, {file2, content2}} {
|
||||
fw, err := zw.Create(f.name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := fw.Write(f.content); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := zw.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
@@ -11,3 +11,20 @@ package runtime
|
||||
func BuildScriptZip(code, fileName string) ([]byte, error) {
|
||||
return buildZip(fileName, []byte(code))
|
||||
}
|
||||
|
||||
// BuildScriptZipWithDeps создаёт zip с кодом и файлом зависимостей.
|
||||
// fileName — имя файла кода (handler.rb, main.php и т.д.)
|
||||
// depsName — имя файла зависимостей (Gemfile, composer.json и т.д.)
|
||||
func BuildScriptZipWithDeps(code, fileName, deps, depsName string) ([]byte, error) {
|
||||
return buildZipTwo(fileName, depsName, []byte(code), []byte(deps))
|
||||
}
|
||||
|
||||
// BuildPythonZip создаёт zip с main.py (и опционально requirements.txt).
|
||||
// Если deps пустой — возвращает raw bytes кода (текущее поведение Python).
|
||||
// Если deps задан — zip с main.py + requirements.txt для pip install.
|
||||
func BuildPythonZip(code, deps string) ([]byte, error) {
|
||||
if deps == "" {
|
||||
return []byte(code), nil
|
||||
}
|
||||
return buildZipTwo("main.py", "requirements.txt", []byte(code), []byte(deps))
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
<div class="nubes">NUBES</div>
|
||||
<div class="product">FISSION CONSOLE</div>
|
||||
</div>
|
||||
<div style="font-size:0.65rem; color:var(--text-secondary); margin-left:10px; align-self:center; opacity:0.7;">v1.3.88</div>
|
||||
<div style="font-size:0.65rem; color:var(--text-secondary); margin-left:10px; align-self:center; opacity:0.7;">v1.3.92</div>
|
||||
</div>
|
||||
<div class="row" style="margin:0;">
|
||||
<button class="btn ghost" onclick="reloadAll()">Refresh</button>
|
||||
@@ -306,6 +306,12 @@
|
||||
<button class="btn ghost" id="cc-gen-btn" onclick="showGenPrompt('cc')">✨ Сгенерировать код LLM</button>
|
||||
<button class="btn ghost" id="cc-exp-btn" onclick="aiExplain('cc-code','cc-lang','cc-ai-result')">📖 LLM: Что делает?</button>
|
||||
</div>
|
||||
<div style="margin-top:10px;">
|
||||
<label id="cc-deps-label" style="font-size:12px; color:var(--text-secondary); margin-bottom:4px; display:block;">Зависимости (requirements.txt)</label>
|
||||
<textarea id="cc-deps" rows="3" placeholder="boto3
|
||||
requests>=2.28
|
||||
psycopg2-binary" style="width:100%; box-sizing:border-box; font-family:monospace; font-size:12px; background:#1a1a2e; border:1px dashed #3a3a5c; border-radius:4px; color:#cdd6f4; padding:6px 8px; resize:none; overflow-y:auto;"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div id="cc-gen-prompt" style="display:none; margin-top:8px; gap:6px; align-items:center;">
|
||||
<input id="cc-gen-desc" type="text"
|
||||
@@ -445,6 +451,12 @@
|
||||
<button class="btn ghost" id="e-exp-btn" onclick="aiExplain('e-code','e-lang-hidden','e-ai-result')">📖
|
||||
LLM: Что делает?</button>
|
||||
</div>
|
||||
<div style="margin-top:10px;">
|
||||
<label id="e-deps-label" style="font-size:12px; color:var(--text-secondary); margin-bottom:4px; display:block;">Зависимости (requirements.txt)</label>
|
||||
<textarea id="e-deps" rows="3" placeholder="boto3
|
||||
requests>=2.28
|
||||
psycopg2-binary" style="width:100%; box-sizing:border-box; font-family:monospace; font-size:12px; background:#1a1a2e; border:1px dashed #3a3a5c; border-radius:4px; color:#cdd6f4; padding:6px 8px; resize:none; overflow-y:auto;"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div id="e-archive-area" style="display:none;">
|
||||
<div id="e-archive-current" style="margin-bottom:10px; padding:8px 12px; background:var(--bg-alt); border-radius:6px; font-size:13px; color:var(--text-secondary);">
|
||||
@@ -614,7 +626,7 @@
|
||||
</div>
|
||||
|
||||
<div class="actions" style="justify-content:space-between; align-items:center;">
|
||||
<span style="font-size:0.75rem; color:var(--text-secondary);">v1.3.88</span>
|
||||
<span style="font-size:0.75rem; color:var(--text-secondary);">v1.3.92</span>
|
||||
<button class="btn ghost" onclick="closeHelp()">Закрыть</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,43 @@
|
||||
/* ai.js — AI/LLM функции: проверка, генерация, объяснение, ассистент */
|
||||
|
||||
// PYTHON_STDLIB — стандартная библиотека Python (не требуют pip install).
|
||||
var PYTHON_STDLIB = new Set([
|
||||
'os','sys','json','time','re','math','io','collections','itertools','functools',
|
||||
'datetime','pathlib','urllib','http','threading','asyncio','logging','random',
|
||||
'string','struct','hashlib','hmac','base64','uuid','copy','abc','typing',
|
||||
'dataclasses','contextlib','enum','warnings','traceback','inspect','importlib',
|
||||
'subprocess','socket','ssl','email','html','xml','csv','sqlite3','unittest',
|
||||
'gc','weakref','array','queue','heapq','bisect','decimal','fractions',
|
||||
'statistics','textwrap','difflib','shutil','glob','fnmatch','tempfile',
|
||||
'zipfile','tarfile','gzip','bz2','lzma','pickle','shelve','codecs',
|
||||
'unicodedata','ast','dis','types','builtins','operator','numbers','cmath',
|
||||
'pprint','reprlib','platform','signal','mmap','ctypes','multiprocessing',
|
||||
'concurrent','select','selectors','errno','atexit','sched','calendar',
|
||||
'locale','gettext','argparse','getopt','shlex','configparser','tokenize',
|
||||
'runpy','pkgutil','site','sysconfig','distutils','zipimport','abc','io',
|
||||
'__future__','_thread','threading','contextvars','netrc','plistlib',
|
||||
'html','xml','http','urllib','email','mailbox','mimetypes','encodings',
|
||||
'codecs','unicodedata','readline','rlcompleter','curses','idlelib','tkinter',
|
||||
]);
|
||||
|
||||
// detectExternalPythonImports — возвращает список модулей, которых нет в stdlib.
|
||||
function detectExternalPythonImports(code) {
|
||||
var external = [];
|
||||
var seen = {};
|
||||
var patterns = [/^import\s+([\w]+)/gm, /^from\s+([\w]+)/gm];
|
||||
for (var pi = 0; pi < patterns.length; pi++) {
|
||||
var m;
|
||||
while ((m = patterns[pi].exec(code)) !== null) {
|
||||
var mod = m[1];
|
||||
if (!PYTHON_STDLIB.has(mod) && !seen[mod]) {
|
||||
seen[mod] = true;
|
||||
external.push(mod);
|
||||
}
|
||||
}
|
||||
}
|
||||
return external;
|
||||
}
|
||||
|
||||
function llmGeneratedWarning(lang) {
|
||||
var text = 'Сделано LLM. Не доверяй, проверяй!';
|
||||
switch (lang) {
|
||||
@@ -45,7 +83,25 @@ async function aiCheck(codeId, langId, resultId) {
|
||||
var data = await requestJSON(API_BASE + '/ai/check', 'POST', { language: lang, code: code });
|
||||
resEl.style.background = data.ok ? '#1a3a1a' : '#3a1a1a';
|
||||
resEl.style.color = data.ok ? '#8f8' : '#f88';
|
||||
resEl.textContent = data.result || '(пустой ответ)';
|
||||
var resultText = data.result || '(пустой ответ)';
|
||||
|
||||
// Для Python: проверяем внешние импорты vs поле зависимостей
|
||||
if (lang === 'python') {
|
||||
var depsId = codeId.replace('-code', '-deps'); // cc-code→cc-deps, e-code→e-deps
|
||||
var depsEl = document.getElementById(depsId);
|
||||
var depsVal = depsEl ? depsEl.value.trim() : '';
|
||||
var external = detectExternalPythonImports(code);
|
||||
if (external.length > 0 && !depsVal) {
|
||||
resultText += '\n\n⚠️ Внешние библиотеки: ' + external.join(', ') +
|
||||
'\nДобавьте их в поле "Зависимости (requirements.txt)" или убедитесь что они уже есть в Python-окружении.';
|
||||
if (data.ok) {
|
||||
resEl.style.background = '#3a2a00';
|
||||
resEl.style.color = '#ffa';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resEl.textContent = resultText;
|
||||
} catch (e) {
|
||||
resEl.style.background = '#3a2a00';
|
||||
resEl.style.color = '#ffa';
|
||||
|
||||
@@ -7,6 +7,23 @@ function onLangChangeCode() {
|
||||
document.getElementById('cc-entry').value = t.entrypoint;
|
||||
document.getElementById('cc-code').value = t.code;
|
||||
}
|
||||
// Обновляем лейбл поля зависимостей под язык
|
||||
var depsLabels = {
|
||||
python: 'Зависимости (requirements.txt)',
|
||||
nodejs: 'Зависимости (package.json dependencies)',
|
||||
php: 'Зависимости (composer.json)',
|
||||
ruby: 'Зависимости (Gemfile)',
|
||||
};
|
||||
var depsPlaceholders = {
|
||||
python: 'boto3\nrequests>=2.28\npsycopg2-binary',
|
||||
nodejs: 'express: ^4.18.2\naxios: ^1.6.0',
|
||||
php: '{\n "require": {\n "guzzlehttp/guzzle": "^7.0"\n }\n}',
|
||||
ruby: "gem 'httparty'\ngem 'pg'",
|
||||
};
|
||||
var label = document.getElementById('cc-deps-label');
|
||||
var area = document.getElementById('cc-deps');
|
||||
if (label) label.textContent = depsLabels[lang] || 'Зависимости';
|
||||
if (area) area.placeholder = depsPlaceholders[lang] || '';
|
||||
}
|
||||
|
||||
function openCreateCode() {
|
||||
@@ -16,6 +33,7 @@ function openCreateCode() {
|
||||
document.getElementById('cc-route').value = '';
|
||||
document.getElementById('cc-methods').value = 'GET';
|
||||
document.getElementById('cc-timeout').value = '60';
|
||||
document.getElementById('cc-deps').value = '';
|
||||
document.getElementById('cc-schedule-enabled').checked = false;
|
||||
document.getElementById('cc-cron').value = '';
|
||||
toggleScheduleFields('cc');
|
||||
@@ -47,7 +65,8 @@ async function submitCreateCode() {
|
||||
route: document.getElementById('cc-route').value.trim(),
|
||||
methods: parseMethods(document.getElementById('cc-methods').value),
|
||||
timeout: parseTimeout(document.getElementById('cc-timeout').value),
|
||||
code: document.getElementById('cc-code').value
|
||||
code: document.getElementById('cc-code').value,
|
||||
deps: document.getElementById('cc-deps').value.trim(),
|
||||
});
|
||||
|
||||
try {
|
||||
|
||||
@@ -188,6 +188,13 @@ async function openEdit(name) {
|
||||
else if (envName.includes('ruby')) lang = 'ruby';
|
||||
else if (envName.includes('php')) lang = 'php';
|
||||
document.getElementById('e-lang-hidden').value = lang;
|
||||
// Обновляем лейбл и плейсхолдер поля зависимостей
|
||||
var depsLabels = {python:'Зависимости (requirements.txt)', nodejs:'Зависимости (package.json dependencies)', php:'Зависимости (composer.json)', ruby:'Зависимости (Gemfile)'};
|
||||
var depsPlaceholders = {python:'boto3\nrequests>=2.28', nodejs:'express: ^4.18.2\naxios: ^1.6.0', php:'{\n "require": {\n "guzzlehttp/guzzle": "^7.0"\n }\n}', ruby:"gem 'httparty'\ngem 'pg'"};
|
||||
var eDepsLabel = document.getElementById('e-deps-label');
|
||||
var eDepsArea = document.getElementById('e-deps');
|
||||
if (eDepsLabel) eDepsLabel.textContent = depsLabels[lang] || 'Зависимости';
|
||||
if (eDepsArea) { eDepsArea.placeholder = depsPlaceholders[lang] || ''; eDepsArea.value = fn.deps || ''; }
|
||||
var aiRes = document.getElementById('e-ai-result');
|
||||
if (aiRes) { aiRes.style.display = 'none'; aiRes.textContent = ''; }
|
||||
var warnEl = document.getElementById('e-tf-warn');
|
||||
@@ -242,6 +249,7 @@ async function submitEdit() {
|
||||
} else {
|
||||
await requestJSON(API_BASE + '/functions/' + encodeURIComponent(name) + '/code', 'PUT', {
|
||||
code: document.getElementById('e-code').value,
|
||||
deps: document.getElementById('e-deps').value.trim(),
|
||||
timeout: parseTimeout(document.getElementById('e-timeout').value)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="126mm" height="91mm" viewBox="0 0 126 91" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<ellipse cx="63" cy="45.5" rx="63" ry="45.5" fill="#cccccc" stroke="#222222" stroke-width="1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 210 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="210mm" height="297mm" viewBox="0 0 210 297" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<ellipse cx="105" cy="148.5" rx="63" ry="45.5" fill="#cccccc" stroke="#222222" stroke-width="1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 214 B |
@@ -0,0 +1,105 @@
|
||||
#!/bin/bash
|
||||
# Fission storage benchmark: local-path PV / vcd-disk-ext4 PV / S3
|
||||
# Каждый вызов = полный путь: curl → Fission Router → Executor → Pod → Storage
|
||||
|
||||
set -e
|
||||
N=${1:-10} # число итераций, по умолчанию 10
|
||||
OUTDIR="$(cd "$(dirname "$0")/../../test-results" 2>/dev/null && pwd || echo /tmp)"
|
||||
OUTFILE="$OUTDIR/bench-storage-$(date +%Y-%m-%d_%H-%M-%S).txt"
|
||||
|
||||
mkdir -p "$OUTDIR"
|
||||
|
||||
# Port-forward router
|
||||
kubectl port-forward svc/router 8889:80 -n fission &>/tmp/pf-bench.log &
|
||||
PF_PID=$!
|
||||
trap "kill $PF_PID 2>/dev/null" EXIT
|
||||
sleep 3
|
||||
|
||||
ROUTER="http://localhost:8889"
|
||||
|
||||
# JWT auth
|
||||
PASSWORD=$(kubectl get secret router -n fission -o jsonpath="{.data.password}" | base64 -d)
|
||||
USERNAME=$(kubectl get secret router -n fission -o jsonpath="{.data.username}" | base64 -d)
|
||||
TOKEN=$(curl -s -X POST "$ROUTER/auth/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"username\":\"$USERNAME\",\"password\":\"$PASSWORD\"}" \
|
||||
| python3 -c "import sys,json; print(json.load(sys.stdin)['accesstoken'])")
|
||||
|
||||
if [ -z "$TOKEN" ]; then
|
||||
echo "ERROR: не удалось получить JWT токен" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
AUTH="-H \"Authorization: Bearer $TOKEN\""
|
||||
|
||||
# Функция запуска N итераций и сбора статистики
|
||||
bench_endpoint() {
|
||||
local LABEL="$1"
|
||||
local URL="$2"
|
||||
local times=()
|
||||
local errors=0
|
||||
|
||||
echo ""
|
||||
echo "=== $LABEL ==="
|
||||
printf "%-5s %-12s %s\n" "iter" "ms" "response"
|
||||
|
||||
for i in $(seq 1 $N); do
|
||||
START=$(date +%s%N)
|
||||
RESP=$(curl -s --max-time 60 -H "Authorization: Bearer $TOKEN" "$URL" 2>/dev/null)
|
||||
END=$(date +%s%N)
|
||||
MS=$(( (END - START) / 1000000 ))
|
||||
|
||||
if echo "$RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['status']=='ok'" 2>/dev/null; then
|
||||
STATUS="ok"
|
||||
INNER=$(echo "$RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); k=list(d.keys()); print(' '.join(f'{k}={d[k]}' for k in k if k not in ['status']))" 2>/dev/null)
|
||||
else
|
||||
STATUS="ERR"
|
||||
INNER="$RESP"
|
||||
(( errors++ )) || true
|
||||
fi
|
||||
|
||||
times+=($MS)
|
||||
printf "%-5s %-12s %s\n" "$i" "${MS}ms" "$STATUS $INNER"
|
||||
done
|
||||
|
||||
# Статистика
|
||||
local sum=0 min=999999999 max=0
|
||||
for t in "${times[@]}"; do
|
||||
sum=$((sum + t))
|
||||
[ $t -lt $min ] && min=$t
|
||||
[ $t -gt $max ] && max=$t
|
||||
done
|
||||
local avg=$((sum / N))
|
||||
local ok=$((N - errors))
|
||||
|
||||
echo "---"
|
||||
printf " Успешно: %d/%d | min=%dms avg=%dms max=%dms\n" "$ok" "$N" "$min" "$avg" "$max"
|
||||
}
|
||||
|
||||
# Заголовок отчёта
|
||||
{
|
||||
echo "============================================================"
|
||||
echo " Fission Storage Benchmark"
|
||||
echo " Дата: $(date)"
|
||||
echo " Итераций: $N на endpoint"
|
||||
echo " Стек: curl → Router → Executor → Pod → Storage"
|
||||
echo "============================================================"
|
||||
|
||||
bench_endpoint "local-path PV (100Mi, rawfile CSI, WaitForFirstConsumer)" \
|
||||
"$ROUTER/check/local"
|
||||
|
||||
bench_endpoint "vcd-disk-ext4 PV (10Gi, VMware Cloud Director)" \
|
||||
"$ROUTER/check/vcd"
|
||||
|
||||
bench_endpoint "S3 ngcloud (s3.msk-1.ngcloud.ru, bucket=sless-functions)" \
|
||||
"$ROUTER/check/s3"
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
echo " Завершено: $(date)"
|
||||
echo "============================================================"
|
||||
|
||||
} | tee "$OUTFILE"
|
||||
|
||||
echo ""
|
||||
echo "Результаты записаны: $OUTFILE"
|
||||
@@ -0,0 +1,31 @@
|
||||
import os
|
||||
import time
|
||||
|
||||
|
||||
def main(event, context):
|
||||
path = "/mnt/data/check.txt"
|
||||
test_data = "storage-check-ok"
|
||||
|
||||
try:
|
||||
# Write
|
||||
t0 = time.time()
|
||||
with open(path, "w") as f:
|
||||
f.write(test_data)
|
||||
write_ms = round((time.time() - t0) * 1000, 2)
|
||||
|
||||
# Read
|
||||
t0 = time.time()
|
||||
with open(path, "r") as f:
|
||||
result = f.read()
|
||||
read_ms = round((time.time() - t0) * 1000, 2)
|
||||
|
||||
# Cleanup
|
||||
os.remove(path)
|
||||
|
||||
if result == test_data:
|
||||
return {"status": "ok", "write_ms": write_ms, "read_ms": read_ms, "mount": path}
|
||||
else:
|
||||
return {"status": "error", "detail": "data mismatch"}
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "detail": str(e)}
|
||||
@@ -0,0 +1,48 @@
|
||||
import time
|
||||
import boto3
|
||||
from botocore.client import Config
|
||||
|
||||
|
||||
def main(event, context):
|
||||
# Credentials mounted by Fission at /secrets/<namespace>/<secretname>/<key>
|
||||
try:
|
||||
with open("/secrets/default/bench-s3-secret/access-key") as f:
|
||||
access_key = f.read().strip()
|
||||
with open("/secrets/default/bench-s3-secret/secret-key") as f:
|
||||
secret_key = f.read().strip()
|
||||
except Exception as e:
|
||||
return {"status": "error", "detail": f"secret read: {e}"}
|
||||
|
||||
s3 = boto3.client(
|
||||
"s3",
|
||||
endpoint_url="https://s3.msk-1.ngcloud.ru",
|
||||
aws_access_key_id=access_key,
|
||||
aws_secret_access_key=secret_key,
|
||||
config=Config(signature_version="s3v4"),
|
||||
)
|
||||
|
||||
bucket = "sless-functions"
|
||||
key = "storage-check/check.txt"
|
||||
test_data = b"storage-check-ok"
|
||||
|
||||
try:
|
||||
# Write
|
||||
t0 = time.time()
|
||||
s3.put_object(Bucket=bucket, Key=key, Body=test_data)
|
||||
write_ms = round((time.time() - t0) * 1000, 2)
|
||||
|
||||
# Read
|
||||
t0 = time.time()
|
||||
result = s3.get_object(Bucket=bucket, Key=key)["Body"].read()
|
||||
read_ms = round((time.time() - t0) * 1000, 2)
|
||||
|
||||
# Cleanup
|
||||
s3.delete_object(Bucket=bucket, Key=key)
|
||||
|
||||
if result == test_data:
|
||||
return {"status": "ok", "write_ms": write_ms, "read_ms": read_ms, "bucket": bucket}
|
||||
else:
|
||||
return {"status": "error", "detail": "data mismatch"}
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "detail": str(e)}
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
NS=default
|
||||
|
||||
echo "=== Removing HTTP triggers ==="
|
||||
fission httptrigger delete --name check-local-t --namespace $NS 2>/dev/null || true
|
||||
fission httptrigger delete --name check-vcd-t --namespace $NS 2>/dev/null || true
|
||||
fission httptrigger delete --name check-s3-t --namespace $NS 2>/dev/null || true
|
||||
|
||||
echo "=== Removing functions ==="
|
||||
fission fn delete --name check-local --namespace $NS 2>/dev/null || true
|
||||
fission fn delete --name check-vcd --namespace $NS 2>/dev/null || true
|
||||
fission fn delete --name check-s3 --namespace $NS 2>/dev/null || true
|
||||
|
||||
echo "=== Removing environment ==="
|
||||
fission env delete --name bench-py --namespace $NS 2>/dev/null || true
|
||||
|
||||
echo "=== Removing PVCs ==="
|
||||
kubectl delete pvc storage-check-local -n $NS 2>/dev/null || true
|
||||
kubectl delete pvc storage-check-vcd -n $NS 2>/dev/null || true
|
||||
|
||||
echo "=== Removing secret ==="
|
||||
kubectl delete secret bench-s3-secret -n $NS 2>/dev/null || true
|
||||
|
||||
echo "Done."
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
NS=default
|
||||
S3_KEY=0GLQRD38H4I6RBDB0EWJ
|
||||
S3_SECRET=eTFibiHmBd96IApj9PYsboTR6OBoD7osxoarHykw
|
||||
|
||||
echo "=== PVCs ==="
|
||||
kubectl apply -f pvc-local.yaml
|
||||
kubectl apply -f pvc-vcd.yaml
|
||||
|
||||
echo "=== S3 Secret ==="
|
||||
kubectl create secret generic bench-s3-secret \
|
||||
--from-literal=access-key="$S3_KEY" \
|
||||
--from-literal=secret-key="$S3_SECRET" \
|
||||
-n $NS 2>/dev/null || echo "secret already exists"
|
||||
|
||||
echo "=== Environment (no resource limits to avoid quota) ==="
|
||||
fission env create --name bench-py --image naeel/fission-python-env:v1.1 \
|
||||
--mincpu 0 --maxcpu 0 --minmemory 0 --maxmemory 0 \
|
||||
--poolsize 1 --namespace $NS 2>/dev/null || echo "env already exists"
|
||||
|
||||
echo "=== Functions ==="
|
||||
# check-local и check-vcd используют newdeploy: 1 под = 1 PVC (RWO работает)
|
||||
fission fn create --name check-local --env bench-py --code check_pv.py \
|
||||
--executortype newdeploy --minscale 1 --maxscale 1 --namespace $NS 2>/dev/null || \
|
||||
fission fn update --name check-local --code check_pv.py --namespace $NS
|
||||
|
||||
fission fn create --name check-vcd --env bench-py --code check_pv.py \
|
||||
--executortype newdeploy --minscale 1 --maxscale 1 --namespace $NS 2>/dev/null || \
|
||||
fission fn update --name check-vcd --code check_pv.py --namespace $NS
|
||||
|
||||
# check-s3 использует poolmgr
|
||||
fission fn create --name check-s3 --env bench-py --code check_s3.py \
|
||||
--namespace $NS --secret bench-s3-secret 2>/dev/null || \
|
||||
fission fn update --name check-s3 --code check_s3.py --namespace $NS --secret bench-s3-secret
|
||||
|
||||
echo "=== Patching podspec (PVC mounts) ==="
|
||||
kubectl patch function check-local -n $NS --type=merge -p "$(cat patch-local.json)"
|
||||
kubectl patch function check-vcd -n $NS --type=merge -p "$(cat patch-vcd.json)"
|
||||
|
||||
echo "=== HTTP Triggers ==="
|
||||
fission httptrigger create --name check-local-t --url /check/local --function check-local --method GET --namespace $NS 2>/dev/null || true
|
||||
fission httptrigger create --name check-vcd-t --url /check/vcd --function check-vcd --method GET --namespace $NS 2>/dev/null || true
|
||||
fission httptrigger create --name check-s3-t --url /check/s3 --function check-s3 --method GET --namespace $NS 2>/dev/null || true
|
||||
|
||||
echo ""
|
||||
echo "Done! Жди ~60s (newdeploy pods + PVC binding), затем: ./test.sh"
|
||||
@@ -0,0 +1,30 @@
|
||||
apiVersion: fission.io/v1
|
||||
kind: Function
|
||||
metadata:
|
||||
name: check-local
|
||||
namespace: default
|
||||
spec:
|
||||
InvokeStrategy:
|
||||
ExecutionStrategy:
|
||||
ExecutorType: newdeploy
|
||||
MinScale: 1
|
||||
MaxScale: 1
|
||||
StrategyType: execution
|
||||
environment:
|
||||
name: bench-py
|
||||
namespace: default
|
||||
package:
|
||||
functionName: main.main
|
||||
packageref:
|
||||
name: check-local-7d087421-7802-4af1-b111-03d71fe738cd
|
||||
namespace: default
|
||||
podspec:
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: storage-check-local
|
||||
containers:
|
||||
- name: bench-py
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /mnt/data
|
||||
@@ -0,0 +1,30 @@
|
||||
apiVersion: fission.io/v1
|
||||
kind: Function
|
||||
metadata:
|
||||
name: check-vcd
|
||||
namespace: default
|
||||
spec:
|
||||
InvokeStrategy:
|
||||
ExecutionStrategy:
|
||||
ExecutorType: newdeploy
|
||||
MinScale: 1
|
||||
MaxScale: 1
|
||||
StrategyType: execution
|
||||
environment:
|
||||
name: bench-py
|
||||
namespace: default
|
||||
package:
|
||||
functionName: main.main
|
||||
packageref:
|
||||
name: check-vcd-6e6076b2-2207-49f3-a12d-83fb50633285
|
||||
namespace: default
|
||||
podspec:
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: storage-check-vcd
|
||||
containers:
|
||||
- name: bench-py
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /mnt/data
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"spec": {
|
||||
"podspec": {
|
||||
"volumes": [
|
||||
{
|
||||
"name": "data",
|
||||
"persistentVolumeClaim": {
|
||||
"claimName": "storage-check-local"
|
||||
}
|
||||
}
|
||||
],
|
||||
"containers": [
|
||||
{
|
||||
"name": "bench-py",
|
||||
"volumeMounts": [
|
||||
{
|
||||
"name": "data",
|
||||
"mountPath": "/mnt/data"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"spec": {
|
||||
"podspec": {
|
||||
"volumes": [
|
||||
{
|
||||
"name": "data",
|
||||
"persistentVolumeClaim": {
|
||||
"claimName": "storage-check-vcd"
|
||||
}
|
||||
}
|
||||
],
|
||||
"containers": [
|
||||
{
|
||||
"name": "bench-py",
|
||||
"volumeMounts": [
|
||||
{
|
||||
"name": "data",
|
||||
"mountPath": "/mnt/data"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: storage-check-local
|
||||
namespace: default
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: local-path
|
||||
resources:
|
||||
requests:
|
||||
storage: 100Mi
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: storage-check-vcd
|
||||
namespace: default
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: vcd-disk-ext4
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Port-forward router to localhost:8888
|
||||
kubectl port-forward svc/router 8888:80 -n fission &>/tmp/pf.log &
|
||||
PF_PID=$!
|
||||
trap "kill $PF_PID 2>/dev/null" EXIT
|
||||
sleep 3
|
||||
|
||||
ROUTER="localhost:8888"
|
||||
echo "Router via port-forward: $ROUTER"
|
||||
echo ""
|
||||
|
||||
echo "=== [1/3] local-path ==="
|
||||
curl -sf --max-time 30 "http://$ROUTER/check/local" | python3 -m json.tool || echo "ERROR: no response"
|
||||
|
||||
echo ""
|
||||
echo "=== [2/3] vcd-disk-ext4 ==="
|
||||
curl -sf --max-time 30 "http://$ROUTER/check/vcd" | python3 -m json.tool || echo "ERROR: no response"
|
||||
|
||||
echo ""
|
||||
echo "=== [3/3] S3 (ngcloud) ==="
|
||||
curl -sf --max-time 30 "http://$ROUTER/check/s3" | python3 -m json.tool || echo "ERROR: no response"
|
||||
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"dashboard": {
|
||||
"title": "Weather Metrics",
|
||||
"uid": "weather-metrics",
|
||||
"timezone": "browser",
|
||||
"refresh": "1m",
|
||||
"time": { "from": "now-6h", "to": "now" },
|
||||
"panels": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "timeseries",
|
||||
"title": "Temperature by City (°C)",
|
||||
"gridPos": { "x": 0, "y": 0, "w": 24, "h": 10 },
|
||||
"datasource": { "type": "grafana-postgresql-datasource", "uid": "fission-pg" },
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "celsius",
|
||||
"custom": { "lineWidth": 2, "drawStyle": "line", "showPoints": "never", "spanNulls": true }
|
||||
}
|
||||
},
|
||||
"options": { "legend": { "displayMode": "table", "placement": "right" } },
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"rawSql": "SELECT\n recorded_at AS time,\n temperature,\n city\nFROM weather_metrics\nWHERE $__timeFilter(recorded_at)\nORDER BY recorded_at",
|
||||
"format": "time_series"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "table",
|
||||
"title": "Current Conditions",
|
||||
"gridPos": { "x": 0, "y": 10, "w": 24, "h": 8 },
|
||||
"datasource": { "type": "grafana-postgresql-datasource", "uid": "fission-pg" },
|
||||
"options": { "sortBy": [{ "displayName": "City" }] },
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"rawSql": "SELECT DISTINCT ON (city)\n city,\n country,\n temperature,\n feels_like,\n humidity,\n wind_speed,\n description,\n recorded_at\nFROM weather_metrics\nORDER BY city, recorded_at DESC",
|
||||
"format": "table"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "timeseries",
|
||||
"title": "Humidity by City (%)",
|
||||
"gridPos": { "x": 0, "y": 18, "w": 12, "h": 8 },
|
||||
"datasource": { "type": "grafana-postgresql-datasource", "uid": "fission-pg" },
|
||||
"fieldConfig": { "defaults": { "unit": "percent", "min": 0, "max": 100, "custom": { "lineWidth": 2, "drawStyle": "line", "showPoints": "never", "spanNulls": true } } },
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"rawSql": "SELECT\n recorded_at AS time,\n humidity,\n city\nFROM weather_metrics\nWHERE $__timeFilter(recorded_at)\nORDER BY recorded_at",
|
||||
"format": "time_series"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "timeseries",
|
||||
"title": "Wind Speed by City (m/s)",
|
||||
"gridPos": { "x": 12, "y": 18, "w": 12, "h": 8 },
|
||||
"datasource": { "type": "grafana-postgresql-datasource", "uid": "fission-pg" },
|
||||
"fieldConfig": { "defaults": { "unit": "velocityms", "custom": { "lineWidth": 2, "drawStyle": "line", "showPoints": "never", "spanNulls": true } } },
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"rawSql": "SELECT\n recorded_at AS time,\n wind_speed,\n city\nFROM weather_metrics\nWHERE $__timeFilter(recorded_at)\nORDER BY recorded_at",
|
||||
"format": "time_series"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"schemaVersion": 38
|
||||
},
|
||||
"overwrite": true
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# test_layer1.sh — простой тест пропатченного Fission NSWatcher (Layer 1 only).
|
||||
# Без консоли. Только kubectl + fission CLI.
|
||||
#
|
||||
# Что проверяем:
|
||||
# 1. Создаём NS с меткой fission.io/managed=true
|
||||
# 2. Executor регистрирует его через NSWatcher (лог)
|
||||
# 3. Создаём Python env + function в новом NS
|
||||
# 4. Вызываем функцию — должна ответить 200
|
||||
#
|
||||
# Запуск: bash ~/terra/fission/test_layer1.sh
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
NS="l1-test-$(date +%s | tail -c 6)"
|
||||
PASS=0; FAIL=0
|
||||
|
||||
green() { echo -e "\033[32m PASS: $1\033[0m"; PASS=$((PASS+1)); }
|
||||
red() { echo -e "\033[31m FAIL: $1\033[0m"; FAIL=$((FAIL+1)); }
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " Layer 1 NSWatcher — простой тест"
|
||||
echo " NS: $NS"
|
||||
echo "========================================"
|
||||
|
||||
# ── 1. Создаём NS с меткой ────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo ">>> [1/5] Создаём NS $NS с меткой fission.io/managed=true..."
|
||||
kubectl create ns "$NS"
|
||||
kubectl label ns "$NS" fission.io/managed=true --overwrite
|
||||
|
||||
kubectl get ns "$NS" --show-labels | grep "fission.io/managed"
|
||||
if [ $? -eq 0 ]; then
|
||||
green "NS создан с меткой"
|
||||
else
|
||||
red "Метка не применилась"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── 2. Ждём регистрации в executor ────────────────────────────────────────────
|
||||
echo ""
|
||||
echo ">>> [2/5] Ждём регистрации NS в executor (до 15 сек)..."
|
||||
|
||||
DEADLINE=$((SECONDS + 15))
|
||||
REGISTERED=false
|
||||
while [ $SECONDS -lt $DEADLINE ]; do
|
||||
if kubectl logs -n fission deployment/executor 2>/dev/null | grep "registered namespace" | grep -q "$NS"; then
|
||||
REGISTERED=true
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ "$REGISTERED" = "true" ]; then
|
||||
green "Executor зарегистрировал NS $NS"
|
||||
else
|
||||
red "Executor НЕ зарегистрировал NS за 15 сек"
|
||||
echo " Лог executor (последние строки про namespace):"
|
||||
kubectl logs -n fission deployment/executor 2>/dev/null | grep "namespace" | tail -5
|
||||
fi
|
||||
|
||||
# ── 3. Создаём environment в новом NS ─────────────────────────────────────────
|
||||
echo ""
|
||||
echo ">>> [3/5] Создаём Python environment в NS $NS..."
|
||||
|
||||
fission env create \
|
||||
--name py-env \
|
||||
--namespace "$NS" \
|
||||
--image ghcr.io/fission/python-env:latest \
|
||||
--poolsize 1 2>&1
|
||||
|
||||
if kubectl get environment py-env -n "$NS" &>/dev/null; then
|
||||
green "Environment py-env создан в $NS"
|
||||
else
|
||||
red "Environment не создался"
|
||||
fi
|
||||
|
||||
# ── 4. Создаём функцию ────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo ">>> [4/5] Создаём функцию hello..."
|
||||
|
||||
cat > /tmp/hello.py << 'EOF'
|
||||
def main():
|
||||
return "hello from layer1"
|
||||
EOF
|
||||
|
||||
fission fn create \
|
||||
--name hello \
|
||||
--namespace "$NS" \
|
||||
--env py-env \
|
||||
--code /tmp/hello.py 2>&1
|
||||
|
||||
fission httptrigger create \
|
||||
--name hello-route \
|
||||
--namespace "$NS" \
|
||||
--function hello \
|
||||
--url "/${NS}/hello" \
|
||||
--method GET 2>&1
|
||||
|
||||
if kubectl get httptrigger hello-route -n "$NS" &>/dev/null; then
|
||||
green "Function + HTTPTrigger созданы"
|
||||
else
|
||||
red "HTTPTrigger не создался"
|
||||
fi
|
||||
|
||||
# ── 5. Вызываем функцию через внутренний router с JWT ────────────────────────
|
||||
echo ""
|
||||
echo ">>> [5/5] Вызываем функцию через internal router + JWT (cold start до 180 сек)..."
|
||||
|
||||
ROUTER_INT="http://router.fission.svc.cluster.local"
|
||||
DEADLINE=$((SECONDS + 180))
|
||||
FN_OK=false
|
||||
|
||||
while [ $SECONDS -lt $DEADLINE ]; do
|
||||
RESULT=$(kubectl run fn-probe-$$ --rm -i --restart=Never \
|
||||
--image=curlimages/curl:8.7.1 \
|
||||
--namespace=fission \
|
||||
-- sh -c "
|
||||
TOKEN=\$(curl -sf --max-time 15 -X POST ${ROUTER_INT}/auth/login \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{\"username\":\"admin\",\"password\":\"7XG1lSg0EFqPLE4pf3He\"}' \
|
||||
| grep -o '\"accesstoken\":\"[^\"]*' | cut -d'\"' -f4)
|
||||
if [ -z \"\$TOKEN\" ]; then echo 'LOGIN_FAIL|||000'; exit 0; fi
|
||||
curl -s -w '|||%{http_code}' --max-time 90 \
|
||||
-H \"Authorization: Bearer \$TOKEN\" \
|
||||
${ROUTER_INT}/${NS}/hello
|
||||
" 2>/dev/null | grep "|||" || echo "|||000")
|
||||
CODE=$(echo "$RESULT" | grep -o '|||[0-9]*' | tr -d '|||')
|
||||
BODY=$(echo "$RESULT" | sed 's/|||[0-9]*$//')
|
||||
echo " HTTP $CODE — $(echo "$BODY" | head -c 80)"
|
||||
[ "$CODE" = "200" ] && FN_OK=true && break
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [ "$FN_OK" = "true" ]; then
|
||||
green "Функция ответила 200: $BODY"
|
||||
else
|
||||
red "Функция не ответила 200 (timeout)"
|
||||
echo " Логи router (last 3):"
|
||||
kubectl logs -n fission deployment/router 2>/dev/null | tail -3
|
||||
fi
|
||||
|
||||
# ── Cleanup ───────────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo ">>> Cleanup..."
|
||||
fission httptrigger delete --name hello-route --namespace "$NS" 2>/dev/null || true
|
||||
fission fn delete --name hello --namespace "$NS" 2>/dev/null || true
|
||||
fission env delete --name py-env --namespace "$NS" 2>/dev/null || true
|
||||
kubectl delete ns "$NS" --wait=false 2>/dev/null || true
|
||||
echo " Готово."
|
||||
|
||||
# ── Итог ─────────────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " ИТОГ: PASS=$PASS FAIL=$FAIL"
|
||||
echo "========================================"
|
||||
[ $FAIL -eq 0 ] && exit 0 || exit 1
|
||||
@@ -1,66 +0,0 @@
|
||||
ntazetdinov@nubes.ru
|
||||
stxVxLvM9eqssgZ
|
||||
|
||||
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoLWFwaSIsInN1YiI6IjAxOWQ3NmM1LTQyNGMtN2MyYy04OWI3LWUzZjA5OTQ5YTk4ZiIsImV4cCI6MTc5MzA3ODQzOSwiaWF0IjoxNzc3NTI2NDM5LCJqdGkiOiI4MTU1ODNkNy0xZTRkLTRiMmMtOGM5ZS0wZDZmMGU2NmJjNTciLCJhdXRoX3RpbWUiOjAsInR5cCI6IiIsImF6cCI6IiIsInNlc3Npb25fc3RhdGUiOiIiLCJhY3IiOiIiLCJhbGxvd2VkLW9yaWdpbnMiOm51bGwsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6bnVsbH0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpudWxsfX0sInNjb3BlIjoiIiwic2lkIjoiIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoiIiwiQ2xpZW50SUQiOiJXWjAxMTEyIiwiY29tcGFueV9pZCI6IjhlYzcwYWMwLTU0NmQtNDJhNy04Y2ZmLTMzOWM4ZmI1MWEyMyIsImNvbXBhbnlfbmFtZSI6ItCe0J7QniDCq9Cd0KPQkdCV0KHCuyIsImlkcF91c3JfdWlkIjoiMDE5ZDc2YzUtNDI0Yy03YzJjLTg5YjctZTNmMDk5NDlhOThmIiwibG9naW4iOiJudGF6ZXRkaW5vdkBudWJlcy5ydSIsImZpcnN0bmFtZSI6ItCd0LDQuNC70YwiLCJtaWRkbGVuYW1lIjoi0KTQsNGA0LjRgtC-0LLQuNGHIiwibGFzdG5hbWUiOiLQotCw0LfQtdGC0LTQuNC90L7QsiIsImdyb3VwcyI6bnVsbCwicHJlZmVycmVkX3VzZXJuYW1lIjoiIiwiZ2l2ZW5fbmFtZSI6IiIsImZhbWlseV9uYW1lIjoiIiwiZW1haWwiOiJudGF6ZXRkaW5vdkBudWJlcy5ydSJ9.SaZsKAha45-fvKuJkLHUe_09AFsbH5QpzBVdPnjiEDQGIhl1A3ThgnM-oEh_H6CBXXfnOs2QDczzgCD8K8_tYzZKU1Wgk5lj04YW_fbTI89kHTO5wLtrAht9tFEjzBQZ-kmnG8mUK5tgqyNEjsngdQcfqVWRvneF366TiiRXk_76poUPpXWQmqdgCCKb3wq1rRgWcKDZcUC3JvDpOaqYL40zITZlM855drlJhMut4Gkg-EEk7ZGykl6YCsTRWVjFKGQ-7C-BDU2dkmIWhxzBAkvzzM2kBwvtlczCEDiUin7bXE-F_io89oJUYDzY4yPeHzwREwxCp6x9eD1NAIKKuA
|
||||
|
||||
----------- token on VM --------------
|
||||
https://deck-api-test.ngcloud.ru/api/v1 - для тест стенда где постгрес, файл с токеном -
|
||||
naeel@naeel-vm:~/terra/sless$ ll test.token
|
||||
-rw-r--r-- 1 naeel naeel 998 Feb 28 07:18 test.token
|
||||
|
||||
--------------------- pg IoT-PG -----------------------
|
||||
users
|
||||
JSON объект (раскройте для просмотра)
|
||||
|
||||
password:
|
||||
BQUF5ruECa1ZFlq4wYt3gPJUEmtBMkA9QNK4MM5Sd8al4ArMDlmT16DIKHYBPyif
|
||||
|
||||
username:
|
||||
super
|
||||
|
||||
Поля состояния (сгенерированные значения)
|
||||
monitoring
|
||||
{
|
||||
"resourceMetrics": "https://grafana.ngcloud.ru/d/vzjb4zd/kubernetes-pods?orgId=954&var-namespace=dc5db45d-f8b4-4fd0-ad33-ec4dd017f2d5&var-resource_realm=iot-naeel"
|
||||
}
|
||||
|
||||
users
|
||||
{
|
||||
"super": {
|
||||
"role": "ddl_user",
|
||||
"username": "super"
|
||||
}
|
||||
}
|
||||
|
||||
backups
|
||||
{}
|
||||
|
||||
databases
|
||||
{
|
||||
"sqsdb": {
|
||||
"dbName": "sqsdb",
|
||||
"dbOwner": "super"
|
||||
}
|
||||
}
|
||||
|
||||
externalConnect
|
||||
{
|
||||
"slave": {
|
||||
"ip": "",
|
||||
"fqdn": "",
|
||||
"uuid": "",
|
||||
"isExternal": false
|
||||
},
|
||||
"master": {
|
||||
"ip": "",
|
||||
"fqdn": "",
|
||||
"uuid": "",
|
||||
"isExternal": false
|
||||
}
|
||||
}
|
||||
|
||||
internalConnect
|
||||
{
|
||||
"slave": "",
|
||||
"master": "postgresqlk8s-master.dc5db45d-f8b4-4fd0-ad33-ec4dd017f2d5.svc.cluster.local"
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<svg width="310mm" height="110mm" viewBox="0 0 310 110" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- ОДНО наклонное кольцо: верхняя кромка синусоида y=40+40*sin(πx/L) -->
|
||||
<polyline fill="none" stroke="#1976d2" stroke-width="2" points="
|
||||
0,40 5,39.47 10,38.95 15,38.45 20,37.95 25,37.48 30,37.02 35,36.59 40,36.18 45,35.80 50,35.45 55,35.13 60,34.84 65,34.59 70,34.37 75,34.19 80,34.05 85,33.95 90,33.88 95,33.86 100,33.87 105,33.93 110,34.02 115,34.15 120,34.32 125,34.53 130,34.78 135,35.06 140,35.38 145,35.73 150,36.12 155,36.54 160,37.00 165,37.48 170,38.00 175,38.54 180,39.11 185,39.70 190,40.31 195,40.94 200,41.59 205,42.24 210,42.90 215,43.56 220,44.22 225,44.86 230,45.48 235,46.08 240,46.64 245,47.16 250,47.63 255,48.05 260,48.41 265,48.70 270,48.92 275,49.06 280,49.12 285,49.09 290,48.97 295,48.76 300,48.45 305,48.04
|
||||
<!-- Нижняя кромка синусоида y=60+40*sin(πx/L) -->
|
||||
<polyline fill="none" stroke="#d32f2f" stroke-width="2" points="
|
||||
0,60 5,59.47 10,58.95 15,58.45 20,57.95 25,57.48 30,57.02 35,56.59 40,56.18 45,55.80 50,55.45 55,55.13 60,54.84 65,54.59 70,54.37 75,54.19 80,54.05 85,53.95 90,53.88 95,53.86 100,53.87 105,53.93 110,54.02 115,54.15 120,54.32 125,54.53 130,54.78 135,55.06 140,55.38 145,55.73 150,56.12 155,56.54 160,57.00 165,57.48 170,58.00 175,58.54 180,59.11 185,59.70 190,60.31 195,60.94 200,61.59 205,62.24 210,62.90 215,63.56 220,64.22 225,64.86 230,65.48 235,66.08 240,66.64 245,67.16 250,67.63 255,68.05 260,68.41 265,68.70 270,68.92 275,69.06 280,69.12 285,69.09 290,68.97 295,68.76 300,68.45 305,68.04
|
||||
" />
|
||||
<!-- Заливка между кромками -->
|
||||
<polygon fill="#e0f7fa" fill-opacity="0.6" stroke="none" points="
|
||||
0,40 5,39.47 10,38.95 15,38.45 20,37.95 25,37.48 30,37.02 35,36.59 40,36.18 45,35.80 50,35.45 55,35.13 60,34.84 65,34.59 70,34.37 75,34.19 80,34.05 85,33.95 90,33.88 95,33.86 100,33.87 105,33.93 110,34.02 115,34.15 120,34.32 125,34.53 130,34.78 135,35.06 140,35.38 145,35.73 150,36.12 155,36.54 160,37.00 165,37.48 170,38.00 175,38.54 180,39.11 185,39.70 190,40.31 195,40.94 200,41.59 205,42.24 210,42.90 215,43.56 220,44.22 225,44.86 230,45.48 235,46.08 240,46.64 245,47.16 250,47.63 255,48.05 260,48.41 265,48.70 270,48.92 275,49.06 280,49.12 285,49.09 290,48.97 295,48.76 300,48.45 305,48.04
|
||||
305,68.04 300,68.45 295,68.76 290,68.97 285,69.09 280,69.12 275,69.06 270,68.92 265,68.70 260,68.41 255,68.05 250,67.63 245,67.16 240,66.64 235,66.08 230,65.48 225,64.86 220,64.22 215,63.56 210,62.90 205,62.24 200,61.59 195,60.94 190,60.31 185,59.70 180,59.11 175,58.54 170,58.00 165,57.48 160,57.00 155,56.54 150,56.12 145,55.73 140,55.38 135,55.06 130,54.78 125,54.53 120,54.32 115,54.15 110,54.02 105,53.93 100,53.87 95,53.86 90,53.88 85,53.95 80,54.05 75,54.19 70,54.37 65,54.59 60,54.84 55,55.13 50,55.45 45,55.80 40,56.18 35,56.59 30,57.02 25,57.48 20,57.95 15,58.45 10,58.95 5,59.47 0,60
|
||||
" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="301.6mm" height="120mm" viewBox="0 0 301.6 120" xmlns="http://www.w3.org/2000/svg">
|
||||
<polyline fill="none" stroke="#1976d2" stroke-width="1.5" points="0.0,50.00 3.0,52.51 6.0,55.01 9.0,57.50 12.1,59.95 15.1,62.36 18.1,64.72 21.1,67.03 24.1,69.27 27.1,71.43 30.2,73.51 33.2,75.50 36.2,77.38 39.2,79.16 42.2,80.82 45.2,82.36 48.3,83.77 51.3,85.05 54.3,86.19 57.3,87.19 60.3,88.04 63.3,88.74 66.4,89.29 69.4,89.68 72.4,89.92 75.4,90.00 78.4,89.92 81.4,89.68 84.4,89.29 87.5,88.74 90.5,88.04 93.5,87.19 96.5,86.19 99.5,85.05 102.5,83.77 105.6,82.36 108.6,80.82 111.6,79.16 114.6,77.38 117.6,75.50 120.6,73.51 123.7,71.43 126.7,69.27 129.7,67.03 132.7,64.72 135.7,62.36 138.7,59.95 141.7,57.50 144.8,55.01 147.8,52.51 150.8,50.00 153.8,47.49 156.8,44.99 159.8,42.50 162.9,40.05 165.9,37.64 168.9,35.28 171.9,32.97 174.9,30.73 177.9,28.57 181.0,26.49 184.0,24.50 187.0,22.62 190.0,20.84 193.0,19.18 196.0,17.64 199.1,16.23 202.1,14.95 205.1,13.81 208.1,12.81 211.1,11.96 214.1,11.26 217.1,10.71 220.2,10.32 223.2,10.08 226.2,10.00 229.2,10.08 232.2,10.32 235.2,10.71 238.3,11.26 241.3,11.96 244.3,12.81 247.3,13.81 250.3,14.95 253.3,16.23 256.4,17.64 259.4,19.18 262.4,20.84 265.4,22.62 268.4,24.50 271.4,26.49 274.4,28.57 277.5,30.73 280.5,32.97 283.5,35.28 286.5,37.64 289.5,40.05 292.5,42.50 295.6,44.99 298.6,47.49 301.6,50.00" />
|
||||
<polyline fill="none" stroke="#d32f2f" stroke-width="1.5" points="0.0,70.00 3.0,72.51 6.0,75.01 9.0,77.50 12.1,79.95 15.1,82.36 18.1,84.72 21.1,87.03 24.1,89.27 27.1,91.43 30.2,93.51 33.2,95.50 36.2,97.38 39.2,99.16 42.2,100.82 45.2,102.36 48.3,103.77 51.3,105.05 54.3,106.19 57.3,107.19 60.3,108.04 63.3,108.74 66.4,109.29 69.4,109.68 72.4,109.92 75.4,110.00 78.4,109.92 81.4,109.68 84.4,109.29 87.5,108.74 90.5,108.04 93.5,107.19 96.5,106.19 99.5,105.05 102.5,103.77 105.6,102.36 108.6,100.82 111.6,99.16 114.6,97.38 117.6,95.50 120.6,93.51 123.7,91.43 126.7,89.27 129.7,87.03 132.7,84.72 135.7,82.36 138.7,79.95 141.7,77.50 144.8,75.01 147.8,72.51 150.8,70.00 153.8,67.49 156.8,64.99 159.8,62.50 162.9,60.05 165.9,57.64 168.9,55.28 171.9,52.97 174.9,50.73 177.9,48.57 181.0,46.49 184.0,44.50 187.0,42.62 190.0,40.84 193.0,39.18 196.0,37.64 199.1,36.23 202.1,34.95 205.1,33.81 208.1,32.81 211.1,31.96 214.1,31.26 217.1,30.71 220.2,30.32 223.2,30.08 226.2,30.00 229.2,30.08 232.2,30.32 235.2,30.71 238.3,31.26 241.3,31.96 244.3,32.81 247.3,33.81 250.3,34.95 253.3,36.23 256.4,37.64 259.4,39.18 262.4,40.84 265.4,42.62 268.4,44.50 271.4,46.49 274.4,48.57 277.5,50.73 280.5,52.97 283.5,55.28 286.5,57.64 289.5,60.05 292.5,62.50 295.6,64.99 298.6,67.49 301.6,70.00" />
|
||||
<polygon fill="#e0f7fa" fill-opacity="0.5" stroke="none" points="0.0,50.00 3.0,52.51 6.0,55.01 9.0,57.50 12.1,59.95 15.1,62.36 18.1,64.72 21.1,67.03 24.1,69.27 27.1,71.43 30.2,73.51 33.2,75.50 36.2,77.38 39.2,79.16 42.2,80.82 45.2,82.36 48.3,83.77 51.3,85.05 54.3,86.19 57.3,87.19 60.3,88.04 63.3,88.74 66.4,89.29 69.4,89.68 72.4,89.92 75.4,90.00 78.4,89.92 81.4,89.68 84.4,89.29 87.5,88.74 90.5,88.04 93.5,87.19 96.5,86.19 99.5,85.05 102.5,83.77 105.6,82.36 108.6,80.82 111.6,79.16 114.6,77.38 117.6,75.50 120.6,73.51 123.7,71.43 126.7,69.27 129.7,67.03 132.7,64.72 135.7,62.36 138.7,59.95 141.7,57.50 144.8,55.01 147.8,52.51 150.8,50.00 153.8,47.49 156.8,44.99 159.8,42.50 162.9,40.05 165.9,37.64 168.9,35.28 171.9,32.97 174.9,30.73 177.9,28.57 181.0,26.49 184.0,24.50 187.0,22.62 190.0,20.84 193.0,19.18 196.0,17.64 199.1,16.23 202.1,14.95 205.1,13.81 208.1,12.81 211.1,11.96 214.1,11.26 217.1,10.71 220.2,10.32 223.2,10.08 226.2,10.00 229.2,10.08 232.2,10.32 235.2,10.71 238.3,11.26 241.3,11.96 244.3,12.81 247.3,13.81 250.3,14.95 253.3,16.23 256.4,17.64 259.4,19.18 262.4,20.84 265.4,22.62 268.4,24.50 271.4,26.49 274.4,28.57 277.5,30.73 280.5,32.97 283.5,35.28 286.5,37.64 289.5,40.05 292.5,42.50 295.6,44.99 298.6,47.49 301.6,50.00 301.6,70.00 298.6,67.49 295.6,64.99 292.5,62.50 289.5,60.05 286.5,57.64 283.5,55.28 280.5,52.97 277.5,50.73 274.4,48.57 271.4,46.49 268.4,44.50 265.4,42.62 262.4,40.84 259.4,39.18 256.4,37.64 253.3,36.23 250.3,34.95 247.3,33.81 244.3,32.81 241.3,31.96 238.3,31.26 235.2,30.71 232.2,30.32 229.2,30.08 226.2,30.00 223.2,30.08 220.2,30.32 217.1,30.71 214.1,31.26 211.1,31.96 208.1,32.81 205.1,33.81 202.1,34.95 199.1,36.23 196.0,37.64 193.0,39.18 190.0,40.84 187.0,42.62 184.0,44.50 181.0,46.49 177.9,48.57 174.9,50.73 171.9,52.97 168.9,55.28 165.9,57.64 162.9,60.05 159.8,62.50 156.8,64.99 153.8,67.49 150.8,70.00 147.8,72.51 144.8,75.01 141.7,77.50 138.7,79.95 135.7,82.36 132.7,84.72 129.7,87.03 126.7,89.27 123.7,91.43 120.6,93.51 117.6,95.50 114.6,97.38 111.6,99.16 108.6,100.82 105.6,102.36 102.5,103.77 99.5,105.05 96.5,106.19 93.5,107.19 90.5,108.04 87.5,108.74 84.4,109.29 81.4,109.68 78.4,109.92 75.4,110.00 72.4,109.92 69.4,109.68 66.4,109.29 63.3,108.74 60.3,108.04 57.3,107.19 54.3,106.19 51.3,105.05 48.3,103.77 45.2,102.36 42.2,100.82 39.2,99.16 36.2,97.38 33.2,95.50 30.2,93.51 27.1,91.43 24.1,89.27 21.1,87.03 18.1,84.72 15.1,82.36 12.1,79.95 9.0,77.50 6.0,75.01 3.0,72.51 0.0,70.00" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
@@ -0,0 +1,3 @@
|
||||
Диаметр цилиндра: 96 мм
|
||||
Вертикальная проекция большой оси эллипса: 80 мм
|
||||
Расстояние между срезами по вертикали: 20 мм
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="150.8mm" height="120mm" viewBox="0 0 150.8 120" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Часть 1 (левая) -->
|
||||
<polygon fill="#e0f7fa" fill-opacity="0.5" stroke="none" points="0.00,50.00 1.51,51.26 3.02,52.51 4.52,53.76 6.03,55.01 7.54,56.26 9.05,57.50 10.56,58.73 12.06,59.95 13.57,61.16 15.08,62.36 16.59,63.55 18.10,64.72 19.60,65.89 21.11,67.03 22.62,68.16 24.13,69.27 25.64,70.36 27.14,71.43 28.65,72.48 30.16,73.51 31.67,74.52 33.18,75.50 34.68,76.45 36.19,77.38 37.70,78.28 39.21,79.16 40.72,80.00 42.22,80.82 43.73,81.61 45.24,82.36 46.75,83.08 48.25,83.77 49.76,84.43 51.27,85.05 52.78,85.64 54.29,86.19 55.79,86.71 57.30,87.19 58.81,87.64 60.32,88.04 61.83,88.41 63.33,88.74 64.84,89.04 66.35,89.29 67.86,89.51 69.37,89.68 70.87,89.82 72.38,89.92 73.89,89.98 75.40,90.00 76.91,89.98 78.41,89.92 79.92,89.82 81.43,89.68 82.94,89.51 84.45,89.29 85.95,89.04 87.46,88.74 88.97,88.41 90.48,88.04 91.99,87.64 93.49,87.19 95.00,86.71 96.51,86.19 98.02,85.64 99.53,85.05 101.03,84.43 102.54,83.77 104.05,83.08 105.56,82.36 107.07,81.61 108.57,80.82 110.08,80.00 111.59,79.16 113.10,78.28 114.61,77.38 116.11,76.45 117.62,75.50 119.13,74.52 120.64,73.51 122.15,72.48 123.65,71.43 125.16,70.36 126.67,69.27 128.18,68.16 129.68,67.03 131.19,65.89 132.70,64.72 134.21,63.55 135.72,62.36 137.22,61.16 138.73,59.95 140.24,58.73 141.75,57.50 143.26,56.26 144.76,55.01 146.27,53.76 147.78,52.51 149.29,51.26 150.80,50.00 150.80,70.00 149.29,71.26 147.78,72.51 146.27,73.76 144.76,75.01 143.26,76.26 141.75,77.50 140.24,78.73 138.73,79.95 137.22,81.16 135.72,82.36 134.21,83.55 132.70,84.72 131.19,85.89 129.68,87.03 128.18,88.16 126.67,89.27 125.16,90.36 123.65,91.43 122.15,92.48 120.64,93.51 119.13,94.52 117.62,95.50 116.11,96.45 114.61,97.38 113.10,98.28 111.59,99.16 110.08,100.00 108.57,100.82 107.07,101.61 105.56,102.36 104.05,103.08 102.54,103.77 101.03,104.43 99.53,105.05 98.02,105.64 96.51,106.19 95.00,106.71 93.49,107.19 91.99,107.64 90.48,108.04 88.97,108.41 87.46,108.74 85.95,109.04 84.45,109.29 82.94,109.51 81.43,109.68 79.92,109.82 78.41,109.92 76.91,109.98 75.40,110.00 73.89,109.98 72.38,109.92 70.87,109.82 69.37,109.68 67.86,109.51 66.35,109.29 64.84,109.04 63.33,108.74 61.83,108.41 60.32,108.04 58.81,107.64 57.30,107.19 55.79,106.71 54.29,106.19 52.78,105.64 51.27,105.05 49.76,104.43 48.25,103.77 46.75,103.08 45.24,102.36 43.73,101.61 42.22,100.82 40.72,100.00 39.21,99.16 37.70,98.28 36.19,97.38 34.68,96.45 33.18,95.50 31.67,94.52 30.16,93.51 28.65,92.48 27.14,91.43 25.64,90.36 24.13,89.27 22.62,88.16 21.11,87.03 19.60,85.89 18.10,84.72 16.59,83.55 15.08,82.36 13.57,81.16 12.06,79.95 10.56,78.73 9.05,77.50 7.54,76.26 6.03,75.01 4.52,73.76 3.02,72.51 1.51,71.26 0.00,70.00" />
|
||||
<polyline fill="none" stroke="#1976d2" stroke-width="1.5" points="0.00,50.00 1.51,51.26 3.02,52.51 4.52,53.76 6.03,55.01 7.54,56.26 9.05,57.50 10.56,58.73 12.06,59.95 13.57,61.16 15.08,62.36 16.59,63.55 18.10,64.72 19.60,65.89 21.11,67.03 22.62,68.16 24.13,69.27 25.64,70.36 27.14,71.43 28.65,72.48 30.16,73.51 31.67,74.52 33.18,75.50 34.68,76.45 36.19,77.38 37.70,78.28 39.21,79.16 40.72,80.00 42.22,80.82 43.73,81.61 45.24,82.36 46.75,83.08 48.25,83.77 49.76,84.43 51.27,85.05 52.78,85.64 54.29,86.19 55.79,86.71 57.30,87.19 58.81,87.64 60.32,88.04 61.83,88.41 63.33,88.74 64.84,89.04 66.35,89.29 67.86,89.51 69.37,89.68 70.87,89.82 72.38,89.92 73.89,89.98 75.40,90.00 76.91,89.98 78.41,89.92 79.92,89.82 81.43,89.68 82.94,89.51 84.45,89.29 85.95,89.04 87.46,88.74 88.97,88.41 90.48,88.04 91.99,87.64 93.49,87.19 95.00,86.71 96.51,86.19 98.02,85.64 99.53,85.05 101.03,84.43 102.54,83.77 104.05,83.08 105.56,82.36 107.07,81.61 108.57,80.82 110.08,80.00 111.59,79.16 113.10,78.28 114.61,77.38 116.11,76.45 117.62,75.50 119.13,74.52 120.64,73.51 122.15,72.48 123.65,71.43 125.16,70.36 126.67,69.27 128.18,68.16 129.68,67.03 131.19,65.89 132.70,64.72 134.21,63.55 135.72,62.36 137.22,61.16 138.73,59.95 140.24,58.73 141.75,57.50 143.26,56.26 144.76,55.01 146.27,53.76 147.78,52.51 149.29,51.26 150.80,50.00" />
|
||||
<polyline fill="none" stroke="#d32f2f" stroke-width="1.5" points="0.00,70.00 1.51,71.26 3.02,72.51 4.52,73.76 6.03,75.01 7.54,76.26 9.05,77.50 10.56,78.73 12.06,79.95 13.57,81.16 15.08,82.36 16.59,83.55 18.10,84.72 19.60,85.89 21.11,87.03 22.62,88.16 24.13,89.27 25.64,90.36 27.14,91.43 28.65,92.48 30.16,93.51 31.67,94.52 33.18,95.50 34.68,96.45 36.19,97.38 37.70,98.28 39.21,99.16 40.72,100.00 42.22,100.82 43.73,101.61 45.24,102.36 46.75,103.08 48.25,103.77 49.76,104.43 51.27,105.05 52.78,105.64 54.29,106.19 55.79,106.71 57.30,107.19 58.81,107.64 60.32,108.04 61.83,108.41 63.33,108.74 64.84,109.04 66.35,109.29 67.86,109.51 69.37,109.68 70.87,109.82 72.38,109.92 73.89,109.98 75.40,110.00 76.91,109.98 78.41,109.92 79.92,109.82 81.43,109.68 82.94,109.51 84.45,109.29 85.95,109.04 87.46,108.74 88.97,108.41 90.48,108.04 91.99,107.64 93.49,107.19 95.00,106.71 96.51,106.19 98.02,105.64 99.53,105.05 101.03,104.43 102.54,103.77 104.05,103.08 105.56,102.36 107.07,101.61 108.57,100.82 110.08,100.00 111.59,99.16 113.10,98.28 114.61,97.38 116.11,96.45 117.62,95.50 119.13,94.52 120.64,93.51 122.15,92.48 123.65,91.43 125.16,90.36 126.67,89.27 128.18,88.16 129.68,87.03 131.19,85.89 132.70,84.72 134.21,83.55 135.72,82.36 137.22,81.16 138.73,79.95 140.24,78.73 141.75,77.50 143.26,76.26 144.76,75.01 146.27,73.76 147.78,72.51 149.29,71.26 150.80,70.00" />
|
||||
<!-- линия склейки -->
|
||||
<line x1="150.8" y1="0" x2="150.8" y2="120" stroke="#888" stroke-width="0.5" stroke-dasharray="3,3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="150.8mm" height="120mm" viewBox="0 0 150.8 120" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Часть 2 (правая) -->
|
||||
<polygon fill="#e0f7fa" fill-opacity="0.5" stroke="none" points="0.00,50.00 1.51,48.74 3.02,47.49 4.52,46.24 6.03,44.99 7.54,43.74 9.05,42.50 10.56,41.27 12.06,40.05 13.57,38.84 15.08,37.64 16.59,36.45 18.10,35.28 19.60,34.11 21.11,32.97 22.62,31.84 24.13,30.73 25.64,29.64 27.14,28.57 28.65,27.52 30.16,26.49 31.67,25.48 33.18,24.50 34.68,23.55 36.19,22.62 37.70,21.72 39.21,20.84 40.72,20.00 42.22,19.18 43.73,18.39 45.24,17.64 46.75,16.92 48.25,16.23 49.76,15.57 51.27,14.95 52.78,14.36 54.29,13.81 55.79,13.29 57.30,12.81 58.81,12.36 60.32,11.96 61.83,11.59 63.33,11.26 64.84,10.96 66.35,10.71 67.86,10.49 69.37,10.32 70.87,10.18 72.38,10.08 73.89,10.02 75.40,10.00 76.91,10.02 78.41,10.08 79.92,10.18 81.43,10.32 82.94,10.49 84.45,10.71 85.95,10.96 87.46,11.26 88.97,11.59 90.48,11.96 91.99,12.36 93.49,12.81 95.00,13.29 96.51,13.81 98.02,14.36 99.53,14.95 101.03,15.57 102.54,16.23 104.05,16.92 105.56,17.64 107.07,18.39 108.57,19.18 110.08,20.00 111.59,20.84 113.10,21.72 114.61,22.62 116.11,23.55 117.62,24.50 119.13,25.48 120.64,26.49 122.15,27.52 123.65,28.57 125.16,29.64 126.67,30.73 128.18,31.84 129.68,32.97 131.19,34.11 132.70,35.28 134.21,36.45 135.72,37.64 137.22,38.84 138.73,40.05 140.24,41.27 141.75,42.50 143.26,43.74 144.76,44.99 146.27,46.24 147.78,47.49 149.29,48.74 150.80,50.00 150.80,70.00 149.29,68.74 147.78,67.49 146.27,66.24 144.76,64.99 143.26,63.74 141.75,62.50 140.24,61.27 138.73,60.05 137.22,58.84 135.72,57.64 134.21,56.45 132.70,55.28 131.19,54.11 129.68,52.97 128.18,51.84 126.67,50.73 125.16,49.64 123.65,48.57 122.15,47.52 120.64,46.49 119.13,45.48 117.62,44.50 116.11,43.55 114.61,42.62 113.10,41.72 111.59,40.84 110.08,40.00 108.57,39.18 107.07,38.39 105.56,37.64 104.05,36.92 102.54,36.23 101.03,35.57 99.53,34.95 98.02,34.36 96.51,33.81 95.00,33.29 93.49,32.81 91.99,32.36 90.48,31.96 88.97,31.59 87.46,31.26 85.95,30.96 84.45,30.71 82.94,30.49 81.43,30.32 79.92,30.18 78.41,30.08 76.91,30.02 75.40,30.00 73.89,30.02 72.38,30.08 70.87,30.18 69.37,30.32 67.86,30.49 66.35,30.71 64.84,30.96 63.33,31.26 61.83,31.59 60.32,31.96 58.81,32.36 57.30,32.81 55.79,33.29 54.29,33.81 52.78,34.36 51.27,34.95 49.76,35.57 48.25,36.23 46.75,36.92 45.24,37.64 43.73,38.39 42.22,39.18 40.72,40.00 39.21,40.84 37.70,41.72 36.19,42.62 34.68,43.55 33.18,44.50 31.67,45.48 30.16,46.49 28.65,47.52 27.14,48.57 25.64,49.64 24.13,50.73 22.62,51.84 21.11,52.97 19.60,54.11 18.10,55.28 16.59,56.45 15.08,57.64 13.57,58.84 12.06,60.05 10.56,61.27 9.05,62.50 7.54,63.74 6.03,64.99 4.52,66.24 3.02,67.49 1.51,68.74 0.00,70.00" />
|
||||
<polyline fill="none" stroke="#1976d2" stroke-width="1.5" points="0.00,50.00 1.51,48.74 3.02,47.49 4.52,46.24 6.03,44.99 7.54,43.74 9.05,42.50 10.56,41.27 12.06,40.05 13.57,38.84 15.08,37.64 16.59,36.45 18.10,35.28 19.60,34.11 21.11,32.97 22.62,31.84 24.13,30.73 25.64,29.64 27.14,28.57 28.65,27.52 30.16,26.49 31.67,25.48 33.18,24.50 34.68,23.55 36.19,22.62 37.70,21.72 39.21,20.84 40.72,20.00 42.22,19.18 43.73,18.39 45.24,17.64 46.75,16.92 48.25,16.23 49.76,15.57 51.27,14.95 52.78,14.36 54.29,13.81 55.79,13.29 57.30,12.81 58.81,12.36 60.32,11.96 61.83,11.59 63.33,11.26 64.84,10.96 66.35,10.71 67.86,10.49 69.37,10.32 70.87,10.18 72.38,10.08 73.89,10.02 75.40,10.00 76.91,10.02 78.41,10.08 79.92,10.18 81.43,10.32 82.94,10.49 84.45,10.71 85.95,10.96 87.46,11.26 88.97,11.59 90.48,11.96 91.99,12.36 93.49,12.81 95.00,13.29 96.51,13.81 98.02,14.36 99.53,14.95 101.03,15.57 102.54,16.23 104.05,16.92 105.56,17.64 107.07,18.39 108.57,19.18 110.08,20.00 111.59,20.84 113.10,21.72 114.61,22.62 116.11,23.55 117.62,24.50 119.13,25.48 120.64,26.49 122.15,27.52 123.65,28.57 125.16,29.64 126.67,30.73 128.18,31.84 129.68,32.97 131.19,34.11 132.70,35.28 134.21,36.45 135.72,37.64 137.22,38.84 138.73,40.05 140.24,41.27 141.75,42.50 143.26,43.74 144.76,44.99 146.27,46.24 147.78,47.49 149.29,48.74 150.80,50.00" />
|
||||
<polyline fill="none" stroke="#d32f2f" stroke-width="1.5" points="0.00,70.00 1.51,68.74 3.02,67.49 4.52,66.24 6.03,64.99 7.54,63.74 9.05,62.50 10.56,61.27 12.06,60.05 13.57,58.84 15.08,57.64 16.59,56.45 18.10,55.28 19.60,54.11 21.11,52.97 22.62,51.84 24.13,50.73 25.64,49.64 27.14,48.57 28.65,47.52 30.16,46.49 31.67,45.48 33.18,44.50 34.68,43.55 36.19,42.62 37.70,41.72 39.21,40.84 40.72,40.00 42.22,39.18 43.73,38.39 45.24,37.64 46.75,36.92 48.25,36.23 49.76,35.57 51.27,34.95 52.78,34.36 54.29,33.81 55.79,33.29 57.30,32.81 58.81,32.36 60.32,31.96 61.83,31.59 63.33,31.26 64.84,30.96 66.35,30.71 67.86,30.49 69.37,30.32 70.87,30.18 72.38,30.08 73.89,30.02 75.40,30.00 76.91,30.02 78.41,30.08 79.92,30.18 81.43,30.32 82.94,30.49 84.45,30.71 85.95,30.96 87.46,31.26 88.97,31.59 90.48,31.96 91.99,32.36 93.49,32.81 95.00,33.29 96.51,33.81 98.02,34.36 99.53,34.95 101.03,35.57 102.54,36.23 104.05,36.92 105.56,37.64 107.07,38.39 108.57,39.18 110.08,40.00 111.59,40.84 113.10,41.72 114.61,42.62 116.11,43.55 117.62,44.50 119.13,45.48 120.64,46.49 122.15,47.52 123.65,48.57 125.16,49.64 126.67,50.73 128.18,51.84 129.68,52.97 131.19,54.11 132.70,55.28 134.21,56.45 135.72,57.64 137.22,58.84 138.73,60.05 140.24,61.27 141.75,62.50 143.26,63.74 144.76,64.99 146.27,66.24 147.78,67.49 149.29,68.74 150.80,70.00" />
|
||||
<!-- линия склейки -->
|
||||
<line x1="150.8" y1="0" x2="150.8" y2="120" stroke="#888" stroke-width="0.5" stroke-dasharray="3,3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.4 KiB |
@@ -0,0 +1,13 @@
|
||||
<svg width="302mm" height="100mm" viewBox="0 0 302 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- UPPER edge (blue) — синусоида от y=0 до y=80 mm -->
|
||||
<polyline fill="none" stroke="#1976d2" stroke-width="2" points="
|
||||
0.00,40.00 1.51,35.97 3.02,32.44 4.53,29.44 6.04,27.04 7.55,25.23 9.06,24.07 10.57,23.62 12.08,23.92 13.59,24.98 15.10,26.81 16.61,29.41 18.12,32.75 19.63,36.82 21.14,41.58 22.65,46.98 24.16,52.96 25.67,59.45 27.18,66.36 28.69,73.62 30.20,81.16 31.71,88.92 33.22,96.82 34.73,104.78 36.24,112.73 37.75,120.60 39.26,128.30 40.77,135.78 42.28,142.97 43.79,149.82 45.30,156.25 46.81,162.21 48.32,167.64 49.83,172.49 51.34,176.71 52.85,180.24 54.36,183.03 55.87,185.05 57.38,186.28 58.89,186.70 60.40,186.28 61.91,185.05 63.42,183.03 64.93,180.24 66.44,176.71 67.95,172.49 69.46,167.64 70.97,162.21 72.48,156.25 73.99,149.82 75.50,142.97 77.01,135.78 78.52,128.30 80.03,120.60 81.54,112.73 83.05,104.78 84.56,96.82 86.07,88.92 87.58,81.16 89.09,73.62 90.60,66.36 92.11,59.45 93.62,52.96 95.13,46.98 96.64,41.58 98.15,36.82 99.66,32.75 101.17,29.41 102.68,26.81 104.19,24.98 105.70,23.92 107.21,23.62 108.72,24.07 110.23,25.23 111.74,27.04 113.25,29.44 114.76,32.44 116.27,35.97 117.78,40.00 119.29,44.50 120.80,49.42 122.31,54.70 123.82,60.29 125.33,66.14 126.84,72.19 128.35,78.38 129.86,84.65 131.37,90.94 132.88,97.18 134.39,103.30 135.90,109.23 137.41,114.90 138.92,120.24 140.43,125.18 141.94,129.66 143.45,133.62 144.96,137.00 146.47,139.73 147.98,141.77 149.49,143.07 151.00,143.61 152.51,143.35 154.02,142.27 155.53,140.35 157.04,137.57 158.55,133.92 160.06,129.40 161.57,124.00 163.08,117.70 164.59,110.50 166.10,102.40 167.61,93.41 169.12,83.55 170.63,72.88 172.14,61.49 173.65,49.49 175.16,37.02 176.67,24.23 178.18,11.26 179.69,-1.76 181.20,-14.80 182.71,-27.74 184.22,-40.50 185.73,-52.94 187.24,-64.99 188.75,-76.54 190.26,-87.53 191.77,-97.79 193.28,-107.27 194.79,-115.88 196.30,-123.55 197.81,-130.22 199.32,-135.81 200.83,-140.26 202.34,-143.52 203.85,-145.54 205.36,-146.30 206.87,-145.77 208.38,-143.92 209.89,-140.75 211.40,-136.24 212.91,-130.38 214.42,-123.17 215.93,-114.63 217.44,-104.76 218.95,-93.61 220.46,-81.23 221.97,-67.66 223.48,-52.98 224.99,-37.27 226.50,-20.61 228.01,-3.10 229.52,15.57 231.03,35.43 232.54,56.24 234.05,77.85 235.56,100.10 237.07,122.82 238.58,145.86 240.09,169.08 241.60,192.31 243.11,215.41 244.62,238.14 246.13,260.38 247.64,281.98 249.15,302.80 250.66,322.71 252.17,341.57 253.68,359.24 255.19,375.61 256.70,390.57 258.21,403.98 259.72,415.76 261.23,425.78 262.74,433.99 264.25,440.36 265.76,444.87 267.27,447.50 268.78,448.24 270.29,447.09 271.80,444.04 273.31,439.11 274.82,432.30 276.33,423.64 278.18,413.17 278.36,403.12 279.35,399.86 280.86,388.78 282.37,376.32 283.88,362.74 285.39,348.28 286.90,333.16 288.41,317.54 289.92,301.57 291.43,285.38 292.94,269.11 294.45,252.90 295.96,236.88 297.47,221.18 298.98,206.00 300.49,191.44 302.00,177.65
|
||||
" />
|
||||
<!-- LOWER edge (red) — синусоида 20 mm ниже (y = base + 20) -->
|
||||
<polyline fill="none" stroke="#d32f2f" stroke-width="2" points="
|
||||
0.00,60.00 1.51,55.97 3.02,52.44 4.53,49.44 6.04,47.04 7.55,45.23 9.06,44.07 10.57,43.62 12.08,43.92 13.59,44.98 15.10,46.81 16.61,49.41 18.12,52.75 19.63,56.82 21.14,61.58 22.65,66.98 24.16,72.96 25.67,79.45 27.18,86.36 28.69,93.62 30.20,101.16 31.71,108.92 33.22,116.82 34.73,124.78 36.24,132.73 37.75,140.60 39.26,148.30 40.77,155.78 42.28,162.97 43.79,169.82 45.30,176.25 46.81,182.21 48.32,187.64 49.83,192.49 51.34,196.71 52.85,200.24 54.36,203.03 55.87,205.05 57.38,206.28 58.89,206.70 60.40,206.28 61.91,205.05 63.42,203.03 64.93,200.24 66.44,196.71 67.95,192.49 69.46,187.64 70.97,182.21 72.48,176.25 73.99,169.82 75.50,162.97 77.01,155.78 78.52,148.30 80.03,140.60 81.54,132.73 83.05,124.78 84.56,116.82 86.07,108.92 87.58,101.16 89.09,93.62 90.60,86.36 92.11,79.45 93.62,72.96 95.13,66.98 96.64,61.58 98.15,56.82 99.66,52.75 101.17,49.41 102.68,46.81 104.19,44.98 105.70,43.92 107.21,43.62 108.72,44.07 110.23,45.23 111.74,47.04 113.25,49.44 114.76,52.44 116.27,55.97 117.78,60.00 119.29,64.50 120.80,69.42 122.31,74.70 123.82,80.29 125.33,86.14 126.84,92.19 128.35,98.38 129.86,104.65 131.37,110.94 132.88,117.18 134.39,123.30 135.90,129.23 137.41,134.90 138.92,140.24 140.43,145.18 141.94,149.66 143.45,153.62 144.96,157.00 146.47,159.73 147.98,161.77 149.49,163.07 151.00,163.61 152.51,163.35 154.02,162.27 155.53,160.35 157.04,157.57 158.55,153.92 160.06,149.40 161.57,144.00 163.08,137.70 164.59,130.50 166.10,122.40 167.61,113.41 169.12,103.55 170.63,92.88 172.14,81.49 173.65,69.49 175.16,57.02 176.67,44.23 178.18,31.26 179.69,18.24 181.20,5.20 182.71,-7.74 184.22,-20.50 185.73,-32.94 187.24,-44.99 188.75,-56.54 190.26,-67.53 191.77,-77.79 193.28,-87.27 194.79,-95.88 196.30,-103.55 197.81,-110.22 199.32,-115.81 200.83,-120.26 202.34,-123.52 203.85,-125.54 205.36,-126.30 206.87,-125.77 208.38,-123.92 209.89,-120.75 211.40,-116.24 212.91,-110.38 214.42,-103.17 215.93,-94.63 217.44,-84.76 218.95,-73.61 220.46,-61.23 221.97,-47.66 223.48,-32.98 224.99,-17.27 226.50,-0.61 228.01,16.89 229.52,35.43 231.03,55.43 232.54,76.24 234.05,97.85 235.56,120.10 237.07,142.82 238.58,165.86 240.09,189.08 241.60,212.31 243.11,235.41 244.62,258.14 246.13,280.38 247.64,301.98 249.15,322.80 250.66,342.71 252.17,361.57 253.68,379.24 255.19,395.61 256.70,410.57 258.21,423.98 259.72,435.76 261.23,445.78 262.74,453.99 264.25,460.36 265.76,464.87 267.27,467.50 268.78,468.24 270.29,467.09 271.80,464.04 273.31,459.11 274.82,452.30 276.33,443.64 278.18,433.17 278.36,423.12 279.35,419.86 280.86,408.78 282.37,396.32 283.88,382.74 285.39,368.28 286.90,353.16 288.41,337.54 289.92,321.57 291.43,305.38 292.94,289.11 294.45,272.90 295.96,256.88 297.47,241.18 298.98,226.00 300.49,211.44 302.00,197.65
|
||||
" />
|
||||
<polygon fill="#e0f7fa" fill-opacity="0.5" stroke="none" points="
|
||||
0.00,30.00 1.51,29.50 3.02,29.05 4.53,28.65 6.04,28.32 7.55,28.06 9.06,27.86 10.57,27.75 12.08,27.71 13.59,27.75 15.10,27.86 16.61,28.06 18.12,28.32 19.63,28.65 21.14,29.05 22.65,29.50 24.16,30.00 25.67,30.50 27.18,30.95 28.69,31.35 30.20,31.68 31.71,31.94 33.22,32.14 34.73,32.25 36.24,32.29 37.75,32.25 39.26,32.14 40.77,31.94 42.28,31.68 43.79,31.35 45.30,30.95 46.81,30.50 48.32,30.00 49.83,29.50 51.34,29.05 52.85,28.65 54.36,28.32 55.87,28.06 57.38,27.86 58.89,27.75 60.40,27.71 61.91,27.75 63.42,27.86 64.93,28.06 66.44,28.32 67.95,28.65 69.46,29.05 70.97,29.50 72.48,30.00 73.99,30.50 75.50,30.95 77.01,31.35 78.52,31.68 80.03,31.94 81.54,32.14 83.05,32.25 84.56,32.29 86.07,32.25 87.58,32.14 89.09,31.94 90.60,31.68 92.11,31.35 93.62,30.95 95.13,30.50 96.64,30.00 98.15,29.50 99.66,29.05 101.17,28.65 102.68,28.32 104.19,28.06 105.70,27.86 107.21,27.75 108.72,27.71 110.23,27.75 111.74,27.86 113.25,28.06 114.76,28.32 116.27,28.65 117.78,29.05 119.29,29.50 120.80,30.00 122.31,30.50 123.82,30.95 125.33,31.35 126.84,31.68 128.35,31.94 129.86,32.14 131.37,32.25 132.88,32.29 134.39,32.25 135.90,32.14 137.41,31.94 138.92,31.68 140.43,31.35 141.94,30.95 143.45,30.50 144.96,30.00 146.47,29.50 147.98,29.05 149.49,28.65 151.00,28.32 152.51,28.06 154.02,27.86 155.53,27.75 157.04,27.71 158.55,27.75 160.06,27.86 161.57,28.06 163.08,28.32 164.59,28.65 166.10,29.05 167.61,29.50 169.12,30.00 170.63,30.50 172.14,30.95 173.65,31.35 175.16,31.68 176.67,31.94 178.18,32.14 179.69,32.25 181.20,32.29 182.71,32.25 184.22,32.14 185.73,31.94 187.24,31.68 188.75,31.35 190.26,30.95 191.77,30.50 193.28,30.00 194.79,29.50 196.30,29.05 197.81,28.65 199.32,28.32 200.83,28.06 202.34,27.86 203.85,27.75 205.36,27.71 206.87,27.75 208.38,27.86 209.89,28.06 211.40,28.32 212.91,28.65 214.42,29.05 215.93,29.50 217.44,30.00 218.95,30.50 220.46,30.95 221.97,31.35 223.48,31.68 224.99,31.94 226.50,32.14 228.01,32.25 229.52,32.29 231.03,32.25 232.54,32.14 234.05,31.94 235.56,31.68 237.07,31.35 238.58,30.95 240.09,30.50 241.60,30.00 243.11,29.50 244.62,29.05 246.13,28.65 247.64,28.32 249.15,28.06 250.66,27.86 252.17,27.75 253.68,27.71 255.19,27.75 256.70,27.86 258.21,28.06 259.72,28.32 261.23,28.65 262.74,29.05 264.25,29.50 265.76,30.00 267.27,30.50 268.78,30.95 270.29,31.35 271.80,31.68 273.31,31.94 274.82,32.14 276.33,32.25 277.84,32.29 279.35,32.25 280.86,32.14 282.37,31.94 283.88,31.68 285.39,31.35 286.90,30.95 288.41,30.50 289.92,30.00 291.43,29.50 292.94,29.05 294.45,28.65 295.96,28.32 297.47,28.06 298.98,27.86 300.49,27.75 302.00,27.71 302.00,47.71 300.49,47.75 298.98,47.86 297.47,48.06 295.96,48.32 294.45,48.65 292.94,49.05 291.43,49.50 289.92,50.00 288.41,50.50 286.90,50.95 285.39,51.35 283.88,51.68 282.37,51.94 280.86,52.14 279.35,52.25 277.84,52.29 276.33,52.25 274.82,52.14 273.31,51.94 271.80,51.68 270.29,51.35 268.78,50.95 267.27,50.50 265.76,50.00 264.25,49.50 262.74,49.05 261.23,48.65 259.72,48.32 258.21,48.06 256.70,47.86 255.19,47.75 253.68,47.71 252.17,47.75 250.66,47.86 249.15,48.06 247.64,48.32 246.13,48.65 244.62,49.05 243.11,49.50 241.60,50.00 240.09,50.50 238.58,50.95 237.07,51.35 235.56,51.68 234.05,51.94 232.54,52.14 231.03,52.25 229.52,52.29 228.01,52.25 226.50,52.14 224.99,51.94 223.48,51.68 221.97,51.35 220.46,50.95 218.95,50.50 217.44,50.00 215.93,49.50 214.42,49.05 212.91,48.65 211.40,48.32 209.89,48.06 208.38,47.86 206.87,47.75 205.36,47.71 203.85,47.75 202.34,47.86 200.83,48.06 199.32,48.32 197.81,48.65 196.30,49.05 194.79,49.50 193.28,50.00 191.77,50.50 190.26,50.95 188.75,51.35 187.24,51.68 185.73,51.94 184.22,52.14 182.71,52.25 181.20,52.29 179.69,52.25 178.18,52.14 176.67,51.94 175.16,51.68 173.65,51.35 172.14,50.95 170.63,50.50 169.12,50.00 167.61,49.50 166.10,49.05 164.59,48.65 163.08,48.32 161.57,48.06 160.06,47.86 158.55,47.75 157.04,47.71 155.53,47.75 154.02,47.86 152.51,48.06 151.00,48.32 149.49,48.65 147.98,49.05 146.47,49.50 144.96,50.00 143.45,50.50 141.94,50.95 140.43,51.35 138.92,51.68 137.41,51.94 135.90,52.14 134.39,52.25 132.88,52.29 131.37,52.25 129.86,52.14 128.35,51.94 126.84,51.68 125.33,51.35 123.82,50.95 122.31,50.50 120.80,50.00 119.29,49.50 117.78,49.05 116.27,48.65 114.76,48.32 113.25,48.06 111.74,47.86 110.23,47.75 108.72,47.71 107.21,47.75 105.70,47.86 104.19,48.06 102.68,48.32 101.17,48.65 99.66,49.05 98.15,49.50 96.64,50.00 95.13,50.50 93.62,50.95 92.11,51.35 90.60,51.68 89.09,51.94 87.58,52.14 86.07,52.25 84.56,52.29 83.05,52.25 81.54,52.14 80.03,51.94 78.52,51.68 77.01,51.35 75.50,50.95 73.99,50.50 72.48,50.00 70.97,49.50 69.46,49.05 67.95,48.65 66.44,48.32 64.93,48.06 63.42,47.86 61.91,47.75 60.40,47.71 58.89,47.75 57.38,47.86 55.87,48.06 54.36,48.32 52.85,48.65 51.34,49.05 49.83,49.50 48.32,50.00 46.81,50.50 45.30,50.95 43.79,51.35 42.28,51.68 40.77,51.94 39.26,52.14 37.75,52.25 36.24,52.29 34.73,52.25 33.22,52.14 31.71,51.94 30.20,51.68 28.69,51.35 27.18,50.95 25.67,50.50 24.16,50.00 22.65,49.50 21.14,49.05 19.63,48.65 18.12,48.32 16.61,48.06 15.10,47.86 13.59,47.75 12.08,47.71 10.57,47.75 9.06,47.86 7.55,48.06 6.04,48.32 4.53,48.65 3.02,49.05 1.51,49.50 0.00,50.00
|
||||
" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 11 KiB |