Merge remote-tracking branch 'origin/master'
This commit is contained in:
3
.obsidian/community-plugins.json
vendored
3
.obsidian/community-plugins.json
vendored
@@ -2,6 +2,5 @@
|
||||
"obsidian-full-calendar",
|
||||
"obsidian-icon-folder",
|
||||
"obsidian-git",
|
||||
"obsidian-smart-typography",
|
||||
"copy-document-as-html"
|
||||
"obsidian-smart-typography"
|
||||
]
|
||||
2
.obsidian/graph.json
vendored
2
.obsidian/graph.json
vendored
@@ -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
12
.obsidian/hotkeys.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"obsidian-git:commit": [
|
||||
{
|
||||
"modifiers": [
|
||||
"Alt",
|
||||
"Mod",
|
||||
"Shift"
|
||||
],
|
||||
"key": "C"
|
||||
}
|
||||
]
|
||||
}
|
||||
6
.obsidian/plugins/obsidian-git/data.json
vendored
6
.obsidian/plugins/obsidian-git/data.json
vendored
@@ -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}}"
|
||||
}
|
||||
}
|
||||
270
.obsidian/plugins/obsidian-git/main.js
vendored
270
.obsidian/plugins/obsidian-git/main.js
vendored
File diff suppressed because one or more lines are too long
2
.obsidian/plugins/obsidian-git/manifest.json
vendored
2
.obsidian/plugins/obsidian-git/manifest.json
vendored
@@ -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"
|
||||
}
|
||||
|
||||
29
.obsidian/plugins/obsidian-git/styles.css
vendored
29
.obsidian/plugins/obsidian-git/styles.css
vendored
@@ -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);
|
||||
}
|
||||
|
||||
81
.obsidian/workspace.json
vendored
81
.obsidian/workspace.json
vendored
@@ -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
65
2025-07-18.md
Normal file
@@ -0,0 +1,65 @@
|
||||
|
||||
### Бэкенд
|
||||
```
|
||||
|
||||
Сохранение проб и образцов
|
||||
|
||||
+ понять и исправить, почему не сохраняются образцы с файлами (неверные реквесты Лазера)
|
||||
+ проверка развязочных таблиц проб/испытаний (на данном этапе всё ок)
|
||||
- Перетрясти валидацию всех полей!
|
||||
+ баг реквеста при выборе заявки
|
||||
+ баги селектов с фронта
|
||||
- доработка реквеста испытаний (после перепиливания окна испытаний)
|
||||
+ Прописать сообщения валидации на русском языке
|
||||
- доделать полноценное сохранение данных образца, проб, испытаний (с испытаниями вопрос, тк нет привязки показателей???)
|
||||
- добавлять одну пробу при отстутсвии
|
||||
- как редактировать состав incoming_object???
|
||||
- нужна кнопка и в заказах и в перечне образцов
|
||||
- сделать ответные методы для обновления при сохранении + валидацию загруженных файлов
|
||||
|
||||
- узнать как работает загрузка данных о пробах и испытаниях из бэка при выборе образцов, дописываем функции выборки
|
||||
- апдейт полей и с чем его едят - проработать большой вопрос с изменением и пересохранением количества образцов, проб, испатаний и что делать с номерами? А с уже проведёнными испытаниями?
|
||||
- Валидация при апдейте
|
||||
- полноценная выгрузка/загрузка файлов документов на сервер
|
||||
- логика реквестов файлов документов
|
||||
- рефакторинг сервисов, логический перенос и чистка кода!
|
||||
- баг с добавлением показателей в методики
|
||||
- как выглядят испытания на бэкенде? Сохранение в таблицы показателей к пробам
|
||||
- Доделать зависимость от родительской сущности в нумераторе - не сброс, а выборка по номеру родительской сущности и выдача следующего номера (таким образом мы сможем добавлять нормальные шифры в рамках родительской сущности в любое время)
|
||||
- документация фронта для редактирования счётчиков нумератора
|
||||
- перетрясти сидеры - создать нормальные образцы/пробы/испытания/привязать хотя бы парочку показателей
|
||||
|
||||
|
||||
|
||||
Динамические поля:
|
||||
|
||||
- добавить поддержку включения необходимых полей при выборе типа и группы ОИ (ПГО)
|
||||
- модели включенных и выключенных полей и передавать их на фронт при загрузке страницы
|
||||
- сделать инициированные данные по полям для первого ФИЗа
|
||||
- сделать динамическую валидацию только включенных полей
|
||||
- добавить механизмы редактирования сохранения пользовательских полей
|
||||
- добавить валидацию пользовательских полей
|
||||
- написать Саше документацию для них, чтобы рендерить, валидировать, сохранять и загружать их (массив custom)
|
||||
- добавление пользовательских полей в макросы документов для выгрузки в документы
|
||||
- наборски фронта для документов и редактирования
|
||||
|
||||
- добавить шаблоны документов выгрузки регистрации образца (+ акт списания)
|
||||
|
||||
|
||||
Система динамических статусов и зависимостей сущностей от предыдущих шагов:
|
||||
|
||||
- проработать статусы и историю их изменений для всех сущностей
|
||||
- рефакторинг фильтров статусов для GET-запросов с фронта
|
||||
|
||||
Сохранение испытаний:
|
||||
- таблица результатов испытаний с привязкой к ответственным, условиям и trials
|
||||
|
||||
Сохранение заявок/сделок:
|
||||
|
||||
Система согласований:
|
||||
|
||||
Система включения-отключения модулей исходя из тарифа
|
||||
- зависимость от бизнес-процесса и статусов
|
||||
- зависимость в модулях нумератора и документов
|
||||
|
||||
```
|
||||
14
My/Июль.md
Normal file
14
My/Июль.md
Normal 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 |
|
||||
| ---- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
|
||||
| | | | | | | | | | | | | |
|
||||
| | | | | | | | | | | | | |
|
||||
| | | | | | | | | | | | | |
|
||||
| | | | | | | | | | | | | |
|
||||
| | | | | | | | | | | | | |
|
||||
| | | | | | | | | | | | | |
|
||||
| | | | | | | | | | | | | |
|
||||
| | | | | | | | | | | | | |
|
||||
| | | | | | | | | | | | | |
|
||||
| | | | | | | | | | | | | |
|
||||
| | | | | | | | | | | | | |
|
||||
218
SKILLS DOCS/JAPANESE частицы.md
Normal file
218
SKILLS DOCS/JAPANESE частицы.md
Normal 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).
|
||||
> «Он встретился с другом на станции и посмотрел фильм».
|
||||
|
||||
Частицы — это просто! Главное — видеть их **как «якоря»** для слов. 🚀
|
||||
@@ -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).
|
||||
- **Готовые команды** для управления колонтитулами.
|
||||
@@ -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.
|
||||
@@ -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-сертификаты
|
||||
|
||||
Структура чатов и комментариев
|
||||
|
||||
Формы – раздельное редактирование и генерация документа. Возможность редактирования сгенерированного документа.
|
||||
|
||||
|
||||
Услуга (оффер) - совокупность показателей (с отдельной ценой) + доп. услуги. Цены на всё.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
@@ -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
|
||||
|
||||
```
|
||||
@@ -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
|
||||
|
||||
@@ -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\t‘app_id’ = 1,\n\t‘app_secret’\n\t….\n],\n‘app_id’ = 2,\n\t‘app_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"}
|
||||
]
|
||||
}
|
||||
21
WORK & PROJECTS/Mol/Планы и диаграммы/notifies.canvas
Normal file
21
WORK & PROJECTS/Mol/Планы и диаграммы/notifies.canvas
Normal 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"}
|
||||
]
|
||||
}
|
||||
@@ -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 | | | | | |
|
||||
|
||||
|
||||
@@ -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 | |
|
||||
@@ -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 |
|
||||
| | …. | | |
|
||||
|
||||
@@ -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) |
|
||||
| | | Заказчиком / Подрядчиком | | | | |
|
||||
@@ -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"}
|
||||
]
|
||||
}
|
||||
@@ -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"}
|
||||
]
|
||||
}
|
||||
@@ -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"}
|
||||
]
|
||||
}
|
||||
@@ -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 | | | | | | | |
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
| id | name | Sort_idx | |
|
||||
| --- | -------------------- | -------- | --- |
|
||||
| 1 | Лаб. сущности | 10 | |
|
||||
| 2 | Документы | 20 | |
|
||||
| 3 | Внутренняя нумерация | 30 | |
|
||||
@@ -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 |
|
||||
| | | | | | | | | | | | | | |
|
||||
@@ -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":[]
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 | | | | | | | | | | |
|
||||
| | | | | | | | | | | |
|
||||
@@ -0,0 +1,10 @@
|
||||
Типы начислений дней или средств
|
||||
|
||||
| 1 | Активация подписки |
|
||||
| --- | ------------------- |
|
||||
| 3 | Обещанный платёж |
|
||||
| 4 | Бонусное начисление |
|
||||
| 5 | Промо-акция1 |
|
||||
| 6 | Промо-акция2 |
|
||||
| 7 | Промо НГ 2026 |
|
||||
| 999 | Коррекция |
|
||||
@@ -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 |
|
||||
@@ -0,0 +1,8 @@
|
||||
Перечень продуктов mol
|
||||
|
||||
| name | month_price | enabled |
|
||||
| -------------- | ----------- | ------- |
|
||||
| mol.LIMS | 29000,00 | true |
|
||||
| mol.Logistics | 24000,00 | true |
|
||||
| mol.Regulatory | 24000,00 | true |
|
||||
|
||||
@@ -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>]] |
|
||||
@@ -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"}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -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/
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
liquibase update
|
||||
@@ -1 +0,0 @@
|
||||
📂cache bitrix
|
||||
@@ -1 +0,0 @@
|
||||
📂doc_archive
|
||||
@@ -1 +0,0 @@
|
||||
CLOUD API
|
||||
@@ -1 +0,0 @@
|
||||
📂doc_archive
|
||||
@@ -1 +0,0 @@
|
||||
# 🗄 git.moldev.ru
|
||||
@@ -1 +0,0 @@
|
||||
Обновляемые конфиги
|
||||
@@ -1 +0,0 @@
|
||||
📂doc_archive
|
||||
@@ -1 +0,0 @@
|
||||
📂bitrix sources
|
||||
@@ -1 +0,0 @@
|
||||
**Cloud Users DB**
|
||||
@@ -1 +0,0 @@
|
||||
client2 apache reverse proxy
|
||||
@@ -1,32 +0,0 @@
|
||||
### CLOUD API (конфигурация облаков)
|
||||
- #### Регистрация нового клиента
|
||||
- создание новой БД клиента из CLEAN_db
|
||||
- внесение данных о клиенте в БД клиента
|
||||
- обновление cloud_users_db
|
||||
- поддомен и статус ssl-сертификатов
|
||||
- рабочий сервер
|
||||
- рабочая папка на сервере
|
||||
- настройки контейнера (подключенные модули, версия кода и тд)
|
||||
- тариф и крайняя дата исп.
|
||||
- БД
|
||||
- Часовой пояс
|
||||
- Информация о логине первого пользователя
|
||||
- переконфигурация сервера
|
||||
- создание/конфигурация файлов кэша, настроек php и индивидуальных архивов
|
||||
- пересоздание/обновление конфигов:
|
||||
- docker
|
||||
- apache
|
||||
- скрипт обновления БД (liquibase)
|
||||
- выпуск ssl нового поддомена
|
||||
- перезапуск apache & docker compose
|
||||
- #### Обновление информации клиента
|
||||
- тариф и дата последнего дня работы
|
||||
- подключённые опции (реконфиг docker)
|
||||
- часовой пояс php
|
||||
- #### Работа с демо-данными
|
||||
- активация демо-данных
|
||||
- удаление демо-данных
|
||||
- #### Удаление клиента
|
||||
- очистка БД клиента
|
||||
- пересоздание конфигов сервера
|
||||
- чистка файлов (контейнеры и конфиги)
|
||||
@@ -1 +0,0 @@
|
||||
Клиентские БД
|
||||
@@ -1 +0,0 @@
|
||||
Папки клиентских данных
|
||||
@@ -1 +0,0 @@
|
||||
📂cache bitrix
|
||||
@@ -1,2 +0,0 @@
|
||||
client 1
|
||||
apache + php container
|
||||
@@ -1 +0,0 @@
|
||||
client3 apache reverse proxy
|
||||
@@ -1 +0,0 @@
|
||||
DEMO_DATA_db
|
||||
@@ -1 +0,0 @@
|
||||
apache cloud API conf
|
||||
@@ -1 +0,0 @@
|
||||
client2_db
|
||||
@@ -1,2 +0,0 @@
|
||||
client 2
|
||||
apache + php container
|
||||
@@ -1 +0,0 @@
|
||||
1C:CRM API
|
||||
@@ -1 +0,0 @@
|
||||
sql all bases update .sh script
|
||||
@@ -1 +0,0 @@
|
||||
CLOUD API
|
||||
@@ -1 +0,0 @@
|
||||
📂alfa sources (.git)
|
||||
@@ -1 +0,0 @@
|
||||
📂cache bitrix
|
||||
@@ -1,2 +0,0 @@
|
||||
client 3
|
||||
apache + php container
|
||||
@@ -1 +0,0 @@
|
||||
client...._db
|
||||
@@ -1 +0,0 @@
|
||||
CLEAN_db
|
||||
@@ -1 +0,0 @@
|
||||
client1 apache reverse proxy
|
||||
@@ -1 +0,0 @@
|
||||
docker-compose.yml
|
||||
@@ -1 +0,0 @@
|
||||
client1_db
|
||||
16
WORK & PROJECTS/Mol/Серверы/Mol Public Prod.md
Normal file
16
WORK & PROJECTS/Mol/Серверы/Mol Public Prod.md
Normal 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
|
||||
|
||||
|
||||
```
|
||||
747
WORK & PROJECTS/Mol/Серверы/PROXY_PASS/Traefik config.md
Normal file
747
WORK & PROJECTS/Mol/Серверы/PROXY_PASS/Traefik config.md
Normal 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 | ❌ (нужно внешнее) |
|
||||
| Простота автоматизации | ✅ | ⚠️ сложнее |
|
||||
| Интеграция с Let’s 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 клиентов | ~8000–9000 RPS |
|
||||
| Angie + 1000 клиентов | ~10000–11000 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 | 300–500 MB | ~0.4 ядра |
|
||||
| 10 000 | 600–800 MB | ~0.7 ядра |
|
||||
| 20 000 | 1–1.5 GB | ~1.2–1.5 ядра |
|
||||
|
||||
> Это оценка для простого reverse proxy без TLS, сложных middleware, WAF и т.п.
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ Рекомендуемая конфигурация для 20 000 RPS + дополнительные сервисы
|
||||
|
||||
| Компонент | Рекомендация |
|
||||
|------------------|-----------------------------------|
|
||||
| CPU | 4–6 ядер (минимум), лучше 8 |
|
||||
| RAM | 4–6 ГБ |
|
||||
| Диск | SSD, 20–40 ГБ |
|
||||
| ОС | Linux (Debian, Ubuntu Server) |
|
||||
| Сеть | 1 Гбит/с минимум |
|
||||
|
||||
> ✅ Если нагрузка будет расти постепенно, можно начать с 2–4 ядер и 2–4 ГБ RAM, масштабируя дальше при необходимости.
|
||||
|
||||
---
|
||||
|
||||
## 💡 Что важнее: RAM или CPU?
|
||||
|
||||
### ✅ Ответ: **и то, и другое важно, но первым ограничителем станет CPU**
|
||||
|
||||
- **RAM** обычно расходуется не очень много (~1–1.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 проверяет, есть ли уже выданный сертификат.
|
||||
- Если нет — запрашивает его у Let’s Encrypt.
|
||||
- Для проверки прав на домен — создаёт временный маршрут `//.well-known/acme-challenge/...`.
|
||||
- После успешной проверки — сохраняет сертификат в `acme.json`.
|
||||
- Запрос перенаправляется на внутренний сервер `http://192.168.10.10:8080`.
|
||||
|
||||
---
|
||||
|
||||
## 3. **Как работает HTTP-челлендж?**
|
||||
|
||||
Let’s 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. **Запрашивает сертификат у Let’s 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` |
|
||||
|
||||
---
|
||||
18
WORK & PROJECTS/Mol/Серверы/Supervisor/job_worker.md
Normal file
18
WORK & PROJECTS/Mol/Серверы/Supervisor/job_worker.md
Normal 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
|
||||
```
|
||||
@@ -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
|
||||
|
||||
```
|
||||
|
||||
@@ -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"}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user