Merge remote-tracking branch 'origin/master'

This commit is contained in:
sShemet
2025-07-22 17:23:14 +05:00
89 changed files with 2159 additions and 774 deletions

View File

@@ -2,6 +2,5 @@
"obsidian-full-calendar",
"obsidian-icon-folder",
"obsidian-git",
"obsidian-smart-typography",
"copy-document-as-html"
"obsidian-smart-typography"
]

View File

@@ -17,6 +17,6 @@
"repelStrength": 10,
"linkStrength": 1,
"linkDistance": 250,
"scale": 1.1122355498645218,
"scale": 1.112235549864522,
"close": true
}

12
.obsidian/hotkeys.json vendored Normal file
View File

@@ -0,0 +1,12 @@
{
"obsidian-git:commit": [
{
"modifiers": [
"Alt",
"Mod",
"Shift"
],
"key": "C"
}
]
}

View File

@@ -1,5 +1,6 @@
{
"commitMessage": "vault backup: {{date}}",
"autoCommitMessage": "vault backup: {{date}}",
"commitDateFormat": "YYYY-MM-DD HH:mm:ss",
"autoSaveInterval": 10,
"autoPushInterval": 0,
@@ -8,6 +9,7 @@
"disablePush": false,
"pullBeforePush": true,
"disablePopups": false,
"showErrorNotices": true,
"disablePopupsForNoChanges": false,
"listChangedFilesInMessageBody": false,
"showStatusBar": true,
@@ -29,6 +31,7 @@
"showFileMenu": true,
"authorInHistoryView": "hide",
"dateInHistoryView": false,
"diffStyle": "split",
"lineAuthor": {
"show": false,
"followMovement": "inactive",
@@ -53,6 +56,5 @@
"gutterSpacingFallbackLength": 5,
"lastShownAuthorDisplay": "initials",
"lastShownDateTimeFormatOptions": "date"
},
"autoCommitMessage": "vault backup: {{date}}"
}
}

File diff suppressed because one or more lines are too long

View File

@@ -6,5 +6,5 @@
"description": "Integrate Git version control with automatic backup and other advanced features.",
"isDesktopOnly": false,
"fundingUrl": "https://ko-fi.com/vinzent",
"version": "2.31.1"
"version": "2.33.0"
}

View File

@@ -574,3 +574,32 @@
.git-split-diff-view .cm-insertedLine .cm-changedText {
background-color: #22bb2230;
}
/* Limits the scrollbar to the view body */
.git-view {
display: flex;
flex-direction: column;
position: relative;
height: 100%;
}
.git-obscure-prompt[git-is-obscured="true"] #git-show-password:after {
-webkit-mask-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-eye"><path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"></path><circle cx="12" cy="12" r="3"></circle></svg>');
}
.git-obscure-prompt[git-is-obscured="false"] #git-show-password:after {
-webkit-mask-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-eye-off"><path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"></path><path d="M14.084 14.158a3 3 0 0 1-4.242-4.242"></path><path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143"></path><path d="m2 2 20 20"></path></svg>');
}
/* Override styling of Codemirror merge view "collapsed lines" indicator */
.git-split-diff-view .ͼ2 .cm-collapsedLines {
background: var(--interactive-normal);
border-radius: var(--radius-m);
color: var(--text-accent);
font-size: var(--font-small);
padding: var(--size-4-1) var(--size-4-1);
}
.git-split-diff-view .ͼ2 .cm-collapsedLines:hover {
background: var(--interactive-hover);
color: var(--text-accent-hover);
}

View File

@@ -173,56 +173,57 @@
},
"active": "80dbf49c3b165c4e",
"lastOpenFiles": [
"WORK & PROJECTS/Mol/Планы и диаграммы/Статусы/Структура статусов.canvas",
"WORK & PROJECTS/Mol/Планы и диаграммы/Статусы",
"WORK & PROJECTS/Mol/Планы и диаграммы/Система тарификации/Расчёт тарифов и списаний.canvas",
"WORK & PROJECTS/Mol/Планы и диаграммы/Система тарификации/Tariffs.md",
"WORK & PROJECTS/Mol/Планы и диаграммы/Система тарификации/Products.md",
"WORK & PROJECTS/Mol/Планы и диаграммы/Система тарификации/Modules.md",
"WORK & PROJECTS/Mol/Планы и диаграммы/Система тарификации/Billing_add_types.md",
"WORK & PROJECTS/Mol/Планы и диаграммы/Система тарификации/Billing.md",
"WORK & PROJECTS/Mol/Планы и диаграммы/Система тарификации",
"WORK & PROJECTS/Mol/Планы и диаграммы/Нумератор/Нумератор_общая_схема.canvas",
"WORK & PROJECTS/Mol/Планы и диаграммы/Нумератор/numerator_counter_types.md",
"WORK & PROJECTS/Mol/Планы и диаграммы/Нумератор/numerator_counter_history.md",
"WORK & PROJECTS/Mol/Планы и диаграммы/Нумератор/numerator_counters.md",
"WORK & PROJECTS/Mol/Планы и диаграммы/Нумератор",
"WORK & PROJECTS/Mol/Серверы/Supervisor/job_worker.md",
"WORK & PROJECTS/Mol/Серверы/Supervisor",
"WORK & PROJECTS/Mol/Серверы/PROXY_PASS/Traefik config.md",
"WORK & PROJECTS/Mol/Серверы/PROXY_PASS",
"WORK & PROJECTS/Mol/Серверы/Mol Public Prod.md",
"WORK & PROJECTS/Mol/Планы и диаграммы/Заказы/ТЗ динамических полей и ФИЗов.canvas",
"WORK & PROJECTS/Mol/Планы и диаграммы/Заказы/Схема образца испытаний и заявки.canvas",
"WORK & PROJECTS/Mol/Планы и диаграммы/Заказы/Структура таблиц. Основа + заказы.canvas",
"WORK & PROJECTS/Mol/Планы и диаграммы/Заказы/samplings table.md",
"WORK & PROJECTS/Mol/Планы и диаграммы/Заказы/pgo_to_groups.md",
"WORK & PROJECTS/Mol/Планы и диаграммы/Заказы/modules_to_dyn_controls.md",
"WORK & PROJECTS/Mol/Планы и диаграммы/Заказы/dynamic_controls_table.md",
"WORK & PROJECTS/Mol/Планы и диаграммы/Заказы",
"WORK & PROJECTS/Mol/Планы и диаграммы/notifies.canvas",
"WORK & PROJECTS/Mol/Планы и диаграммы/Alfa Cloud/Websockets scheme.canvas",
"WORK & PROJECTS/Mol/Планы и диаграммы/Alfa Cloud/Alfa PROD NODE script.md",
"WORK & PROJECTS/Mol/Планы и диаграммы/00001_Редакторорм/Описание первой версии.md",
"WORK & PROJECTS/Mol/Документы для ТЗ ЛИМС/Формы импорта оборудования.zip",
"WORK & PROJECTS/Mol/Документы ООО Моль-софт/Счета оплаты серверов/Operation_Check_PJSC_Sberbank_17072025.pdf",
"WORK & PROJECTS/Mol/Документы ООО Моль-софт/Счета оплаты серверов/Operation_Check_PJSC_Sberbank_09062025.pdf",
"WORK & PROJECTS/Mol/Документы ООО Моль-софт/Счета оплаты серверов/Operation_Check_PJSC_Sberbank_06052025.pdf",
"SKILLS DOCS/JAPANESE частицы.md",
"SKILLS DOCS/DOCS/Load Balancer 2.md",
"SKILLS DOCS/DOCS/Load Balancer.md",
"My/Июль.md",
"2025-07-18.md",
"WORK & PROJECTS/Mol/Code Chunks/Tiptap resizeTableColumnWidth.md",
"PERSONAL PROJECTS/P2EP/cdRead.canvas",
"PERSONAL PROJECTS/P2EP/pseudoCode/MakeSmallChar.md",
"PERSONAL PROJECTS/P2EP/pseudoCode/FUN_80015674 Update Entity Stats.md",
"PERSONAL PROJECTS/P2EP/pseudoCode/FUN_800453e0 SystemEventManager.md",
"PERSONAL PROJECTS/P2EP/pseudoCode/FUN_80012df4 RenderInterface.md",
"PERSONAL PROJECTS/P2EP/pseudoCode/FUN_80011f2c InitInterfaceManager.md",
"PERSONAL PROJECTS/PS1 DOCS/PSX code - Texture show from file.md",
"PERSONAL PROJECTS/PS1 DOCS/PSX code - modules load and exec.md",
"PERSONAL PROJECTS/PS1 DOCS/PSX code - Load texture from SECTOR!.md",
"PERSONAL PROJECTS/PS1 DOCS/PSX code texture load unpack show and fade.md",
"PERSONAL PROJECTS/Persona 1/Script Format.md",
"My/Diet 2.md",
"WORK & PROJECTS/Mol/Серверы/git.moldev.ru.md",
"PERSONAL PROJECTS/PS1 DOCS/CD_Execute_Reading.md",
"PERSONAL PROJECTS/PS1 DOCS/PSX code logo show.md",
"PERSONAL PROJECTS/PS1 DOCS/PSX code Gamepad code.md",
"WORK & PROJECTS/Mol/Планы и диаграммы/Разработка - ЭПИКИ.md",
"WORK & PROJECTS/Mol/Code Chunks/TipTap Plugin.md",
"WORK & PROJECTS/Mol/Code Chunks",
"PERSONAL PROJECTS/PS1 DOCS/PSX code IRQ handling.md",
"PERSONAL PROJECTS/PS1 DOCS/PSX code inline asm.md",
"PERSONAL PROJECTS/PS1 DOCS/PS1 Vblank.md",
"PERSONAL PROJECTS/PS1 DOCS/PS1 Ordering table.md",
"PERSONAL PROJECTS/PS1 DOCS/PS1 IRQ.md",
"PERSONAL PROJECTS/PS1 DOCS/PS1 MDEC.md",
"PERSONAL PROJECTS/PS1 DOCS/PS1 Gpu-DMA.md",
"PERSONAL PROJECTS/PS1 DOCS",
"PERSONAL PROJECTS/P2EP/pseudoCode",
"WORK & PROJECTS/Mol/Документы для ТЗ ЛИМС/СМК/КУек_лист_10_ошибок_УД_скачивание.pdf",
"WORK & PROJECTS/Mol/Документы для ТЗ ЛИМС/СМК",
"WORK & PROJECTS/Mol/Планы и диаграммы/Схема связей юрлиц и адресов.canvas",
"My",
"WORK & PROJECTS/Mol/Планы и диаграммы/Alfa Cloud",
"Структура объектов испытаний.png",
"Схема связей юрлиц и адресов.png",
"WORK & PROJECTS/Mol/Планы и диаграммы/00001_Редакторорм/Архитектура редактора и генератора (Alfa + Mol).canvas",
"WORK & PROJECTS/Mol/Планы и диаграммы/00001_Редакторорм/Таблицы нумератора и документов.canvas",
"Структура объектов испытаний.png.0.pdnSave",
"WORK & PROJECTS/Mol/Документы для ТЗ ЛИМС/АФ-01 Альбом форм.pdf",
"WORK & PROJECTS/Mol/Серверы/Схема инфраструктуры.canvas",
"WORK & PROJECTS/Mol/Планы и диаграммы/00001_Быстрый старт/Быстрый старт.canvas",
"WORK & PROJECTS/Mol/Планы и диаграммы/Alfa Cloud/Alfa cloud prod.canvas",
"WORK & PROJECTS/Ulab/Автобан/щебень 5-20.xlsx",
"WORK & PROJECTS/Mol/Серверы/Alfa cloud prod.canvas",
"P2EP/cdRead.canvas",
"WORK & PROJECTS/img/Pasted image 20241212175419.png",
"WORK & PROJECTS/img/Pasted image 20241212175847.png",
"WORK & PROJECTS/img/Pasted image 20241212175929.png",
"WORK & PROJECTS/img/Pasted image 20241212175851.png",
"Без названия 2.canvas"
"WORK & PROJECTS/img/Pasted image 20241212175851.png"
]
}

65
2025-07-18.md Normal file
View File

@@ -0,0 +1,65 @@
### Бэкенд
```
Сохранение проб и образцов
+ понять и исправить, почему не сохраняются образцы с файлами (неверные реквесты Лазера)
+ проверка развязочных таблиц проб/испытаний (на данном этапе всё ок)
- Перетрясти валидацию всех полей!
+ баг реквеста при выборе заявки
+ баги селектов с фронта
- доработка реквеста испытаний (после перепиливания окна испытаний)
+ Прописать сообщения валидации на русском языке
- доделать полноценное сохранение данных образца, проб, испытаний (с испытаниями вопрос, тк нет привязки показателей???)
- добавлять одну пробу при отстутсвии
- как редактировать состав incoming_object???
- нужна кнопка и в заказах и в перечне образцов
- сделать ответные методы для обновления при сохранении + валидацию загруженных файлов
- узнать как работает загрузка данных о пробах и испытаниях из бэка при выборе образцов, дописываем функции выборки
- апдейт полей и с чем его едят - проработать большой вопрос с изменением и пересохранением количества образцов, проб, испатаний и что делать с номерами? А с уже проведёнными испытаниями?
- Валидация при апдейте
- полноценная выгрузка/загрузка файлов документов на сервер
- логика реквестов файлов документов
- рефакторинг сервисов, логический перенос и чистка кода!
- баг с добавлением показателей в методики
- как выглядят испытания на бэкенде? Сохранение в таблицы показателей к пробам
- Доделать зависимость от родительской сущности в нумераторе - не сброс, а выборка по номеру родительской сущности и выдача следующего номера (таким образом мы сможем добавлять нормальные шифры в рамках родительской сущности в любое время)
- документация фронта для редактирования счётчиков нумератора
- перетрясти сидеры - создать нормальные образцы/пробы/испытания/привязать хотя бы парочку показателей
Динамические поля:
- добавить поддержку включения необходимых полей при выборе типа и группы ОИ (ПГО)
- модели включенных и выключенных полей и передавать их на фронт при загрузке страницы
- сделать инициированные данные по полям для первого ФИЗа
- сделать динамическую валидацию только включенных полей
- добавить механизмы редактирования сохранения пользовательских полей
- добавить валидацию пользовательских полей
- написать Саше документацию для них, чтобы рендерить, валидировать, сохранять и загружать их (массив custom)
- добавление пользовательских полей в макросы документов для выгрузки в документы
- наборски фронта для документов и редактирования
- добавить шаблоны документов выгрузки регистрации образца (+ акт списания)
Система динамических статусов и зависимостей сущностей от предыдущих шагов:
- проработать статусы и историю их изменений для всех сущностей
- рефакторинг фильтров статусов для GET-запросов с фронта
Сохранение испытаний:
- таблица результатов испытаний с привязкой к ответственным, условиям и trials
Сохранение заявок/сделок:
Система согласований:
Система включения-отключения модулей исходя из тарифа
- зависимость от бизнес-процесса и статусов
- зависимость в модулях нумератора и документов
```

14
My/Июль.md Normal file
View File

@@ -0,0 +1,14 @@
| Наим | 13.07 | 15.07 | 16.07 | 17.07 | 18.07 | 19.07 | 20.07 | 14.07 | 14.07 | 14.07 | 14.07 | 14.07 |
| ---- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
| | | | | | | | | | | | | |
| | | | | | | | | | | | | |
| | | | | | | | | | | | | |
| | | | | | | | | | | | | |
| | | | | | | | | | | | | |
| | | | | | | | | | | | | |
| | | | | | | | | | | | | |
| | | | | | | | | | | | | |
| | | | | | | | | | | | | |
| | | | | | | | | | | | | |
| | | | | | | | | | | | | |

View File

