Compare commits
14 Commits
VORKOUT-21
...
VORKOUT-16
Author | SHA1 | Date | |
---|---|---|---|
6db2821781 | |||
3f87970653 | |||
ee4051f523 | |||
65ed6b9561 | |||
b503bc0bef | |||
5cdb6e460f | |||
5966b7d6f1 | |||
8b0659fc89 | |||
51344ef218 | |||
51171377e2 | |||
943f4ded2d | |||
443293d420 | |||
91c8efe13f | |||
e10430310f |
@@ -1,15 +1,15 @@
|
|||||||
import logging
|
|
||||||
import sys
|
import sys
|
||||||
|
import logging
|
||||||
|
|
||||||
import loguru
|
import loguru
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from uvicorn import run
|
from uvicorn import run
|
||||||
|
|
||||||
from api.config import DefaultSettings, get_settings
|
from api.config import get_settings, DefaultSettings
|
||||||
from api.endpoints import list_of_routes
|
from api.endpoints import list_of_routes
|
||||||
from api.services.middleware import MiddlewareAccessTokenValidadtion
|
|
||||||
from api.utils.common import get_hostname
|
from api.utils.common import get_hostname
|
||||||
|
from api.services.middleware import MiddlewareAccessTokenValidadtion
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
logger.setLevel(logging.DEBUG)
|
logger.setLevel(logging.DEBUG)
|
||||||
@@ -52,6 +52,7 @@ prod_origins = [""]
|
|||||||
|
|
||||||
origins = dev_origins if get_settings().ENV == "local" else prod_origins
|
origins = dev_origins if get_settings().ENV == "local" else prod_origins
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
settings_for_application = get_settings()
|
settings_for_application = get_settings()
|
||||||
if settings_for_application.ENV == "prod":
|
if settings_for_application.ENV == "prod":
|
||||||
|
@@ -7,7 +7,7 @@ from sqlalchemy import pool
|
|||||||
|
|
||||||
from alembic import context
|
from alembic import context
|
||||||
|
|
||||||
from orm import metadata, tables
|
from api.db import metadata, tables
|
||||||
|
|
||||||
# this is the Alembic Config object, which provides
|
# this is the Alembic Config object, which provides
|
||||||
# access to the values within the .ini file in use.
|
# access to the values within the .ini file in use.
|
||||||
|
@@ -1,38 +0,0 @@
|
|||||||
"""empty message
|
|
||||||
|
|
||||||
Revision ID: 816be8c60ab4
|
|
||||||
Revises: 93106fbe7d83
|
|
||||||
Create Date: 2025-09-12 14:48:47.726269
|
|
||||||
|
|
||||||
"""
|
|
||||||
from typing import Sequence, Union
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
from sqlalchemy.dialects import mysql
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision: str = '816be8c60ab4'
|
|
||||||
down_revision: Union[str, None] = '93106fbe7d83'
|
|
||||||
branch_labels: Union[str, Sequence[str], None] = None
|
|
||||||
depends_on: Union[str, Sequence[str], None] = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
"""Upgrade schema."""
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.alter_column('ps_node', 'node_type',
|
|
||||||
existing_type=mysql.ENUM('TYPE1', 'TYPE2', 'TYPE3'),
|
|
||||||
type_=sa.Enum('LISTEN', 'IF', 'START', name='nodetype'),
|
|
||||||
existing_nullable=False)
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
"""Downgrade schema."""
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.alter_column('ps_node', 'node_type',
|
|
||||||
existing_type=sa.Enum('LISTEN', 'IF', 'START', name='nodetype'),
|
|
||||||
type_=mysql.ENUM('TYPE1', 'TYPE2', 'TYPE3'),
|
|
||||||
existing_nullable=False)
|
|
||||||
# ### end Alembic commands ###
|
|
@@ -1,32 +0,0 @@
|
|||||||
"""update node_link_table link_point_id default
|
|
||||||
|
|
||||||
Revision ID: cc3b95f1f99d
|
|
||||||
Revises: 816be8c60ab4
|
|
||||||
Create Date: 2025-09-12 19:17:03.125276
|
|
||||||
|
|
||||||
"""
|
|
||||||
from typing import Sequence, Union
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
from sqlalchemy.dialects import mysql
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision: str = 'cc3b95f1f99d'
|
|
||||||
down_revision: Union[str, None] = '816be8c60ab4'
|
|
||||||
branch_labels: Union[str, Sequence[str], None] = None
|
|
||||||
depends_on: Union[str, Sequence[str], None] = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
"""Upgrade schema."""
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.add_column('node_link', sa.Column('link_point_id', sa.Integer().with_variant(mysql.INTEGER(unsigned=True), 'mysql'), nullable=False))
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
"""Downgrade schema."""
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_column('node_link', 'link_point_id')
|
|
||||||
# ### end Alembic commands ###
|
|
@@ -6,7 +6,7 @@ from typing import Optional
|
|||||||
from sqlalchemy import insert, select, func, or_, and_, asc, desc
|
from sqlalchemy import insert, select, func, or_, and_, asc, desc
|
||||||
from sqlalchemy.ext.asyncio import AsyncConnection
|
from sqlalchemy.ext.asyncio import AsyncConnection
|
||||||
|
|
||||||
from orm.tables.account import account_table
|
from api.db.tables.account import account_table
|
||||||
from api.schemas.account.account import User
|
from api.schemas.account.account import User
|
||||||
from api.schemas.endpoints.account import all_user_adapter, AllUser, AllUserResponse, UserCreate, UserFilterDTO
|
from api.schemas.endpoints.account import all_user_adapter, AllUser, AllUserResponse, UserCreate, UserFilterDTO
|
||||||
|
|
||||||
|
@@ -4,7 +4,7 @@ from sqlalchemy import select, update
|
|||||||
from sqlalchemy.ext.asyncio import AsyncConnection
|
from sqlalchemy.ext.asyncio import AsyncConnection
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from orm.tables.account import account_table, account_keyring_table, KeyType, KeyStatus
|
from api.db.tables.account import account_table, account_keyring_table, KeyType, KeyStatus
|
||||||
|
|
||||||
from api.schemas.account.account import User
|
from api.schemas.account.account import User
|
||||||
from api.schemas.account.account_keyring import AccountKeyring
|
from api.schemas.account.account_keyring import AccountKeyring
|
||||||
|
@@ -6,7 +6,7 @@ from sqlalchemy import insert, select, update
|
|||||||
from sqlalchemy.dialects.mysql import insert as mysql_insert
|
from sqlalchemy.dialects.mysql import insert as mysql_insert
|
||||||
from sqlalchemy.ext.asyncio import AsyncConnection
|
from sqlalchemy.ext.asyncio import AsyncConnection
|
||||||
|
|
||||||
from orm.tables.account import account_keyring_table, KeyStatus, KeyType
|
from api.db.tables.account import account_keyring_table, KeyStatus, KeyType
|
||||||
from api.schemas.account.account_keyring import AccountKeyring
|
from api.schemas.account.account_keyring import AccountKeyring
|
||||||
from api.utils.hasher import hasher
|
from api.utils.hasher import hasher
|
||||||
|
|
||||||
|
@@ -7,7 +7,7 @@ from datetime import datetime, timezone
|
|||||||
from sqlalchemy import insert, select, func, or_, and_, asc, desc
|
from sqlalchemy import insert, select, func, or_, and_, asc, desc
|
||||||
from sqlalchemy.ext.asyncio import AsyncConnection
|
from sqlalchemy.ext.asyncio import AsyncConnection
|
||||||
|
|
||||||
from orm.tables.events import list_events_table
|
from api.db.tables.events import list_events_table
|
||||||
|
|
||||||
from api.schemas.events.list_events import ListEvent
|
from api.schemas.events.list_events import ListEvent
|
||||||
|
|
||||||
|
@@ -1,81 +0,0 @@
|
|||||||
from typing import Optional
|
|
||||||
|
|
||||||
from datetime import datetime, timezone
|
|
||||||
|
|
||||||
from sqlalchemy import insert, select, desc
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncConnection
|
|
||||||
|
|
||||||
from api.schemas.process.node_link import NodeLink
|
|
||||||
|
|
||||||
from orm.tables.process import ps_node_table, node_link_table
|
|
||||||
from orm.tables.process import NodeLinkStatus
|
|
||||||
|
|
||||||
|
|
||||||
async def get_last_link_name_by_node_id(connection: AsyncConnection, ps_id: int) -> Optional[str]:
|
|
||||||
"""
|
|
||||||
Получает link_name из последней записи node_link по ps_id.
|
|
||||||
Находит все node_id в ps_node по ps_id, затем ищет связи в node_link
|
|
||||||
и возвращает link_name из самой последней записи.
|
|
||||||
"""
|
|
||||||
query = (
|
|
||||||
select(node_link_table.c.link_name)
|
|
||||||
.where(node_link_table.c.node_id.in_(select(ps_node_table.c.id).where(ps_node_table.c.ps_id == ps_id)))
|
|
||||||
.order_by(desc(node_link_table.c.created_at))
|
|
||||||
.limit(1)
|
|
||||||
)
|
|
||||||
|
|
||||||
result = await connection.execute(query)
|
|
||||||
link_name = result.scalar_one_or_none()
|
|
||||||
|
|
||||||
return link_name
|
|
||||||
|
|
||||||
|
|
||||||
async def get_last_node_link_by_creator_and_ps_id(
|
|
||||||
connection: AsyncConnection, creator_id: int, node_link_id: int
|
|
||||||
) -> Optional[NodeLink]:
|
|
||||||
"""
|
|
||||||
Получает последнюю созданную node_link для данного создателя и процесса.
|
|
||||||
"""
|
|
||||||
query = (
|
|
||||||
select(node_link_table)
|
|
||||||
.where(
|
|
||||||
node_link_table.c.creator_id == creator_id,
|
|
||||||
node_link_table.c.node_id.in_(select(ps_node_table.c.id).where(ps_node_table.c.id == node_link_id)),
|
|
||||||
)
|
|
||||||
.order_by(desc(node_link_table.c.created_at))
|
|
||||||
.limit(1)
|
|
||||||
)
|
|
||||||
|
|
||||||
node_link_db_cursor = await connection.execute(query)
|
|
||||||
node_link_data = node_link_db_cursor.mappings().one_or_none()
|
|
||||||
|
|
||||||
if not node_link_data:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return NodeLink.model_validate(node_link_data)
|
|
||||||
|
|
||||||
|
|
||||||
async def create_node_link_schema(
|
|
||||||
connection: AsyncConnection,
|
|
||||||
validated_link_schema,
|
|
||||||
creator_id: int,
|
|
||||||
) -> Optional[NodeLink]:
|
|
||||||
"""
|
|
||||||
Создает нове поле в таблице process_schema_table.
|
|
||||||
"""
|
|
||||||
query = insert(node_link_table).values(
|
|
||||||
link_name=validated_link_schema.link_name,
|
|
||||||
node_id=validated_link_schema.from_id,
|
|
||||||
link_point_id=validated_link_schema.parent_port_number,
|
|
||||||
next_node_id=validated_link_schema.to_id,
|
|
||||||
settings={},
|
|
||||||
creator_id=creator_id,
|
|
||||||
created_at=datetime.now(timezone.utc),
|
|
||||||
status=NodeLinkStatus.ACTIVE.value,
|
|
||||||
)
|
|
||||||
|
|
||||||
await connection.execute(query)
|
|
||||||
|
|
||||||
await connection.commit()
|
|
||||||
|
|
||||||
return await get_last_node_link_by_creator_and_ps_id(connection, creator_id, validated_link_schema.from_id)
|
|
@@ -1,4 +1,4 @@
|
|||||||
from typing import Optional, Dict, Any
|
from typing import Optional
|
||||||
import math
|
import math
|
||||||
|
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
@@ -6,7 +6,7 @@ from datetime import datetime, timezone
|
|||||||
from sqlalchemy import insert, select, func, or_, and_, asc, desc
|
from sqlalchemy import insert, select, func, or_, and_, asc, desc
|
||||||
from sqlalchemy.ext.asyncio import AsyncConnection
|
from sqlalchemy.ext.asyncio import AsyncConnection
|
||||||
|
|
||||||
from orm.tables.process import process_schema_table, ProcessStatus
|
from api.db.tables.process import process_schema_table
|
||||||
|
|
||||||
from api.schemas.process.process_schema import ProcessSchema
|
from api.schemas.process.process_schema import ProcessSchema
|
||||||
|
|
||||||
@@ -50,9 +50,8 @@ async def get_process_schema_page_DTO(
|
|||||||
or_(process_schema_table.c.title.ilike(search_term), process_schema_table.c.description.ilike(search_term))
|
or_(process_schema_table.c.title.ilike(search_term), process_schema_table.c.description.ilike(search_term))
|
||||||
)
|
)
|
||||||
|
|
||||||
filter_conditions = []
|
|
||||||
|
|
||||||
if filter_dto.filters:
|
if filter_dto.filters:
|
||||||
|
filter_conditions = []
|
||||||
for field, values in filter_dto.filters.items():
|
for field, values in filter_dto.filters.items():
|
||||||
column = getattr(process_schema_table.c, field, None)
|
column = getattr(process_schema_table.c, field, None)
|
||||||
if column is not None and values:
|
if column is not None and values:
|
||||||
@@ -61,11 +60,8 @@ async def get_process_schema_page_DTO(
|
|||||||
else:
|
else:
|
||||||
filter_conditions.append(column.in_(values))
|
filter_conditions.append(column.in_(values))
|
||||||
|
|
||||||
if filter_dto.filters is None or "status" not in filter_dto.filters:
|
if filter_conditions:
|
||||||
filter_conditions.append(process_schema_table.c.status != "DELETED")
|
query = query.where(and_(*filter_conditions))
|
||||||
|
|
||||||
if filter_conditions:
|
|
||||||
query = query.where(and_(*filter_conditions))
|
|
||||||
|
|
||||||
if filter_dto.order:
|
if filter_dto.order:
|
||||||
order_field = filter_dto.order.get("field", "id")
|
order_field = filter_dto.order.get("field", "id")
|
||||||
@@ -90,7 +86,7 @@ async def get_process_schema_page_DTO(
|
|||||||
or_(process_schema_table.c.title.ilike(search_term), process_schema_table.c.description.ilike(search_term))
|
or_(process_schema_table.c.title.ilike(search_term), process_schema_table.c.description.ilike(search_term))
|
||||||
)
|
)
|
||||||
|
|
||||||
if filter_conditions:
|
if filter_dto.filters and filter_conditions:
|
||||||
count_query = count_query.where(and_(*filter_conditions))
|
count_query = count_query.where(and_(*filter_conditions))
|
||||||
|
|
||||||
result = await connection.execute(query)
|
result = await connection.execute(query)
|
||||||
@@ -156,67 +152,24 @@ async def update_process_schema_by_id(connection: AsyncConnection, update_values
|
|||||||
await connection.commit()
|
await connection.commit()
|
||||||
|
|
||||||
|
|
||||||
async def update_process_schema_settings_by_id(
|
|
||||||
connection: AsyncConnection, process_schema_id: int, node_data: Dict[str, Any]
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Добавляет новый узел в массив 'nodes' в настройках процесса.
|
|
||||||
Если массив 'nodes' не существует, создает его.
|
|
||||||
"""
|
|
||||||
# Получаем текущие settings
|
|
||||||
query = select(process_schema_table.c.settings).where(process_schema_table.c.id == process_schema_id)
|
|
||||||
result = await connection.execute(query)
|
|
||||||
current_settings = result.scalar_one_or_none()
|
|
||||||
|
|
||||||
# Если settings пустые, создаем пустой словарь
|
|
||||||
if current_settings is None:
|
|
||||||
current_settings = {}
|
|
||||||
|
|
||||||
# Инициализируем массив nodes, если его нет
|
|
||||||
if "nodes" not in current_settings:
|
|
||||||
current_settings["nodes"] = []
|
|
||||||
|
|
||||||
# Добавляем новый узел в массив
|
|
||||||
current_settings["nodes"].append(node_data)
|
|
||||||
|
|
||||||
# Обновляем поле settings
|
|
||||||
await connection.execute(
|
|
||||||
process_schema_table.update()
|
|
||||||
.where(process_schema_table.c.id == process_schema_id)
|
|
||||||
.values(settings=current_settings)
|
|
||||||
)
|
|
||||||
|
|
||||||
await connection.commit()
|
|
||||||
|
|
||||||
|
|
||||||
async def get_last_created_process_schema(connection: AsyncConnection) -> Optional[int]:
|
|
||||||
"""
|
|
||||||
Получает ID последней созданной схемы процесса.
|
|
||||||
"""
|
|
||||||
query = select(process_schema_table.c.id).order_by(desc(process_schema_table.c.id)).limit(1)
|
|
||||||
|
|
||||||
result = await connection.execute(query)
|
|
||||||
last_id = result.scalar_one_or_none()
|
|
||||||
|
|
||||||
return last_id
|
|
||||||
|
|
||||||
|
|
||||||
async def create_process_schema(
|
async def create_process_schema(
|
||||||
connection: AsyncConnection, creator_id: int, title: str, description: str
|
connection: AsyncConnection, process_schema: ProcessSchema, creator_id: int
|
||||||
) -> Optional[ProcessSchema]:
|
) -> Optional[ProcessSchema]:
|
||||||
"""
|
"""
|
||||||
Создает нове поле в таблице process_schema_table.
|
Создает нове поле в таблице process_schema_table.
|
||||||
"""
|
"""
|
||||||
query = insert(process_schema_table).values(
|
query = insert(process_schema_table).values(
|
||||||
title=title,
|
title=process_schema.title,
|
||||||
description=description,
|
description=process_schema.description,
|
||||||
owner_id=creator_id,
|
owner_id=process_schema.owner_id,
|
||||||
creator_id=creator_id,
|
creator_id=creator_id,
|
||||||
created_at=datetime.now(timezone.utc),
|
created_at=datetime.now(timezone.utc),
|
||||||
settings={},
|
settings=process_schema.settings,
|
||||||
status=ProcessStatus.ACTIVE.value,
|
status=process_schema.status.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
await connection.execute(query)
|
await connection.execute(query)
|
||||||
|
|
||||||
await connection.commit()
|
await connection.commit()
|
||||||
|
|
||||||
|
return process_schema
|
||||||
|
@@ -1,110 +0,0 @@
|
|||||||
from typing import Optional
|
|
||||||
|
|
||||||
from datetime import datetime, timezone
|
|
||||||
|
|
||||||
from sqlalchemy import insert, select, desc
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncConnection
|
|
||||||
|
|
||||||
from orm.tables.process import ps_node_table
|
|
||||||
|
|
||||||
from api.schemas.process.ps_node import Ps_Node
|
|
||||||
from model_nodes.node_listen_models import ListenNodeCoreSchema
|
|
||||||
from orm.tables.process import NodeStatus, NodeType
|
|
||||||
|
|
||||||
|
|
||||||
async def get_ps_node_by_id(connection: AsyncConnection, id: int) -> Optional[Ps_Node]:
|
|
||||||
"""
|
|
||||||
Получает process_schema по id.
|
|
||||||
"""
|
|
||||||
query = select(ps_node_table).where(ps_node_table.c.id == id)
|
|
||||||
|
|
||||||
ps_node_db_cursor = await connection.execute(query)
|
|
||||||
|
|
||||||
ps_node_data = ps_node_db_cursor.mappings().one_or_none()
|
|
||||||
if not ps_node_data:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return Ps_Node.model_validate(ps_node_data)
|
|
||||||
|
|
||||||
|
|
||||||
async def get_ps_node_by_type_and_ps_id(connection: AsyncConnection, node_type: str, ps_id: int) -> Optional[Ps_Node]:
|
|
||||||
"""
|
|
||||||
Получает ps_node по node_type и ps_id.
|
|
||||||
"""
|
|
||||||
query = select(ps_node_table).where(ps_node_table.c.node_type == node_type, ps_node_table.c.ps_id == ps_id)
|
|
||||||
|
|
||||||
ps_node_db_cursor = await connection.execute(query)
|
|
||||||
|
|
||||||
ps_node_data = ps_node_db_cursor.mappings().one_or_none()
|
|
||||||
if not ps_node_data:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return Ps_Node.model_validate(ps_node_data)
|
|
||||||
|
|
||||||
|
|
||||||
async def create_ps_node_start_schema(
|
|
||||||
connection: AsyncConnection, validated_listen_schema: ListenNodeCoreSchema, creator_id: int
|
|
||||||
) -> Optional[ListenNodeCoreSchema]:
|
|
||||||
"""
|
|
||||||
Создает нове поле в таблице process_schema_table.
|
|
||||||
"""
|
|
||||||
query = insert(ps_node_table).values(
|
|
||||||
ps_id=validated_listen_schema.ps_id,
|
|
||||||
node_type=NodeType.LISTEN.value,
|
|
||||||
settings=validated_listen_schema.data.model_dump(),
|
|
||||||
creator_id=creator_id,
|
|
||||||
created_at=datetime.now(timezone.utc),
|
|
||||||
status=NodeStatus.ACTIVE.value,
|
|
||||||
)
|
|
||||||
|
|
||||||
await connection.execute(query)
|
|
||||||
|
|
||||||
await connection.commit()
|
|
||||||
|
|
||||||
# return validated_listen_schema
|
|
||||||
|
|
||||||
|
|
||||||
async def get_last_ps_node_by_creator_and_ps_id(
|
|
||||||
connection: AsyncConnection, creator_id: int, ps_id: int
|
|
||||||
) -> Optional[Ps_Node]:
|
|
||||||
"""
|
|
||||||
Получает последнюю созданную ps_node для данного создателя и процесса.
|
|
||||||
"""
|
|
||||||
query = (
|
|
||||||
select(ps_node_table)
|
|
||||||
.where(ps_node_table.c.creator_id == creator_id, ps_node_table.c.ps_id == ps_id)
|
|
||||||
.order_by(desc(ps_node_table.c.created_at))
|
|
||||||
.limit(1)
|
|
||||||
)
|
|
||||||
|
|
||||||
ps_node_db_cursor = await connection.execute(query)
|
|
||||||
ps_node_data = ps_node_db_cursor.mappings().one_or_none()
|
|
||||||
|
|
||||||
if not ps_node_data:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return Ps_Node.model_validate(ps_node_data)
|
|
||||||
|
|
||||||
|
|
||||||
async def create_ps_node_schema(
|
|
||||||
connection: AsyncConnection,
|
|
||||||
validated_schema,
|
|
||||||
creator_id: int,
|
|
||||||
) -> Optional[ListenNodeCoreSchema]:
|
|
||||||
"""
|
|
||||||
Создает нове поле в таблице process_schema_table.
|
|
||||||
"""
|
|
||||||
query = insert(ps_node_table).values(
|
|
||||||
ps_id=validated_schema.ps_id,
|
|
||||||
node_type=validated_schema.node_type,
|
|
||||||
settings=validated_schema.data.model_dump(),
|
|
||||||
creator_id=creator_id,
|
|
||||||
created_at=datetime.now(timezone.utc),
|
|
||||||
status=NodeStatus.ACTIVE.value,
|
|
||||||
)
|
|
||||||
|
|
||||||
await connection.execute(query)
|
|
||||||
|
|
||||||
await connection.commit()
|
|
||||||
|
|
||||||
return await get_last_ps_node_by_creator_and_ps_id(connection, creator_id, validated_schema.ps_id)
|
|
18
api/api/db/sql_types.py
Normal file
18
api/api/db/sql_types.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
__all__ = ["BigIntegerPK", "SAEnum", "UnsignedInt"]
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from sqlalchemy import BigInteger, Enum, Integer
|
||||||
|
from sqlalchemy.dialects import mysql
|
||||||
|
|
||||||
|
|
||||||
|
# class SAEnum(Enum):
|
||||||
|
# def __init__(self, *enums: object, **kw: Any):
|
||||||
|
# validate_strings = kw.pop("validate_strings", True)
|
||||||
|
# super().__init__(*enums, **kw, validate_strings=validate_strings)
|
||||||
|
|
||||||
|
|
||||||
|
# # https://docs.sqlalchemy.org/en/20/dialects/sqlite.html#allowing-autoincrement-behavior-sqlalchemy-types-other-than-integer-integer
|
||||||
|
|
||||||
|
# BigIntegerPK = BigInteger().with_variant(Integer, "sqlite")
|
||||||
|
UnsignedInt = Integer().with_variant(mysql.INTEGER(unsigned=True), "mysql")
|
1
api/api/db/tables/__init__.py
Normal file
1
api/api/db/tables/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import account, events, process
|
65
api/api/db/tables/account.py
Normal file
65
api/api/db/tables/account.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import enum
|
||||||
|
|
||||||
|
from sqlalchemy import Table, Column, String, Enum as SQLAEnum, JSON, ForeignKey, DateTime, Index
|
||||||
|
from sqlalchemy.sql import func
|
||||||
|
|
||||||
|
from api.db.sql_types import UnsignedInt
|
||||||
|
from api.db import metadata
|
||||||
|
|
||||||
|
|
||||||
|
class AccountRole(enum.StrEnum):
|
||||||
|
OWNER = "OWNER"
|
||||||
|
ADMIN = "ADMIN"
|
||||||
|
EDITOR = "EDITOR"
|
||||||
|
VIEWER = "VIEWER"
|
||||||
|
|
||||||
|
|
||||||
|
class AccountStatus(enum.StrEnum):
|
||||||
|
ACTIVE = "ACTIVE"
|
||||||
|
DISABLED = "DISABLED"
|
||||||
|
BLOCKED = "BLOCKED"
|
||||||
|
DELETED = "DELETED"
|
||||||
|
|
||||||
|
|
||||||
|
account_table = Table(
|
||||||
|
"account",
|
||||||
|
metadata,
|
||||||
|
Column("id", UnsignedInt, primary_key=True, autoincrement=True),
|
||||||
|
Column("name", String(100), nullable=False),
|
||||||
|
Column("login", String(100), nullable=False),
|
||||||
|
Column("email", String(100), nullable=True),
|
||||||
|
Column("bind_tenant_id", String(40), nullable=True),
|
||||||
|
Column("role", SQLAEnum(AccountRole), nullable=False),
|
||||||
|
Column("meta", JSON, default={}),
|
||||||
|
Column("creator_id", UnsignedInt, ForeignKey("account.id"), nullable=True),
|
||||||
|
Column("created_at", DateTime(timezone=True), server_default=func.now()),
|
||||||
|
Column("status", SQLAEnum(AccountStatus), nullable=False),
|
||||||
|
Index("idx_login", "login"),
|
||||||
|
Index("idx_name", "name"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class KeyType(enum.StrEnum):
|
||||||
|
PASSWORD = "PASSWORD"
|
||||||
|
ACCESS_TOKEN = "ACCESS_TOKEN"
|
||||||
|
REFRESH_TOKEN = "REFRESH_TOKEN"
|
||||||
|
API_KEY = "API_KEY"
|
||||||
|
|
||||||
|
|
||||||
|
class KeyStatus(enum.StrEnum):
|
||||||
|
ACTIVE = "ACTIVE"
|
||||||
|
EXPIRED = "EXPIRED"
|
||||||
|
DELETED = "DELETED"
|
||||||
|
|
||||||
|
|
||||||
|
account_keyring_table = Table(
|
||||||
|
"account_keyring",
|
||||||
|
metadata,
|
||||||
|
Column("owner_id", UnsignedInt, ForeignKey("account.id"), primary_key=True, nullable=False),
|
||||||
|
Column("key_type", SQLAEnum(KeyType), primary_key=True, nullable=False),
|
||||||
|
Column("key_id", String(40), primary_key=True, default=None),
|
||||||
|
Column("key_value", String(512), nullable=False),
|
||||||
|
Column("created_at", DateTime(timezone=True), server_default=func.now()),
|
||||||
|
Column("expiry", DateTime(timezone=True), nullable=True),
|
||||||
|
Column("status", SQLAEnum(KeyStatus), nullable=False),
|
||||||
|
)
|
33
api/api/db/tables/events.py
Normal file
33
api/api/db/tables/events.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import enum
|
||||||
|
|
||||||
|
from sqlalchemy import Table, Column, String, Enum as SQLAEnum, JSON, ForeignKey, DateTime
|
||||||
|
from sqlalchemy.sql import func
|
||||||
|
|
||||||
|
from api.db.sql_types import UnsignedInt
|
||||||
|
|
||||||
|
from api.db import metadata
|
||||||
|
|
||||||
|
|
||||||
|
class EventState(enum.StrEnum):
|
||||||
|
AUTO = "AUTO"
|
||||||
|
DESCRIPTED = "DESCRIPTED"
|
||||||
|
|
||||||
|
|
||||||
|
class EventStatus(enum.StrEnum):
|
||||||
|
ACTIVE = "ACTIVE"
|
||||||
|
DISABLED = "DISABLED"
|
||||||
|
DELETED = "DELETED"
|
||||||
|
|
||||||
|
|
||||||
|
list_events_table = Table(
|
||||||
|
"list_events",
|
||||||
|
metadata,
|
||||||
|
Column("id", UnsignedInt, primary_key=True, autoincrement=True),
|
||||||
|
Column("name", String(40, collation="latin1_bin"), nullable=False, unique=True),
|
||||||
|
Column("title", String(64), nullable=False),
|
||||||
|
Column("creator_id", UnsignedInt, ForeignKey("account.id"), nullable=False),
|
||||||
|
Column("created_at", DateTime(timezone=True), server_default=func.now()),
|
||||||
|
Column("schema", JSON, default={}),
|
||||||
|
Column("state", SQLAEnum(EventState), nullable=False),
|
||||||
|
Column("status", SQLAEnum(EventStatus), nullable=False),
|
||||||
|
)
|
107
api/api/db/tables/process.py
Normal file
107
api/api/db/tables/process.py
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import enum
|
||||||
|
|
||||||
|
from sqlalchemy import (
|
||||||
|
Table,
|
||||||
|
Column,
|
||||||
|
String,
|
||||||
|
Text,
|
||||||
|
Enum as SQLAEnum,
|
||||||
|
JSON,
|
||||||
|
ForeignKey,
|
||||||
|
DateTime,
|
||||||
|
Index,
|
||||||
|
PrimaryKeyConstraint,
|
||||||
|
)
|
||||||
|
from sqlalchemy.sql import func
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
from api.db.sql_types import UnsignedInt
|
||||||
|
|
||||||
|
from api.db import metadata
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessStatus(enum.StrEnum):
|
||||||
|
ACTIVE = "ACTIVE"
|
||||||
|
STOPPING = "STOPPING"
|
||||||
|
STOPPED = "STOPPED"
|
||||||
|
DELETED = "DELETED"
|
||||||
|
|
||||||
|
|
||||||
|
process_schema_table = Table(
|
||||||
|
"process_schema",
|
||||||
|
metadata,
|
||||||
|
Column("id", UnsignedInt, primary_key=True, autoincrement=True),
|
||||||
|
Column("title", String(100), nullable=False),
|
||||||
|
Column("description", Text, nullable=False),
|
||||||
|
Column("owner_id", UnsignedInt, ForeignKey("account.id"), nullable=False),
|
||||||
|
Column("creator_id", UnsignedInt, ForeignKey("account.id"), nullable=False),
|
||||||
|
Column("created_at", DateTime(timezone=True), server_default=func.now()),
|
||||||
|
Column("settings", JSON, default={}),
|
||||||
|
Column("status", SQLAEnum(ProcessStatus), nullable=False),
|
||||||
|
Index(
|
||||||
|
"idx_owner_id",
|
||||||
|
"owner_id",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
process_version_archive_table = Table(
|
||||||
|
"process_version_archive",
|
||||||
|
metadata,
|
||||||
|
Column("id", UnsignedInt, autoincrement=True, nullable=False),
|
||||||
|
Column("ps_id", UnsignedInt, ForeignKey("process_schema.id"), nullable=False),
|
||||||
|
Column("version", UnsignedInt, default=1, nullable=False),
|
||||||
|
Column("snapshot", JSON, default={}),
|
||||||
|
Column("owner_id", UnsignedInt, ForeignKey("account.id"), nullable=False),
|
||||||
|
Column("created_at", DateTime(timezone=True), server_default=func.now()),
|
||||||
|
Column("is_last", UnsignedInt, default=0),
|
||||||
|
PrimaryKeyConstraint("id", "version"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NodeStatus(enum.StrEnum):
|
||||||
|
ACTIVE = "ACTIVE"
|
||||||
|
DISABLED = "DISABLED"
|
||||||
|
DELETED = "DELETED"
|
||||||
|
|
||||||
|
|
||||||
|
class NodeType(Enum):
|
||||||
|
TYPE1 = "Type1"
|
||||||
|
TYPE2 = "Type2"
|
||||||
|
TYPE3 = "Type3"
|
||||||
|
|
||||||
|
|
||||||
|
ps_node_table = Table(
|
||||||
|
"ps_node",
|
||||||
|
metadata,
|
||||||
|
Column("id", UnsignedInt, autoincrement=True, primary_key=True, nullable=False),
|
||||||
|
Column("ps_id", UnsignedInt, ForeignKey("process_schema.id"), nullable=False),
|
||||||
|
Column("node_type", SQLAEnum(NodeType), nullable=False),
|
||||||
|
Column("settings", JSON, default={}),
|
||||||
|
Column("creator_id", UnsignedInt, ForeignKey("account.id"), nullable=False),
|
||||||
|
Column("created_at", DateTime(timezone=True), server_default=func.now()),
|
||||||
|
Column("status", SQLAEnum(NodeStatus), nullable=False),
|
||||||
|
Index("idx_ps_id", "ps_id"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NodeLinkStatus(enum.StrEnum):
|
||||||
|
ACTIVE = "ACTIVE"
|
||||||
|
STOPPING = "STOPPING"
|
||||||
|
STOPPED = "STOPPED"
|
||||||
|
DELETED = "DELETED"
|
||||||
|
|
||||||
|
|
||||||
|
node_link_table = Table(
|
||||||
|
"node_link",
|
||||||
|
metadata,
|
||||||
|
Column("id", UnsignedInt, autoincrement=True, primary_key=True, nullable=False),
|
||||||
|
Column("link_name", String(20), nullable=False),
|
||||||
|
Column("node_id", UnsignedInt, ForeignKey("ps_node.id"), nullable=False),
|
||||||
|
Column("next_node_id", UnsignedInt, ForeignKey("ps_node.id"), nullable=False),
|
||||||
|
Column("settings", JSON, default={}),
|
||||||
|
Column("creator_id", UnsignedInt, ForeignKey("account.id"), nullable=False),
|
||||||
|
Column("created_at", DateTime(timezone=True), server_default=func.now()),
|
||||||
|
Column("status", SQLAEnum(NodeLinkStatus), nullable=False),
|
||||||
|
Index("idx_node_id", "node_id"),
|
||||||
|
Index("idx_next_node_id", "next_node_id"),
|
||||||
|
)
|
@@ -4,17 +4,8 @@ from api.endpoints.account import api_router as account_router
|
|||||||
from api.endpoints.keyring import api_router as keyring_router
|
from api.endpoints.keyring import api_router as keyring_router
|
||||||
from api.endpoints.list_events import api_router as listevents_router
|
from api.endpoints.list_events import api_router as listevents_router
|
||||||
from api.endpoints.process_schema import api_router as processschema_router
|
from api.endpoints.process_schema import api_router as processschema_router
|
||||||
from api.endpoints.ps_node import api_router as ps_node_router
|
|
||||||
|
|
||||||
list_of_routes = [
|
list_of_routes = [auth_router, profile_router, account_router, keyring_router, listevents_router, processschema_router]
|
||||||
auth_router,
|
|
||||||
profile_router,
|
|
||||||
account_router,
|
|
||||||
keyring_router,
|
|
||||||
listevents_router,
|
|
||||||
processschema_router,
|
|
||||||
ps_node_router,
|
|
||||||
]
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"list_of_routes",
|
"list_of_routes",
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
from typing import List, Optional
|
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
from typing import Optional, List
|
||||||
from orm.tables.account import AccountStatus
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncConnection
|
from sqlalchemy.ext.asyncio import AsyncConnection
|
||||||
|
|
||||||
from api.db.connection.session import get_connection_dep
|
from api.db.connection.session import get_connection_dep
|
||||||
@@ -13,9 +12,10 @@ from api.db.logic.account import (
|
|||||||
update_user_by_id,
|
update_user_by_id,
|
||||||
)
|
)
|
||||||
from api.db.logic.keyring import create_password_key, update_password_key
|
from api.db.logic.keyring import create_password_key, update_password_key
|
||||||
|
from api.db.tables.account import AccountStatus
|
||||||
from api.schemas.account.account import User
|
from api.schemas.account.account import User
|
||||||
from api.schemas.base import bearer_schema
|
from api.schemas.base import bearer_schema
|
||||||
from api.schemas.endpoints.account import AllUserResponse, UserCreate, UserFilterDTO, UserUpdate
|
from api.schemas.endpoints.account import AllUserResponse, UserCreate, UserUpdate, UserFilterDTO
|
||||||
from api.services.auth import get_current_user
|
from api.services.auth import get_current_user
|
||||||
from api.services.user_role_validation import db_user_role_validation
|
from api.services.user_role_validation import db_user_role_validation
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ api_router = APIRouter(
|
|||||||
|
|
||||||
|
|
||||||
@api_router.get("", dependencies=[Depends(bearer_schema)], response_model=AllUserResponse)
|
@api_router.get("", dependencies=[Depends(bearer_schema)], response_model=AllUserResponse)
|
||||||
async def get_all_account_endpoint(
|
async def get_all_account(
|
||||||
page: int = Query(1, description="Page number", gt=0),
|
page: int = Query(1, description="Page number", gt=0),
|
||||||
limit: int = Query(10, description="КNumber of items per page", gt=0),
|
limit: int = Query(10, description="КNumber of items per page", gt=0),
|
||||||
search: Optional[str] = Query(None, description="Search term to filter by name or login or email"),
|
search: Optional[str] = Query(None, description="Search term to filter by name or login or email"),
|
||||||
@@ -62,7 +62,7 @@ async def get_all_account_endpoint(
|
|||||||
|
|
||||||
|
|
||||||
@api_router.get("/{user_id}", dependencies=[Depends(bearer_schema)], response_model=User)
|
@api_router.get("/{user_id}", dependencies=[Depends(bearer_schema)], response_model=User)
|
||||||
async def get_account_endpoint(
|
async def get_account(
|
||||||
user_id: int,
|
user_id: int,
|
||||||
connection: AsyncConnection = Depends(get_connection_dep),
|
connection: AsyncConnection = Depends(get_connection_dep),
|
||||||
current_user=Depends(get_current_user),
|
current_user=Depends(get_current_user),
|
||||||
@@ -78,7 +78,7 @@ async def get_account_endpoint(
|
|||||||
|
|
||||||
|
|
||||||
@api_router.post("", dependencies=[Depends(bearer_schema)], response_model=User)
|
@api_router.post("", dependencies=[Depends(bearer_schema)], response_model=User)
|
||||||
async def create_account_endpoint(
|
async def create_account(
|
||||||
user: UserCreate,
|
user: UserCreate,
|
||||||
connection: AsyncConnection = Depends(get_connection_dep),
|
connection: AsyncConnection = Depends(get_connection_dep),
|
||||||
current_user=Depends(get_current_user),
|
current_user=Depends(get_current_user),
|
||||||
@@ -98,7 +98,7 @@ async def create_account_endpoint(
|
|||||||
|
|
||||||
|
|
||||||
@api_router.put("/{user_id}", dependencies=[Depends(bearer_schema)], response_model=UserUpdate)
|
@api_router.put("/{user_id}", dependencies=[Depends(bearer_schema)], response_model=UserUpdate)
|
||||||
async def update_account_endpoint(
|
async def update_account(
|
||||||
user_id: int,
|
user_id: int,
|
||||||
user_update: UserUpdate,
|
user_update: UserUpdate,
|
||||||
connection: AsyncConnection = Depends(get_connection_dep),
|
connection: AsyncConnection = Depends(get_connection_dep),
|
||||||
@@ -126,7 +126,7 @@ async def update_account_endpoint(
|
|||||||
|
|
||||||
|
|
||||||
@api_router.delete("/{user_id}", dependencies=[Depends(bearer_schema)], response_model=User)
|
@api_router.delete("/{user_id}", dependencies=[Depends(bearer_schema)], response_model=User)
|
||||||
async def delete_account_endpoint(
|
async def delete_account(
|
||||||
user_id: int,
|
user_id: int,
|
||||||
connection: AsyncConnection = Depends(get_connection_dep),
|
connection: AsyncConnection = Depends(get_connection_dep),
|
||||||
current_user=Depends(get_current_user),
|
current_user=Depends(get_current_user),
|
||||||
|
@@ -49,7 +49,7 @@ def get_config():
|
|||||||
|
|
||||||
|
|
||||||
@api_router.post("", response_model=Tokens)
|
@api_router.post("", response_model=Tokens)
|
||||||
async def login_for_access_token_endpoint(
|
async def login_for_access_token(
|
||||||
user: Auth,
|
user: Auth,
|
||||||
response: Response,
|
response: Response,
|
||||||
connection: AsyncConnection = Depends(get_connection_dep),
|
connection: AsyncConnection = Depends(get_connection_dep),
|
||||||
@@ -84,7 +84,7 @@ async def login_for_access_token_endpoint(
|
|||||||
|
|
||||||
|
|
||||||
@api_router.post("/refresh", response_model=Tokens)
|
@api_router.post("/refresh", response_model=Tokens)
|
||||||
async def refresh_endpoint(
|
async def refresh(
|
||||||
request: Request,
|
request: Request,
|
||||||
connection: AsyncConnection = Depends(get_connection_dep),
|
connection: AsyncConnection = Depends(get_connection_dep),
|
||||||
Authorize: AuthJWT = Depends(),
|
Authorize: AuthJWT = Depends(),
|
||||||
|
@@ -1,20 +1,31 @@
|
|||||||
from fastapi import (
|
from fastapi import (
|
||||||
APIRouter,
|
APIRouter,
|
||||||
|
Body,
|
||||||
Depends,
|
Depends,
|
||||||
|
Form,
|
||||||
HTTPException,
|
HTTPException,
|
||||||
|
Response,
|
||||||
status,
|
status,
|
||||||
)
|
)
|
||||||
from orm.tables.account import KeyStatus
|
|
||||||
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncConnection
|
from sqlalchemy.ext.asyncio import AsyncConnection
|
||||||
|
|
||||||
from api.db.connection.session import get_connection_dep
|
from api.db.connection.session import get_connection_dep
|
||||||
from api.db.logic.keyring import create_key, get_key_by_id, update_key_by_id
|
|
||||||
from api.schemas.account.account_keyring import AccountKeyring
|
from api.db.logic.keyring import get_key_by_id, create_key, update_key_by_id
|
||||||
|
|
||||||
|
|
||||||
|
from api.db.tables.account import KeyStatus
|
||||||
from api.schemas.base import bearer_schema
|
from api.schemas.base import bearer_schema
|
||||||
from api.schemas.endpoints.account_keyring import AccountKeyringUpdate
|
from api.schemas.endpoints.account_keyring import AccountKeyringUpdate
|
||||||
|
|
||||||
|
from api.schemas.account.account_keyring import AccountKeyring
|
||||||
from api.services.auth import get_current_user
|
from api.services.auth import get_current_user
|
||||||
|
|
||||||
from api.services.user_role_validation import db_user_role_validation
|
from api.services.user_role_validation import db_user_role_validation
|
||||||
|
|
||||||
|
|
||||||
api_router = APIRouter(
|
api_router = APIRouter(
|
||||||
prefix="/keyring",
|
prefix="/keyring",
|
||||||
tags=["User KeyringModel"],
|
tags=["User KeyringModel"],
|
||||||
@@ -22,7 +33,7 @@ api_router = APIRouter(
|
|||||||
|
|
||||||
|
|
||||||
@api_router.get("/{user_id}/{key_id}", dependencies=[Depends(bearer_schema)], response_model=AccountKeyring)
|
@api_router.get("/{user_id}/{key_id}", dependencies=[Depends(bearer_schema)], response_model=AccountKeyring)
|
||||||
async def get_keyring_endpoint(
|
async def get_keyring(
|
||||||
key_id: str, connection: AsyncConnection = Depends(get_connection_dep), current_user=Depends(get_current_user)
|
key_id: str, connection: AsyncConnection = Depends(get_connection_dep), current_user=Depends(get_current_user)
|
||||||
):
|
):
|
||||||
authorize_user = await db_user_role_validation(connection, current_user)
|
authorize_user = await db_user_role_validation(connection, current_user)
|
||||||
@@ -36,7 +47,7 @@ async def get_keyring_endpoint(
|
|||||||
|
|
||||||
|
|
||||||
@api_router.post("/{user_id}/{key_id}", dependencies=[Depends(bearer_schema)], response_model=AccountKeyring)
|
@api_router.post("/{user_id}/{key_id}", dependencies=[Depends(bearer_schema)], response_model=AccountKeyring)
|
||||||
async def create_keyring_endpoint(
|
async def create_keyring(
|
||||||
user_id: int,
|
user_id: int,
|
||||||
key_id: str,
|
key_id: str,
|
||||||
key: AccountKeyringUpdate,
|
key: AccountKeyringUpdate,
|
||||||
@@ -62,7 +73,7 @@ async def create_keyring_endpoint(
|
|||||||
|
|
||||||
|
|
||||||
@api_router.put("/{user_id}/{key_id}", dependencies=[Depends(bearer_schema)], response_model=AccountKeyring)
|
@api_router.put("/{user_id}/{key_id}", dependencies=[Depends(bearer_schema)], response_model=AccountKeyring)
|
||||||
async def update_keyring_endpoint(
|
async def update_keyring(
|
||||||
user_id: int,
|
user_id: int,
|
||||||
key_id: str,
|
key_id: str,
|
||||||
keyring_update: AccountKeyringUpdate,
|
keyring_update: AccountKeyringUpdate,
|
||||||
@@ -88,7 +99,7 @@ async def update_keyring_endpoint(
|
|||||||
|
|
||||||
|
|
||||||
@api_router.delete("/{user_id}/{key_id}", dependencies=[Depends(bearer_schema)], response_model=AccountKeyring)
|
@api_router.delete("/{user_id}/{key_id}", dependencies=[Depends(bearer_schema)], response_model=AccountKeyring)
|
||||||
async def delete_keyring_endpoint(
|
async def delete_keyring(
|
||||||
user_id: int,
|
user_id: int,
|
||||||
key_id: str,
|
key_id: str,
|
||||||
connection: AsyncConnection = Depends(get_connection_dep),
|
connection: AsyncConnection = Depends(get_connection_dep),
|
||||||
|
@@ -1,27 +1,37 @@
|
|||||||
from typing import List, Optional
|
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||||
|
|
||||||
|
from typing import Optional, List
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
|
||||||
from orm.tables.events import EventStatus
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncConnection
|
from sqlalchemy.ext.asyncio import AsyncConnection
|
||||||
|
|
||||||
from api.db.connection.session import get_connection_dep
|
from api.db.connection.session import get_connection_dep
|
||||||
|
|
||||||
from api.db.logic.account import get_user_by_login
|
from api.db.logic.account import get_user_by_login
|
||||||
|
|
||||||
from api.db.logic.list_events import (
|
from api.db.logic.list_events import (
|
||||||
create_list_events,
|
|
||||||
get_list_events_by_id,
|
|
||||||
get_list_events_by_name,
|
get_list_events_by_name,
|
||||||
get_list_events_page_DTO,
|
get_list_events_by_id,
|
||||||
|
create_list_events,
|
||||||
update_list_events_by_id,
|
update_list_events_by_id,
|
||||||
|
get_list_events_page_DTO,
|
||||||
)
|
)
|
||||||
from api.schemas.base import bearer_schema
|
|
||||||
from api.schemas.endpoints.list_events import AllListEventResponse, ListEventFilterDTO, ListEventUpdate
|
|
||||||
from api.schemas.events.list_events import ListEvent
|
from api.schemas.events.list_events import ListEvent
|
||||||
|
from api.db.tables.events import EventStatus
|
||||||
|
|
||||||
|
from api.schemas.base import bearer_schema
|
||||||
|
|
||||||
|
from api.schemas.endpoints.list_events import ListEventUpdate, AllListEventResponse, ListEventFilterDTO
|
||||||
|
|
||||||
from api.services.auth import get_current_user
|
from api.services.auth import get_current_user
|
||||||
|
|
||||||
from api.services.user_role_validation import (
|
from api.services.user_role_validation import (
|
||||||
db_user_role_validation_for_list_events_and_process_schema,
|
|
||||||
db_user_role_validation_for_list_events_and_process_schema_by_list_event_id,
|
db_user_role_validation_for_list_events_and_process_schema_by_list_event_id,
|
||||||
|
db_user_role_validation_for_list_events_and_process_schema,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
api_router = APIRouter(
|
api_router = APIRouter(
|
||||||
prefix="/list_events",
|
prefix="/list_events",
|
||||||
tags=["list events"],
|
tags=["list events"],
|
||||||
@@ -29,7 +39,7 @@ api_router = APIRouter(
|
|||||||
|
|
||||||
|
|
||||||
@api_router.get("", dependencies=[Depends(bearer_schema)], response_model=AllListEventResponse)
|
@api_router.get("", dependencies=[Depends(bearer_schema)], response_model=AllListEventResponse)
|
||||||
async def get_all_list_events_endpoint(
|
async def get_all_list_events(
|
||||||
page: int = Query(1, description="Page number", gt=0),
|
page: int = Query(1, description="Page number", gt=0),
|
||||||
limit: int = Query(10, description="Number of items per page", gt=0),
|
limit: int = Query(10, description="Number of items per page", gt=0),
|
||||||
search: Optional[str] = Query(None, description="Search term to filter by title or name"),
|
search: Optional[str] = Query(None, description="Search term to filter by title or name"),
|
||||||
@@ -72,7 +82,7 @@ async def get_all_list_events_endpoint(
|
|||||||
|
|
||||||
|
|
||||||
@api_router.get("/{list_events_id}", dependencies=[Depends(bearer_schema)], response_model=ListEvent)
|
@api_router.get("/{list_events_id}", dependencies=[Depends(bearer_schema)], response_model=ListEvent)
|
||||||
async def get_list_events_endpoint(
|
async def get_list_events(
|
||||||
list_events_id: int,
|
list_events_id: int,
|
||||||
connection: AsyncConnection = Depends(get_connection_dep),
|
connection: AsyncConnection = Depends(get_connection_dep),
|
||||||
current_user=Depends(get_current_user),
|
current_user=Depends(get_current_user),
|
||||||
@@ -93,7 +103,7 @@ async def get_list_events_endpoint(
|
|||||||
|
|
||||||
|
|
||||||
@api_router.post("", dependencies=[Depends(bearer_schema)], response_model=ListEvent)
|
@api_router.post("", dependencies=[Depends(bearer_schema)], response_model=ListEvent)
|
||||||
async def create_list_events_endpoint(
|
async def create_list_events(
|
||||||
list_events: ListEventUpdate,
|
list_events: ListEventUpdate,
|
||||||
connection: AsyncConnection = Depends(get_connection_dep),
|
connection: AsyncConnection = Depends(get_connection_dep),
|
||||||
current_user=Depends(get_current_user),
|
current_user=Depends(get_current_user),
|
||||||
@@ -141,7 +151,7 @@ async def update_list_events(
|
|||||||
|
|
||||||
|
|
||||||
@api_router.delete("/{list_events_id}", dependencies=[Depends(bearer_schema)], response_model=ListEvent)
|
@api_router.delete("/{list_events_id}", dependencies=[Depends(bearer_schema)], response_model=ListEvent)
|
||||||
async def delete_list_events_endpoint(
|
async def delete_list_events(
|
||||||
list_events_id: int,
|
list_events_id: int,
|
||||||
connection: AsyncConnection = Depends(get_connection_dep),
|
connection: AsyncConnection = Depends(get_connection_dep),
|
||||||
current_user=Depends(get_current_user),
|
current_user=Depends(get_current_user),
|
||||||
|
@@ -1,44 +1,36 @@
|
|||||||
from typing import List, Optional
|
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
from typing import Optional, List
|
||||||
from orm.tables.process import ProcessStatus
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncConnection
|
from sqlalchemy.ext.asyncio import AsyncConnection
|
||||||
|
|
||||||
from api.db.connection.session import get_connection_dep
|
from api.db.connection.session import get_connection_dep
|
||||||
|
|
||||||
from api.db.logic.account import get_user_by_login
|
from api.db.logic.account import get_user_by_login
|
||||||
|
|
||||||
from api.db.logic.process_schema import (
|
from api.db.logic.process_schema import (
|
||||||
|
get_process_schema_by_title,
|
||||||
create_process_schema,
|
create_process_schema,
|
||||||
get_process_schema_by_id,
|
get_process_schema_by_id,
|
||||||
get_process_schema_by_title,
|
|
||||||
get_process_schema_page_DTO,
|
|
||||||
update_process_schema_by_id,
|
update_process_schema_by_id,
|
||||||
|
get_process_schema_page_DTO,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from api.schemas.process.process_schema import ProcessSchema
|
||||||
|
|
||||||
|
from api.db.tables.process import ProcessStatus
|
||||||
|
|
||||||
from api.schemas.base import bearer_schema
|
from api.schemas.base import bearer_schema
|
||||||
from api.schemas.endpoints.process_schema import AllProcessSchemaResponse, ProcessSchemaFilterDTO, ProcessSchemaUpdate
|
|
||||||
from api.schemas.process.process_schema import ProcessSchema, ProcessSchemaSettingsNode, ProcessSchemaResponse
|
from api.schemas.endpoints.process_schema import ProcessSchemaUpdate, AllProcessSchemaResponse, ProcessSchemaFilterDTO
|
||||||
from api.schemas.process.ps_node import Ps_NodeFrontResponseNode
|
|
||||||
from api.schemas.process.ps_node import Ps_NodeFrontResponse
|
|
||||||
from api.services.auth import get_current_user
|
from api.services.auth import get_current_user
|
||||||
|
|
||||||
from api.services.user_role_validation import (
|
from api.services.user_role_validation import (
|
||||||
db_user_role_validation_for_list_events_and_process_schema,
|
|
||||||
db_user_role_validation_for_list_events_and_process_schema_by_list_event_id,
|
db_user_role_validation_for_list_events_and_process_schema_by_list_event_id,
|
||||||
|
db_user_role_validation_for_list_events_and_process_schema,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
from api.db.logic.ps_node import create_ps_node_schema
|
|
||||||
from api.db.logic.process_schema import update_process_schema_settings_by_id
|
|
||||||
|
|
||||||
from orm.tables.process import NodeType
|
|
||||||
|
|
||||||
from api.utils.to_camel_dict import to_camel_dict
|
|
||||||
|
|
||||||
from core import VorkNodeRegistry
|
|
||||||
|
|
||||||
from model_nodes import ListenNodeData
|
|
||||||
|
|
||||||
from api.utils.node_counter import increment_node_counter
|
|
||||||
|
|
||||||
|
|
||||||
api_router = APIRouter(
|
api_router = APIRouter(
|
||||||
prefix="/process_schema",
|
prefix="/process_schema",
|
||||||
tags=["process schema"],
|
tags=["process schema"],
|
||||||
@@ -46,7 +38,7 @@ api_router = APIRouter(
|
|||||||
|
|
||||||
|
|
||||||
@api_router.get("", dependencies=[Depends(bearer_schema)], response_model=AllProcessSchemaResponse)
|
@api_router.get("", dependencies=[Depends(bearer_schema)], response_model=AllProcessSchemaResponse)
|
||||||
async def get_all_process_schema_endpoint(
|
async def get_all_process_schema(
|
||||||
page: int = Query(1, description="Page number", gt=0),
|
page: int = Query(1, description="Page number", gt=0),
|
||||||
limit: int = Query(10, description="Number of items per page", gt=0),
|
limit: int = Query(10, description="Number of items per page", gt=0),
|
||||||
search: Optional[str] = Query(None, description="Search term to filter by title or description"),
|
search: Optional[str] = Query(None, description="Search term to filter by title or description"),
|
||||||
@@ -54,20 +46,12 @@ async def get_all_process_schema_endpoint(
|
|||||||
order_direction: Optional[str] = Query("asc", description="Sort direction (asc/desc)"),
|
order_direction: Optional[str] = Query("asc", description="Sort direction (asc/desc)"),
|
||||||
status_filter: Optional[List[str]] = Query(None, description="Filter by status"),
|
status_filter: Optional[List[str]] = Query(None, description="Filter by status"),
|
||||||
owner_id: Optional[List[str]] = Query(None, description="Filter by owner id"),
|
owner_id: Optional[List[str]] = Query(None, description="Filter by owner id"),
|
||||||
show_deleted: bool = Query(False, description="Show only deleted schemas"),
|
|
||||||
connection: AsyncConnection = Depends(get_connection_dep),
|
connection: AsyncConnection = Depends(get_connection_dep),
|
||||||
creator_id: Optional[int] = Query(None, description="Filter by creator id"),
|
creator_id: Optional[int] = Query(None, description="Filter by creator id"),
|
||||||
current_user=Depends(get_current_user),
|
current_user=Depends(get_current_user),
|
||||||
):
|
):
|
||||||
if show_deleted:
|
|
||||||
status_to_filter = ["DELETED"]
|
|
||||||
elif status_filter:
|
|
||||||
status_to_filter = status_filter
|
|
||||||
else:
|
|
||||||
status_to_filter = None
|
|
||||||
|
|
||||||
filters = {
|
filters = {
|
||||||
**({"status": status_to_filter} if status_to_filter else {}),
|
**({"status": status_filter} if status_filter else {}),
|
||||||
**({"owner_id": owner_id} if owner_id else {}),
|
**({"owner_id": owner_id} if owner_id else {}),
|
||||||
**({"creator_id": [str(creator_id)]} if creator_id else {}),
|
**({"creator_id": [str(creator_id)]} if creator_id else {}),
|
||||||
}
|
}
|
||||||
@@ -93,11 +77,11 @@ async def get_all_process_schema_endpoint(
|
|||||||
if process_schema_page is None:
|
if process_schema_page is None:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Process schema not found")
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Process schema not found")
|
||||||
|
|
||||||
return to_camel_dict(process_schema_page.model_dump())
|
return process_schema_page
|
||||||
|
|
||||||
|
|
||||||
@api_router.get("/{process_schema_id}", dependencies=[Depends(bearer_schema)], response_model=ProcessSchema)
|
@api_router.get("/{process_schema_id}", dependencies=[Depends(bearer_schema)], response_model=ProcessSchema)
|
||||||
async def get_process_schema_endpoint(
|
async def get_process_schema(
|
||||||
process_schema_id: int,
|
process_schema_id: int,
|
||||||
connection: AsyncConnection = Depends(get_connection_dep),
|
connection: AsyncConnection = Depends(get_connection_dep),
|
||||||
current_user=Depends(get_current_user),
|
current_user=Depends(get_current_user),
|
||||||
@@ -114,75 +98,31 @@ async def get_process_schema_endpoint(
|
|||||||
if process_schema_id is None:
|
if process_schema_id is None:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Process schema not found")
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Process schema not found")
|
||||||
|
|
||||||
return to_camel_dict(process_schema_validation.model_dump())
|
return process_schema_validation
|
||||||
|
|
||||||
|
|
||||||
@api_router.post("", dependencies=[Depends(bearer_schema)], response_model=ProcessSchemaResponse)
|
@api_router.post("", dependencies=[Depends(bearer_schema)], response_model=ProcessSchema)
|
||||||
async def create_processschema_endpoint(
|
async def create_processschema(
|
||||||
|
process_schema: ProcessSchemaUpdate,
|
||||||
connection: AsyncConnection = Depends(get_connection_dep),
|
connection: AsyncConnection = Depends(get_connection_dep),
|
||||||
current_user=Depends(get_current_user),
|
current_user=Depends(get_current_user),
|
||||||
):
|
):
|
||||||
user_validation = await get_user_by_login(connection, current_user)
|
user_validation = await get_user_by_login(connection, current_user)
|
||||||
|
process_schema_validation = await get_process_schema_by_title(connection, process_schema.title)
|
||||||
|
|
||||||
current_node_counter = increment_node_counter()
|
if process_schema_validation is None:
|
||||||
title = f"Новая схема {current_node_counter}"
|
await create_process_schema(connection, process_schema, user_validation.id)
|
||||||
|
process_schema_new = await get_process_schema_by_title(connection, process_schema.title)
|
||||||
|
return process_schema_new
|
||||||
|
|
||||||
description = "Default description"
|
else:
|
||||||
|
raise HTTPException(
|
||||||
await create_process_schema(connection, user_validation.id, title, description)
|
status_code=status.HTTP_400_BAD_REQUEST, detail="An process schema with this information already exists."
|
||||||
|
)
|
||||||
process_schema_new = await get_process_schema_by_title(connection, title)
|
|
||||||
|
|
||||||
start_node_data = ListenNodeData(ps_id=process_schema_new.id, node_type=NodeType.START.value, is_start="True")
|
|
||||||
|
|
||||||
start_node_links = {}
|
|
||||||
|
|
||||||
registery = VorkNodeRegistry()
|
|
||||||
|
|
||||||
vork_node = registery.get("LISTEN")
|
|
||||||
|
|
||||||
node_descriptor = vork_node.form()
|
|
||||||
|
|
||||||
start_node = vork_node(data=start_node_data.model_dump(), links=start_node_links)
|
|
||||||
|
|
||||||
validated_start_schema = start_node.validate()
|
|
||||||
|
|
||||||
print(validated_start_schema)
|
|
||||||
|
|
||||||
db_start_schema = await create_ps_node_schema(connection, validated_start_schema, user_validation.id)
|
|
||||||
|
|
||||||
node = ProcessSchemaSettingsNode(
|
|
||||||
id=db_start_schema.id,
|
|
||||||
node_type=NodeType.LISTEN.value,
|
|
||||||
data=validated_start_schema.data.model_dump(),
|
|
||||||
from_node=None,
|
|
||||||
links=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
settings_dict = {"node": node.model_dump(mode="json")}
|
|
||||||
|
|
||||||
await update_process_schema_settings_by_id(connection, process_schema_new.id, settings_dict)
|
|
||||||
|
|
||||||
process_schema_new = await get_process_schema_by_title(connection, title)
|
|
||||||
|
|
||||||
ps_node_front_response = Ps_NodeFrontResponse(
|
|
||||||
description=node_descriptor.model_dump(),
|
|
||||||
node=Ps_NodeFrontResponseNode(
|
|
||||||
id=db_start_schema.id, node_type=NodeType.LISTEN.value, data=validated_start_schema.data.model_dump()
|
|
||||||
),
|
|
||||||
link=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
response_data = {
|
|
||||||
"process_schema": process_schema_new.model_dump(),
|
|
||||||
"node_listen": ps_node_front_response.model_dump(),
|
|
||||||
}
|
|
||||||
|
|
||||||
return to_camel_dict(response_data)
|
|
||||||
|
|
||||||
|
|
||||||
@api_router.put("/{process_schema_id}", dependencies=[Depends(bearer_schema)], response_model=ProcessSchema)
|
@api_router.put("/{process_schema_id}", dependencies=[Depends(bearer_schema)], response_model=ProcessSchema)
|
||||||
async def update_process_schema_endpoint(
|
async def update_process_schema(
|
||||||
process_schema_id: int,
|
process_schema_id: int,
|
||||||
process_schema_update: ProcessSchemaUpdate,
|
process_schema_update: ProcessSchemaUpdate,
|
||||||
connection: AsyncConnection = Depends(get_connection_dep),
|
connection: AsyncConnection = Depends(get_connection_dep),
|
||||||
@@ -209,8 +149,8 @@ async def update_process_schema_endpoint(
|
|||||||
return process_schema
|
return process_schema
|
||||||
|
|
||||||
|
|
||||||
@api_router.delete("/{process_schema_id}", dependencies=[Depends(bearer_schema)], status_code=status.HTTP_200_OK)
|
@api_router.delete("/{process_schema_id}", dependencies=[Depends(bearer_schema)], response_model=ProcessSchema)
|
||||||
async def delete_process_schema_endpoint(
|
async def delete_process_schema(
|
||||||
process_schema_id: int,
|
process_schema_id: int,
|
||||||
connection: AsyncConnection = Depends(get_connection_dep),
|
connection: AsyncConnection = Depends(get_connection_dep),
|
||||||
current_user=Depends(get_current_user),
|
current_user=Depends(get_current_user),
|
||||||
@@ -233,6 +173,6 @@ async def delete_process_schema_endpoint(
|
|||||||
|
|
||||||
await update_process_schema_by_id(connection, updated_values, process_schema_validation)
|
await update_process_schema_by_id(connection, updated_values, process_schema_validation)
|
||||||
|
|
||||||
await get_process_schema_by_id(connection, process_schema_id)
|
process_schema = await get_process_schema_by_id(connection, process_schema_id)
|
||||||
|
|
||||||
return HTTPException(status_code=status.HTTP_200_OK, detail="Process schema deleted successfully")
|
return process_schema
|
||||||
|
@@ -4,15 +4,19 @@ from fastapi import (
|
|||||||
HTTPException,
|
HTTPException,
|
||||||
status,
|
status,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncConnection
|
from sqlalchemy.ext.asyncio import AsyncConnection
|
||||||
|
|
||||||
from api.db.connection.session import get_connection_dep
|
from api.db.connection.session import get_connection_dep
|
||||||
from api.db.logic.account import get_user_by_id, get_user_by_login, update_user_by_id
|
from api.db.logic.account import get_user_by_id, update_user_by_id, get_user_by_login
|
||||||
from api.schemas.account.account import User
|
|
||||||
from api.schemas.base import bearer_schema
|
from api.schemas.base import bearer_schema
|
||||||
from api.schemas.endpoints.account import UserUpdate
|
|
||||||
from api.services.auth import get_current_user
|
from api.services.auth import get_current_user
|
||||||
|
|
||||||
|
from api.schemas.endpoints.account import UserUpdate
|
||||||
|
from api.schemas.account.account import User
|
||||||
|
|
||||||
|
|
||||||
api_router = APIRouter(
|
api_router = APIRouter(
|
||||||
prefix="/profile",
|
prefix="/profile",
|
||||||
tags=["User accountModel"],
|
tags=["User accountModel"],
|
||||||
|
@@ -1,102 +0,0 @@
|
|||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
|
||||||
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncConnection
|
|
||||||
|
|
||||||
from api.db.connection.session import get_connection_dep
|
|
||||||
from api.db.logic.account import get_user_by_login
|
|
||||||
|
|
||||||
from api.schemas.base import bearer_schema
|
|
||||||
from api.schemas.process.process_schema import ProcessSchemaSettingsNodeLink, ProcessSchemaSettingsNode
|
|
||||||
from api.schemas.process.ps_node import Ps_NodeFrontResponseNode, Ps_NodeRequest
|
|
||||||
from api.schemas.process.ps_node import Ps_NodeFrontResponse
|
|
||||||
from api.services.auth import get_current_user
|
|
||||||
|
|
||||||
|
|
||||||
from api.db.logic.ps_node import create_ps_node_schema
|
|
||||||
from api.db.logic.node_link import get_last_link_name_by_node_id, create_node_link_schema
|
|
||||||
|
|
||||||
from api.db.logic.process_schema import update_process_schema_settings_by_id
|
|
||||||
|
|
||||||
from core import VorkNodeRegistry, VorkNodeLink
|
|
||||||
|
|
||||||
from model_nodes import VorkNodeLinkData
|
|
||||||
from api.utils.to_camel_dict import to_camel_dict
|
|
||||||
|
|
||||||
|
|
||||||
api_router = APIRouter(
|
|
||||||
prefix="/ps_node",
|
|
||||||
tags=["ps node"],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@api_router.post("", dependencies=[Depends(bearer_schema)], response_model=Ps_NodeFrontResponse)
|
|
||||||
async def create_ps_node_endpoint(
|
|
||||||
ps_node: Ps_NodeRequest,
|
|
||||||
connection: AsyncConnection = Depends(get_connection_dep),
|
|
||||||
current_user=Depends(get_current_user),
|
|
||||||
):
|
|
||||||
user_validation = await get_user_by_login(connection, current_user)
|
|
||||||
|
|
||||||
registery = VorkNodeRegistry()
|
|
||||||
|
|
||||||
vork_node = registery.get(ps_node.data["node_type"])
|
|
||||||
|
|
||||||
if vork_node is None:
|
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Node not found")
|
|
||||||
|
|
||||||
node_descriptor = vork_node.form()
|
|
||||||
try:
|
|
||||||
node_instance = vork_node(data=ps_node.data, links=ps_node.links)
|
|
||||||
|
|
||||||
node_instance_validated = node_instance.validate()
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
||||||
|
|
||||||
db_ps_node = await create_ps_node_schema(connection, node_instance_validated, user_validation.id)
|
|
||||||
link_name = await get_last_link_name_by_node_id(connection, db_ps_node.ps_id)
|
|
||||||
|
|
||||||
link_data = VorkNodeLinkData(
|
|
||||||
parent_port_number=node_instance_validated.parent_port_number,
|
|
||||||
to_id=db_ps_node.id,
|
|
||||||
from_id=node_instance_validated.parent_id,
|
|
||||||
last_link_name=link_name,
|
|
||||||
)
|
|
||||||
|
|
||||||
link = VorkNodeLink(data=link_data.model_dump())
|
|
||||||
|
|
||||||
validated_link = link.validate()
|
|
||||||
|
|
||||||
db_node_link = await create_node_link_schema(connection, validated_link, user_validation.id)
|
|
||||||
|
|
||||||
links_settings = ProcessSchemaSettingsNodeLink(
|
|
||||||
id=db_node_link.id,
|
|
||||||
link_name=db_node_link.link_name,
|
|
||||||
parent_port_number=db_node_link.link_point_id,
|
|
||||||
from_id=db_node_link.node_id,
|
|
||||||
to_id=db_node_link.next_node_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
node_settings = ProcessSchemaSettingsNode(
|
|
||||||
id=db_ps_node.id,
|
|
||||||
node_type=db_ps_node.node_type,
|
|
||||||
data=node_instance_validated.data.model_dump(),
|
|
||||||
from_node=None,
|
|
||||||
links=[{"links": links_settings.model_dump()}],
|
|
||||||
)
|
|
||||||
|
|
||||||
settings_dict = {"node": node_settings.model_dump(mode="json")}
|
|
||||||
|
|
||||||
await update_process_schema_settings_by_id(connection, db_ps_node.ps_id, settings_dict)
|
|
||||||
|
|
||||||
ps_node_front_response = Ps_NodeFrontResponse(
|
|
||||||
description=node_descriptor.model_dump(),
|
|
||||||
node=Ps_NodeFrontResponseNode(
|
|
||||||
id=db_ps_node.id,
|
|
||||||
node_type=db_ps_node.node_type,
|
|
||||||
data=to_camel_dict(node_instance_validated.data.model_dump()),
|
|
||||||
),
|
|
||||||
links=[{"links": links_settings.model_dump()}],
|
|
||||||
)
|
|
||||||
|
|
||||||
return ps_node_front_response
|
|
@@ -1,8 +1,8 @@
|
|||||||
|
import datetime
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from orm.tables.account import AccountRole, AccountStatus
|
|
||||||
from pydantic import EmailStr, Field
|
from pydantic import EmailStr, Field
|
||||||
|
from api.db.tables.account import AccountRole, AccountStatus
|
||||||
|
|
||||||
from api.schemas.base import Base
|
from api.schemas.base import Base
|
||||||
|
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
from datetime import datetime
|
import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from orm.tables.account import KeyStatus, KeyType
|
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
from datetime import datetime
|
||||||
|
from api.db.tables.account import KeyType, KeyStatus
|
||||||
|
|
||||||
from api.schemas.base import Base
|
from api.schemas.base import Base
|
||||||
|
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict, List, Optional
|
from typing import List, Optional, Dict
|
||||||
|
|
||||||
from orm.tables.account import AccountRole, AccountStatus
|
|
||||||
from pydantic import EmailStr, Field, TypeAdapter
|
from pydantic import EmailStr, Field, TypeAdapter
|
||||||
|
|
||||||
|
from api.db.tables.account import AccountRole, AccountStatus
|
||||||
from api.schemas.base import Base
|
from api.schemas.base import Base
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from orm.tables.account import KeyStatus, KeyType
|
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
from api.db.tables.account import KeyType, KeyStatus
|
||||||
from api.schemas.base import Base
|
from api.schemas.base import Base
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
from api.schemas.base import Base
|
from api.schemas.base import Base
|
||||||
|
|
||||||
|
|
||||||
# Таблица для получения информации из запроса
|
# Таблица для получения информации из запроса
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
from datetime import datetime
|
|
||||||
from typing import Any, Dict, List, Optional
|
|
||||||
|
|
||||||
from orm.tables.events import EventState, EventStatus
|
|
||||||
from pydantic import Field, TypeAdapter
|
from pydantic import Field, TypeAdapter
|
||||||
|
from typing import Optional, Dict, Any, List
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
from api.schemas.base import Base
|
from api.schemas.base import Base
|
||||||
|
from api.db.tables.events import EventState, EventStatus
|
||||||
|
|
||||||
|
|
||||||
class ListEventUpdate(Base):
|
class ListEventUpdate(Base):
|
||||||
|
@@ -1,16 +1,16 @@
|
|||||||
from datetime import datetime
|
|
||||||
from typing import Any, Dict, List, Optional
|
|
||||||
|
|
||||||
from orm.tables.process import ProcessStatus
|
|
||||||
from pydantic import Field, TypeAdapter
|
from pydantic import Field, TypeAdapter
|
||||||
|
from typing import Optional, Dict, Any, List
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
from api.schemas.base import Base
|
from api.schemas.base import Base
|
||||||
|
from api.db.tables.process import ProcessStatus
|
||||||
|
|
||||||
|
|
||||||
class ProcessSchemaUpdate(Base):
|
class ProcessSchemaUpdate(Base):
|
||||||
title: Optional[str] = Field(None, max_length=100)
|
title: Optional[str] = Field(None, max_length=100)
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
# owner_id: Optional[int] = None
|
owner_id: Optional[int] = None
|
||||||
settings: Optional[Dict[str, Any]] = None
|
settings: Optional[Dict[str, Any]] = None
|
||||||
status: Optional[ProcessStatus] = None
|
status: Optional[ProcessStatus] = None
|
||||||
|
|
||||||
|
@@ -1,10 +1,9 @@
|
|||||||
from datetime import datetime
|
|
||||||
from typing import Any, Dict
|
|
||||||
|
|
||||||
from orm.tables.events import EventState, EventStatus
|
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
from typing import Dict, Any
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from api.schemas.base import Base
|
from api.schemas.base import Base
|
||||||
|
from api.db.tables.events import EventState, EventStatus
|
||||||
|
|
||||||
|
|
||||||
class ListEvent(Base):
|
class ListEvent(Base):
|
||||||
|
@@ -1,17 +1,15 @@
|
|||||||
from datetime import datetime
|
|
||||||
from typing import Any, Dict
|
|
||||||
|
|
||||||
from orm.tables.process import NodeStatus
|
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
from typing import Dict, Any
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from api.schemas.base import Base
|
from api.schemas.base import Base
|
||||||
|
from api.db.tables.process import NodeStatus
|
||||||
|
|
||||||
|
|
||||||
class NodeLink(Base):
|
class MyModel(Base):
|
||||||
id: int
|
id: int
|
||||||
link_name: str = Field(..., max_length=20)
|
link_name: str = Field(..., max_length=20)
|
||||||
node_id: int
|
node_id: int
|
||||||
link_point_id: int
|
|
||||||
next_node_id: int
|
next_node_id: int
|
||||||
settings: Dict[str, Any]
|
settings: Dict[str, Any]
|
||||||
creator_id: int
|
creator_id: int
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
from datetime import datetime
|
|
||||||
from typing import Any, Dict, Optional, List
|
|
||||||
|
|
||||||
from orm.tables.process import ProcessStatus, NodeType
|
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
from typing import Dict, Any
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from api.schemas.base import Base
|
from api.schemas.base import Base
|
||||||
from api.schemas.process.ps_node import Ps_NodeFrontResponse
|
from api.db.tables.process import ProcessStatus
|
||||||
|
|
||||||
|
|
||||||
class ProcessSchema(Base):
|
class ProcessSchema(Base):
|
||||||
@@ -17,24 +15,3 @@ class ProcessSchema(Base):
|
|||||||
created_at: datetime
|
created_at: datetime
|
||||||
settings: Dict[str, Any]
|
settings: Dict[str, Any]
|
||||||
status: ProcessStatus
|
status: ProcessStatus
|
||||||
|
|
||||||
|
|
||||||
class ProcessSchemaSettingsNodeLink(Base):
|
|
||||||
id: int
|
|
||||||
link_name: str
|
|
||||||
parent_port_number: int
|
|
||||||
from_id: int
|
|
||||||
to_id: int
|
|
||||||
|
|
||||||
|
|
||||||
class ProcessSchemaSettingsNode(Base):
|
|
||||||
id: int
|
|
||||||
node_type: NodeType
|
|
||||||
from_node: Optional[Dict[str, Any]] = None
|
|
||||||
data: Dict[str, Any] # Переименовано с 'from' на 'from_node'
|
|
||||||
links: Optional[List[Dict[str, Any]]] = None
|
|
||||||
|
|
||||||
|
|
||||||
class ProcessSchemaResponse(Base):
|
|
||||||
process_schema: ProcessSchema
|
|
||||||
node_listen: Ps_NodeFrontResponse
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
|
from typing import Dict, Any
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any, Dict
|
|
||||||
|
|
||||||
from api.schemas.base import Base
|
from api.schemas.base import Base
|
||||||
|
|
||||||
|
@@ -1,14 +1,8 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any, Dict, Optional, List
|
from typing import Dict, Any
|
||||||
|
|
||||||
from orm.tables.process import NodeStatus, NodeType
|
|
||||||
|
|
||||||
from api.schemas.base import Base
|
from api.schemas.base import Base
|
||||||
|
from api.db.tables.process import NodeType, NodeStatus
|
||||||
|
|
||||||
class Ps_NodeRequest(Base):
|
|
||||||
data: Dict[str, Any]
|
|
||||||
links: Dict[str, Any]
|
|
||||||
|
|
||||||
|
|
||||||
class Ps_Node(Base):
|
class Ps_Node(Base):
|
||||||
@@ -16,26 +10,6 @@ class Ps_Node(Base):
|
|||||||
ps_id: int
|
ps_id: int
|
||||||
node_type: NodeType
|
node_type: NodeType
|
||||||
settings: dict
|
settings: dict
|
||||||
creator_id: int
|
creator_id: Dict[str, Any]
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
status: NodeStatus
|
status: NodeStatus
|
||||||
|
|
||||||
|
|
||||||
class Ps_NodeFrontResponseLink(Base):
|
|
||||||
id: int
|
|
||||||
link_name: str
|
|
||||||
parent_port_number: int
|
|
||||||
from_id: int
|
|
||||||
to_id: int
|
|
||||||
|
|
||||||
|
|
||||||
class Ps_NodeFrontResponseNode(Base):
|
|
||||||
id: int
|
|
||||||
node_type: NodeType
|
|
||||||
data: Dict[str, Any] # Переименовано с 'from' на 'from_node'
|
|
||||||
|
|
||||||
|
|
||||||
class Ps_NodeFrontResponse(Base):
|
|
||||||
description: Optional[Dict[str, Any]] = None
|
|
||||||
node: Optional[Ps_NodeFrontResponseNode] = None
|
|
||||||
links: Optional[List[Dict[str, Any]]] = None
|
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from fastapi import HTTPException, Request
|
from fastapi import HTTPException, Request
|
||||||
from orm.tables.account import AccountStatus
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncConnection
|
from sqlalchemy.ext.asyncio import AsyncConnection
|
||||||
|
|
||||||
from api.db.logic.auth import get_user
|
from api.db.logic.auth import get_user
|
||||||
|
from api.db.tables.account import AccountStatus
|
||||||
from api.schemas.endpoints.account import AllUser
|
from api.schemas.endpoints.account import AllUser
|
||||||
from api.utils.hasher import hasher
|
from api.utils.hasher import hasher
|
||||||
|
|
||||||
|
@@ -1,16 +1,16 @@
|
|||||||
import re
|
from fastapi_jwt_auth import AuthJWT
|
||||||
from re import escape
|
from starlette.middleware.base import BaseHTTPMiddleware
|
||||||
|
|
||||||
from fastapi import (
|
from fastapi import (
|
||||||
Request,
|
Request,
|
||||||
status,
|
status,
|
||||||
)
|
)
|
||||||
from fastapi.responses import JSONResponse
|
|
||||||
from fastapi_jwt_auth import AuthJWT
|
|
||||||
from starlette.middleware.base import BaseHTTPMiddleware
|
|
||||||
|
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
from api.config import get_settings
|
from api.config import get_settings
|
||||||
|
|
||||||
|
import re
|
||||||
|
from re import escape
|
||||||
|
|
||||||
|
|
||||||
class MiddlewareAccessTokenValidadtion(BaseHTTPMiddleware):
|
class MiddlewareAccessTokenValidadtion(BaseHTTPMiddleware):
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
|
@@ -2,9 +2,8 @@ from fastapi import (
|
|||||||
HTTPException,
|
HTTPException,
|
||||||
status,
|
status,
|
||||||
)
|
)
|
||||||
from orm.tables.account import AccountRole
|
|
||||||
|
|
||||||
from api.db.logic.account import get_user_by_login
|
from api.db.logic.account import get_user_by_login
|
||||||
|
from api.db.tables.account import AccountRole
|
||||||
|
|
||||||
|
|
||||||
async def db_user_role_validation(connection, current_user):
|
async def db_user_role_validation(connection, current_user):
|
||||||
|
@@ -1,9 +1,8 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from orm.tables.account import account_keyring_table, account_table, AccountRole, KeyStatus, KeyType
|
|
||||||
|
|
||||||
from api.db.connection.session import get_connection
|
from api.db.connection.session import get_connection
|
||||||
|
from api.db.tables.account import account_keyring_table, account_table, AccountRole, KeyStatus, KeyType
|
||||||
from api.utils.hasher import hasher
|
from api.utils.hasher import hasher
|
||||||
from api.utils.key_id_gen import KeyIdGenerator
|
from api.utils.key_id_gen import KeyIdGenerator
|
||||||
|
|
||||||
|
@@ -1,52 +0,0 @@
|
|||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict
|
|
||||||
|
|
||||||
|
|
||||||
# Путь к файлу счётчика (в корне проекта)
|
|
||||||
COUNTER_FILE_PATH = Path(__file__).parent.parent.parent / "node_counter.json"
|
|
||||||
|
|
||||||
|
|
||||||
def get_node_counter() -> int:
|
|
||||||
"""
|
|
||||||
Открывает JSON файл и возвращает значение node_counter.
|
|
||||||
Если файл не существует, создаёт его со значением по умолчанию 0.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: Текущее значение счётчика узлов
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not COUNTER_FILE_PATH.exists():
|
|
||||||
initial_data: Dict[str, int] = {"node_counter": 0}
|
|
||||||
with open(COUNTER_FILE_PATH, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(initial_data, f, indent=2, ensure_ascii=False)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(COUNTER_FILE_PATH, "r", encoding="utf-8") as f:
|
|
||||||
data = json.load(f)
|
|
||||||
return data.get("node_counter", 0)
|
|
||||||
except (json.JSONDecodeError, IOError):
|
|
||||||
initial_data = {"node_counter": 0}
|
|
||||||
with open(COUNTER_FILE_PATH, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(initial_data, f, indent=2, ensure_ascii=False)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def increment_node_counter() -> int:
|
|
||||||
"""
|
|
||||||
Увеличивает значение node_counter на 1, сохраняет в файл и возвращает новое значение.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: Новое значение счётчика (старое значение + 1)
|
|
||||||
"""
|
|
||||||
|
|
||||||
current_value = get_node_counter()
|
|
||||||
|
|
||||||
new_value = current_value + 1
|
|
||||||
|
|
||||||
data: Dict[str, int] = {"node_counter": new_value}
|
|
||||||
with open(COUNTER_FILE_PATH, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
||||||
|
|
||||||
return new_value
|
|
@@ -1,10 +0,0 @@
|
|||||||
from pydantic.alias_generators import to_camel
|
|
||||||
|
|
||||||
|
|
||||||
def to_camel_dict(obj):
|
|
||||||
if isinstance(obj, dict):
|
|
||||||
return {to_camel(key): to_camel_dict(value) for key, value in obj.items()}
|
|
||||||
elif isinstance(obj, list):
|
|
||||||
return [to_camel_dict(item) for item in obj]
|
|
||||||
else:
|
|
||||||
return obj
|
|
2121
api/poetry.lock
generated
2121
api/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -16,9 +16,7 @@ dependencies = [
|
|||||||
"cryptography (>=44.0.2,<45.0.0)",
|
"cryptography (>=44.0.2,<45.0.0)",
|
||||||
"pydantic[email] (>=2.11.3,<3.0.0)",
|
"pydantic[email] (>=2.11.3,<3.0.0)",
|
||||||
"python-multipart (>=0.0.20,<0.0.21)",
|
"python-multipart (>=0.0.20,<0.0.21)",
|
||||||
"requests (>=2.31.0,<3.0.0)",
|
|
||||||
"fastapi-jwt-auth @ git+https://github.com/vvpreo/fastapi-jwt-auth",
|
"fastapi-jwt-auth @ git+https://github.com/vvpreo/fastapi-jwt-auth",
|
||||||
"vork-core @ git+http://88.86.199.167:3000/Nox/CORE.git",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
234
client/package-lock.json
generated
234
client/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "client",
|
"name": "client",
|
||||||
"version": "0.0.3",
|
"version": "0.0.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "client",
|
"name": "client",
|
||||||
"version": "0.0.3",
|
"version": "0.0.5",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^5.6.1",
|
"@ant-design/icons": "^5.6.1",
|
||||||
"@testing-library/dom": "^10.4.0",
|
"@testing-library/dom": "^10.4.0",
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
"@types/jest": "^27.5.2",
|
"@types/jest": "^27.5.2",
|
||||||
"@types/react": "^19.0.11",
|
"@types/react": "^19.0.11",
|
||||||
"@types/react-dom": "^19.0.4",
|
"@types/react-dom": "^19.0.4",
|
||||||
|
"@xyflow/react": "^12.8.1",
|
||||||
"antd": "^5.24.7",
|
"antd": "^5.24.7",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"axios-retry": "^4.5.0",
|
"axios-retry": "^4.5.0",
|
||||||
@@ -1567,6 +1568,55 @@
|
|||||||
"@babel/types": "^7.20.7"
|
"@babel/types": "^7.20.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/d3-color": {
|
||||||
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
|
||||||
|
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-drag": {
|
||||||
|
"version": "3.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
|
||||||
|
"integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-selection": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-interpolate": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-color": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-selection": {
|
||||||
|
"version": "3.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz",
|
||||||
|
"integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-transition": {
|
||||||
|
"version": "3.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz",
|
||||||
|
"integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-selection": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-zoom": {
|
||||||
|
"version": "3.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
|
||||||
|
"integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-interpolate": "*",
|
||||||
|
"@types/d3-selection": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/estree": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||||
@@ -1633,6 +1683,66 @@
|
|||||||
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0"
|
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@xyflow/react": {
|
||||||
|
"version": "12.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.8.1.tgz",
|
||||||
|
"integrity": "sha512-t5Rame4Gc/540VcOZd28yFe9Xd8lyjKUX+VTiyb1x4ykNXZH5zyDmsu+lj9je2O/jGBVb0pj1Vjcxrxyn+Xk2g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@xyflow/system": "0.0.65",
|
||||||
|
"classcat": "^5.0.3",
|
||||||
|
"zustand": "^4.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=17",
|
||||||
|
"react-dom": ">=17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@xyflow/react/node_modules/zustand": {
|
||||||
|
"version": "4.5.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
|
||||||
|
"integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"use-sync-external-store": "^1.2.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.7.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": ">=16.8",
|
||||||
|
"immer": ">=9.0.6",
|
||||||
|
"react": ">=16.8"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"immer": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@xyflow/system": {
|
||||||
|
"version": "0.0.65",
|
||||||
|
"resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.65.tgz",
|
||||||
|
"integrity": "sha512-AliQPQeurQMoNlOdySnRoDQl9yDSA/1Lqi47Eo0m98lHcfrTdD9jK75H0tiGj+0qRC10SKNUXyMkT0KL0opg4g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-drag": "^3.0.7",
|
||||||
|
"@types/d3-interpolate": "^3.0.4",
|
||||||
|
"@types/d3-selection": "^3.0.10",
|
||||||
|
"@types/d3-transition": "^3.0.8",
|
||||||
|
"@types/d3-zoom": "^3.0.8",
|
||||||
|
"d3-drag": "^3.0.0",
|
||||||
|
"d3-interpolate": "^3.0.1",
|
||||||
|
"d3-selection": "^3.0.0",
|
||||||
|
"d3-zoom": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ansi-regex": {
|
"node_modules/ansi-regex": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
@@ -2160,6 +2270,12 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/classcat": {
|
||||||
|
"version": "5.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz",
|
||||||
|
"integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/classnames": {
|
"node_modules/classnames": {
|
||||||
"version": "2.5.1",
|
"version": "2.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
||||||
@@ -2340,6 +2456,111 @@
|
|||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/d3-color": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-dispatch": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-drag": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-dispatch": "1 - 3",
|
||||||
|
"d3-selection": "3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-ease": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-interpolate": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-color": "1 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-selection": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-timer": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-transition": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-color": "1 - 3",
|
||||||
|
"d3-dispatch": "1 - 3",
|
||||||
|
"d3-ease": "1 - 3",
|
||||||
|
"d3-interpolate": "1 - 3",
|
||||||
|
"d3-timer": "1 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"d3-selection": "2 - 3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-zoom": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-dispatch": "1 - 3",
|
||||||
|
"d3-drag": "2 - 3",
|
||||||
|
"d3-interpolate": "1 - 3",
|
||||||
|
"d3-selection": "2 - 3",
|
||||||
|
"d3-transition": "2 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dayjs": {
|
"node_modules/dayjs": {
|
||||||
"version": "1.11.13",
|
"version": "1.11.13",
|
||||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
|
||||||
@@ -5107,6 +5328,15 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/use-sync-external-store": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/util": {
|
"node_modules/util": {
|
||||||
"version": "0.12.5",
|
"version": "0.12.5",
|
||||||
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
|
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
|
||||||
|
@@ -11,6 +11,7 @@
|
|||||||
"@types/jest": "^27.5.2",
|
"@types/jest": "^27.5.2",
|
||||||
"@types/react": "^19.0.11",
|
"@types/react": "^19.0.11",
|
||||||
"@types/react-dom": "^19.0.4",
|
"@types/react-dom": "^19.0.4",
|
||||||
|
"@xyflow/react": "^12.8.1",
|
||||||
"antd": "^5.24.7",
|
"antd": "^5.24.7",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"axios-retry": "^4.5.0",
|
"axios-retry": "^4.5.0",
|
||||||
|
10
client/public/icons/node/calculate.svg
Normal file
10
client/public/icons/node/calculate.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<mask id="mask0_381_806" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="24">
|
||||||
|
<rect width="24" height="24" fill="#D9D9D9"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask0_381_806)">
|
||||||
|
<path d="M12 13.75H17V12.25H12V13.75ZM12 11.25H17V9.75H12V11.25ZM5 21C4.45 21 3.97917 20.8042 3.5875 20.4125C3.19583 20.0208 3 19.55 3 19V5C3 4.45 3.19583 3.97917 3.5875 3.5875C3.97917 3.19583 4.45 3 5 3H19C19.55 3 20.0208 3.19583 20.4125 3.5875C20.8042 3.97917 21 4.45 21 5V19C21 19.55 20.8042 20.0208 20.4125 20.4125C20.0208 20.8042 19.55 21 19 21H5ZM5 19H19V5H5V19Z" fill="#606060"/>
|
||||||
|
<path d="M9.75 14.0179H7.75V12.5179H9.75V14.0179Z" fill="#606060"/>
|
||||||
|
<path d="M9.75 11.5179H7.75V10.0179H9.75V11.5179Z" fill="#606060"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 828 B |
3
client/public/icons/node/home.svg
Normal file
3
client/public/icons/node/home.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="18" viewBox="0 0 16 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M2 16H5V10H11V16H14V7L8 2.5L2 7V16ZM0 18V6L8 0L16 6V18H9V12H7V18H0Z" fill="#606060"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 198 B |
8
client/public/icons/node/ifElse.svg
Normal file
8
client/public/icons/node/ifElse.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg width="28" height="31" viewBox="0 0 28 31" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0 28.6L1.4 30L3 28.425L4.575 30L6 28.6L4.4 27L6 25.425L4.575 24L3 25.6L1.4 24L0 25.425L1.575 27L0 28.6Z" fill="#606060"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M13 0C14.1 0 15.0419 0.391472 15.8252 1.1748C16.6085 1.95814 17 2.9 17 4C17 5.1 16.6085 6.04186 15.8252 6.8252C15.0419 7.60853 14.1 8 13 8C11.9 8 10.9581 7.60853 10.1748 6.8252C9.39147 6.04186 9 5.1 9 4C9 2.9 9.39147 1.95814 10.1748 1.1748C10.9581 0.391471 11.9 0 13 0ZM13 2C12.45 2 11.9796 2.19622 11.5879 2.58789C11.1962 2.97956 11 3.45 11 4C11 4.53333 11.1962 5.00039 11.5879 5.40039C11.9795 5.80013 12.4502 6 13 6C13.5498 6 14.0205 5.80013 14.4121 5.40039C14.8038 5.00039 15 4.53333 15 4C15 3.45 14.8038 2.97956 14.4121 2.58789C14.0204 2.19622 13.55 2 13 2Z" fill="#606060"/>
|
||||||
|
<path d="M11.8 9H13.8V14H11.8V9Z" fill="#606060"/>
|
||||||
|
<path d="M20 27.55L22.825 30.375L27.775 25.425L26.35 24L22.825 27.55L21.4 26.125L20 27.55Z" fill="#606060"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.167 18.417L12.75 24.834L7.25005 19.334H4.00005L4 23H2L2.00005 17.5H7.25005L12.75 12L19.167 18.417ZM8.9229 18.417L12.75 22.2441L16.5772 18.417L12.75 14.5898L8.9229 18.417Z" fill="#606060"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.5 19.334L22 19.334V23H24V17.5L17.5 17.5V19.334Z" fill="#606060"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
@@ -1,175 +0,0 @@
|
|||||||
import { Drawer } from 'antd';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { Avatar, Typography } from 'antd';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useUserSelector } from '@/store/userStore';
|
|
||||||
|
|
||||||
interface ContentDrawerProps {
|
|
||||||
open: boolean;
|
|
||||||
closeDrawer: () => void;
|
|
||||||
children: React.ReactNode;
|
|
||||||
type: 'create' | 'edit';
|
|
||||||
login?: string;
|
|
||||||
name?: string;
|
|
||||||
email?: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ContentDrawer({
|
|
||||||
open,
|
|
||||||
closeDrawer,
|
|
||||||
children,
|
|
||||||
type,
|
|
||||||
login,
|
|
||||||
name,
|
|
||||||
email,
|
|
||||||
}: ContentDrawerProps) {
|
|
||||||
const user = useUserSelector();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [width, setWidth] = useState<number | string>('30%');
|
|
||||||
|
|
||||||
const calculateWidths = () => {
|
|
||||||
const windowWidth = window.innerWidth;
|
|
||||||
const expanded = Math.max(windowWidth * 0.3, 300);
|
|
||||||
setWidth(expanded);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
calculateWidths();
|
|
||||||
window.addEventListener('resize', calculateWidths);
|
|
||||||
return () => window.removeEventListener('resize', calculateWidths);
|
|
||||||
}, []);
|
|
||||||
console.log(login, user?.login, login === user?.login);
|
|
||||||
|
|
||||||
const editDrawerTitle = (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
gap: 12,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
onClick={closeDrawer}
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
height: '24px',
|
|
||||||
width: '24px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="./icons/drawer/arrow_back.svg"
|
|
||||||
alt="close_drawer"
|
|
||||||
style={{ height: '16px', width: '16px' }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, flex: 1 }}>
|
|
||||||
<Avatar
|
|
||||||
src={
|
|
||||||
login ? `https://gamma.heado.ru/go/ava?name=${login}` : undefined
|
|
||||||
}
|
|
||||||
size={40}
|
|
||||||
style={{ flexShrink: 0 }}
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
<Typography.Text
|
|
||||||
strong
|
|
||||||
style={{ display: 'block', fontSize: '20px' }}
|
|
||||||
>
|
|
||||||
{name} {login === user?.login ? t('you') : ''}
|
|
||||||
</Typography.Text>
|
|
||||||
<Typography.Text type="secondary" style={{ fontSize: 14 }}>
|
|
||||||
{email}
|
|
||||||
</Typography.Text>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
height: '24px',
|
|
||||||
width: '24px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="./icons/drawer/delete.svg"
|
|
||||||
alt="delete"
|
|
||||||
style={{ height: '18px', width: '16px' }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const createDrawerTitle = (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
gap: 12,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
onClick={closeDrawer}
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
height: '24px',
|
|
||||||
width: '24px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="./icons/drawer/arrow_back.svg"
|
|
||||||
alt="close_drawer"
|
|
||||||
style={{ height: '16px', width: '16px' }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 12,
|
|
||||||
flex: 1,
|
|
||||||
fontSize: '20px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('newAccount')}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
height: '24px',
|
|
||||||
width: '24px',
|
|
||||||
}}
|
|
||||||
onClick={closeDrawer}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="./icons/drawer/delete.svg"
|
|
||||||
alt="delete"
|
|
||||||
style={{ height: '18px', width: '16px' }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Drawer
|
|
||||||
title={type === 'create' ? createDrawerTitle : editDrawerTitle}
|
|
||||||
placement="right"
|
|
||||||
open={open}
|
|
||||||
width={width}
|
|
||||||
destroyOnHidden={true}
|
|
||||||
closable={false}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Drawer>
|
|
||||||
);
|
|
||||||
}
|
|
@@ -2,8 +2,8 @@ import { useUserSelector } from '@/store/userStore';
|
|||||||
import { Avatar } from 'antd';
|
import { Avatar } from 'antd';
|
||||||
import Title from 'antd/es/typography/Title';
|
import Title from 'antd/es/typography/Title';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import ContentDrawer from './ContentDrawer';
|
import ContentDrawer from './drawers/ContentDrawer';
|
||||||
import UserEdit from './UserEdit';
|
import UserEdit from './drawers/users/UserEdit';
|
||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -68,7 +68,7 @@ export default function Header({ title, additionalContent }: HeaderProps) {
|
|||||||
email={user?.email}
|
email={user?.email}
|
||||||
open={openEdit}
|
open={openEdit}
|
||||||
closeDrawer={closeEditDrawer}
|
closeDrawer={closeEditDrawer}
|
||||||
type="edit"
|
type="userEdit"
|
||||||
>
|
>
|
||||||
{user?.id && <UserEdit closeDrawer={closeEditDrawer} userId={user?.id} />}
|
{user?.id && <UserEdit closeDrawer={closeEditDrawer} userId={user?.id} />}
|
||||||
</ContentDrawer>
|
</ContentDrawer>
|
||||||
|
281
client/src/components/ReactFlowDrawer.tsx
Normal file
281
client/src/components/ReactFlowDrawer.tsx
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
import {
|
||||||
|
ReactFlow,
|
||||||
|
Background,
|
||||||
|
Controls,
|
||||||
|
useNodesState,
|
||||||
|
useEdgesState,
|
||||||
|
Node,
|
||||||
|
Edge,
|
||||||
|
} from "@xyflow/react";
|
||||||
|
import { useMemo, useState } from "react";
|
||||||
|
import customEdgeStyle from "./edges/defaultEdgeStyle";
|
||||||
|
import { Dropdown, DropdownProps } from "antd";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { edgeTitleGenerator } from "@/utils/edge";
|
||||||
|
import IfElseNode from "./nodes/IfElseNode";
|
||||||
|
import AppropriationNode from "./nodes/AppropriationNode";
|
||||||
|
import StartNode from "./nodes/StartNode";
|
||||||
|
|
||||||
|
const initialNodes: Node[] = [
|
||||||
|
{
|
||||||
|
id: "1",
|
||||||
|
type: "startNode",
|
||||||
|
position: { x: 100, y: 0 },
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "2",
|
||||||
|
type: "ifElse",
|
||||||
|
position: { x: 100, y: 200 },
|
||||||
|
data: { condition: "B=2" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3",
|
||||||
|
type: "appropriation",
|
||||||
|
position: { x: 100, y: 400 },
|
||||||
|
data: { value: "Выбрать {{account.email}}" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "4",
|
||||||
|
type: "appropriation",
|
||||||
|
position: { x: 400, y: 400 },
|
||||||
|
data: { value: "Выбрать {{account.role}}" },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const initialEdges: Edge[] = [
|
||||||
|
// {
|
||||||
|
// id: "e1-3",
|
||||||
|
// source: "1",
|
||||||
|
// sourceHandle: "1",
|
||||||
|
// target: "3",
|
||||||
|
// targetHandle: null,
|
||||||
|
// label: "A1",
|
||||||
|
// ...customEdgeStyle,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// id: "e1-2",
|
||||||
|
// source: "1",
|
||||||
|
// sourceHandle: "2",
|
||||||
|
// target: "2",
|
||||||
|
// label: "B1",
|
||||||
|
// ...customEdgeStyle,
|
||||||
|
// },
|
||||||
|
];
|
||||||
|
|
||||||
|
interface ReactFlowDrawerProps {
|
||||||
|
showDrawer: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ReactFlowDrawer({ showDrawer }: ReactFlowDrawerProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||||
|
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||||
|
|
||||||
|
const [menuVisible, setMenuVisible] = useState(false);
|
||||||
|
const [selectedHandleId, setSelectedHandleId] = useState<string | null>(null);
|
||||||
|
const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 });
|
||||||
|
|
||||||
|
const handleOpenChange: DropdownProps["onOpenChange"] = (nextOpen, info) => {
|
||||||
|
if (info.source === "trigger" || nextOpen) {
|
||||||
|
setMenuVisible(nextOpen);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClick = (e: React.MouseEvent, node: Node) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const target = e.target as HTMLElement;
|
||||||
|
const handleElement = target.closest(".react-flow__handle") as HTMLElement;
|
||||||
|
|
||||||
|
if (!handleElement) return;
|
||||||
|
|
||||||
|
const handleId = handleElement.getAttribute("data-handleid");
|
||||||
|
if (!handleId) return;
|
||||||
|
|
||||||
|
const handlePos = handleElement.getAttribute("data-handlepos");
|
||||||
|
if (handlePos === "top") return;
|
||||||
|
|
||||||
|
setSelectedHandleId(`${node.id}-${handleId}`);
|
||||||
|
|
||||||
|
const flowWrapper = document.querySelector(".react-flow") as HTMLElement;
|
||||||
|
if (flowWrapper) {
|
||||||
|
const wrapperRect = flowWrapper.getBoundingClientRect();
|
||||||
|
const handleRect = handleElement.getBoundingClientRect();
|
||||||
|
setMenuPosition({
|
||||||
|
x: handleRect.right - wrapperRect.left,
|
||||||
|
y: handleRect.top - wrapperRect.top + 20,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setMenuVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMenuItemClick = (targetNodeId: string) => {
|
||||||
|
if (!selectedHandleId) return;
|
||||||
|
|
||||||
|
const [sourceNodeId, sourceHandleId] = selectedHandleId.split("-");
|
||||||
|
|
||||||
|
const label = edgeTitleGenerator(edges.length + 1);
|
||||||
|
|
||||||
|
const newEdge: Edge = {
|
||||||
|
id: `e${sourceNodeId}-${sourceHandleId}-${targetNodeId}:${label}`,
|
||||||
|
source: sourceNodeId,
|
||||||
|
sourceHandle: sourceHandleId,
|
||||||
|
target: targetNodeId,
|
||||||
|
label,
|
||||||
|
...customEdgeStyle,
|
||||||
|
};
|
||||||
|
|
||||||
|
setEdges((eds) => [...eds, newEdge]);
|
||||||
|
setMenuVisible(false);
|
||||||
|
setSelectedHandleId(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreateNode = (type: string) => {
|
||||||
|
if (!selectedHandleId) return;
|
||||||
|
const [sourceNodeId, sourceHandleId] = selectedHandleId.split("-");
|
||||||
|
const sourceNode = nodes.find((n) => n.id === sourceNodeId);
|
||||||
|
const newId = (
|
||||||
|
Math.max(...nodes.map((n) => Number(n.id) || 0)) + 1
|
||||||
|
).toString();
|
||||||
|
let newNode;
|
||||||
|
if (type === "ifElse") {
|
||||||
|
newNode = {
|
||||||
|
id: newId,
|
||||||
|
type: "ifElse",
|
||||||
|
position: {
|
||||||
|
x: (sourceNode?.position.x || 0) + 200,
|
||||||
|
y: (sourceNode?.position.y || 0) + 100,
|
||||||
|
},
|
||||||
|
data: { condition: "" },
|
||||||
|
};
|
||||||
|
} else if (type === "appropriation") {
|
||||||
|
newNode = {
|
||||||
|
id: newId,
|
||||||
|
type: "appropriation",
|
||||||
|
position: {
|
||||||
|
x: (sourceNode?.position.x || 0) + 200,
|
||||||
|
y: (sourceNode?.position.y || 0) + 100,
|
||||||
|
},
|
||||||
|
data: { value: "" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (newNode) {
|
||||||
|
setNodes((nds) => [...nds, newNode]);
|
||||||
|
const label = edgeTitleGenerator(edges.length + 1);
|
||||||
|
const newEdge = {
|
||||||
|
id: `e${sourceNodeId}-${sourceHandleId}-${newId}:${label}`,
|
||||||
|
source: sourceNodeId,
|
||||||
|
sourceHandle: sourceHandleId,
|
||||||
|
target: newId,
|
||||||
|
label,
|
||||||
|
...customEdgeStyle,
|
||||||
|
};
|
||||||
|
setEdges((eds) => [...eds, newEdge]);
|
||||||
|
setMenuVisible(false);
|
||||||
|
setSelectedHandleId(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const newNodeTypes = [
|
||||||
|
{ key: "ifElse", label: t("ifElseNode") },
|
||||||
|
{
|
||||||
|
key: "appropriation",
|
||||||
|
label: t("appropriationNode"),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const existingNodes = nodes
|
||||||
|
.filter((node) => node.id !== selectedHandleId?.split("-")[0])
|
||||||
|
.filter((node) => {
|
||||||
|
if (!selectedHandleId || node.type === "startNode") return false;
|
||||||
|
const [sourceNodeId, sourceHandleId] = selectedHandleId.split("-");
|
||||||
|
return !edges.some(
|
||||||
|
(edge) =>
|
||||||
|
edge.source === sourceNodeId &&
|
||||||
|
edge.sourceHandle === sourceHandleId &&
|
||||||
|
edge.target === node.id
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const menuItems = [
|
||||||
|
{
|
||||||
|
key: "connectToExisting",
|
||||||
|
label: t("connectToExisting"),
|
||||||
|
children: existingNodes.map((node) => ({
|
||||||
|
key: node.id,
|
||||||
|
label: t("connectTo", { nodeId: node.id }),
|
||||||
|
onClick: () => handleMenuItemClick(node.id),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
...newNodeTypes.map((nt) => ({
|
||||||
|
key: `createNew-${nt.key}`,
|
||||||
|
label: nt.label,
|
||||||
|
onClick: () => handleCreateNode(nt.key),
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
|
||||||
|
const nodeTypes = useMemo(
|
||||||
|
() => ({
|
||||||
|
startNode: (props: any) => <StartNode {...props} edges={edges} />,
|
||||||
|
ifElse: (props: any) => <IfElseNode {...props} edges={edges} />,
|
||||||
|
appropriation: (props: any) => (
|
||||||
|
<AppropriationNode {...props} edges={edges} />
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
[edges]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ReactFlow
|
||||||
|
nodes={nodes}
|
||||||
|
edges={edges}
|
||||||
|
nodesDraggable={false}
|
||||||
|
elementsSelectable={false}
|
||||||
|
nodesConnectable={false}
|
||||||
|
onNodeClick={(event, node) => {
|
||||||
|
const target = event.target as HTMLElement;
|
||||||
|
|
||||||
|
if (!target.closest(".react-flow__handle")) {
|
||||||
|
console.log("node clicked");
|
||||||
|
showDrawer();
|
||||||
|
} else {
|
||||||
|
handleClick(event, node);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
nodeTypes={nodeTypes}
|
||||||
|
fitView
|
||||||
|
minZoom={0.5}
|
||||||
|
maxZoom={1.0}
|
||||||
|
>
|
||||||
|
<Background color="#F2F2F2" />
|
||||||
|
<Controls
|
||||||
|
position="bottom-center"
|
||||||
|
orientation="horizontal"
|
||||||
|
showInteractive={false}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{menuVisible && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
left: `${menuPosition.x}px`,
|
||||||
|
top: `${menuPosition.y}px`,
|
||||||
|
zIndex: 9999,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Dropdown
|
||||||
|
menu={{ items: menuItems }}
|
||||||
|
open={menuVisible}
|
||||||
|
onOpenChange={handleOpenChange}
|
||||||
|
placement="bottom"
|
||||||
|
>
|
||||||
|
<div style={{ width: 1, height: 1 }} />
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</ReactFlow>
|
||||||
|
);
|
||||||
|
}
|
245
client/src/components/drawers/ContentDrawer.tsx
Normal file
245
client/src/components/drawers/ContentDrawer.tsx
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
import { Drawer } from "antd";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Avatar, Typography } from "antd";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useUserSelector } from "@/store/userStore";
|
||||||
|
|
||||||
|
interface ContentDrawerProps {
|
||||||
|
open: boolean;
|
||||||
|
closeDrawer: () => void;
|
||||||
|
children: React.ReactNode;
|
||||||
|
type: "userCreate" | "userEdit" | "nodeEdit";
|
||||||
|
login?: string;
|
||||||
|
name?: string;
|
||||||
|
email?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ContentDrawer({
|
||||||
|
open,
|
||||||
|
closeDrawer,
|
||||||
|
children,
|
||||||
|
type,
|
||||||
|
login,
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
}: ContentDrawerProps) {
|
||||||
|
const user = useUserSelector();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [width, setWidth] = useState<number | string>("30%");
|
||||||
|
|
||||||
|
const calculateWidths = () => {
|
||||||
|
const windowWidth = window.innerWidth;
|
||||||
|
const expanded = Math.max(windowWidth * 0.3, 300);
|
||||||
|
setWidth(expanded);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
calculateWidths();
|
||||||
|
window.addEventListener("resize", calculateWidths);
|
||||||
|
return () => window.removeEventListener("resize", calculateWidths);
|
||||||
|
}, []);
|
||||||
|
console.log(login, user?.login, login === user?.login);
|
||||||
|
|
||||||
|
const userEditDrawerTitle = (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
gap: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
onClick={closeDrawer}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "24px",
|
||||||
|
width: "24px",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="./icons/drawer/arrow_back.svg"
|
||||||
|
alt="close_drawer"
|
||||||
|
style={{ height: "16px", width: "16px" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: "flex", alignItems: "center", gap: 12, flex: 1 }}>
|
||||||
|
<Avatar
|
||||||
|
src={
|
||||||
|
login ? `https://gamma.heado.ru/go/ava?name=${login}` : undefined
|
||||||
|
}
|
||||||
|
size={40}
|
||||||
|
style={{ flexShrink: 0 }}
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<Typography.Text
|
||||||
|
strong
|
||||||
|
style={{ display: "block", fontSize: "20px" }}
|
||||||
|
>
|
||||||
|
{name} {login === user?.login ? t("you") : ""}
|
||||||
|
</Typography.Text>
|
||||||
|
<Typography.Text type="secondary" style={{ fontSize: 14 }}>
|
||||||
|
{email}
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "24px",
|
||||||
|
width: "24px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="./icons/drawer/delete.svg"
|
||||||
|
alt="delete"
|
||||||
|
style={{ height: "18px", width: "16px" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const userCreateDrawerTitle = (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
gap: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
onClick={closeDrawer}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "24px",
|
||||||
|
width: "24px",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="./icons/drawer/arrow_back.svg"
|
||||||
|
alt="close_drawer"
|
||||||
|
style={{ height: "16px", width: "16px" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 12,
|
||||||
|
flex: 1,
|
||||||
|
fontSize: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("newAccount")}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "24px",
|
||||||
|
width: "24px",
|
||||||
|
}}
|
||||||
|
onClick={closeDrawer}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="./icons/drawer/delete.svg"
|
||||||
|
alt="delete"
|
||||||
|
style={{ height: "18px", width: "16px" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const nodeEditDrawerTitle = (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
gap: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
onClick={closeDrawer}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "24px",
|
||||||
|
width: "24px",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="./icons/drawer/arrow_back.svg"
|
||||||
|
alt="close_drawer"
|
||||||
|
style={{ height: "16px", width: "16px" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 12,
|
||||||
|
flex: 1,
|
||||||
|
fontSize: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Редактирование блока
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "24px",
|
||||||
|
width: "24px",
|
||||||
|
}}
|
||||||
|
onClick={closeDrawer}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="./icons/drawer/delete.svg"
|
||||||
|
alt="delete"
|
||||||
|
style={{ height: "18px", width: "16px" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
// title={
|
||||||
|
// type === "userCreate" ? userCreateDrawerTitle : userEditDrawerTitle
|
||||||
|
// }
|
||||||
|
title={(() => {
|
||||||
|
switch (type) {
|
||||||
|
case "userCreate":
|
||||||
|
return userCreateDrawerTitle;
|
||||||
|
case "userEdit":
|
||||||
|
return userEditDrawerTitle;
|
||||||
|
case "nodeEdit":
|
||||||
|
return nodeEditDrawerTitle;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
placement="right"
|
||||||
|
open={open}
|
||||||
|
width={width}
|
||||||
|
destroyOnHidden={true}
|
||||||
|
closable={false}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
}
|
213
client/src/components/drawers/DrawerTitles.tsx
Normal file
213
client/src/components/drawers/DrawerTitles.tsx
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
import { User } from "@/types/user";
|
||||||
|
import { Avatar, Typography } from "antd";
|
||||||
|
import { TFunction } from "i18next";
|
||||||
|
|
||||||
|
interface DrawerTitleProps {
|
||||||
|
closeDrawer: () => void;
|
||||||
|
t: TFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserEditDrawerTitleProps extends DrawerTitleProps {
|
||||||
|
login?: string;
|
||||||
|
name?: string;
|
||||||
|
email?: string | null;
|
||||||
|
user: User | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserCreateDrawerTitleProps extends DrawerTitleProps {}
|
||||||
|
|
||||||
|
interface NodeEditDrawerTitleProps extends DrawerTitleProps {}
|
||||||
|
|
||||||
|
const UserEditDrawerTitle = ({
|
||||||
|
closeDrawer,
|
||||||
|
login,
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
user,
|
||||||
|
t,
|
||||||
|
}: UserEditDrawerTitleProps) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
gap: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
onClick={closeDrawer}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "24px",
|
||||||
|
width: "24px",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="./icons/drawer/arrow_back.svg"
|
||||||
|
alt="close_drawer"
|
||||||
|
style={{ height: "16px", width: "16px" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: "flex", alignItems: "center", gap: 12, flex: 1 }}>
|
||||||
|
<Avatar
|
||||||
|
src={
|
||||||
|
login ? `https://gamma.heado.ru/go/ava?name=${login}` : undefined
|
||||||
|
}
|
||||||
|
size={40}
|
||||||
|
style={{ flexShrink: 0 }}
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<Typography.Text
|
||||||
|
strong
|
||||||
|
style={{ display: "block", fontSize: "20px" }}
|
||||||
|
>
|
||||||
|
{name} {login === user?.login ? t("you") : ""}
|
||||||
|
</Typography.Text>
|
||||||
|
<Typography.Text type="secondary" style={{ fontSize: 14 }}>
|
||||||
|
{email}
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "24px",
|
||||||
|
width: "24px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="./icons/drawer/delete.svg"
|
||||||
|
alt="delete"
|
||||||
|
style={{ height: "18px", width: "16px" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const UserCreateDrawerTitle = ({
|
||||||
|
closeDrawer,
|
||||||
|
t,
|
||||||
|
}: UserCreateDrawerTitleProps) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
gap: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
onClick={closeDrawer}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "24px",
|
||||||
|
width: "24px",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="./icons/drawer/arrow_back.svg"
|
||||||
|
alt="close_drawer"
|
||||||
|
style={{ height: "16px", width: "16px" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "12px",
|
||||||
|
flex: 1,
|
||||||
|
fontSize: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("newAccount")}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "24px",
|
||||||
|
width: "24px",
|
||||||
|
}}
|
||||||
|
onClick={closeDrawer}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="./icons/drawer/delete.svg"
|
||||||
|
alt="delete"
|
||||||
|
style={{ height: "18px", width: "16px" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const NodeEditDrawerTitle = ({ closeDrawer, t }: NodeEditDrawerTitleProps) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
gap: "12px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
onClick={closeDrawer}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "24px",
|
||||||
|
width: "24px",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="./icons/drawer/arrow_back.svg"
|
||||||
|
alt="close_drawer"
|
||||||
|
style={{ height: "16px", width: "16px" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "12px",
|
||||||
|
flex: 1,
|
||||||
|
fontSize: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("editNode")}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "24px",
|
||||||
|
width: "24px",
|
||||||
|
}}
|
||||||
|
onClick={closeDrawer}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="./icons/drawer/delete.svg"
|
||||||
|
alt="delete"
|
||||||
|
style={{ height: "18px", width: "16px" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { UserEditDrawerTitle, UserCreateDrawerTitle, NodeEditDrawerTitle };
|
17
client/src/components/edges/defaultEdgeStyle.tsx
Normal file
17
client/src/components/edges/defaultEdgeStyle.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { MarkerType } from "@xyflow/react";
|
||||||
|
|
||||||
|
const customEdgeStyle = {
|
||||||
|
markerEnd: {
|
||||||
|
type: MarkerType.Arrow,
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
color: "#BDBDBD",
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
strokeWidth: 2,
|
||||||
|
stroke: "#BDBDBD",
|
||||||
|
},
|
||||||
|
type: "step",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default customEdgeStyle;
|
63
client/src/components/nodes/AppropriationNode.tsx
Normal file
63
client/src/components/nodes/AppropriationNode.tsx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { Handle, Node, NodeProps, Position, Edge } from "@xyflow/react";
|
||||||
|
import { HANDLE_STYLE_CONNECTED } from "./defaultHandleStyle";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
type AppropriationNodeData = { value: string; edges?: Edge[] };
|
||||||
|
|
||||||
|
export default function AppropriationNode({
|
||||||
|
data,
|
||||||
|
id,
|
||||||
|
edges = [],
|
||||||
|
}: NodeProps<Node & AppropriationNodeData> & { edges?: Edge[] }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
border: "0px solid",
|
||||||
|
borderRadius: 8,
|
||||||
|
backgroundColor: "white",
|
||||||
|
width: "248px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
paddingLeft: "12px",
|
||||||
|
height: "48px",
|
||||||
|
fontWeight: "600px",
|
||||||
|
fontSize: "16px",
|
||||||
|
gap: "12px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
style={{ height: "24px", width: "24px" }}
|
||||||
|
src="/icons/node/calculate.svg"
|
||||||
|
alt="appropriation logo"
|
||||||
|
/>
|
||||||
|
{t("appropriationNode")}
|
||||||
|
</div>
|
||||||
|
<div style={{ height: "1px", backgroundColor: "#E2E2E2" }}></div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: "12px",
|
||||||
|
fontSize: "14px",
|
||||||
|
minHeight: "48px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{data.value as string}
|
||||||
|
</div>
|
||||||
|
<Handle
|
||||||
|
style={{
|
||||||
|
...HANDLE_STYLE_CONNECTED,
|
||||||
|
}}
|
||||||
|
type="target"
|
||||||
|
position={Position.Top}
|
||||||
|
id="input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
119
client/src/components/nodes/IfElseNode.tsx
Normal file
119
client/src/components/nodes/IfElseNode.tsx
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import { Handle, NodeProps, Position, Node, Edge } from "@xyflow/react";
|
||||||
|
import {
|
||||||
|
HANDLE_STYLE_CONNECTED,
|
||||||
|
HANDLE_STYLE_CONNECTED_V,
|
||||||
|
HANDLE_STYLE_DISCONNECTED,
|
||||||
|
HANDLE_STYLE_DISCONNECTED_V,
|
||||||
|
} from "./defaultHandleStyle";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
interface IfElseNodeProps extends Node {
|
||||||
|
condition: string;
|
||||||
|
edges?: Edge[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function IfElseNode({
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
edges = [],
|
||||||
|
}: NodeProps<IfElseNodeProps> & { edges?: Edge[] }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const isHandle1Connected = edges.some(
|
||||||
|
(e: Edge) => e.source === id && e.sourceHandle === "1"
|
||||||
|
);
|
||||||
|
const isHandle2Connected = edges.some(
|
||||||
|
(e: Edge) => e.source === id && e.sourceHandle === "2"
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
border: "0px solid",
|
||||||
|
borderRadius: 8,
|
||||||
|
backgroundColor: "white",
|
||||||
|
width: "248px",
|
||||||
|
minHeight: "144px",
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
paddingLeft: "12px",
|
||||||
|
height: "48px",
|
||||||
|
fontWeight: "600px",
|
||||||
|
fontSize: "16px",
|
||||||
|
gap: "12px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
style={{ height: "24px", width: "24px" }}
|
||||||
|
src="/icons/node/ifElse.svg"
|
||||||
|
alt="if else logo"
|
||||||
|
/>
|
||||||
|
{t("ifElseNode")}
|
||||||
|
</div>
|
||||||
|
<div style={{ height: "1px", backgroundColor: "#E2E2E2" }}></div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: "12px",
|
||||||
|
fontSize: "14px",
|
||||||
|
minHeight: "48px",
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("conditionIf", { condition: data.condition })}
|
||||||
|
<Handle
|
||||||
|
type="source"
|
||||||
|
position={Position.Right}
|
||||||
|
id="1"
|
||||||
|
style={{
|
||||||
|
...(isHandle1Connected
|
||||||
|
? { ...HANDLE_STYLE_CONNECTED_V }
|
||||||
|
: { ...HANDLE_STYLE_DISCONNECTED_V }),
|
||||||
|
position: "absolute",
|
||||||
|
top: "50%",
|
||||||
|
transform: "translateY(-50%)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ height: "1px", backgroundColor: "#E2E2E2" }}></div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
paddingLeft: "12px",
|
||||||
|
fontSize: "14px",
|
||||||
|
height: "48px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("conditionElse")}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Handle
|
||||||
|
type="target"
|
||||||
|
position={Position.Top}
|
||||||
|
id="input"
|
||||||
|
style={{ ...HANDLE_STYLE_CONNECTED }}
|
||||||
|
/>
|
||||||
|
<Handle
|
||||||
|
type="source"
|
||||||
|
position={Position.Bottom}
|
||||||
|
id="2"
|
||||||
|
style={
|
||||||
|
isHandle2Connected
|
||||||
|
? { ...HANDLE_STYLE_CONNECTED }
|
||||||
|
: { ...HANDLE_STYLE_DISCONNECTED }
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
83
client/src/components/nodes/StartNode.tsx
Normal file
83
client/src/components/nodes/StartNode.tsx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import { Edge, Handle, Node, NodeProps, Position } from "@xyflow/react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import {
|
||||||
|
HANDLE_STYLE_CONNECTED,
|
||||||
|
HANDLE_STYLE_DISCONNECTED,
|
||||||
|
} from "./defaultHandleStyle";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
interface StartNodeProps extends Node {
|
||||||
|
edges?: Edge[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function StartNode({
|
||||||
|
id,
|
||||||
|
edges = [],
|
||||||
|
}: NodeProps<StartNodeProps> & { edges?: Edge[] }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const isHandleConnected = edges.some(
|
||||||
|
(e: Edge) => e.source === id && e.sourceHandle === "1"
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
border: "0px solid",
|
||||||
|
borderRadius: 8,
|
||||||
|
backgroundColor: "white",
|
||||||
|
width: "248px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
paddingLeft: "12px",
|
||||||
|
height: "40px",
|
||||||
|
fontWeight: "600px",
|
||||||
|
fontSize: "16px",
|
||||||
|
gap: "12px",
|
||||||
|
backgroundColor: "#D4E0BD",
|
||||||
|
borderRadius: "8px 8px 0 0",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: "24px",
|
||||||
|
width: "24px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
style={{ height: "16px", width: "18px" }}
|
||||||
|
src="/icons/node/home.svg"
|
||||||
|
alt="start logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{t("startNode")}
|
||||||
|
</div>
|
||||||
|
<div style={{ height: "1px", backgroundColor: "#E2E2E2" }}></div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: "12px",
|
||||||
|
fontSize: "14px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("startNodeDescription")}
|
||||||
|
</div>
|
||||||
|
<Handle
|
||||||
|
type="source"
|
||||||
|
position={Position.Bottom}
|
||||||
|
id="1"
|
||||||
|
style={
|
||||||
|
isHandleConnected
|
||||||
|
? { ...HANDLE_STYLE_CONNECTED }
|
||||||
|
: { ...HANDLE_STYLE_DISCONNECTED }
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
34
client/src/components/nodes/defaultHandleStyle.ts
Normal file
34
client/src/components/nodes/defaultHandleStyle.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
// horizontal
|
||||||
|
const HANDLE_STYLE_CONNECTED = {
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
backgroundColor: "#BDBDBD",
|
||||||
|
};
|
||||||
|
|
||||||
|
// horizontal
|
||||||
|
const HANDLE_STYLE_DISCONNECTED = {
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
backgroundColor: "#C7D95A",
|
||||||
|
borderColor: "#606060",
|
||||||
|
borderWidth: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
// vertical
|
||||||
|
const HANDLE_STYLE_CONNECTED_V = {
|
||||||
|
...HANDLE_STYLE_CONNECTED,
|
||||||
|
right: "-4px",
|
||||||
|
};
|
||||||
|
|
||||||
|
// vertical
|
||||||
|
const HANDLE_STYLE_DISCONNECTED_V = {
|
||||||
|
...HANDLE_STYLE_DISCONNECTED,
|
||||||
|
right: "-10px",
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
HANDLE_STYLE_CONNECTED,
|
||||||
|
HANDLE_STYLE_DISCONNECTED,
|
||||||
|
HANDLE_STYLE_CONNECTED_V,
|
||||||
|
HANDLE_STYLE_DISCONNECTED_V,
|
||||||
|
};
|
@@ -1,99 +1,119 @@
|
|||||||
import i18n from 'i18next';
|
import i18n from "i18next";
|
||||||
import { initReactI18next } from 'react-i18next';
|
import { initReactI18next } from "react-i18next";
|
||||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
import LanguageDetector from "i18next-browser-languagedetector";
|
||||||
|
|
||||||
i18n
|
i18n
|
||||||
.use(LanguageDetector)
|
.use(LanguageDetector)
|
||||||
.use(initReactI18next)
|
.use(initReactI18next)
|
||||||
.init({
|
.init({
|
||||||
fallbackLng: 'en',
|
fallbackLng: "en",
|
||||||
supportedLngs: ['en', 'ru'],
|
supportedLngs: ["en", "ru"],
|
||||||
interpolation: { escapeValue: false },
|
interpolation: { escapeValue: false },
|
||||||
resources: {
|
resources: {
|
||||||
en: {
|
en: {
|
||||||
translation: {
|
translation: {
|
||||||
accounts: 'Accounts',
|
accounts: "Accounts",
|
||||||
processDiagrams: 'Process diagrams',
|
processDiagrams: "Process diagrams",
|
||||||
runningProcesses: 'Running processes',
|
runningProcesses: "Running processes",
|
||||||
settings: 'Settings',
|
settings: "Settings",
|
||||||
eventsList: 'Events list',
|
eventsList: "Events list",
|
||||||
configuration: 'Configuration',
|
configuration: "Configuration",
|
||||||
selectPhoto: 'Select photo',
|
selectPhoto: "Select photo",
|
||||||
name: 'Name',
|
name: "Name",
|
||||||
login: 'Login',
|
login: "Login",
|
||||||
password: 'Password',
|
password: "Password",
|
||||||
email: 'Email',
|
email: "Email",
|
||||||
tenant: 'Tenant',
|
tenant: "Tenant",
|
||||||
role: 'Role',
|
role: "Role",
|
||||||
status: 'Status',
|
status: "Status",
|
||||||
nameMessage: 'Enter name',
|
nameMessage: "Enter name",
|
||||||
loginMessage: 'Enter login',
|
loginMessage: "Enter login",
|
||||||
passwordMessage: 'Enter password',
|
passwordMessage: "Enter password",
|
||||||
emailMessage: 'Enter email',
|
emailMessage: "Enter email",
|
||||||
emailErrorMessage: 'Incorrect email',
|
emailErrorMessage: "Incorrect email",
|
||||||
tenantMessage: 'Enter tenant',
|
tenantMessage: "Enter tenant",
|
||||||
roleMessage: 'Choose role',
|
roleMessage: "Choose role",
|
||||||
statusMessage: 'Choose status',
|
statusMessage: "Choose status",
|
||||||
addAccount: 'Add account',
|
addAccount: "Add account",
|
||||||
save: 'Save changes',
|
save: "Save changes",
|
||||||
newAccount: 'New account',
|
newAccount: "New account",
|
||||||
ACTIVE: 'Active',
|
ACTIVE: "Active",
|
||||||
DISABLED: 'Disabled',
|
DISABLED: "Disabled",
|
||||||
BLOCKED: 'Blocked',
|
BLOCKED: "Blocked",
|
||||||
DELETED: 'Deleted',
|
DELETED: "Deleted",
|
||||||
OWNER: 'Owner',
|
OWNER: "Owner",
|
||||||
ADMIN: 'Admin',
|
ADMIN: "Admin",
|
||||||
EDITOR: 'Editor',
|
EDITOR: "Editor",
|
||||||
VIEWER: 'Viewer',
|
VIEWER: "Viewer",
|
||||||
nameLogin: 'Name, login',
|
nameLogin: "Name, login",
|
||||||
createdAt: 'Created',
|
createdAt: "Created",
|
||||||
saving: 'Saving...',
|
saving: "Saving...",
|
||||||
createdAccountMessage: 'User successfully created!',
|
createdAccountMessage: "User successfully created!",
|
||||||
editAccountMessage: 'User successfully updated!',
|
editAccountMessage: "User successfully updated!",
|
||||||
you: '(You)',
|
you: "(You)",
|
||||||
|
// nodes
|
||||||
|
startNode: "Start",
|
||||||
|
startNodeDescription: "Start",
|
||||||
|
connectToExisting: "Connect to existing",
|
||||||
|
editNode: "Editing a block",
|
||||||
|
connectTo: "Connect to {{nodeId}}",
|
||||||
|
ifElseNode: "IF - ELSE",
|
||||||
|
conditionIf: "If {{condition}}, then",
|
||||||
|
conditionElse: "Else",
|
||||||
|
appropriationNode: "Appropriation",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ru: {
|
ru: {
|
||||||
translation: {
|
translation: {
|
||||||
accounts: 'Учетные записи',
|
accounts: "Учетные записи",
|
||||||
processDiagrams: 'Схемы процессов',
|
processDiagrams: "Схемы процессов",
|
||||||
runningProcesses: 'Запущенные процессы',
|
runningProcesses: "Запущенные процессы",
|
||||||
settings: 'Настройки',
|
settings: "Настройки",
|
||||||
eventsList: 'Справочкин событий',
|
eventsList: "Справочкин событий",
|
||||||
configuration: 'Конфигурация',
|
configuration: "Конфигурация",
|
||||||
selectPhoto: 'Выбрать фото',
|
selectPhoto: "Выбрать фото",
|
||||||
name: 'Имя',
|
name: "Имя",
|
||||||
login: 'Логин',
|
login: "Логин",
|
||||||
password: 'Пароль',
|
password: "Пароль",
|
||||||
email: 'Имейл',
|
email: "Имейл",
|
||||||
tenant: 'Привязка',
|
tenant: "Привязка",
|
||||||
role: 'Роль',
|
role: "Роль",
|
||||||
status: 'Статус',
|
status: "Статус",
|
||||||
nameMessage: 'Введите имя',
|
nameMessage: "Введите имя",
|
||||||
loginMessage: 'Введите логин',
|
loginMessage: "Введите логин",
|
||||||
passwordMessage: 'Введите пароль',
|
passwordMessage: "Введите пароль",
|
||||||
emailMessage: 'Введите имейл',
|
emailMessage: "Введите имейл",
|
||||||
emailErrorMessage: 'Некорректный имейл',
|
emailErrorMessage: "Некорректный имейл",
|
||||||
tenantMessage: 'Введите привязку',
|
tenantMessage: "Введите привязку",
|
||||||
roleMessage: 'Выберите роль',
|
roleMessage: "Выберите роль",
|
||||||
statusMessage: 'Выберите статус',
|
statusMessage: "Выберите статус",
|
||||||
addAccount: 'Добавить аккаунт',
|
addAccount: "Добавить аккаунт",
|
||||||
save: 'Сохранить изменения',
|
save: "Сохранить изменения",
|
||||||
newAccount: 'Новая учетная запись',
|
newAccount: "Новая учетная запись",
|
||||||
ACTIVE: 'Активен',
|
ACTIVE: "Активен",
|
||||||
DISABLED: 'Выключен',
|
DISABLED: "Выключен",
|
||||||
BLOCKED: 'Заблокирован',
|
BLOCKED: "Заблокирован",
|
||||||
DELETED: 'Удален',
|
DELETED: "Удален",
|
||||||
OWNER: 'Владелец',
|
OWNER: "Владелец",
|
||||||
ADMIN: 'Админ',
|
ADMIN: "Админ",
|
||||||
EDITOR: 'Редактор',
|
EDITOR: "Редактор",
|
||||||
VIEWER: 'Наблюдатель',
|
VIEWER: "Наблюдатель",
|
||||||
nameLogin: 'Имя, Логин',
|
nameLogin: "Имя, Логин",
|
||||||
createdAt: 'Создано',
|
createdAt: "Создано",
|
||||||
saving: 'Сохранение...',
|
saving: "Сохранение...",
|
||||||
createdAccountMessage: 'Пользователь успешно создан!',
|
createdAccountMessage: "Пользователь успешно создан!",
|
||||||
editAccountMessage: 'Пользователь успешно обновлен!',
|
editAccountMessage: "Пользователь успешно обновлен!",
|
||||||
you: '(Вы)',
|
you: "(Вы)",
|
||||||
|
// nodes
|
||||||
|
startNode: "Запуск",
|
||||||
|
startNodeDescription: "Включение",
|
||||||
|
connectToExisting: "Подключить к существующей",
|
||||||
|
editNode: "Редактирование блока",
|
||||||
|
connectTo: `Подключить к {{nodeId}}`,
|
||||||
|
ifElseNode: "ЕСЛИ - ТО",
|
||||||
|
conditionIf: "Если {{condition}}, то",
|
||||||
|
conditionElse: "Иначе",
|
||||||
|
appropriationNode: "Присвоение",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@@ -1,11 +1,12 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from "react-dom/client";
|
||||||
import '@/index.css';
|
import "@/index.css";
|
||||||
import App from '@/App';
|
import App from "@/App";
|
||||||
import AppWrapper from '@/config/AppWrapper';
|
import AppWrapper from "@/config/AppWrapper";
|
||||||
|
import "@xyflow/react/dist/style.css";
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(
|
const root = ReactDOM.createRoot(
|
||||||
document.getElementById('root') as HTMLElement
|
document.getElementById("root") as HTMLElement
|
||||||
);
|
);
|
||||||
|
|
||||||
root.render(
|
root.render(
|
||||||
|
@@ -2,12 +2,12 @@ import { useEffect, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { AccountStatus, AllUser, AllUserResponse } from "@/types/user";
|
import { AccountStatus, AllUser, AllUserResponse } from "@/types/user";
|
||||||
import Header from "@/components/Header";
|
import Header from "@/components/Header";
|
||||||
import ContentDrawer from "@/components/ContentDrawer";
|
import ContentDrawer from "@/components/drawers/ContentDrawer";
|
||||||
import UserCreate from "@/components/UserCreate";
|
import UserCreate from "@/components/drawers/users/UserCreate";
|
||||||
import { Avatar, Table } from "antd";
|
import { Avatar, Table } from "antd";
|
||||||
import { TableProps } from "antd/lib";
|
import { TableProps } from "antd/lib";
|
||||||
import { UserService } from "@/services/userService";
|
import { UserService } from "@/services/userService";
|
||||||
import UserEdit from "@/components/UserEdit";
|
import UserEdit from "@/components/drawers/users/UserEdit";
|
||||||
import { useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
export default function AccountsPage() {
|
export default function AccountsPage() {
|
||||||
@@ -201,7 +201,7 @@ export default function AccountsPage() {
|
|||||||
<ContentDrawer
|
<ContentDrawer
|
||||||
open={openCreate}
|
open={openCreate}
|
||||||
closeDrawer={closeCreateDrawer}
|
closeDrawer={closeCreateDrawer}
|
||||||
type="create"
|
type="userCreate"
|
||||||
>
|
>
|
||||||
<UserCreate getUsers={getUsers} closeDrawer={closeCreateDrawer} />
|
<UserCreate getUsers={getUsers} closeDrawer={closeCreateDrawer} />
|
||||||
</ContentDrawer>
|
</ContentDrawer>
|
||||||
@@ -211,7 +211,7 @@ export default function AccountsPage() {
|
|||||||
email={activeAccount?.email}
|
email={activeAccount?.email}
|
||||||
open={openEdit}
|
open={openEdit}
|
||||||
closeDrawer={closeEditDrawer}
|
closeDrawer={closeEditDrawer}
|
||||||
type="edit"
|
type="userEdit"
|
||||||
>
|
>
|
||||||
<UserEdit userId={activeAccount?.id} closeDrawer={closeEditDrawer} />
|
<UserEdit userId={activeAccount?.id} closeDrawer={closeEditDrawer} />
|
||||||
</ContentDrawer>
|
</ContentDrawer>
|
||||||
|
@@ -1,22 +1,22 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from "react";
|
||||||
import { Layout } from 'antd';
|
import { Layout } from "antd";
|
||||||
import Sider from 'antd/es/layout/Sider';
|
import Sider from "antd/es/layout/Sider";
|
||||||
import { Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
import { Route, Routes, useLocation, useNavigate } from "react-router-dom";
|
||||||
import SiderMenu from '@/components/SiderMenu';
|
import SiderMenu from "@/components/SiderMenu";
|
||||||
import ProcessDiagramPage from './ProcessDiagramPage';
|
import ProcessDiagramPage from "./ProcessDiagramPage";
|
||||||
import RunningProcessesPage from './RunningProcessesPage';
|
import AccountsPage from "./AccountsPage";
|
||||||
import AccountsPage from './AccountsPage';
|
import ConfigurationPage from "./ConfigurationPage";
|
||||||
import EventsListPage from './EventsListPage';
|
import EventsListPage from "./EventsListPage";
|
||||||
import ConfigurationPage from './ConfigurationPage';
|
import RunningProcessesPage from "./RunningProcessesPage";
|
||||||
|
|
||||||
export default function MainLayout() {
|
export default function MainLayout() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
const [selectedKey, setSelectedKey] = useState('1');
|
const [selectedKey, setSelectedKey] = useState("1");
|
||||||
|
|
||||||
const [width, setWidth] = useState<number | string>('15%');
|
const [width, setWidth] = useState<number | string>("15%");
|
||||||
const [collapsedWidth, setCollapsedWidth] = useState(50);
|
const [collapsedWidth, setCollapsedWidth] = useState(50);
|
||||||
|
|
||||||
const calculateWidths = () => {
|
const calculateWidths = () => {
|
||||||
@@ -29,24 +29,24 @@ export default function MainLayout() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
calculateWidths();
|
calculateWidths();
|
||||||
window.addEventListener('resize', calculateWidths);
|
window.addEventListener("resize", calculateWidths);
|
||||||
return () => window.removeEventListener('resize', calculateWidths);
|
return () => window.removeEventListener("resize", calculateWidths);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (location.pathname === '/') {
|
if (location.pathname === "/") {
|
||||||
navigate('/process-diagram');
|
navigate("/process-diagram");
|
||||||
}
|
}
|
||||||
setSelectedKey(location.pathname);
|
setSelectedKey(location.pathname);
|
||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
function hangleMenuClick(e: any) {
|
function hangleMenuClick(e: any) {
|
||||||
const key = e.key;
|
const key = e.key;
|
||||||
if (key === 'toggle') {
|
if (key === "toggle") {
|
||||||
setCollapsed(!collapsed);
|
setCollapsed(!collapsed);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (key === 'divider') {
|
if (key === "divider") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ export default function MainLayout() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout style={{ minHeight: '100vh' }}>
|
<Layout style={{ minHeight: "100vh" }}>
|
||||||
<Sider
|
<Sider
|
||||||
className="sider"
|
className="sider"
|
||||||
collapsible
|
collapsible
|
||||||
|
@@ -1,11 +1,33 @@
|
|||||||
import Header from '@/components/Header';
|
import Header from "@/components/Header";
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import ReactFlowDrawer from "@/components/ReactFlowDrawer";
|
||||||
|
import { useState } from "react";
|
||||||
|
import ContentDrawer from "@/components/drawers/ContentDrawer";
|
||||||
|
|
||||||
export default function ProcessDiagramPage() {
|
export default function ProcessDiagramPage() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
||||||
|
|
||||||
|
const showDrawer = () => setIsDrawerOpen(true);
|
||||||
|
const closeDrawer = () => {
|
||||||
|
setIsDrawerOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header title={t('processDiagrams')} />
|
<Header title={t("processDiagrams")} />
|
||||||
|
<div style={{ width: "100%", height: "100%" }}>
|
||||||
|
<ReactFlowDrawer showDrawer={showDrawer} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ContentDrawer
|
||||||
|
open={isDrawerOpen}
|
||||||
|
closeDrawer={closeDrawer}
|
||||||
|
children={undefined}
|
||||||
|
type={"nodeEdit"}
|
||||||
|
></ContentDrawer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
25
client/src/store/nodeStore.ts
Normal file
25
client/src/store/nodeStore.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { Edge, Node } from "@xyflow/react";
|
||||||
|
import { create } from "zustand";
|
||||||
|
import { devtools } from "zustand/middleware";
|
||||||
|
|
||||||
|
type NodeStoreState = {
|
||||||
|
node: Node;
|
||||||
|
edge: Edge;
|
||||||
|
loading: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type NodeStoreActions = {
|
||||||
|
addNode: (node: Node) => void;
|
||||||
|
addEdge: (edge: Edge) => void;
|
||||||
|
removeNode: (nodeId: string) => void;
|
||||||
|
removeEdge: (edgeId: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type NodeStore = NodeStoreState & NodeStoreActions;
|
||||||
|
|
||||||
|
export const useNodeStore = create<NodeStore>()(
|
||||||
|
devtools((set) => ({
|
||||||
|
nodes: [],
|
||||||
|
edges: [],
|
||||||
|
}))
|
||||||
|
);
|
8
client/src/utils/edge.ts
Normal file
8
client/src/utils/edge.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
function edgeTitleGenerator(counter: number) {
|
||||||
|
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
const num = Math.ceil(counter / chars.length);
|
||||||
|
const letterIndex = (counter - 1) % chars.length;
|
||||||
|
return `${chars[letterIndex]}${num}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { edgeTitleGenerator };
|
Reference in New Issue
Block a user