@@ -0,0 +1,218 @@
### **Японские частицы: полный гид (кроме の) + сложные предложения**
Частицы в японском — как **дорожные знаки** для слов: они указывают, какая роль у каждого элемента. Разберём ключевые частицы и их комбинации в сложных конструкциях.
---
## **🔹 Основные частицы**
### **1. は (wa) — Тема предложения**
**Функция:**
- Помечает **главную тему** (о чём речь). Не всегда подлежащее!
- Контрастирует с другими вариантами.
**Примеры:**
> 私は学生です。
> *Watashi wa gakusei desu.*
> «Я — студент» (тема: «что касается меня»).
> 寿司は美味しい。
> *Sushi wa oishii.*
> «Суши (а не что-то другое) — вкусные».
**Сложные предложения:**
> 私は猫が好きだが、犬は苦手だ。
> *Watashi wa neko ga suki da ga, inu wa nigate da.*
> «Я люблю кошек, но собак не выношу» (контраст через は).
---
### **2. が (ga) — Подлежащее / Акцент**
**Функция:**
- Указывает **подлежащее** (кто совершает действие).
- Выделяет **новую информацию** или **неожиданность**.
**Примеры:**
> 猫が魚を食べた。
> *Neko ga sakana o tabeta.*
> «Кот (именно он!) съел рыбу».
> 雨が降っている。
> *Ame ga futte iru.*
> «Дождь идёт» (акцент на явлении).
**Сложные предложения:**
> 彼が来たら、教えて。
> *Kare ga kitara, oshiete.*
> «Если **он** придёт — скажи мне» (условие с が).
---
### **3. を (o) — Прямое дополнение**
**Функция:**
- Показывает **объект действия** (что/кого делают?).
**Пример:**
> 本を読む。
> *Hon o yomu.*
> «Читать книгу».
**Сложные предложения:**
> 映画を見に行く。
> *Eiga o mi ni iku.*
> «Иду смотреть фильм» (два глагола: «смотреть» + «идти»).
---
### **4. に (ni) — Направление / Время / Получатель**
**Функция:**
- Куда? Кому? Когда?
**Примеры:**
> 学校に行く。
> *Gakkō ni iku.*
> «Иду **в** школу».
> 友達にプレゼントをあげた。
> *Tomodachi ni purezento o ageta.*
> «Дал подарок **другу**».
> 3時に会う。
> *San-ji ni au.*
> «Встречаемся **в** 3 часа».
**Сложные предложения:**
> 日本に住んでいる間、日本語を勉強した。
> *Nihon ni sunde iru aida, nihongo o benkyō shita.*
> «Пока жил **в** Японии, учил японский».
---
### **5. で (de) — Место / Инструмент / Причина**
**Функция:**
- Где? Чем? Почему?
**Примеры:**
> カフェで勉強する。
> *Kafe de benkyō suru.*
> «Учусь **в** кафе».
> 箸で食べる。
> *Hashi de taberu.*
> «Ем **палочками**».
> 風邪で休んだ。
> *Kaze de yasunda.*
> «Отсутствовал **из-за** простуды».
**Сложные предложения:**
> 電車で通勤しながら、音楽を聞く。
> *Densha de tsūkin shinagara, ongaku o kiku.*
> «Еду **на** поезде **и** слушаю музыку» (параллельные действия).
---
### **6. と (to) — «И» / «С»**
**Функция:**
- Перечисление или совместное действие.
**Примеры:**
> 猫と犬
> *Neko to inu*
> «Кот **и** собака».
> 友達と遊ぶ。
> *Tomodachi to asobu.*
> «Играть **с** другом».
**Сложные предложения:**
> 彼と話すと、元気になる。
> *Kare to hanasu to, genki ni naru.*
> «Когда говорю **с** ним, становлюсь бодрее» (условие + результат).
---
### **7. から (kara) / まで (made) — «От» / «До»**
**Функция:**
- Начало и конец (время, место).
**Пример:**
> 9時から5時まで働く。
> *Ku-ji kara go-ji made hataraku.*
> «Работаю **с** 9 **до** 5».
**Сложные предложения:**
> 東京から大阪まで新幹線で行った。
> *Tōkyō kara Ōsaka made shinkansen de itta.*
> «Ехал **от** Токио **до** Осаки на синкансэне».
---
### **8. も (mo) — «Тоже» / «Даже»**
**Функция:**
- Добавление или усиление.
**Пример:**
> 私も行く。
> *Watashi mo iku.*
> «Я **тоже** пойду».
**Сложные предложения:**
> 雨も降っているし、風も強い。
> *Ame mo futte iru shi, kaze mo tsuyoi.*
> «И дождь идёт, **и** ветер сильный» (перечисление).
---
## **🔹 Сочетание частиц в сложных предложениях**
### **1. Условия (~たら / ~ば / ~と)**
> お金があれば、旅行する。
> *O-kane ga areba, ryokō suru.*
> «**Если** будут деньги — поеду путешествовать».
- **が** + **ば**: «Если [подлежащее] есть…».
### **2. Причина (~から / ~ので)**
> 疲れたから、寝る。
> *Tsukareta kara, neru.*
> «**Так как** устал — спать».
- **から** стоит **после причины**.
### **3. Параллельные действия (~て / ~ながら)**
> 音楽を聞きながら、走る。
> *Ongaku o kinagara, hashiru.*
> «Бегу, **слушая** музыку».
- **を** + **ながら**: объект + одновременное действие.
### **4. Относительные клаузы (~とき / ~もの)**
> 日本にいたとき、刺身を食べた。
> *Nihon ni ita toki, sashimi o tabeta.*
> «**Когда** был в Японии, ел сашими».
- **に** + **とき**: место + время.
---
## **🔹 Почему порядок частиц важен?**
- **Частица всегда относится к слову ПЕРЕД ней**:
> 友達**[と]**公園**[で]**遊んだ。
> *Tomodachi **[to]** kōen **[de]** asonda.*
> «Играл **с** другом **в** парке».
- **Глагол — в конце**, частицы направляют к нему:
> 私は (тема) 彼に (кому) 本を (что) あげた (глагол).
> «Я дал ему книгу».
---
## **Итог:**
1. **Частицы — это грамматические маркеры**, а не предлоги.
2. **Порядок:**
- Тема (は) → Подлежащее (が) → Объекты (を/に/で) → Глагол.
3. **В сложных предложениях** частицы связывают клаузы:
- から = причина, と = условие, ながら = параллельное действие.
Попробуйте разобрать:
> 彼は (тема) 駅で (место) 友達と (с кем) 会って (глагол 1), 映画を見た (глагол 2).
> «Он встретился с другом на станции и посмотрел фильм».
Частицы — это просто! Главное — видеть их **как «якоря»** для слов. 🚀

View File

@@ -1,183 +0,0 @@
Хорошо! Давайте создадим плагин для **Tiptap** во **Vue 3 (Composition API)**, который добавляет **колонтитул (footer)** как отдельный узел (`Node`), сохраняемый и загружаемый вместе с документом.
---
## **1. Создаём плагин для колонтитула (`FooterNode`)**
Сначала опишем сам узел (`Node`), который будет представлять колонтитул.
### **`FooterExtension.ts`**
```typescript
import { Node } from '@tiptap/core';
export const FooterExtension = Node.create({
name: 'footer', // Уникальное имя узла
group: 'block', // Группа (block, inline и т. д.)
content: 'inline*', // Разрешённый контент внутри (текст, ссылки и т. п.)
// Парсинг HTML → ProseMirror-узел
parseHTML() {
return [
{
tag: 'footer', // Будет парсить `<footer>` в документе
},
];
},
// Как узел будет отрендерен в HTML
renderHTML({ HTMLAttributes }) {
return ['footer', HTMLAttributes, 0]; // <footer>...</footer>
},
// Добавляем команды для работы с колонтитулом
addCommands() {
return {
setFooter:
(content) =>
({ commands }) => {
return commands.insertContent({
type: this.name,
content: [
{
type: 'text',
text: content || 'Текст колонтитула',
},
],
});
},
};
},
});
```
---
## **2. Подключаем плагин в редактор (Vue 3 Composition API)**
Теперь создадим компонент редактора с использованием **Vue 3 + Tiptap**.
### **`TiptapEditor.vue`**
```vue
<script setup lang="ts">
import { useEditor, EditorContent } from '@tiptap/vue-3';
import StarterKit from '@tiptap/starter-kit';
import { FooterExtension } from './FooterExtension';
const editor = useEditor({
extensions: [
StarterKit,
FooterExtension, // Подключаем наш плагин
],
content: `
<p>Основной контент документа...</p>
<footer>Колонтитул (подвал)</footer>
`,
});
// Функция для добавления колонтитула
const insertFooter = () => {
editor.value?.commands.setFooter('Новый текст колонтитула');
};
// Сохранение документа (например, в JSON)
const saveContent = () => {
const json = editor.value?.getJSON();
console.log(json);
// Можно отправить на сервер или сохранить в localStorage
};
</script>
<template>
<div class="editor">
<div v-if="editor" class="menu-bar">
<button @click="insertFooter">Добавить колонтитул</button>
<button @click="saveContent">Сохранить</button>
</div>
<EditorContent :editor="editor" />
</div>
</template>
<style>
.editor {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.menu-bar {
margin-bottom: 10px;
}
footer {
margin-top: 2rem;
padding: 1rem;
background: #f5f5f5;
border-top: 1px solid #ddd;
}
</style>
```
---
## **3. Как это работает?**
- **`FooterExtension`** определяет новый тип узла `<footer>`, который:
- Может содержать текст (`inline*`).
- Сохраняется в JSON при вызове `editor.getJSON()`.
- Загружается обратно при установке `editor.content`.
- **Команда `setFooter`** позволяет программно вставлять колонтитул.
- **Сохранение и загрузка** работают автоматически благодаря Tiptap.
---
## **4. Дополнительные улучшения**
### **Добавление атрибутов (например, даты в колонтитул)**
Можно расширить плагин, чтобы он поддерживал атрибуты:
```typescript
// В FooterExtension.ts
addAttributes() {
return {
date: {
default: new Date().toLocaleDateString(),
renderHTML: (attrs) => ({ 'data-date': attrs.date }),
},
};
},
```
Теперь при рендеринге будет:
```html
<footer data-date="2024-04-20">Текст колонтитула</footer>
```
### **Валидация структуры документа**
Если колонтитул должен быть **только в конце документа**, можно добавить проверку в `addCommands`:
```typescript
setFooter:
(content) =>
({ commands, state }) => {
const { doc } = state;
const lastNode = doc.lastChild;
// Если последний элемент уже footer — заменяем его
if (lastNode?.type.name === 'footer') {
return commands.insertContentAt(
doc.content.size - 1,
{
type: this.name,
content: [{ type: 'text', text: content }],
}
);
}
// Иначе добавляем в конец
return commands.insertContentAt(
doc.content.size,
{
type: this.name,
content: [{ type: 'text', text: content }],
}
);
},
```
---
## **Итог**
- **Кастомный узел `<footer>`**, который сохраняется и загружается с документом.
- **Интеграция с Vue 3** (Composition API).
- **Готовые команды** для управления колонтитулами.

View File

@@ -1,318 +0,0 @@
# Creating a Tiptap Plugin for Resizable Table Columns in Vue 3
To create a Tiptap plugin that allows users to resize table columns and persists the width information in the node's JSON, you'll need to:
1. Extend the Table extension
2. Add resize handles
3. Handle mouse events for resizing
4. Store column widths in node attributes
Here's a comprehensive solution:
## 1. First, install required dependencies (if not already installed)
```bash
npm install @tiptap/vue-3 @tiptap/pm @tiptap/starter-kit @tiptap/extension-table @tiptap/extension-table-row @tiptap/extension-table-header @tiptap/extension-table-cell
```
## 2. Create a custom Table extension with resizable columns
```javascript
// ResizableTableExtension.js
import { Table } from '@tiptap/extension-table'
import { Plugin, PluginKey } from '@tiptap/pm/state'
export const ResizableTableExtension = Table.extend({
addAttributes() {
return {
...this.parent?.(),
colWidths: {
default: null,
parseHTML: element => element.getAttribute('data-col-widths'),
renderHTML: attributes => {
if (!attributes.colWidths) {
return {}
}
return {
'data-col-widths': attributes.colWidths.join(',')
}
}
}
}
},
addProseMirrorPlugins() {
const parentPlugins = this.parent?.() || []
return [
...parentPlugins,
resizableColumnsPlugin({
handleWidth: 5,
cellMinWidth: 25,
onColumnResize: (colIndex, width, colWidths, node) => {
const transaction = this.editor.state.tr.setNodeMarkup(
this.editor.state.selection.$anchor.posAtIndex(0),
undefined,
{
...node.attrs,
colWidths: colWidths
}
)
this.editor.view.dispatch(transaction)
}
})
]
}
})
function resizableColumnsPlugin(options) {
const pluginKey = new PluginKey('resizableColumns')
let resizing = false
let startX, startWidth, colIndex, table, colWidths
return new Plugin({
key: pluginKey,
props: {
handleDOMEvents: {
mousedown: (view, event) => {
const target = event.target
if (target.classList.contains('column-resize-handle')) {
event.preventDefault()
startResizing(view, target, event.clientX)
return true
}
},
mousemove: (view, event) => {
if (!resizing) return
const tableRect = table.getBoundingClientRect()
const totalWidth = tableRect.width
const newWidth = startWidth + (event.clientX - startX)
const percentageWidth = (newWidth / totalWidth) * 100
// Update the column widths array
const newColWidths = [...colWidths]
newColWidths[colIndex] = Math.max(options.cellMinWidth, percentageWidth)
// Apply the new widths to the table columns
applyColumnWidths(table, newColWidths)
if (options.onColumnResize) {
const node = view.state.doc.nodeAt(view.state.selection.$anchor.posAtIndex(0))
options.onColumnResize(colIndex, percentageWidth, newColWidths, node)
}
return true
},
mouseup: () => {
if (resizing) {
resizing = false
document.body.style.cursor = ''
return true
}
}
}
},
view: (view) => {
return {
update: (view) => {
// Add resize handles to columns
const tables = view.dom.querySelectorAll('table')
tables.forEach(table => {
const rows = table.querySelectorAll('tr')
if (rows.length === 0) return
// Remove existing handles
const existingHandles = table.querySelectorAll('.column-resize-handle')
existingHandles.forEach(handle => handle.remove())
// Get column widths from node attributes or calculate equal distribution
const node = view.state.doc.nodeAt(view.state.selection.$anchor.posAtIndex(0))
const attrs = node?.attrs || {}
colWidths = attrs.colWidths
? attrs.colWidths.split(',').map(Number)
: Array(rows[0].cells.length).fill(100 / rows[0].cells.length)
// Apply initial column widths
applyColumnWidths(table, colWidths)
// Add resize handles
const firstRow = rows[0]
Array.from(firstRow.cells).forEach((cell, index) => {
const handle = document.createElement('div')
handle.className = 'column-resize-handle'
handle.style.position = 'absolute'
handle.style.top = '0'
handle.style.right = '0'
handle.style.width = `${options.handleWidth}px`
handle.style.height = '100%'
handle.style.cursor = 'col-resize'
handle.style.backgroundColor = 'transparent'
handle.style.zIndex = '10'
cell.style.position = 'relative'
cell.appendChild(handle)
})
})
}
}
}
})
function startResizing(view, handle, clientX) {
resizing = true
startX = clientX
const cell = handle.parentElement
const row = cell.parentElement
table = row.closest('table')
colIndex = Array.from(row.cells).indexOf(cell)
// Get current column widths
const node = view.state.doc.nodeAt(view.state.selection.$anchor.posAtIndex(0))
const attrs = node?.attrs || {}
colWidths = attrs.colWidths
? attrs.colWidths.split(',').map(Number)
: Array(row.cells.length).fill(100 / row.cells.length)
const tableRect = table.getBoundingClientRect()
const totalWidth = tableRect.width
startWidth = (colWidths[colIndex] / 100) * totalWidth
document.body.style.cursor = 'col-resize'
}
function applyColumnWidths(table, widths) {
const rows = table.querySelectorAll('tr')
if (rows.length === 0) return
rows.forEach(row => {
Array.from(row.cells).forEach((cell, index) => {
cell.style.width = `${widths[index]}%`
})
})
}
}
```
## 3. Use the extension in your Vue 3 component
```vue
<template>
<div>
<editor-content :editor="editor" />
</div>
</template>
<script>
import { Editor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import Table from '@tiptap/extension-table'
import TableRow from '@tiptap/extension-table-row'
import TableHeader from '@tiptap/extension-table-header'
import TableCell from '@tiptap/extension-table-cell'
import { ResizableTableExtension } from './ResizableTableExtension'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
extensions: [
StarterKit,
ResizableTableExtension,
TableRow,
TableHeader,
TableCell.configure({
HTMLAttributes: {
style: 'position: relative;',
},
}),
],
content: `
<table>
<tr>
<th>Header 1</th>
<th>Header 2</th>
<th>Header 3</th>
</tr>
<tr>
<td>Cell 1</td>
<td>Cell 2</td>
<td>Cell 3</td>
</tr>
</table>
`,
})
},
beforeUnmount() {
this.editor.destroy()
},
}
</script>
<style>
.column-resize-handle {
position: absolute;
top: 0;
right: 0;
width: 5px;
height: 100%;
cursor: col-resize;
background-color: transparent;
z-index: 10;
}
.column-resize-handle:hover {
background-color: #adf;
}
table {
border-collapse: collapse;
margin: 1rem 0;
width: 100%;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
position: relative;
}
</style>
```
## Key Features of This Implementation:
1. **Column Width Storage**: Column widths are stored in the table node's attributes as a comma-separated string of percentages.
2. **Resize Handles**: Adds invisible resize handles to each column that become active on hover.
3. **Persistence**: When you save the editor content to JSON, the column widths are preserved in the node attributes.
4. **Initialization**: If no widths are specified, columns are initially equally distributed.
5. **Minimum Width**: Enforces a minimum column width to prevent columns from becoming too narrow.
## Usage Notes:
- The plugin adds resize handles to the right side of each column header
- Users can drag these handles to resize columns
- The widths are stored as percentages to maintain responsiveness
- The data is saved in the table node's attributes and will be included when you call `editor.getJSON()`
## Customization Options:
You can adjust these parameters in the plugin options:
- `handleWidth`: Width of the resize handle in pixels
- `cellMinWidth`: Minimum column width in percentage
- `onColumnResize`: Callback function when a column is resized
This implementation provides a complete solution for resizable table columns in Tiptap with Vue 3, with proper persistence of column widths in the document JSON.

View File

@@ -14,7 +14,7 @@ https://config.fsa.gov.ru/api/dictionary/property_measured/elements?limit=1000
### Импорт аккредитации из PDF документов
### СИЛА Моль - Сообщество испытательных лабораторий
### СИЛА Моль - Совет испытательных лабораторий
### Массовая привязка различных параметров в журнале
@@ -45,5 +45,18 @@ Mol+alfa
Часовые пояса для пользователей — берутся по городу, привязанного к месту испытаний
Редактор формул + сервис калькуляции с приёмом данных.
Отдельная услуга — изолированные базы данных.
Прописать ГОСТы информационной безопасности — например, основываясь на [ГОСТ Р 57580.1-2017](https://docs.cntd.ru/document/1200146534)
Купить нормальные SSL-сертификаты
Структура чатов и комментариев
Формы раздельное редактирование и генерация документа. Возможность редактирования сгенерированного документа.
Услуга (оффер) - совокупность показателей (с отдельной ценой) + доп. услуги. Цены на всё.

View File

@@ -0,0 +1,34 @@
Наконец-то закончена версия 0.00001 редактора форм.
С ним ещё предстоит много поработать, чтобы всё выглядело лучше и управлялось приятнее.
В данный момент реализовано
макросы (переменные) с разбивкой по типам и возможностью вставлять их в документ (спасибо Саше за основу)
поддержка простых колонтитулов (верхний и нижний с поддержкой номера страницы и общего количества страниц)
механизм задания динамических строк в таблицах
выгрузка всего этого веселья в документ Word с полноценным парсингом и подставкой данных и стилей
подготовлена база для подключения к любому ЛИМС.
Редактор доступен по адресу
https://forms.moldev.ru/editor?lab=bSjeYOAi2wEWrrJ6vVaoi2RrkPj6P7KrHf7mB6JvoeJ4UViOZ5&type=4&id=1
В конце можете указать id 1,2 или 3 он должен загрузить готовые формы, сохранённых в тестовой ЛИМС.
Для создания нового документа просто удалите &id=. Главное, чтобы остался хэш лабы и type= для сохранения типа.
lab=набор символов это уникальный ключ лаборатории, который определяет БД клиента с формами и макросами на облачном сервере. Строку на релизе мы скроем + будет ещё пара степеней защиты (таким же образом планируется работа с формами Mol счета и тд).
Этот URL будет открываться из ЛИМС при редактировании выбранной формы.
После будущей синхронизации сервисов Alfa и Mol мы добавим проверку прав через sanctum. И возможно вообще перенесём редактор в Mol
Работа с редактором:
Работа проста, работают горячие клавиши (Ctrl-S, Ctrl-Z) но есть несколько нюансов:
При изменении стиля границ таблицы - курсор должен стоять в этой таблице.
Для вставки макроса поставьте курсор в нужное место и выберите меню “вставка - макрос”, затем найдите либо просто выберите нужные вам данные.
Редактирование колонтитулов находится во вкладке “страница”.
Для создания динамических строк в таблице нужно создать строку, выбрать её и выбрать в меню “таблица" пункт “сделать строку динамической”. Затем, нужно вставить данные макросами, но САМОЕ ВАЖНОЕ — из одной тематической группы макросов.
Исключением являются лишь служебные макросы. Например, можно вставить в таблицу порядковый номер строки.
Все макросы содержат в себе тестовые данные - как обычные, так и табличные.
Тестовые данные имитируют получение данных из ЛИМС, форматы данных разработаны и ждут своего часа.
Планируемые доработки:
Добить косяки UI, добавить горячие клавиши для вставки макросов и дин. таблиц
Добить стили выгрузки (ширины столбцов, выравнивания текста)
Придумать динамические столбцы, форматы данных и работу с ними.
Короче, я уже ненавижу word xD

View File

@@ -0,0 +1,100 @@
system.sh
```bash
#system init
if [ "$#" -ne 2 ]; then
echo "Usage: $0 <servername> <mol-user-password>"
exit 1
fi
hostnamectl set-hostname $1
adduser moluser
usermod -aG sudo moluser
echo "moluser:$2" | sudo chpasswd
#packages install
apt install mc nginx docker docker-compose composer npm mariadb-server -y
npm install -g typescript
#firewall install & enable
apt install ufw -y
#ufw reset -y
ufw default allow incoming
ufw default allow outgoing
ufw allow in on eth0 proto tcp to any port 22
ufw deny in on eth0
ufw allow from 10.16.0.0/16
ufw limit 22/tcp comment "Rate limit SSH"
ufw enable
#docker eth0 connections disable
sudo iptables -I DOCKER-USER -i eth0 -j DROP
ufw status numbered
#node exporter run + cerberus prometheus add
#mariadb config
bind_address:0.0.0.0
#remote config for cloud init
mkdir -p /var/www/.ssh
chown -R www-data:www-data /var/www
sudo -u www-data ssh-keygen -t rsa -N "" -f /var/www/.ssh/id_rsa
sudo usermod -s /bin/bash www-data
#sudo sed -i 's#www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin#www-data:x:33:33:www-data:/var/www:/bin/bash#' /etc/passwd
#logging
sudo sed -i '/^#\?\(PrintLastLog\|LogLevel\|X11Forwarding\)/d; $a\
PrintLastLog yes\
LogLevel VERBOSE\
X11Forwarding no' /etc/ssh/sshd_config
systemctl restart sshd
echo -e "www-data ALL=(ALL) NOPASSWD: /usr/bin/docker\nwww-data ALL=(ALL) NOPASSWD: /usr/sbin/nginx\nwww-data ALL=(ALL) NOPASSWD: /usr/bin/docker-compose" | sudo tee /etc/sudoers.d/01-www-cloud-permissions
sudo chmod 440 /etc/sudoers.d/01-www-cloud-permissions
```
Get /var/www/.ssh/id_rsa.pub to bitbucket repository
Add /var/www/.ssh/authorized_keys key of cloud server
```bash
sudo -u www-data echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDXfsFoKk7o5HBVcBmS1noFAiZ1HWcJ/ndHNNdoezTYUe2xFmDU2iYeILycOOi/WS2E3YQl3xhxW6lC1waYesxxgnB6NSk6BOU7QOOMmeKgyUCROMDNCf16g2Ycth3behgj/h7mk3q1ruyo3OnvJncfPnZM5DAl0hFYE81hsN4hiNcuRc+t1MSWXzs4o0X/5sxHdvyupZe7fmr5Jziwci9w0H3lA8yO7RVtD4pPTAqdGRgsbgmoE6c6FrZAG0E5NXKP/+bq6L+VMCnEFKCTB3rl2a0to2yfhL2ivd2tdKJjohvtqETsHCMKg3k0J21ijAapRcKx3mpN782FDmCK7pXZugmMNsKQPCYbipgyufB3d9LqcjQFw18HARciIEZsgddWh/AXBmS2fF3b73FOaPpwnaSDV2ipYY9NYISDZlgGoDNoFHdWurOixWMTRzgfB3jgYgG4kchjHf+JPIWEq1h1ZjfU5gsbyg8Lo7RX8nY2HVTF/1w4B4nrBtAWYikG0R0= www-data@mol-public" > /var/www/.ssh/authorized_keys
```
```bash
#repos init
mkdir /srv/www
mkdir /srv/docker
mkdir /srv/docker/clients
chown -R www-data:www-data /srv/www
chown -R www-data:www-data /srv/docker
#get repos (alfa + websocket host)
#template .env set for building
#repos init
#nginx config for websocket service
nginx -s reload
#supervisor config
```

View File

@@ -20,7 +20,8 @@ GET:
/clients/{id}/maintain/{1/0} - включить или отключить страницу "Техническое обслуживание" у клиента
/clients/{id}/send_success - отправить письмо клиенту с успешной регистрацией облака с логином и паролем
/clients/{id}/send_expire - отправить письмо клиенту о скором истечении использования (можно/нужно автоматизировать)
/clients/{id}/expire_dates_history - история изменения продлений дат пользователя
/clients/billing_types - получить все типы биллинга
/clients/reconfigurate - git pull и обновить все .env файлы у ВСЕХ облаков из актуального .env.example репозитория
```
@@ -28,11 +29,12 @@ GET:
POST:
```
/servers/ — создать сервер в БД (при наличии id - обновление)
/clients/ — создать клиента (при наличии id - обновление данных, здесь не нужно отправлять пароль, а задавать его командой ниже)
/clients/ — создать клиента
здесь не нужно отправлять пароль, а задавать его командой ниже)
при создании - после проверки сразу вернётся id.
во время создания нужно таймером опрашивать
/clients/{id}/createlog и получать актуальный лог о создании контейнера (поднимать ws пока не будем). За окончание создания отвечает статус "completed" => "ok | error".
PUT /clients/{id} - обновление данных клиента
/clients/{id}/admin_password — задать новый пароль администратора лаборатории
/clients/{id}/expire_date - обновление крайней даты использования системы
TODO: перенос данных клиента с одного сервера на другой
@@ -64,8 +66,8 @@ DELETE
/containers/{server_id}/build_dev/{new_version} - получение кода из ветки dev, сборка фронта и нового образа (промежуточная версия/хотфикс)
/containers/{server_id}/build_master/{new_version} - получение кода из ветки master, сборка нового образа (промежуточная версия/хотфикс)
POST /containers/{server_id}/build_dev/{new_version} - получение кода из ветки dev, сборка фронта и нового образа (промежуточная версия/хотфикс)
/containers/{server_id}/build_master/{new_version} - получение кода из ветки master, сборка нового образа (релизная версия)
/containers/{server_id}/prune - удалить неиспользуемые версии образов (containers/image prune -a --force) (docker system prune -a --volumes --force)
/clients/{id}/switch/{image_name} - принудительно переключить клиента на образ другой версии
@@ -109,6 +111,7 @@ phone - телефон администратора (для будущих оп
city - город
timezone - часовой пояс (для контейнера лаборатории)
version_tag - версия образа (доступные версии получаются из запроса /server/id/images)
allowed_ips - string - json-массив, в котором содержится список разрешённых IP или масок.
config - конфигурация лаборатории. Условный текст.
license - лицензия лаборатории. Условный текст.
license_expires_at - datetime окончания лицензии (пишется в БД клиента)
@@ -154,7 +157,10 @@ nginx can bash
nginx authorized key from master API
nvm.sh для пользователя nginx, содержащий импорт путей к nvm/npm
сделать nginx пользователя по-умолчанию www-data для правильного предоставления прав из-под контейнера.
supervisor с конфигами
- reverb для websockets
**только облачном на сервере**
- php artisan queue:work --queue=client_creation - Job фонового создания клиента -
www-data/nginx sudo NOPASSWD /usr/bin/docker (remote cmd exec)
www-data/nginx sudo NOPASSWD /usr/bin/docker-compose (remote cmd exec)
@@ -167,7 +173,7 @@ www-data ALL=(ALL) NOPASSWD: /usr/bin/docker-compose
www-data sudo NOPASSWD certbot (remote cmd exec)
### Права на каталоги сервера (nginx:nginx 775):
### Права на каталоги сервера (www-data:www-data 775):
/srv/www — current code (composer install && npm run build)
/srv/docker docker-compose.yml and Dockerfile (copy all from /srv/git without .git)
/srv/docker/clients/client1/.env — client files mount dir

View File

@@ -0,0 +1,22 @@
{
"nodes":[
{"id":"b232eaaa765801cd","x":-407,"y":-535,"width":967,"height":915,"type":"group","label":"prod server"},
{"id":"950a3f18343d6666","type":"text","text":"**LARAVEL REVERB**","x":-241,"y":-159,"width":305,"height":60},
{"id":"03d9dc2aac19b21c","type":"text","text":"supervisor","x":-387,"y":-159,"width":146,"height":60},
{"id":"23f9f9e9f6df3204","type":"text","text":"alfa cloud client 2\n\nreverb_host = ws.domain.ru\napp_id = 2\napp_secret…","x":260,"y":-305,"width":280,"height":206},
{"id":"b466a29fd81a1549","x":260,"y":-515,"width":280,"height":207,"type":"text","text":"alfa cloud client 1\n\nreverb_host = ws.domain.ru\napp_id = 1\napp_secret…"},
{"id":"da229572124dccdb","type":"text","text":"alfa cloud client 3\n\nreverb_host = ws.domain.ru\napp_id = 3\napp_secret…","x":260,"y":-86,"width":280,"height":206},
{"id":"b4e4d7af3683f831","type":"text","text":"nginx ssl proxy_pass ws.domain.ru","x":-265,"y":-308,"width":354,"height":57},
{"id":"6f76b2e274e21883","type":"text","text":"config/reverb.php\n```\n[apps => [\n\tapp_id = 1,\n\tapp_secret\n\t….\n],\napp_id = 2,\n\tapp_secret\n\t….]\n```","x":-240,"y":-40,"width":305,"height":395},
{"id":"d5594d4a090579fa","x":-213,"y":-740,"width":250,"height":60,"type":"text","text":"Frontend"}
],
"edges":[
{"id":"faa1c92017d6155f","fromNode":"950a3f18343d6666","fromSide":"bottom","toNode":"6f76b2e274e21883","toSide":"top"},
{"id":"4e9f26cedadd4093","fromNode":"b4e4d7af3683f831","fromSide":"bottom","toNode":"950a3f18343d6666","toSide":"top"},
{"id":"4ed52e28301cd3a1","fromNode":"b466a29fd81a1549","fromSide":"top","toNode":"d5594d4a090579fa","toSide":"bottom"},
{"id":"b0f6cae45f7e4643","fromNode":"d5594d4a090579fa","fromSide":"bottom","toNode":"b4e4d7af3683f831","toSide":"top"},
{"id":"71c58971acde2531","fromNode":"b466a29fd81a1549","fromSide":"left","toNode":"b4e4d7af3683f831","toSide":"right"},
{"id":"e2b1a0ccff519381","fromNode":"23f9f9e9f6df3204","fromSide":"left","toNode":"b4e4d7af3683f831","toSide":"right"},
{"id":"4ab0e1a786c5fd54","fromNode":"da229572124dccdb","fromSide":"left","toNode":"b4e4d7af3683f831","toSide":"right"}
]
}

View File

@@ -0,0 +1,21 @@
{
"nodes":[
{"id":"7b5552a5f0686cf9","x":-840,"y":-150,"width":250,"height":60,"type":"text","text":"user\n"},
{"id":"1a2c378d7c1df73b","type":"text","text":"user\n","x":-840,"y":-90,"width":250,"height":60},
{"id":"2ed7e3fe497d9212","type":"text","text":"user\n","x":-840,"y":-30,"width":250,"height":60},
{"id":"ab6acb1e165755c9","type":"text","text":"user\n","x":-840,"y":30,"width":250,"height":60},
{"id":"24e2e698d8aa7339","x":-380,"y":-120,"width":250,"height":190,"type":"text","text":"**notify_to_users**\nuser_id\nnotify_id\nis_read"},
{"id":"8bd7b516330c25b6","x":40,"y":-120,"width":250,"height":60,"type":"text","text":"user_notifies"},
{"id":"60aec1053e78ad1b","type":"text","text":"user_notifies","x":40,"y":-60,"width":250,"height":60},
{"id":"0e9831ddc945aa30","type":"text","text":"user_notifies","x":40,"y":0,"width":250,"height":60}
],
"edges":[
{"id":"d8ffebdcbc5f9f46","fromNode":"7b5552a5f0686cf9","fromSide":"right","toNode":"24e2e698d8aa7339","toSide":"left"},
{"id":"79ef48ec5c0c2b08","fromNode":"1a2c378d7c1df73b","fromSide":"right","toNode":"24e2e698d8aa7339","toSide":"left"},
{"id":"90cdf5e14654ac2a","fromNode":"2ed7e3fe497d9212","fromSide":"right","toNode":"24e2e698d8aa7339","toSide":"left"},
{"id":"1b58f2b35bb0bafc","fromNode":"ab6acb1e165755c9","fromSide":"right","toNode":"24e2e698d8aa7339","toSide":"left"},
{"id":"531c9ad82d4d1b9e","fromNode":"24e2e698d8aa7339","fromSide":"right","toNode":"8bd7b516330c25b6","toSide":"left"},
{"id":"43c16f581711ec84","fromNode":"24e2e698d8aa7339","fromSide":"right","toNode":"60aec1053e78ad1b","toSide":"left"},
{"id":"9226c9cc80df5d57","fromNode":"24e2e698d8aa7339","fromSide":"right","toNode":"0e9831ddc945aa30","toSide":"left"}
]
}

View File

@@ -0,0 +1,16 @@
| id | name | custom | required | placeholder | type | label | sort_id | renderData (json) | on_edit | destination_table |
| --- | ------------------ | ------ | -------- | -------------- | -------- | ----------------------- | ------- | -------------------------------------------------------------------------- | ---------- | ----------------- |
| 1 | sample_name_doc | 0 | 1 | Введите… | text | Наим. ОИ для документов | | | | |
| 2 | sample_name_lab | … | 1 | | text | Наим. ОИ для лаб. | | | | |
| 3 | tn_ved | … | 1 | Введите ТН ВЭД | text | ТН ВЭД | | | | sample_details |
| 4 | probe_quantity | … | 1 | | number | Кол-во проб | | | setProbesQ | sample_details |
| 5 | manufacturer | … | 1 | | select | Изготовитель | | {<br>“table” : “contragents”,<br>“group” : 4<br>} | | sample_details |
| 6 | manufacture_date | | 1 | | date | Дата изготовления | | | | sample_details |
| 7 | storage_conditions | | 0 | | text | Условия хранения | | | | sample_details |
| 8 | package | | 0 | | text | Упаковка | | | | sample_details |
| | | | | | | | | | | |
| 9 | sampling_by | | | | select | Отбор осуществляется | | { “options”:<br>[<br>“1” : “Заказчиком”,<br>“2” : “Исполнителем”<br>]<br>} | | |
| 10 | trials_in_OA_range | | | | checkbox | | | | | |

View File

@@ -0,0 +1,18 @@
| id | containing_pages | object_type_id | trials_type | pgo | dyn_control_id |
| --- | ----------------------------- | -------------- | ----------- | ---- | -------------- |
| int | string | int | int | int | int |
| 1 | ['“deals”] | 1 (продукция) | null | null | 1 |
| 2 | | null | null | null | 2 |
| 3 | | 1 (продукция) | null | null | 3 |
| 4 | | null | null | null | 4 |
| 5 | | | null | null | 5 |
| 6 | | | null | null | 6 |
| 7 | | | null | null | 7 |
| 8 | [“request”, “sample_details”] | 1 | 1 | 1 | 3 |
| 9 | | | null | 1 | 9 |
| 10 | | | null | 1 | 10 |
| 11 | | | null | 1 | 11 |
| 12 | | | null | 1 | 12 |
| 13 | | | null | 1 | 13 |
| 14 | | | null | 1 | 14 |
| 15 | | | | 1 | |

View File

@@ -0,0 +1,12 @@
| id | pgo_group (fiz) | object_group_id | atmosperical_group_id |
| --- | --------------- | --------------- | --------------------- |
| 1 | 1 | 1 | null |
| 2 | 1 | 2 | null |
| 3 | 1 | 3 | null |
| 4 | 1 | 4 | null |
| 5 | 1 | 5 | null |
| 6 | 1 | 6 | null |
| 7 | 1 | 7 | null |
| 8 | 2 | null | 1 |
| | …. | | |

View File

@@ -0,0 +1,7 @@
| | | Отбор осуществляется | Документ | Наименование | Дата документа | Скан документа отбора |
| --- | ------------------ | ------------------------ | --------------------------------------------------- | ------------------------------------------------------------------------ | ----------------------------------------- | --------------------- |
| | Привязка к IO | | select<br>(показывать, если sampling == Заказчиком) | text<br>Ввод текста, если Заказчиком, подгрузка, если выбран из селекта) | date<br>подгрузка, если выбран из селекта | file upload |
| **id** | **incoming_object_id** | **sampling_by** | **sampling_act_id** | **sampling_doc_name** | **sampling_date** | **sampling_doc_file** |
| int | int | text | int | string | date | string (file) |
| | | Заказчиком / Подрядчиком | | | | |

View File

@@ -0,0 +1,56 @@
{
"nodes":[
{"id":"e2c25eeb469135d1","type":"group","x":-2080,"y":-1454,"width":840,"height":496,"label":"Продукт \"Заказы\""},
{"id":"7da105f25a470a3a","type":"group","x":-780,"y":-1320,"width":493,"height":228,"label":"Общ. инф. о принятых образцах"},
{"id":"3bf2ea8e911a11fb","type":"text","text":"measured_properties\nОпределяемые характеристики","x":-2560,"y":-776,"width":250,"height":106,"color":"5"},
{"id":"29393ee0f930d8b4","type":"text","text":"m2m\n**measurements**","x":-2560,"y":-621,"width":250,"height":80,"color":"5"},
{"id":"8887866d82552513","type":"text","text":"regulatory_docs\nНормативная документация","x":-2560,"y":-483,"width":250,"height":89,"color":"5"},
{"id":"4445396fe3541b75","type":"text","text":"ДЕТАЛИ ЗАПРОСА\n**deal_details**\nid\ndeal_id\n…","x":-2060,"y":-1434,"width":250,"height":456,"color":"5"},
{"id":"9df01bc074ef517d","type":"text","text":"**deals**\nid\nnumber (char!)\ncontragent_id\ncomments","x":-1600,"y":-1426,"width":250,"height":146,"color":"5"},
{"id":"71cc03ce75e3d4fc","type":"text","text":"**requests**\nid\ndeal_id\nincoming_object_id\nnumber (char!)]\ncomments","x":-1600,"y":-1153,"width":250,"height":175},
{"id":"9fa733aa38520412","type":"text","text":"m2m\n**measurements_to_incoming_object**\nmeasurement_id\nsort_index\nincobj_id\nprice","x":-1765,"y":-684,"width":330,"height":201},
{"id":"a0ab18e1b63a5fda","type":"text","text":"m2m\n**measurements_to_probe**\nmeasurement_id\nsort_index","x":-2154,"y":-142,"width":270,"height":124},
{"id":"c4690f52973944d0","type":"text","text":"**incoming_object**\nid\ntest_object text (nullable)\ntest_object_type_id\ntest_object_group(nullable)\nenvironment_group(nullable)\n","x":-662,"y":-692,"width":260,"height":220,"color":"4"},
{"id":"bb313e88caf2ec24","type":"text","text":"1 ЗАЯВКА = 1 ОИ = 1 i_o","x":-662,"y":-472,"width":260,"height":50},
{"id":"d94920b520ac672b","type":"text","text":"## sample_details\nid\nincoming_object_id\nmain_cipher\nstatus all|accepted|in_progress|completed|disposed","x":-760,"y":-1298,"width":440,"height":190},
{"id":"5453c91591d3b2b6","type":"text","text":"Планирование","x":-665,"y":-1568,"width":250,"height":60},
{"id":"2d314da1c43e2e8c","type":"text","text":"test_object_group","x":-200,"y":-1318,"width":260,"height":60,"color":"5"},
{"id":"f022da86180ea998","type":"text","text":"enviroment_group","x":94,"y":-1320,"width":250,"height":60,"color":"5"},
{"id":"79d610a57239cf74","type":"text","text":"Referral","x":-809,"y":164,"width":555,"height":60},
{"id":"858ccce1b3abf6e1","type":"text","text":"Trials","x":-809,"y":304,"width":555,"height":60},
{"id":"e3e1f62056d2cbeb","type":"text","text":"**probes**\n\nid\ncipher\nstatus char\nincoming_object_id\nsample_details_id","x":-634,"y":-180,"width":205,"height":200},
{"id":"97d83c8605a857e4","type":"text","text":"samples","x":-652,"y":-340,"width":241,"height":58},
{"id":"f3726d8bd642fef6","type":"text","text":"Отбор","x":344,"y":-1005,"width":250,"height":50},
{"id":"04db4de345b3d180","type":"text","text":"","x":720,"y":-603,"width":73,"height":55},
{"id":"8c8f1c06b78a532e","type":"text","text":"fiz 1\nid\ntest_object_type = 1\ntest_object_group = 1\nincoming_object_id\nкакие-то данные","x":920,"y":-918,"width":250,"height":178},
{"id":"8b293c5775c2dcfa","type":"text","text":"fiz 2\nid\nincoming_object_id\ntest_object_type = 2\nкакие-то данные","x":920,"y":-715,"width":250,"height":140},
{"id":"47c64a97a70e1f95","type":"text","text":"fiz 3\nid\nincoming_object_id\ntest_object_type = 3\nкакие-то данные","x":920,"y":-544,"width":250,"height":137},
{"id":"768279285e43afbb","type":"text","text":"CUSTOM FIZ","x":920,"y":-374,"width":250,"height":50,"color":"6"}
],
"edges":[
{"id":"ee7e0c6c68fc1c3b","fromNode":"29393ee0f930d8b4","fromSide":"right","toNode":"9fa733aa38520412","toSide":"left","label":"o2m"},
{"id":"c848c81687e3eca2","fromNode":"9fa733aa38520412","fromSide":"right","toNode":"c4690f52973944d0","toSide":"left","label":"m2o"},
{"id":"47ce10a250c6bdf0","fromNode":"3bf2ea8e911a11fb","fromSide":"bottom","toNode":"29393ee0f930d8b4","toSide":"top"},
{"id":"226a281b17415c78","fromNode":"8887866d82552513","fromSide":"top","toNode":"29393ee0f930d8b4","toSide":"bottom"},
{"id":"ab5fdaba094f231c","fromNode":"2d314da1c43e2e8c","fromSide":"bottom","toNode":"c4690f52973944d0","toSide":"right","label":"o2m"},
{"id":"53af7e1ffe4e101d","fromNode":"8c8f1c06b78a532e","fromSide":"left","toNode":"04db4de345b3d180","toSide":"right"},
{"id":"eae6b786d8929004","fromNode":"8b293c5775c2dcfa","fromSide":"left","toNode":"04db4de345b3d180","toSide":"right"},
{"id":"afb8456c3cb766e0","fromNode":"47c64a97a70e1f95","fromSide":"left","toNode":"04db4de345b3d180","toSide":"right"},
{"id":"65d82f832aa11440","fromNode":"f022da86180ea998","fromSide":"bottom","toNode":"c4690f52973944d0","toSide":"right"},
{"id":"9db96381444cf255","fromNode":"768279285e43afbb","fromSide":"left","toNode":"04db4de345b3d180","toSide":"right"},
{"id":"17039bb0291942fb","fromNode":"29393ee0f930d8b4","fromSide":"right","toNode":"a0ab18e1b63a5fda","toSide":"left"},
{"id":"efe7ac07b59c3fe0","fromNode":"a0ab18e1b63a5fda","fromSide":"right","toNode":"e3e1f62056d2cbeb","toSide":"left"},
{"id":"90a69d47f157ae83","fromNode":"e3e1f62056d2cbeb","fromSide":"bottom","toNode":"79d610a57239cf74","toSide":"top"},
{"id":"98988b7bee687ddc","fromNode":"79d610a57239cf74","fromSide":"bottom","toNode":"858ccce1b3abf6e1","toSide":"top"},
{"id":"506ccdef9d015ac9","fromNode":"04db4de345b3d180","fromSide":"left","toNode":"c4690f52973944d0","toSide":"right"},
{"id":"259b3421c70807f0","fromNode":"9df01bc074ef517d","fromSide":"bottom","toNode":"71cc03ce75e3d4fc","toSide":"top","label":"o2m"},
{"id":"e5f8dc5d2429fea3","fromNode":"71cc03ce75e3d4fc","fromSide":"bottom","toNode":"c4690f52973944d0","toSide":"left","label":"o2o"},
{"id":"c8b0e2428250a96b","fromNode":"4445396fe3541b75","fromSide":"right","toNode":"9df01bc074ef517d","toSide":"left","label":"o2o"},
{"id":"97893a85bbc17b27","fromNode":"e2c25eeb469135d1","fromSide":"right","toNode":"7da105f25a470a3a","toSide":"left","fromEnd":"arrow"},
{"id":"972efb74850fda4e","fromNode":"5453c91591d3b2b6","fromSide":"bottom","toNode":"7da105f25a470a3a","toSide":"top"},
{"id":"2cf6421083fc36a1","fromNode":"7da105f25a470a3a","fromSide":"bottom","toNode":"c4690f52973944d0","toSide":"top"},
{"id":"da6a5e5253da8f7e","fromNode":"f3726d8bd642fef6","fromSide":"left","toNode":"c4690f52973944d0","toSide":"right"},
{"id":"9f4b0a9da4df1a32","fromNode":"bb313e88caf2ec24","fromSide":"bottom","toNode":"97d83c8605a857e4","toSide":"top","label":"o2m"},
{"id":"787ff1f5f23a0b14","fromNode":"97d83c8605a857e4","fromSide":"bottom","toNode":"e3e1f62056d2cbeb","toSide":"top","label":"o2m"}
]
}

View File

@@ -0,0 +1,63 @@
{
"nodes":[
{"id":"b1a5db44045670cb","type":"group","x":-240,"y":120,"width":290,"height":100,"color":"4","label":"incoming object"},
{"id":"7e8835e3e1ba3352","type":"text","text":"Заказы","x":-697,"y":-100,"width":300,"height":60,"color":"2"},
{"id":"e62e3ee025fe6894","type":"text","text":"ЛК Клиента (Mol)","x":-517,"y":-320,"width":160,"height":60,"color":"2"},
{"id":"e208050bdbb358e2","type":"text","text":"ОРЗ (Alfa)","x":-720,"y":-320,"width":150,"height":60,"color":"2"},
{"id":"c08c3234b640c706","type":"text","text":"Заказ (Mol)","x":-517,"y":-220,"width":160,"height":55,"color":"2"},
{"id":"e090fe1cebbbb03b","type":"text","text":"Заявка","x":-622,"y":29,"width":250,"height":60,"color":"2"},
{"id":"53b07b8c63ac6754","type":"text","text":"Методики","x":-697,"y":240,"width":223,"height":60},
{"id":"35dfe1d95c320bcb","type":"text","text":"Планирование","x":-197,"y":-320,"width":200,"height":60,"color":"5"},
{"id":"654e93b8481acfcc","type":"text","text":"Группа ОИ","x":173,"y":-320,"width":145,"height":60},
{"id":"c08d8b950cb48267","type":"text","text":"ОБЪЕКТ ИСПЫТАНИЙ","x":121,"y":-220,"width":250,"height":60},
{"id":"482821432f314713","type":"text","text":"Площадка испытаний","x":453,"y":-320,"width":250,"height":60,"color":"6"},
{"id":"a834100b1387af66","type":"text","text":"Образец","x":-349,"y":360,"width":250,"height":60,"color":"3"},
{"id":"3ac7bb5ae11d545c","type":"text","text":"Приёмка","x":-350,"y":455,"width":250,"height":60,"color":"3"},
{"id":"6155d0ff327bbc83","type":"text","text":"Отбор","x":-159,"y":-100,"width":120,"height":60,"color":"1"},
{"id":"f1c7215226dfb570","type":"text","text":"1 заявка, 1 ОИ","x":-220,"y":140,"width":250,"height":60},
{"id":"029cec42ed40b0b6","type":"text","text":"Детали входящего объекта","x":164,"y":120,"width":164,"height":100},
{"id":"a5878407dd1a1e7f","type":"text","text":"Обязательные базовые модули","x":500,"y":120,"width":250,"height":60},
{"id":"cfbeb745837968f2","type":"text","text":"Проба","x":-350,"y":600,"width":125,"height":60,"color":"3"},
{"id":"95ef9df31f4df66b","type":"text","text":"Проба","x":-225,"y":600,"width":125,"height":60,"color":"3"},
{"id":"c535308a75c1d9cb","type":"text","text":"Схемы испытаний","x":980,"y":89,"width":250,"height":60},
{"id":"0a468ffbee0b64e4","type":"text","text":"ПРОБОПОДГОТОВКА","x":980,"y":340,"width":250,"height":60},
{"id":"898f7f83c4d8323c","type":"text","text":"Продукт “Заказы”","x":500,"y":180,"width":250,"height":60,"color":"2"},
{"id":"d033d9c7cdef83d5","type":"text","text":"Продукт “Планирование”","x":500,"y":240,"width":250,"height":60,"color":"5"},
{"id":"01bf54199afdcb9a","type":"text","text":"Продукт “Отборы”","x":500,"y":300,"width":250,"height":60,"color":"1"},
{"id":"29a70650d69550cb","type":"text","text":"Продукт “Измерения”","x":500,"y":360,"width":250,"height":60,"color":"6"},
{"id":"e987c2e3df3c6566","type":"text","text":"Продукт “Работа с образцами”","x":500,"y":420,"width":250,"height":60,"color":"3"},
{"id":"bbd52f79b8abf91b","type":"text","text":"Продукт “Согласования”","x":500,"y":480,"width":250,"height":60,"color":"#1d00fa"},
{"id":"3dbb9eac3025a43f","type":"text","text":"Испытания","x":-357,"y":800,"width":1073,"height":60},
{"id":"04afb995ff0aa877","type":"text","text":"ПРОТОКОЛЫ","x":55,"y":900,"width":250,"height":60},
{"id":"f697de3055093896","type":"text","text":"Согласования","x":328,"y":-40,"width":250,"height":60,"color":"#1d00fa"},
{"id":"5e65b3fb85e74024","type":"text","text":"Точка","x":256,"y":460,"width":125,"height":50,"color":"6"},
{"id":"687451563188a0eb","type":"text","text":"Точка","x":121,"y":460,"width":125,"height":50,"color":"6"}
],
"edges":[
{"id":"e4421be921911201","fromNode":"654e93b8481acfcc","fromSide":"bottom","toNode":"c08d8b950cb48267","toSide":"top"},
{"id":"126cf8b2ef8d1797","fromNode":"b1a5db44045670cb","fromSide":"bottom","toNode":"a834100b1387af66","toSide":"top","color":"3","label":"Био и продукция"},
{"id":"7379698c5adc9dbe","fromNode":"a834100b1387af66","fromSide":"bottom","toNode":"3ac7bb5ae11d545c","toSide":"top","color":"3"},
{"id":"1e5d668ac3aa1ebc","fromNode":"3ac7bb5ae11d545c","fromSide":"bottom","toNode":"cfbeb745837968f2","toSide":"top","color":"3"},
{"id":"e1c9a6906e0dea7c","fromNode":"3ac7bb5ae11d545c","fromSide":"bottom","toNode":"95ef9df31f4df66b","toSide":"top","color":"3"},
{"id":"5b027f8024bbc4d3","fromNode":"35dfe1d95c320bcb","fromSide":"bottom","toNode":"6155d0ff327bbc83","toSide":"top","color":"5"},
{"id":"09d8577f20495e84","fromNode":"6155d0ff327bbc83","fromSide":"bottom","toNode":"b1a5db44045670cb","toSide":"top","label":"пл и отбор"},
{"id":"8ce602bbf989248c","fromNode":"b1a5db44045670cb","fromSide":"right","toNode":"029cec42ed40b0b6","toSide":"left","fromEnd":"arrow"},
{"id":"d2765c92d6da48fa","fromNode":"e208050bdbb358e2","fromSide":"bottom","toNode":"7e8835e3e1ba3352","toSide":"top","color":"2"},
{"id":"6a221ab455ed9990","fromNode":"7e8835e3e1ba3352","fromSide":"right","toNode":"6155d0ff327bbc83","toSide":"left","color":"2"},
{"id":"d1c9bbf68c49f92b","fromNode":"7e8835e3e1ba3352","fromSide":"bottom","toNode":"e090fe1cebbbb03b","toSide":"top","color":"2","label":"o2m"},
{"id":"eab4d6459d8b75ab","fromNode":"e62e3ee025fe6894","fromSide":"bottom","toNode":"c08c3234b640c706","toSide":"top","color":"2"},
{"id":"862a5777a054fece","fromNode":"c08c3234b640c706","fromSide":"bottom","toNode":"7e8835e3e1ba3352","toSide":"top","color":"2"},
{"id":"a01fcf4e19455977","fromNode":"53b07b8c63ac6754","fromSide":"right","toNode":"b1a5db44045670cb","toSide":"left","label":"m2m"},
{"id":"945cf1b3fbbce6b4","fromNode":"c08d8b950cb48267","fromSide":"bottom","toNode":"b1a5db44045670cb","toSide":"top","label":"m2o"},
{"id":"039a11fc4b56bff1","fromNode":"e090fe1cebbbb03b","fromSide":"bottom","toNode":"b1a5db44045670cb","toSide":"left","color":"2","label":"o2o"},
{"id":"283bf77cca95aeb8","fromNode":"482821432f314713","fromSide":"bottom","toNode":"c08d8b950cb48267","toSide":"right","color":"6"},
{"id":"472b24c0651c6ffd","fromNode":"95ef9df31f4df66b","fromSide":"bottom","toNode":"3dbb9eac3025a43f","toSide":"top","color":"3"},
{"id":"dd035ecad4759df8","fromNode":"cfbeb745837968f2","fromSide":"bottom","toNode":"3dbb9eac3025a43f","toSide":"top","color":"3"},
{"id":"7c0b5238f130148a","fromNode":"b1a5db44045670cb","fromSide":"bottom","toNode":"5e65b3fb85e74024","toSide":"top","color":"6","label":"Производств. и окруж среда"},
{"id":"a266254c49ad70f5","fromNode":"5e65b3fb85e74024","fromSide":"bottom","toNode":"3dbb9eac3025a43f","toSide":"top","color":"6"},
{"id":"54ab3f7cfc47f2fa","fromNode":"b1a5db44045670cb","fromSide":"bottom","toNode":"687451563188a0eb","toSide":"top","color":"6"},
{"id":"69f7724f74ce5028","fromNode":"687451563188a0eb","fromSide":"bottom","toNode":"3dbb9eac3025a43f","toSide":"top","color":"6"},
{"id":"46da6215bf433eb3","fromNode":"f697de3055093896","fromSide":"bottom","toNode":"b1a5db44045670cb","toSide":"top","color":"#1d09b8"},
{"id":"3593c0de9e81e4cd","fromNode":"3dbb9eac3025a43f","fromSide":"bottom","toNode":"04afb995ff0aa877","toSide":"top"}
]
}

View File

@@ -0,0 +1,34 @@
{
"nodes":[
{"id":"471fc15ab64b3b4b","type":"group","x":-1586,"y":-280,"width":2966,"height":1140,"color":"4","label":"Загрузка полей и данных"},
{"id":"1bdc3fd8f4fc131a","type":"group","x":-1586,"y":-1483,"width":2966,"height":1103,"color":"5","label":"Структура данных"},
{"id":"730071c06b433cd8","type":"group","x":-1220,"y":920,"width":2220,"height":420,"color":"6","label":"Сохранение"},
{"id":"9eb9ce3354acd504","type":"group","x":-802,"y":1440,"width":1382,"height":280,"color":"3","label":"TODO:"},
{"id":"ae68b7d5dfd17b3e","type":"text","text":"Участвующие в динамическом распределении данных таблицы sample_details, requests и т.д. **ОБЯЗАНЫ** привязку к текущему incoming_object и иметь поле dynamic_data, в котором будут храниться данные, сохранённые из динамических полей","x":-765,"y":-560,"width":565,"height":140},
{"id":"2219c7c391038f10","type":"text","text":"### Инициация\n\nКрасные поля в таблице “Распределение данных” и поля, **которые ни от чего не зависят** - грузятся в форму на фронте и сохраняются в свои соответствующие таблицы (поля с заказчиком, основание, договор, контактные лица, менеджер и тд) - они общие для всех и их лучше обрабатывать как нормальные данные.\n\nПосле выбора ТИПА ОИ, ТИПА ИСПЫТАНИЙ, ГРУППЫ ОИ (в окне Заявки или Образца) начинается подгрузка динамических полей.","x":-1520,"y":-260,"width":600,"height":463},
{"id":"78a47b6bae2c6690","type":"text","text":"### Определение динамических полей\n\nИз группы ОИ получается PGO (пакет группы испытаний (бывший ФИЗ)) из таблицы pgo_to_groups\nВыполняется выборка dyn_controls_id из таблицы **modules_to_dyn_controls** согласно выбранным object_type, trials_type, pgo и модуля (page), запрашивающего данные.\nПо каждому элементу массива dyn_controls_id выполняется выборка из таблицы dynamic_controls_table c сортировкой по sort_id.","x":-800,"y":-260,"width":600,"height":463},
{"id":"4c9e712b802ea35e","type":"text","text":"### Подгрузка сохранённых данных\n\nЭту процедуру можно выполнять в предыдущем цикле обработки каждого поля\nСобираются все уникальные значения destination_table у текущих выбранных полей\nДелается выборка по таблицам destination_table по id открытого документа или destination_table.incoming_object.id\nУ выбранных объектов получаются json-массивы сохранённых данных из поля dynamic_data\nКаждое динамическое поле проверяется на соответствие и присутствие в сохранённых данных.\nЕсли данные присутствуют - динамическому полю добавляется поле “value” со значением из документа.\nТаким образом можно собирать данные, присвоенные в совершенно разных динамических полях заявки/документов. Пример сохранённых данных показан ниже","x":600,"y":-260,"width":600,"height":463},
{"id":"d3bed003864f4b06","type":"text","text":"### Форматирование динамических полей\n\nКаждый элемент поля проходит проверку и подготавливается для отправки на фронтэнд.\nПроверяется каждое поле renderData и если есть ключ table, то делается выборка из соответствующей таблицы с параметрами и поле трансформируется в options с готовыми данными.\n\nКаждому полю на фронте присваивается свойство dynamic_control_id, чтобы обработать его данные при сохранении","x":-80,"y":-260,"width":600,"height":463},
{"id":"36f032eaa8b89a57","type":"text","text":"### Содержание поля dynamic_data у таблиц с данными динамических полей.\n\n```json\n[\n\t{\n\t\t\"id\": \"3\",\n\t\t\"name: \"tn_ved\",\n\t\t\"value\" : \"3022 4433\"\n\t},\n\t{\n\t\t\"id\": \"7\",\n\t\t\"name: \"storage_conditions\",\n\t\t\"value\" : \"Пакет целлофановый\"\n\t},\n\t{\n\t\t\"id\": \"9\",\n\t\t\"name: \"trials_in_OA_range\",\n\t\t\"value\" : true\n\t},\n]\n```","x":-400,"y":283,"width":535,"height":517},
{"id":"4aefb6d2d59c1c9a","type":"file","file":"WORK & PROJECTS/Mol/Планы и диаграммы/Заказы/modules_to_dyn_controls.md","x":-960,"y":-1309,"width":760,"height":669},
{"id":"330a5e6e96216ba6","type":"file","file":"WORK & PROJECTS/Mol/Планы и диаграммы/Заказы/pgo_to_groups.md","x":-1500,"y":-1206,"width":400,"height":464},
{"id":"68b998893332eba2","type":"text","text":"trials_type\n1 - Лабораторные\n2 - Выездные","x":-560,"y":-1440,"width":250,"height":103},
{"id":"3c06fbe8ba52b772","type":"text","text":"### Отправка данных на backend\n\nС фронтенда прилетают данные полей со свойством dynamic_control_id и value","x":-1120,"y":940,"width":600,"height":380},
{"id":"2f8ebcf398e207de","type":"text","text":"### Формирование и сохранение массива данных\n\nДелается выборка полей id, name, destination_table из таблицы dynamic_control_id из массива всех полученных dynamic_control_id\n\nСобирается несколько массивов dynamic_data с группировкой по destination_table\nПроходя по каждой записи destination_table выбирается объект для записи из таблицы по текущему incoming_object_id (прилетает с фронтенда или создаётся при первичном сохранении).\nТекущий массив данных сохраняется в поле dynamic_data в таблицу destination_table","x":340,"y":940,"width":600,"height":380},
{"id":"a936e40ca6c658a7","type":"text","text":"### Валидация динамических полей\n\nTODO!","x":-384,"y":940,"width":609,"height":380},
{"id":"625877f2c5a88673","type":"text","text":"Продумать полностью формат хранения динамических контролов и подгружаемых данных","x":-782,"y":1460,"width":1342,"height":50},
{"id":"2ec086ec8e32925e","type":"text","text":"Сформировать первичные данные таблиц pgo_to_groups, dynamic_controls_table и modules_to_dyn_controls, необходимые для отображения/сохранения данных по текущим данным. Для начала вручную. Можно задуматься о ТЗ редактора :)","x":-782,"y":1510,"width":1342,"height":70},
{"id":"ddb16308453a51d9","type":"text","text":"Продумать поддержку зависимости от объектов, глубже incoming_object (Испытания/протоколы)","x":-779,"y":1580,"width":1339,"height":60},
{"id":"e968c77fea8190c3","type":"text","text":"Валидация полей","x":-779,"y":1640,"width":1339,"height":60},
{"id":"b630f9de672de8d5","type":"file","file":"WORK & PROJECTS/Mol/Планы и диаграммы/Заказы/dynamic_controls_table.md","x":-40,"y":-1349,"width":1360,"height":749}
],
"edges":[
{"id":"15a75fcef9b6166b","fromNode":"4aefb6d2d59c1c9a","fromSide":"right","toNode":"b630f9de672de8d5","toSide":"left"},
{"id":"ef85aa37ebd26a2a","fromNode":"330a5e6e96216ba6","fromSide":"right","toNode":"4aefb6d2d59c1c9a","toSide":"left"},
{"id":"4b3eb7d74e747c79","fromNode":"2219c7c391038f10","fromSide":"right","toNode":"78a47b6bae2c6690","toSide":"left"},
{"id":"d9451be02d13baec","fromNode":"78a47b6bae2c6690","fromSide":"right","toNode":"d3bed003864f4b06","toSide":"left"},
{"id":"1cabf9437f0fdd76","fromNode":"d3bed003864f4b06","fromSide":"right","toNode":"4c9e712b802ea35e","toSide":"left"},
{"id":"dd879e7b5dca46fd","fromNode":"3c06fbe8ba52b772","fromSide":"right","toNode":"a936e40ca6c658a7","toSide":"left"},
{"id":"fa6b923d8a4f2053","fromNode":"a936e40ca6c658a7","fromSide":"right","toNode":"2f8ebcf398e207de","toSide":"left"}
]
}

View File

@@ -0,0 +1,10 @@
| id | counter_id | table | entity_id | number | formatted_number | template | is_draft |
| --- | ---------- | ------- | --------- | ------ | ---------------- | ---------- | -------- |
| 1 | 1 | samples | 1 | 6 | 0006/24 | {n###}/{y} | false |
| 2 | 1 | samples | 2 | 7 | 0007/24 | {n###}/{y} | false |
| 3 | 1 | samples | 3 | 8 | 0007/24 | {n###}/{y} | false |
| 4 | 1 | samples | 4 | 9 | 0009/24 | {n###}/{y} | false |
| 5 | | | | | | | |
| 6 | | | | | | | |
| 7 | | | | | | | |

View File

@@ -0,0 +1,6 @@
| id | name | Sort_idx | |
| --- | -------------------- | -------- | --- |
| 1 | Лаб. сущности | 10 | |
| 2 | Документы | 20 | |
| 3 | Внутренняя нумерация | 30 | |

View File

@@ -0,0 +1,9 @@
| id | type_id | name | only_in_modules | table | parent_tables | child_tables | next_number | increment | reset_by_day | reset_by_month | reset_by_year | template | sort_idx |
| --- | ------- | -------------------- | --------------- | --------- | ------------------------------------------------------------------------------------------------------------------ | ------------------- | ----------- | --------- | ------------ | -------------- | ------------- | ---------- | -------- |
| 1 | 1 | Образец | | samples | [‘Заявка‘: request] | [‘Пробы‘: probes] | 1 | 1 | false | false | false | {n###}/{y} | 10 |
| 2 | 1 | Проба | | probes | [‘Образец’: samples] | | 1 | 1 | false | false | false | {n} | 20 |
| 3 | 2 | Заявка | [“commercial”] | requests | `['Образец': { 'relation': 'samples', 'model': 'Sample', 'column': 'number', 'mode':'table/history', ОИ: objects]` | | 1 | 1 | false | false | true | {n}/{y} | 30 |
| 4 | 2 | Акт приёмки образцов | | | | | 1 | 1 | false | false | false | {n}/{y} | 40 |
| 5 | 2 | Протокол | | protocols | | | 1 | 1 | false | false | true | {n}/{y} | 50 |
| | | | | | | | | | | | | | |

View File

@@ -0,0 +1,8 @@
{
"nodes":[
{"id":"4b2ef7e515c6f21c","x":-760,"y":-180,"width":400,"height":460,"type":"file","file":"WORK & PROJECTS/Mol/Планы и диаграммы/Нумератор/numerator_counter_types.md"},
{"id":"2e783075b2513a3e","x":-320,"y":-180,"width":1460,"height":460,"type":"file","file":"WORK & PROJECTS/Mol/Планы и диаграммы/Нумератор/numerator_counters.md"},
{"id":"597e047a2d2566f0","x":-320,"y":360,"width":940,"height":360,"type":"file","file":"WORK & PROJECTS/Mol/Планы и диаграммы/Нумератор/numerator_counter_history.md"}
],
"edges":[]
}

View File

@@ -1,25 +1,27 @@
#### Апрель 2025
- Редактор/конструктор форм
+/- Редактор/конструктор форм
+ Создать сервис редактора/генератора форм с сохранением в БД
+ Связать редактор с облачным сервисом для подключения к БД пользователей
+ Настроить открытие/создание/сохранение форм в БД пользователей
+ Доработать загрузку стиля страниц из БД при открытии документа
+ Включить поддержку макросов и таблиц из Сашиных плагинов
+/- Создать структуры макросов и их начальный набор в БД (какая-то часть уже есть)
+ Создать структуры макросов и их начальный набор в БД
- Сделать миграции и стандартные формы каждого документа
- Выгрузка документов/таблиц и макросов
+ Выгрузка документов/таблиц и макросов
+ Доработать поддержку всех стилей документа в генераторе
- Разработать поддержку динамических таблиц в редакторе
+/- Добавить выгрузку входящих данных и генерацию таблиц
+ Разработать поддержку динамических таблиц в редакторе
+ Добавить выгрузку входящих данных и генерацию таблиц
- Фронт выбора документа для редактора в Alfa и назначение документов
- Поддержка динамических колонтитулов (плагины???)
- Поддержка динамических колонтитулов
+ Регулярно проверяем обновления редактора в npm
+/- Усиление защиты редактора (проверка на вызывающий URL, проверка кук юзера, запрос облачного API через BasicAuth или BearerToken)
- Объекты испытаний
+ Создать группы ОИ
+/- Привязать классификатор продовольственных товаров к нашим группам ОИ
+/- Создать группы ОИ - закончить
+ Привязать классификатор продовольственных товаров к нашим группам ОИ
- создать стандартные миграции групп
- Таблицы заявок
- Нумератор документов (Alfa)
- привязка ссылок номеров документов к текущему заказу
- поддержка формата номеров в ссылочных номерах
@@ -32,7 +34,15 @@
- Организация прод-среды и тестирование деплоя и всей сети
- Разработка импорта для турбо-старта (Alfa)
- Турбо-старт можно организовать в коде Alfa, т.к. не будет импорта в Mol
- RabbitMQ/Kafka и менеджеры сообщений
- синхронизация всех пользователей/всех лаб с Mol
- создание ЛК клиентов
- синхронизация системы сообщений
- Разработка минимального серверного API для интеграции внешних сервисов
- Документация для клиентов (Swagger)
- Уведомления - reverb на сервере или в каждом облаке?
- Поддержка редактором документов платформы Mol

View File

@@ -0,0 +1,88 @@
#### Июнь 2025
+/- Редактор/конструктор форм
+ Создать сервис редактора/генератора форм с сохранением в БД
+ Связать редактор с облачным сервисом для подключения к БД пользователей
+ Настроить открытие/создание/сохранение форм в БД пользователей
+ Доработать загрузку стиля страниц из БД при открытии документа
+ Включить поддержку макросов и таблиц из Сашиных плагинов
+ Создать структуры макросов и их начальный набор в БД
+ Сделать миграции и стандартные формы каждого документа
+ Выгрузка документов/таблиц и макросов
+ Доработать поддержку всех стилей документа в генераторе
+ Разработать поддержку динамических таблиц в редакторе
+ Добавить выгрузку входящих данных и генерацию таблиц
- Фронт выбора документа для редактора в Alfa и назначение документов
- Поддержка динамических колонтитулов
+ Регулярно проверяем обновления редактора в npm
+/- Усиление защиты редактора (проверка на вызывающий URL, проверка кук юзера, запрос облачного API через BasicAuth или BearerToken)
- ЛИМС
+/- Создать группы ОИ - закончить
+ Привязать классификатор продовольственных товаров к нашим группам ОИ
+ создать стандартные миграции групп продовольств.
+ схемы бэка запросов/заявок
+ схемы динамических полей для разных типов ОИ
- проработать до конца взаимодействие всех возможных типов и групп ОИ с разными модулями
- Проработать таблицы бэка для множественных образцов и связыванием с incoming_object
- Добавить тарифную конфигурацию
- Нумератор документов
- привязка ссылок номеров документов к текущему заказу
- поддержка формата номеров в ссылочных номерах
- Система статусов с историей
- Структура чатов и комментариев (к сущностям и согласованиям)
- Mol.Облако
+ фронт облачного управления
- бессрочный демо-тариф
- разработать динамическую тарификацию со списанием средств
- админский раздел с регистрацией и разделением прав
- доделать все мелкие функции для облаков
+ смена админского пароля
- баги биллинга при создании облака и добавления тарифа
- красивые шаблоны для e-mail
+ перезапуск облака
- переконфигурация .env с нуля у каждой лабы
- обновление на новую версию (+ миграции и реконфигурация) - как со стороны облака, так и со стороны пользователя (проверять версию в настройках и на сервере)
- тестирование доступности всех облаков путём теста портов (бэк-сервис)
- добавить метрики Prometheus в каждое облако и отслеживать дополнительно
- reverb-сервер с динамической конфигурацией у каждого prod-сервера
- оптимизация скорости разворачивания облака (перенести csv-данные в прямые sql-запросы?)
- взаимодействие с Mol.лабРеестр
- подгрузка данных лабораторий по номеру аккредитации
- создание юр. лиц в разворачиваемом облаке из данных ФСА
- парсинг ОА ФСА и применение его к лаборатории
- Mol.лабРеестр
+ Парсинг новых лабораторий и их данных + ОА (доделать все типы ОА)
+ доделать фронтенд
+ Экспорт-импорт лидов в 1С-Битрикс
+ Авторизация
+ Развернуть на сервере
+ Парсинг дат аккредитации и след. проверки
- Рефакторинг - перенос сервиса пересчёта гос.флага
- Покупка прод-серверов и боевое разворачивание
- тестирование/доработка скрипта быстрого разворачивания сервера
- нагрузочное тестирование Цербера
- Разработка импорта для турбо-старта (Alfa) без ФСА (но с PDF-файлом)
- импорт помещений
- импорт сотрудников
- импорт оборудования
#### Июль 2025
- RabbitMQ/Kafka и менеджеры сообщений
- синхронизация всех пользователей/всех лаб с Mol
- создание ЛК клиентов
- синхронизация системы сообщений
- Разработка минимального серверного API для интеграции внешних сервисов
- Документация для клиентов (Swagger)
- Поддержка редактором документов платформы Mol

View File

@@ -0,0 +1,89 @@
#### Июнь 2025
+/- Редактор/конструктор форм
+ Создать сервис редактора/генератора форм с сохранением в БД
+ Связать редактор с облачным сервисом для подключения к БД пользователей
+ Настроить открытие/создание/сохранение форм в БД пользователей
+ Доработать загрузку стиля страниц из БД при открытии документа
+ Включить поддержку макросов и таблиц из Сашиных плагинов
+ Создать структуры макросов и их начальный набор в БД
+ Сделать миграции и стандартные формы каждого документа
+ Выгрузка документов/таблиц и макросов
+ Доработать поддержку всех стилей документа в генераторе
+ Разработать поддержку динамических таблиц в редакторе
+ Добавить выгрузку входящих данных и генерацию таблиц
- Фронт выбора документа для редактора в Alfa и назначение документов
- Поддержка динамических колонтитулов
+ Регулярно проверяем обновления редактора в npm
+/- Усиление защиты редактора (проверка на вызывающий URL, проверка кук юзера, запрос облачного API через BasicAuth или BearerToken)
- ЛИМС
+/- Создать группы ОИ - закончить
+ Привязать классификатор продовольственных товаров к нашим группам ОИ
+ создать стандартные миграции групп продовольств.
+ схемы бэка запросов/заявок
+ динамические поля для разных типов ОИ
- таблицы полей и хранение информации
- проработать до конца взаимодействие всех возможных типов и групп ОИ с разными модулями
- Проработать таблицы бэка для множественных образцов и связыванием с incoming_object
- Добавить тарифную конфигурацию
+/- Нумератор документов
+ привязка ссылок номеров документов к текущему заказу
+ поддержка формата номеров в ссылочных номерах
- Система статусов с историей
- Структура чатов и комментариев (к сущностям и согласованиям)
- Mol.Облако
+ фронт облачного управления
- бессрочный демо-тариф
- разработать динамическую тарификацию со списанием средств
- админский раздел с регистрацией и разделением прав
- доделать все мелкие функции для облаков
+ смена админского пароля
- баги биллинга при создании облака и добавления тарифа
- красивые шаблоны для e-mail
+ перезапуск облака
- переконфигурация .env с нуля у каждой лабы
- обновление на новую версию (+ миграции и реконфигурация) - как со стороны облака, так и со стороны пользователя (проверять версию в настройках и на сервере)
- тестирование доступности всех облаков путём теста портов (бэк-сервис)
- добавить метрики Prometheus в каждое облако и отслеживать дополнительно
- reverb-сервер с динамической конфигурацией у каждого prod-сервера
- оптимизация скорости разворачивания облака (перенести csv-данные в прямые sql-запросы?)
- взаимодействие с Mol.лабРеестр
- подгрузка данных лабораторий по номеру аккредитации
- создание юр. лиц в разворачиваемом облаке из данных ФСА
- парсинг ОА ФСА и применение его к лаборатории
- Mol.лабРеестр
+ Парсинг новых лабораторий и их данных + ОА (доделать все типы ОА)
+ доделать фронтенд
+ Экспорт-импорт лидов в 1С-Битрикс
+ Авторизация
+ Развернуть на сервере
+ Парсинг дат аккредитации и след. проверки
- Рефакторинг - перенос сервиса пересчёта гос.флага
- Покупка прод-серверов и боевое разворачивание
- тестирование/доработка скрипта быстрого разворачивания сервера
- нагрузочное тестирование Цербера
- Разработка импорта для турбо-старта (Alfa) без ФСА (но с PDF-файлом)
- импорт помещений
- импорт сотрудников
- импорт оборудования
#### Июль 2025
- RabbitMQ/Kafka и менеджеры сообщений
- синхронизация всех пользователей/всех лаб с Mol
- создание ЛК клиентов
- синхронизация системы сообщений
- Разработка минимального серверного API для интеграции внешних сервисов
- Документация для клиентов (Swagger)
- Поддержка редактором документов платформы Mol

View File

@@ -0,0 +1,8 @@
| client_id | product_id | type_id | **tariff** | **basis** | user | summ | currency | discount | days | days_left |
| --------- | ---------- | ------- | ---------- | ---------- | -------- | -------- | -------- | -------- | ---- | ----------------- |
| 1 | 1 | 1 | 2 | счёт №4234 | Менеджер | 81000,00 | RUB | 15 | 365 | calc from fin_acc |
| 1 | 2 | 1 | 1 | | | | | | | |
| 2 | | | | | | | | | | |
| 2 | | | | | | | | | | |
| | | | | | | | | | | |

View File

@@ -0,0 +1,10 @@
Типы начислений дней или средств
| 1 | Активация подписки |
| --- | ------------------- |
| 3 | Обещанный платёж |
| 4 | Бонусное начисление |
| 5 | Промо-акция1 |
| 6 | Промо-акция2 |
| 7 | Промо НГ 2026 |
| 999 | Коррекция |

View File

@@ -0,0 +1,8 @@
| name | month_price | enabled |
| ---------- | ----------- | ------- |
| deals | 29000,00 | true |
| agreements | 24000,00 | true |
| commercial | 24000,00 | true |
| GPO 1 | 0,0 | true |
| SIL | 30000,00 | true |

View File

@@ -0,0 +1,8 @@
Перечень продуктов mol
| name | month_price | enabled |
| -------------- | ----------- | ------- |
| mol.LIMS | 29000,00 | true |
| mol.Logistics | 24000,00 | true |
| mol.Regulatory | 24000,00 | true |

View File

@@ -0,0 +1,7 @@
| name | product_id | month_price | discount_array |
| ----------- | ---------- | ----------- | --------------------------------------------------- |
| base | 1 | 0,00 | {} |
| commercial | 1 | 81000,00 | {['months' : [<br> 6 => 12,<br> 12 => 15<br>]]` |
| manufacture | 1 | 75000,00 | {['months' : [<br> 6 => 12,<br> 12 => 15<br>]] |
| base | 2 | 37000,00 | {['months' : [<br> 6 => 12,<br> 12 => 15<br>]] |

View File

@@ -0,0 +1,17 @@
{
"nodes":[
{"id":"eb3c1b013563e015","type":"text","text":"### Client table\nfinancial_account\ndaily_withdrawal `summ - (summ * 0,01 * discount)`","x":-1520,"y":-1096,"width":405,"height":156},
{"id":"0a153f80ab8f7533","x":-660,"y":-1360,"width":320,"height":420,"type":"file","file":"WORK & PROJECTS/Mol/Планы и диаграммы/Система тарификации/Billing_add_types.md"},
{"id":"7fa31e07c63ccd2d","x":-1080,"y":-1360,"width":400,"height":420,"type":"file","file":"WORK & PROJECTS/Mol/Планы и диаграммы/Система тарификации/Products.md"},
{"id":"02737c4081f9953a","type":"file","file":"WORK & PROJECTS/Mol/Планы и диаграммы/Система тарификации/Billing.md","x":-1080,"y":-900,"width":1115,"height":266},
{"id":"1eabd68c1a33fe8f","type":"text","text":"Таблица списаний","x":-255,"y":-1360,"width":290,"height":420},
{"id":"5e2d2e90f5a23569","type":"file","file":"WORK & PROJECTS/Mol/Планы и диаграммы/Система тарификации/Tariffs.md","x":-1580,"y":-580,"width":500,"height":460},
{"id":"cc6102d4b208ab1b","type":"text","text":"m2m\nenabled_modules","x":-1020,"y":-380,"width":250,"height":60},
{"id":"4513da48716f1769","type":"file","file":"WORK & PROJECTS/Mol/Планы и диаграммы/Система тарификации/Modules.md","x":-720,"y":-497,"width":351,"height":295}
],
"edges":[
{"id":"e9a0a5288907178c","fromNode":"5e2d2e90f5a23569","fromSide":"right","toNode":"cc6102d4b208ab1b","toSide":"left"},
{"id":"dcc2ccffee848fdc","fromNode":"cc6102d4b208ab1b","fromSide":"right","toNode":"4513da48716f1769","toSide":"left"},
{"id":"d6491986d9ea02a5","fromNode":"0a153f80ab8f7533","fromSide":"bottom","toNode":"02737c4081f9953a","toSide":"top"}
]
}

View File

@@ -0,0 +1,27 @@
root@212.67.13.241
```
uz5m9Mf1*Alviola16tyu@uz5m9Mf1*Alviola16tyu@
```
narooto
ons2So8KGworzwkdDuUzBEoP
1С
molsoft
FSfF1qLgHaXnxq1SK7Cikfik
me@gufranov.ru
vnc
—act:
rrOjiEPzSi79
—spectate:
ylJLWLSK3gpr
grafana
http://212.67.13.241:3000/
prometeus
http://212.67.13.241:9090/

View File

@@ -1,32 +0,0 @@
### CLOUD API (конфигурация облаков)
- #### Регистрация нового клиента
- создание новой БД клиента из CLEAN_db
- внесение данных о клиенте в БД клиента
- обновление cloud_users_db
- поддомен и статус ssl-сертификатов
- рабочий сервер
- рабочая папка на сервере
- настройки контейнера (подключенные модули, версия кода и тд)
- тариф и крайняя дата исп.
- БД
- Часовой пояс
- Информация о логине первого пользователя
- переконфигурация сервера
- создание/конфигурация файлов кэша, настроек php и индивидуальных архивов
- пересоздание/обновление конфигов:
- docker
- apache
- скрипт обновления БД (liquibase)
- выпуск ssl нового поддомена
- перезапуск apache & docker compose
- #### Обновление информации клиента
- тариф и дата последнего дня работы
- подключённые опции (реконфиг docker)
- часовой пояс php
- #### Работа с демо-данными
- активация демо-данных
- удаление демо-данных
- #### Удаление клиента
- очистка БД клиента
- пересоздание конфигов сервера
- чистка файлов (контейнеры и конфиги)

View File

@@ -1 +0,0 @@
Папки клиентских данных

View File

@@ -0,0 +1,16 @@
```
root
Qoh*IHX0GxEsGIHw*F80Mh
publicadmin
ljuJdjh5ljuJdjh5
mariadb
public
Ur7NofAEm!4vGRWRVD
mariadb-dump --databases lab-parser | gzip -c > /home/publicadmin/lab-parser.sql.gz
```

View File

@@ -0,0 +1,747 @@
Отличный вопрос! Давай сравним, **как реализовать ту же задачу в Traefik**, и посмотрим, проще ли это будет по сравнению с Angie.
---
# 🎯 Цель (повторение)
Нужно организовать систему обратного прокси, где:
- Пользователи разделены на группы:
- `public` — публичный доступ ко всем общедоступным сайтам.
- `internal` — сотрудникам доступны внутренние сервисы (git, jira и т.д.).
- `client-X` — клиенты с доступом только к своему поддомену (`client1.site.com`, `client2.site.com`).
- Каждая группа имеет свои **IP-подсети**.
- Каждый домен/поддомен имеет список разрешённых групп.
- Конфиги должны обновляться **динамически** без перезапуска.
- Желательно наличие **GUI** и **API управления**.
---
# ✅ Вывод сразу: **Traefik лучше подходит для этой задачи**
Почему?
| Функция | Traefik ✅ / ❌ | Angie ✅ / ❌ |
|----------------------------------|----------------------|----------------------|
| ACL по IP | ✅ | ✅ |
| Группы пользователей | ✅ через middleware | ✅ через allow/deny |
| Динамическая конфигурация | ✅ через API или файл | ✅ через `/control` |
| Поддержка GUI | ✅ встроенный dashboard | ❌ (нужно внешнее) |
| Простота автоматизации | ✅ | ⚠️ сложнее |
| Интеграция с Lets Encrypt | ✅ | ⚠️ нужно допилить |
| Удобство масштабирования | ✅ | ✅ |
---
# 🔧 Как реализовать в Traefik
## 1. **Базовая структура конфигурации**
Traefik использует два типа конфигов:
- `traefik.yml` — статическая часть (порт, провайдеры, логирование и т.п.)
- `dynamic_conf.yml` — динамическая часть (маршруты, middleware, TLS и т.п.)
Также можно управлять через **REST API**, но начнём с файла.
---
## 2. **Middleware для групп доступа**
В Traefik есть мощная система **middleware**, которую мы будем использовать для контроля доступа по IP.
### Пример: определяем middleware для каждой группы
```yaml
http:
middlewares:
public-access:
ipWhiteList:
sourceRange:
- "0.0.0.0/0" # все IP
internal-access:
ipWhiteList:
sourceRange:
- "192.168.1.0/24"
- "10.10.0.0/16"
client1-access:
ipWhiteList:
sourceRange:
- "203.0.113.0/24"
```
---
## 3. **Роуты с указанием middleware**
Каждому роуту можно указать, какие middleware применять.
### Пример: маршрут для site1.com
```yaml
http:
routers:
site1-router:
rule: "Host(`site1.com`, `www.site1.com`)"
entryPoints:
- web
service: site1-service
middlewares:
- public-access
- internal-access
```
### Пример: маршрут для git.site1.com
```yaml
http:
routers:
git-router:
rule: "Host(`git.site1.com`)"
entryPoints:
- web
service: git-service
middlewares:
- internal-access
```
### Пример: маршрут для client1.site.com
```yaml
http:
routers:
client1-router:
rule: "Host(`client1.site.com`)"
entryPoints:
- web
service: client1-service
middlewares:
- client1-access
```
---
## 4. **Сервисы (бэкенды)**
Определяешь бэкенд для каждого сайта:
```yaml
http:
services:
site1-service:
loadBalancer:
servers:
- url: http://192.168.10.10:8080
git-service:
loadBalancer:
servers:
- url: http://192.168.10.11:3000
client1-service:
loadBalancer:
servers:
- url: http://192.168.20.10:8000
```
---
## 5. **Динамическое обновление конфига**
Можно использовать:
### А. **Файл с динамическим конфигом**
Traefik следит за изменениями в файле и применяет их автоматически.
Пример запуска:
```yaml
providers:
file:
filename: /etc/traefik/dynamic_conf.yml
watch: true
```
> При изменении `dynamic_conf.yml` — конфиг применяется без перезагрузки!
### Б. **REST API**
Если хочешь полностью управлять через API, можешь использовать [Traefik Pilot](https://pilot.traefik.io/) или свой микросервис.
Пример добавления маршрута через API:
```bash
curl -X POST "http://traefik:8080/api/http/routers" \
-H "Content-Type: application/json" \
-d '{
"name": "client2-router",
"rule": "Host(`client2.site.com`)",
"service": "client2-service",
"entryPoints": ["web"],
"middlewares": ["client2-access"]
}'
```
---
## 6. **Генерация конфигов**
Как и в случае с Angie, можно написать небольшой **генератор конфигов**, который:
- Получает данные о новом клиенте.
- Создаёт middleware с его IP-диапазоном.
- Создаёт новый роут.
- Обновляет `dynamic_conf.yml`.
Или делает всё через API.
---
## 7. **Dashboard (GUI)**
Traefik предоставляет встроенный **веб-интерфейс** по адресу `http://localhost:8080`.
Там ты увидишь:
- Все маршруты,
- Сервисы,
- Middleware,
- Состояние бэкендов.
---
## 🔄 Автоматизация всего процесса
Ты можешь создать:
- **API-сервис**, принимающий запросы на добавление клиентов/групп.
- **Шаблонизатор**, генерирующий YAML или JSON.
- **Систему хранения правил** (например, PostgreSQL или Redis).
- **Синхронизацию с файлом или API Traefik**.
---
## 🔐 Как работает проверка доступа?
1. Запрос приходит в Traefik.
2. Traefik проверяет IP клиента через middleware `ipWhiteList`.
3. Если IP не в списке — возвращает `403 Forbidden`.
4. Иначе — направляет запрос на нужный бэкенд.
---
## 📦 Пример структуры проекта
```
traefik/
├── traefik.yml # статический конфиг
├── dynamic_conf.yml # динамические маршруты и middleware
└── data/
└── clients/ # шаблоны или скрипты для клиентов
```
---
# 🎯 Общая цель
Ты хочешь реализовать систему, где:
1. **Клиенты добавляются/удаляются автоматически** из внешней БД.
2. **Правила доступа пересоздаются регулярно**, оставляя только актуальные.
3. **Скорость обработки не критична**, но важно понимать, как поведёт себя система при росте числа правил.
---
# ✅ Как это сделать в Traefik
## 1. **Механизм хранения правил**
В Traefik есть 2 основных способа управления конфигом:
| Способ | Подходит для динамических правил? | Простота интеграции |
|---------------------------|-----------------------------------|---------------------|
| Файл `dynamic_conf.yml` | ✅ | ✅ |
| REST API | ✅ | ⚠️ требует кода |
### Рекомендация:
Используй **файл с динамическим конфигом (`file provider`)** — он проще в управлении, особенно если ты хочешь периодически перезаписывать его целиком на основе данных из БД.
---
## 2. **Интеграция с внешней БД**
### Шаги:
1. Периодически запускай скрипт (например, раз в час или по cron):
- Выбираешь из БД список активных клиентов.
- Для каждого клиента генерируешь middleware (ACL) и router (маршрут).
- Перезаписываешь файл `dynamic_conf.yml`.
2. Traefik сам применяет изменения (если `watch: true`).
### Пример структуры файла `dynamic_conf.yml`:
```yaml
http:
middlewares:
client1-access:
ipWhiteList:
sourceRange:
- "203.0.113.0/24"
client2-access:
ipWhiteList:
sourceRange:
- "198.51.100.0/24"
routers:
client1-router:
rule: "Host(`client1.example.com`)"
service: client1-service
middlewares:
- client1-access
client2-router:
rule: "Host(`client2.example.com`)"
service: client2-service
middlewares:
- client2-access
services:
client1-service:
loadBalancer:
servers:
- url: http://192.168.20.10:8080
client2-service:
loadBalancer:
servers:
- url: http://192.168.20.20:8080
```
---
## 🧪 Пример нагрузки (гипотетический)
| Конфигурация | Запросов в секунду (RPS) |
|-------------------------------------|--------------------------|
| Traefik + 1000 клиентов | ~80009000 RPS |
| Angie + 1000 клиентов | ~1000011000 RPS |
| Traefik + простой прокси (без ACL) | ~12000 RPS |
| Angie + простой прокси (без ACL) | ~14000 RPS |
# 🔄 Рекомендуемая архитектура
```
[База данных] → [Генератор dynamic_conf.yml] → [Traefik]
```
1. **База данных** — хранит список клиентов, их домены и IP-подсети.
2. **Генератор** — скрипт (Python, Go и т.п.), который берёт данные из БД и генерирует YAML.
3. **Traefik** — читает файл и применяет правила без перезапуска.
---
## **Можно ли разнести конфигурацию общего доступа + сотрудников и клиентов?**
### ✅ Да, и это даже рекомендуется!
Разделение конфигов позволяет:
- Упростить поддержку.
- Лучше управлять обновлениями.
- Изолировать статические правила от динамических.
---
### 🛠️ Как это сделать в Traefik?
Traefik поддерживает **множественные файловые провайдеры**, что позволяет загружать несколько YAML-файлов независимо.
#### Пример структуры:
```
traefik/
├── traefik.yml # основной статический конфиг
├── dynamic-common.yml # маршруты и middleware для public/internal
└── dynamic-clients.yml # маршруты и middleware для клиентов
```
#### В `traefik.yml` указываешь:
```yaml
providers:
file:
directory: /etc/traefik/dynamic
watch: true
```
> Traefik автоматически загрузит все `.yml` из этой папки.
---
### Пример `dynamic-common.yml` (для всех):
```yaml
http:
middlewares:
public-access:
ipWhiteList:
sourceRange:
- "0.0.0.0/0"
internal-access:
ipWhiteList:
sourceRange:
- "192.168.1.0/24"
- "10.10.0.0/16"
routers:
site1-router:
rule: "Host(`site1.com`, `www.site1.com`)"
service: site1-service
middlewares:
- public-access
- internal-access
git-router:
rule: "Host(`git.site1.com`)"
service: git-service
middlewares:
- internal-access
```
---
### Пример `dynamic-clients.yml` (динамически обновляется):
```yaml
http:
middlewares:
client1-access:
ipWhiteList:
sourceRange:
- "203.0.113.0/24"
client2-access:
ipWhiteList:
sourceRange:
- "198.51.100.0/24"
routers:
client1-router:
rule: "Host(`client1.site.com`)"
service: client1-service
middlewares:
- client1-access
client2-router:
rule: "Host(`client2.site.com`)"
service: client2-service
middlewares:
- client2-access
services:
client1-service:
loadBalancer:
servers:
- url: http://192.168.20.10:8080
client2-service:
loadBalancer:
servers:
- url: http://192.168.20.20:8080
```
---
## 🔄 Обновление только клиентской части
Поскольку файлы разделены, можно:
- **Часто обновлять только `dynamic-clients.yml`** (например, ежечасно) без затрагивания остальной конфигурации.
- **Не трогать `dynamic-common.yml`**, если нет изменений в общих правилах.
---
## 📦 Полезная архитектура
```
[База данных] → [Генератор dynamic-clients.yml] ↔ [Traefik]
[API для других сервисов]
```
- **База данных** — хранит клиентов, группы, IP.
- **Генератор** — создаёт/обновляет `dynamic-clients.yml`.
- **Traefik** — применяет конфиг без перезагрузки.
- **Внешний API** — даёт доступ к данным о клиентах (в т.ч. IP-диапазонам).
Отличный и важный вопрос! Давай разберём, **какие требования к железу будут у Traefik**, если:
- Он будет работать вместе с:
- **UFW** (брандмауэр),
- **Prometheus** (мониторинг),
- **Grafana** (визуализация),
- И в будущем ожидается нагрузка до **20 000 RPS** (запросов в секунду),
- Нужно понять: **что важнее — CPU или RAM?**
- А также: **можно ли логировать подключения и собирать метрики для Prometheus**
---
# 🧮 1. Требования к серверу
Traefik написан на Go, работает быстро и эффективно, но при высокой нагрузке требует определённых ресурсов.
## 🔁 Примерная производительность Traefik (на одном ядре):
| Запросы/сек | RAM (примерно) | CPU (ядро ~Xeon E5) |
|-------------|----------------|----------------------|
| 5 000 | 300500 MB | ~0.4 ядра |
| 10 000 | 600800 MB | ~0.7 ядра |
| 20 000 | 11.5 GB | ~1.21.5 ядра |
> Это оценка для простого reverse proxy без TLS, сложных middleware, WAF и т.п.
---
## 🖥️ Рекомендуемая конфигурация для 20 000 RPS + дополнительные сервисы
| Компонент | Рекомендация |
|------------------|-----------------------------------|
| CPU | 46 ядер (минимум), лучше 8 |
| RAM | 46 ГБ |
| Диск | SSD, 2040 ГБ |
| ОС | Linux (Debian, Ubuntu Server) |
| Сеть | 1 Гбит/с минимум |
> ✅ Если нагрузка будет расти постепенно, можно начать с 24 ядер и 24 ГБ RAM, масштабируя дальше при необходимости.
---
## 💡 Что важнее: RAM или CPU?
### ✅ Ответ: **и то, и другое важно, но первым ограничителем станет CPU**
- **RAM** обычно расходуется не очень много (~11.5 ГБ при 20K RPS).
- **CPU** становится бутылочным горлышком из-за обработки маршрутов, middleware, шифрования TLS (если есть), проверок IP, генерации логов и т.п.
---
# 📊 2. Логирование и мониторинг через Prometheus
Traefik имеет **встроенную поддержку метрик Prometheus**, а также позволяет логировать запросы.
## ✅ Включить экспорт метрик Prometheus:
Добавь в `traefik.yml`:
```yaml
metrics:
prometheus:
addRoutersLabels: true
addServicesLabels: true
entryPoint: metrics
```
И открой соответствующий `entryPoint`:
```yaml
entryPoints:
metrics:
address: ":8082"
```
Теперь ты можешь забирать метрики по адресу:
👉 `http://<traefik>:8082/metrics`
Примеры доступных метрик:
- `traefik_http_requests_total{status, method, router}`
- `traefik_http_in_flight_requests`
- `traefik_backend_requests_total`
- `traefik_router_requests_total`
---
## 📝 Включить логирование запросов:
Добавь в `traefik.yml`:
```yaml
accessLog:
filePath: "/var/log/traefik/access.log"
format: "json" # или "common", "jsonWithTime"
```
Можно дополнительно отправлять логи в Loki, Fluentd, ELK и т.д.
---
## 📈 Пример сбора данных в Prometheus + Grafana
1. **Prometheus** сканирует `/metrics` у Traefik.
2. **Grafana** строит дашборды:
- Запросы в секунду,
- Коды ответов (2xx, 4xx, 5xx),
- Время отклика,
- Нагрузка на конкретные роутеры и сервисы.
---
# ✅ Выводы
| Пункт | Рекомендация |
|-----------------------------------|------------------------------------------------------------|
| Минимальный CPU | 4 ядра (начало), 8 комфортно |
| Минимальный RAM | 4 ГБ |
| Диск | SSD, 20+ ГБ |
| Что важнее | CPU |
| Логирование | ✅ Да, через `accessLog` |
| Мониторинг через Prometheus | ✅ Да, встроено |
| Можно ли всё запустить на одном сервере | ✅ Да, через Docker или Kubernetes |
| Как масштабироваться | Горизонтально: добавление ещё Traefik-нод (в кластере) |
---
Отлично, давай разберём **конкретный сценарий**:
> Ты хочешь:
>
> - Добавить новый поддомен (например, `client1.example.com`),
> - Чтобы Traefik автоматически получил для него **TLS-сертификат через HTTP-01 чаллендж**,
> - Чтобы домен работал по HTTPS,
> - А внутрь сети проксировался на бэкенд в подсети (например, `http://192.168.10.10:8080`),
> - И всё это — без ручного участия после добавления конфига.
---
# ✅ Что нужно сделать
## 1. **Настроить статический конфиг (`traefik.yml`)**
```yaml
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
providers:
file:
filename: /etc/traefik/dynamic_conf.yml
watch: true
certificatesResolvers:
myresolver:
acme:
email: admin@example.com
storage: acme.json
httpChallenge:
entryPoint: web
```
### 📌 Важные моменты:
- `myresolver` — это имя resolver'а, которое будем использовать в динамическом конфиге.
- `storage: acme.json` — файл, где будут храниться сертификаты.
- `httpChallenge.entryPoint: web` — указывает, что проверка будет идти через порт 80.
---
## 2. **Добавить динамический маршрут в `dynamic_conf.yml`**
```yaml
http:
routers:
client1-router:
rule: "Host(`client1.example.com`)"
service: client1-service
entryPoints:
- websecure
tls:
certResolver: myresolver
services:
client1-service:
loadBalancer:
servers:
- url: http://192.168.10.10:8080
```
### 🔍 Что здесь происходит:
- При запросе `https://client1.example.com`:
- Traefik проверяет, есть ли уже выданный сертификат.
- Если нет — запрашивает его у Lets Encrypt.
- Для проверки прав на домен — создаёт временный маршрут `//.well-known/acme-challenge/...`.
- После успешной проверки — сохраняет сертификат в `acme.json`.
- Запрос перенаправляется на внутренний сервер `http://192.168.10.10:8080`.
---
## 3. **Как работает HTTP-челлендж?**
Lets Encrypt делает GET-запрос:
```
GET http://client1.example.com/.well-known/acme-challenge/<токен>
```
Traefik:
- Автоматически отвечает на этот запрос,
- Не требует дополнительных правил,
- Работает **без участия пользователя**.
> ⚠️ Убедись, что порт **80 открыт** и доступен извне (Let's Encrypt должен видеть твой сервер).
---
## 4. **Файл `acme.json`**
Это **ключевой файл**, в котором Traefik хранит:
- Приватные ключи,
- Сертификаты,
- Сроки действия,
- Информацию о доменах.
Создай его и установи права:
```bash
touch /etc/traefik/acme.json
chmod 600 /etc/traefik/acme.json
```
---
## 🧩 Как работает вся система?
1. **Ты добавляешь новый поддомен в `dynamic_conf.yml`**
2. **Traefik обнаруживает изменение** (если включен `watch: true`)
3. **Запрашивает сертификат у Lets Encrypt**
- Через HTTP-чаллендж (порт 80),
- Проверяет владение доменом,
- Получает сертификат.
4. **Начинает обслуживать сайт по HTTPS**
5. **Проксирует запросы во внутреннюю сеть**
6. **Автоматически обновляет сертификат за 7 дней до истечения**
---
## 📋 Пример полной структуры файлов
```
/etc/traefik/
├── traefik.yml # основной статический конфиг
├── dynamic_conf.yml # динамические маршруты
├── acme.json # хранилище сертификатов
```
---
## 📈 Полезные советы
| Задача | Совет |
|-------------------------------------|----------------------------------------------------------------------|
| Обновление сертификатов | Происходит автоматически каждые 24 часа |
| Проверка сертификатов | Открой Traefik Dashboard → раздел HTTPS > Certificates |
| Лимиты Let's Encrypt | Не более 300 новых сертификатов в день на один домен |
| Избегать блокировок | Не делай много ошибочных запросов |
| Бэкапы | Регулярно бэкапь `acme.json` |
---

View File

@@ -0,0 +1,18 @@
```
sudo nano /etc/supervisor/conf.d/laravel-worker.conf
```
```
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/your-project/artisan queue:work --queue=client_creation --sleep=3 --tries=3
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=1
redirect_stderr=true
stdout_logfile=/var/www/your-project/storage/logs/worker.log
```

View File

@@ -1 +1,61 @@
REVERSE PROXY + FIREWALL
```
root
*oZXx2L36kHd*WD60dfHo4ThUtZ*GR2FtkoaCsuc78*jzxr7
cerberusadmin
f,hfrflf,hf8
```
grafana
```
host:3000
admin
f,hfrflf,hf8
```
node_explorer
traefik GUI / host:9000
```
prometheus
ljuJdjh5ljuJdjh5
```
node exporter install
```
mkdir /etc/node_exporter
cat <<EOF > /etc/node_exporter/config.yml
basic_auth_users:
prometheus: "\$2a\$12\$9fI6ZRV.1FAIRbqH9TnwgOSyCrQ18yMC0AoYcwErYmTD97sfJjMq2"
EOF
wget https://github.com/prometheus/node_exporter/releases/download/v1.9.1/node_exporter-1.9.1.linux-amd64.tar.gz
tar -xvf node_exporter-*.tar.gz
sudo mv node_exporter-1.9.1.linux-amd64/node_exporter /usr/local/bin/
rm -rf node_*
cat <<EOF > /etc/systemd/system/node_exporter.service
[Unit]
Description=Node Exporter
After=network.target
[Service]
User=root
ExecStart=/usr/local/bin/node_exporter --web.config.file="/etc/node_exporter/config.yml"
Restart=always
[Install]
WantedBy=multi-user.target
EOF
systemctl enable --now node_exporter
systemctl start node_exporter
```

View File

@@ -1,13 +1,72 @@
{
"nodes":[
{"id":"b4b26b5ea7c96446","type":"text","text":"Beget","x":-960,"y":-673,"width":680,"height":701},
{"id":"74a9c9df4b90fdb3","type":"file","file":"WORK & PROJECTS/Mol/Серверы/Jira - Service - Confluence - Crm.md","x":-900,"y":-419,"width":560,"height":50,"color":"5"},
{"id":"b0513e10e691a707","type":"file","file":"WORK & PROJECTS/Mol/Серверы/mol-desk.mol-soft.PRODs.md","x":-900,"y":-289,"width":560,"height":55,"color":"1"},
{"id":"49ccb0df1f6b1353","type":"file","file":"WORK & PROJECTS/Mol/Серверы/1С Бухгалтерия.md","x":-900,"y":-160,"width":560,"height":50,"color":"4"},
{"id":"898531ae75948834","type":"file","file":"WORK & PROJECTS/Mol/Серверы/VPN-FIREWALL-GATE (Cerberus).md","x":-900,"y":-600,"width":400,"height":60,"color":"1"},
{"id":"6ac127d86d27711a","type":"file","file":"WORK & PROJECTS/Mol/Серверы/git.moldev.ru.md","x":-2340,"y":-372,"width":400,"height":50,"color":"4"},
{"id":"7cab99cbcc329310","type":"file","file":"WORK & PROJECTS/Mol/Серверы/mail.mol-soft.ru.md","x":-2340,"y":-242,"width":400,"height":55,"color":"4"},
{"id":"0103d77dbe794bef","type":"text","text":"firstvds","x":-2400,"y":-625,"width":520,"height":701}
{"id":"2e1cc678d432c54b","type":"group","x":-2800,"y":-673,"width":730,"height":1053,"label":"clients.mol-desk.ru"},
{"id":"a2eba28266676357","type":"group","x":-2780,"y":-600,"width":290,"height":980,"label":"Prod 1"},
{"id":"a4b22ee32128aab4","type":"group","x":-2380,"y":-600,"width":290,"height":980,"label":"Prod 2"},
{"id":"9116c3eb07839ead","type":"group","x":-940,"y":-1240,"width":300,"height":682,"label":"FirstVDS"},
{"id":"5d2dc9bca77d8555","type":"group","x":-2175,"y":-1300,"width":800,"height":247,"color":"5","label":"Cerberus - firewall / router"},
{"id":"526a304cfaf6b2d7","type":"group","x":-1340,"y":-673,"width":290,"height":627,"label":"Public & Main Mol"},
{"id":"d82c5eb6240f42ce","type":"group","x":-1880,"y":-640,"width":285,"height":439,"label":"Service & CRM"},
{"id":"4608064a92b6a9d1","type":"text","text":"#### TRAEFIK\nОсновной reverse proxy + обновляемые из облачного сервиса конфиги","x":-2155,"y":-1280,"width":315,"height":151},
{"id":"d5d47118fc6e4e60","type":"text","text":"#### **Пользователи**","x":-1936,"y":-1620,"width":321,"height":59,"color":"4"},
{"id":"fb0a2d4c41b9c5d3","type":"text","text":"Фильтрация осуществляется на основе подсетей","x":-1936,"y":-1561,"width":321,"height":76},
{"id":"b38c800618b2b662","type":"text","text":"ufw","x":-1635,"y":-1280,"width":220,"height":50},
{"id":"937f7b75703cac6a","type":"text","text":"Публичный доступ","x":-1895,"y":-923,"width":240,"height":50,"color":"3"},
{"id":"fe6811c5af10638c","type":"text","text":"Клиенты","x":-2187,"y":-923,"width":190,"height":50,"color":"1"},
{"id":"7c82756a6bea5a1f","type":"text","text":"Сотрудники","x":-1565,"y":-923,"width":190,"height":50,"color":"4"},
{"id":"4d7318cf070c2903","type":"text","text":"**Alfa docker 1: 8001**\nclient1.mol-desk.ru\n*updatable .env*","x":-2760,"y":-580,"width":250,"height":86},
{"id":"63cfc1586ebec00c","type":"text","text":"**Alfa docker 2: 8002**\nclient2.mol-desk.ru\n*updatable .env*","x":-2760,"y":-494,"width":250,"height":86},
{"id":"3d137edce1138976","type":"text","text":"**Alfa docker 3: 8003**\nclient3.mol-desk.ru\n*updatable .env*","x":-2760,"y":-408,"width":250,"height":86},
{"id":"0c22d657398409d6","type":"text","text":"**Alfa docker 4: 8004**\nclient4.mol-desk.ru\n*updatable .env*","x":-2760,"y":-322,"width":250,"height":86},
{"id":"446f63ae369ba423","type":"text","text":"mariadb","x":-2760,"y":-180,"width":250,"height":60},
{"id":"f5fbdfcb37d7b10b","type":"text","text":"**Alfa docker 5: 8005**\nclient5.mol-desk.ru\n*updatable .env*","x":-2360,"y":-580,"width":250,"height":86},
{"id":"dbaaef3ca9500ce8","type":"text","text":"**Alfa docker 6: 8006**\nclient6.mol-desk.ru\n*updatable .env*","x":-2360,"y":-494,"width":250,"height":86},
{"id":"a84852a25c7d7cd1","type":"text","text":"**Alfa docker 7: 8007**\nclient7.mol-desk.ru\n*updatable .env*","x":-2360,"y":-408,"width":250,"height":86},
{"id":"3d98a573d756770d","type":"text","text":"**Alfa docker 8: 8008**\nclient8.mol-desk.ru\n*updatable .env*","x":-2360,"y":-322,"width":250,"height":86},
{"id":"a6f10fc2544d8770","type":"text","text":"Prometeus","x":-1635,"y":-1230,"width":220,"height":50},
{"id":"8b425eb6bf3182ab","type":"text","text":"Grafana","x":-1635,"y":-1180,"width":220,"height":50},
{"id":"35e61e001bb26357","type":"text","text":"Certbot","x":-1635,"y":-1130,"width":220,"height":50},
{"id":"db8f55014e48e08a","type":"text","text":"#### configure firewall for connections from internal network ONLY","x":-2360,"y":202,"width":250,"height":138},
{"id":"7ef68fafb89cd8ae","type":"text","text":"#### configure firewall for connections from internal network ONLY","x":-2760,"y":202,"width":250,"height":138},
{"id":"f0ece97cdfd84922","type":"text","text":"mariadb","x":-2360,"y":-180,"width":250,"height":60},
{"id":"bd52d1f7367a3e5f","type":"text","text":"**nginx\nsupervisor\nlaravel reverb**\nws2.mol-desk.ru\n*updatable config*","x":-2360,"y":-120,"width":250,"height":148},
{"id":"907f90495a891eb8","type":"text","text":"**nginx\nsupervisor\nlaravel reverb**\nws1.mol-desk.ru\n*updatable config*","x":-2760,"y":-120,"width":250,"height":148},
{"id":"a93aede560834707","type":"text","text":"**additional soft**\ngit\ndocker\ndocker-compose","x":-2760,"y":60,"width":250,"height":120},
{"id":"a4f3440a7fe2161e","type":"text","text":"**additional soft**\ngit\ndocker\ndocker-compose","x":-2360,"y":60,"width":250,"height":120},
{"id":"26dde50716bb84ec","type":"text","text":"**docs.mol-soft.ru**\nconfluence","x":-1860,"y":-620,"width":250,"height":60},
{"id":"2580b707c35c25fa","type":"text","text":"**crm.mol-soft.ru**\n1C Bitrix CRM","x":-1860,"y":-538,"width":250,"height":60},
{"id":"5cc6627b1ec7f80b","type":"text","text":"**jira.mol-soft.ru**\nJira","x":-1860,"y":-439,"width":250,"height":60},
{"id":"fba068dda9f84c43","type":"text","text":"**service.mol-soft.ru**\n**jira.mol-soft.ru/servicedesk/**\nJira Service Portal","x":-1860,"y":-362,"width":250,"height":116},
{"id":"9eab03c82f9aabbf","type":"text","text":"mail.mol-soft.ru\nк почтовому серверу должен быть доступ для всех почтовых серверов","x":-920,"y":-1213,"width":250,"height":108},
{"id":"a3e5ab2044e04fdb","type":"text","text":"test.moldev.ru","x":-920,"y":-1080,"width":250,"height":60},
{"id":"4e63a1f0f81b5ab5","type":"text","text":"git.moldev.ru","x":-920,"y":-1020,"width":250,"height":60},
{"id":"eada78c18bad690b","type":"text","text":"Настроить приём трафика только из Цербера. Нагрузка к гиту и тестовому окружению будет невысокой\n\nОсенью, как закончатся деньги на тарифе - перенесём в основную подсеть","x":-920,"y":-897,"width":250,"height":317},
{"id":"c2b86758f042a830","type":"text","text":"**mol-soft.ru**","x":-1320,"y":-653,"width":250,"height":60},
{"id":"db5ad41733784639","type":"text","text":"**mol-desk.ru**\nfrontend & backend","x":-1320,"y":-574,"width":250,"height":67},
{"id":"7aa691ba92a7ad44","type":"text","text":"**Cloud admin**\n**Cloud API**","x":-1320,"y":-472,"width":250,"height":60},
{"id":"0180fcbaf9c87523","type":"text","text":"**editor.mol-desk.ru**","x":-1320,"y":-397,"width":250,"height":60},
{"id":"17acca0b1fdfb275","type":"text","text":"RabbitMQ server","x":-1320,"y":-180,"width":250,"height":60}
],
"edges":[]
"edges":[
{"id":"ae16f1fbd7654a7d","fromNode":"fb0a2d4c41b9c5d3","fromSide":"bottom","toNode":"5d2dc9bca77d8555","toSide":"top"},
{"id":"e8646033fdf864d6","fromNode":"5d2dc9bca77d8555","fromSide":"bottom","toNode":"fe6811c5af10638c","toSide":"top","color":"1"},
{"id":"36d904b501b850fa","fromNode":"5d2dc9bca77d8555","fromSide":"bottom","toNode":"937f7b75703cac6a","toSide":"top","color":"3"},
{"id":"77e8525117a9ae93","fromNode":"5d2dc9bca77d8555","fromSide":"bottom","toNode":"7c82756a6bea5a1f","toSide":"top","color":"4"},
{"id":"8b9d267ab3e6f3d9","fromNode":"7c82756a6bea5a1f","fromSide":"bottom","toNode":"26dde50716bb84ec","toSide":"right","color":"4"},
{"id":"b7322a7a9fd07268","fromNode":"7c82756a6bea5a1f","fromSide":"bottom","toNode":"2580b707c35c25fa","toSide":"right","color":"4"},
{"id":"7dc90a9cff77c432","fromNode":"7c82756a6bea5a1f","fromSide":"bottom","toNode":"5cc6627b1ec7f80b","toSide":"right","color":"4"},
{"id":"ae9600f18ce30b06","fromNode":"7c82756a6bea5a1f","fromSide":"bottom","toNode":"7aa691ba92a7ad44","toSide":"left","color":"4"},
{"id":"0fd20b582a956ba3","fromNode":"db5ad41733784639","fromSide":"right","toNode":"17acca0b1fdfb275","toSide":"right","fromEnd":"arrow"},
{"id":"c9a2441c069530ca","fromNode":"17acca0b1fdfb275","fromSide":"left","toNode":"2e1cc678d432c54b","toSide":"right","fromEnd":"arrow","label":"Синхронизация чатов / клиентов / польз."},
{"id":"3ac0d9cbb4a53988","fromNode":"937f7b75703cac6a","fromSide":"bottom","toNode":"c2b86758f042a830","toSide":"left","color":"3"},
{"id":"4329be991c59aa8c","fromNode":"fe6811c5af10638c","fromSide":"bottom","toNode":"4d7318cf070c2903","toSide":"right","color":"1"},
{"id":"40eb31ea39b6dd2b","fromNode":"fe6811c5af10638c","fromSide":"bottom","toNode":"f5fbdfcb37d7b10b","toSide":"right","color":"1"},
{"id":"d7807199582650c5","fromNode":"fe6811c5af10638c","fromSide":"bottom","toNode":"bd52d1f7367a3e5f","toSide":"right","color":"1"},
{"id":"991f3c698a3e59ef","fromNode":"fe6811c5af10638c","fromSide":"bottom","toNode":"907f90495a891eb8","toSide":"right","color":"1"},
{"id":"c1adfbd349a6994e","fromNode":"fe6811c5af10638c","fromSide":"bottom","toNode":"fba068dda9f84c43","toSide":"left","color":"1","label":" СИЛА"},
{"id":"7af69d23ef8cb579","fromNode":"937f7b75703cac6a","fromSide":"bottom","toNode":"db5ad41733784639","toSide":"left","color":"3"},
{"id":"ed19d51d2487a0ab","fromNode":"fe6811c5af10638c","fromSide":"bottom","toNode":"0180fcbaf9c87523","toSide":"left","color":"1"},
{"id":"ddd10860524a3425","fromNode":"7c82756a6bea5a1f","fromSide":"right","toNode":"9116c3eb07839ead","toSide":"left","color":"4","label":"?"},
{"id":"3df06bd04d51030e","fromNode":"7c82756a6bea5a1f","fromSide":"bottom","toNode":"fba068dda9f84c43","toSide":"right","color":"4"}
]
}