3 Commits

60 changed files with 465 additions and 1910 deletions

View File

@@ -1,15 +1,15 @@
import sys
import logging
import sys
import loguru
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from uvicorn import run
from api.config import get_settings, DefaultSettings
from api.config import DefaultSettings, get_settings
from api.endpoints import list_of_routes
from api.utils.common import get_hostname
from api.services.middleware import MiddlewareAccessTokenValidadtion
from api.utils.common import get_hostname
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
@@ -52,7 +52,6 @@ prod_origins = [""]
origins = dev_origins if get_settings().ENV == "local" else prod_origins
if __name__ == "__main__":
settings_for_application = get_settings()
if settings_for_application.ENV == "prod":

View File

@@ -1,7 +0,0 @@
from sqlalchemy import MetaData
metadata = MetaData()
__all__ = [
"metadata",
]

View File

@@ -7,7 +7,7 @@ from sqlalchemy import pool
from alembic import context
from api.db import metadata, tables
from orm import metadata, tables
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.

View File

@@ -6,7 +6,7 @@ from typing import Optional
from sqlalchemy import insert, select, func, or_, and_, asc, desc
from sqlalchemy.ext.asyncio import AsyncConnection
from api.db.tables.account import account_table
from orm.tables.account import account_table
from api.schemas.account.account import User
from api.schemas.endpoints.account import all_user_adapter, AllUser, AllUserResponse, UserCreate, UserFilterDTO

View File

@@ -4,7 +4,7 @@ from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncConnection
from enum import Enum
from api.db.tables.account import account_table, account_keyring_table, KeyType, KeyStatus
from orm.tables.account import account_table, account_keyring_table, KeyType, KeyStatus
from api.schemas.account.account import User
from api.schemas.account.account_keyring import AccountKeyring

View File

@@ -6,7 +6,7 @@ from sqlalchemy import insert, select, update
from sqlalchemy.dialects.mysql import insert as mysql_insert
from sqlalchemy.ext.asyncio import AsyncConnection
from api.db.tables.account import account_keyring_table, KeyStatus, KeyType
from orm.tables.account import account_keyring_table, KeyStatus, KeyType
from api.schemas.account.account_keyring import AccountKeyring
from api.utils.hasher import hasher

View File

@@ -7,7 +7,7 @@ from datetime import datetime, timezone
from sqlalchemy import insert, select, func, or_, and_, asc, desc
from sqlalchemy.ext.asyncio import AsyncConnection
from api.db.tables.events import list_events_table
from orm.tables.events import list_events_table
from api.schemas.events.list_events import ListEvent

View File

@@ -6,7 +6,7 @@ from datetime import datetime, timezone
from sqlalchemy import insert, select, func, or_, and_, asc, desc
from sqlalchemy.ext.asyncio import AsyncConnection
from api.db.tables.process import process_schema_table
from orm.tables.process import process_schema_table
from api.schemas.process.process_schema import ProcessSchema

View File

@@ -1,18 +0,0 @@
__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")

View File

@@ -1 +0,0 @@
from . import account, events, process

View File

@@ -1,65 +0,0 @@
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),
)

View File

@@ -1,33 +0,0 @@
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),
)

View File

@@ -1,107 +0,0 @@
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"),
)

View File

@@ -1,6 +1,7 @@
from fastapi import APIRouter, Depends, HTTPException, status, Query
from typing import List, Optional
from typing import Optional, List
from fastapi import APIRouter, Depends, HTTPException, Query, status
from orm.tables.account import AccountStatus
from sqlalchemy.ext.asyncio import AsyncConnection
from api.db.connection.session import get_connection_dep
@@ -12,10 +13,9 @@ from api.db.logic.account import (
update_user_by_id,
)
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.base import bearer_schema
from api.schemas.endpoints.account import AllUserResponse, UserCreate, UserUpdate, UserFilterDTO
from api.schemas.endpoints.account import AllUserResponse, UserCreate, UserFilterDTO, UserUpdate
from api.services.auth import get_current_user
from api.services.user_role_validation import db_user_role_validation

View File

@@ -1,31 +1,20 @@
from fastapi import (
APIRouter,
Body,
Depends,
Form,
HTTPException,
Response,
status,
)
from orm.tables.account import KeyStatus
from sqlalchemy.ext.asyncio import AsyncConnection
from api.db.connection.session import get_connection_dep
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.db.logic.keyring import create_key, get_key_by_id, update_key_by_id
from api.schemas.account.account_keyring import AccountKeyring
from api.schemas.base import bearer_schema
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.user_role_validation import db_user_role_validation
api_router = APIRouter(
prefix="/keyring",
tags=["User KeyringModel"],

View File

@@ -1,37 +1,27 @@
from fastapi import APIRouter, Depends, HTTPException, status, Query
from typing import Optional, List
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query, status
from orm.tables.events import EventStatus
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.db.logic.list_events import (
get_list_events_by_name,
get_list_events_by_id,
create_list_events,
update_list_events_by_id,
get_list_events_by_id,
get_list_events_by_name,
get_list_events_page_DTO,
update_list_events_by_id,
)
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.schemas.endpoints.list_events import AllListEventResponse, ListEventFilterDTO, ListEventUpdate
from api.schemas.events.list_events import ListEvent
from api.services.auth import get_current_user
from api.services.user_role_validation import (
db_user_role_validation_for_list_events_and_process_schema_by_list_event_id,
db_user_role_validation_for_list_events_and_process_schema,
db_user_role_validation_for_list_events_and_process_schema_by_list_event_id,
)
api_router = APIRouter(
prefix="/list_events",
tags=["list events"],

View File

@@ -1,36 +1,27 @@
from fastapi import APIRouter, Depends, HTTPException, status, Query
from typing import List, Optional
from typing import Optional, List
from fastapi import APIRouter, Depends, HTTPException, Query, status
from orm.tables.process import ProcessStatus
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.db.logic.process_schema import (
get_process_schema_by_title,
create_process_schema,
get_process_schema_by_id,
update_process_schema_by_id,
get_process_schema_by_title,
get_process_schema_page_DTO,
update_process_schema_by_id,
)
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.endpoints.process_schema import ProcessSchemaUpdate, AllProcessSchemaResponse, ProcessSchemaFilterDTO
from api.schemas.endpoints.process_schema import AllProcessSchemaResponse, ProcessSchemaFilterDTO, ProcessSchemaUpdate
from api.schemas.process.process_schema import ProcessSchema
from api.services.auth import get_current_user
from api.services.user_role_validation import (
db_user_role_validation_for_list_events_and_process_schema_by_list_event_id,
db_user_role_validation_for_list_events_and_process_schema,
db_user_role_validation_for_list_events_and_process_schema_by_list_event_id,
)
api_router = APIRouter(
prefix="/process_schema",
tags=["process schema"],

View File

@@ -4,18 +4,14 @@ from fastapi import (
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_id, update_user_by_id, get_user_by_login
from api.schemas.base import bearer_schema
from api.services.auth import get_current_user
from api.schemas.endpoints.account import UserUpdate
from api.db.logic.account import get_user_by_id, get_user_by_login, update_user_by_id
from api.schemas.account.account import User
from api.schemas.base import bearer_schema
from api.schemas.endpoints.account import UserUpdate
from api.services.auth import get_current_user
api_router = APIRouter(
prefix="/profile",

View File

@@ -1,8 +1,8 @@
import datetime
from datetime import datetime
from typing import Optional
from orm.tables.account import AccountRole, AccountStatus
from pydantic import EmailStr, Field
from api.db.tables.account import AccountRole, AccountStatus
from api.schemas.base import Base

View File

@@ -1,8 +1,8 @@
import datetime
from typing import Optional
from pydantic import Field
from datetime import datetime
from api.db.tables.account import KeyType, KeyStatus
from typing import Optional
from orm.tables.account import KeyStatus, KeyType
from pydantic import Field
from api.schemas.base import Base

View File

@@ -1,9 +1,9 @@
from datetime import datetime
from typing import List, Optional, Dict
from typing import Dict, List, Optional
from orm.tables.account import AccountRole, AccountStatus
from pydantic import EmailStr, Field, TypeAdapter
from api.db.tables.account import AccountRole, AccountStatus
from api.schemas.base import Base

View File

@@ -1,6 +1,8 @@
from typing import Optional
from orm.tables.account import KeyStatus, KeyType
from pydantic import Field
from api.db.tables.account import KeyType, KeyStatus
from api.schemas.base import Base

View File

@@ -1,5 +1,6 @@
from api.schemas.base import Base
# Таблица для получения информации из запроса

View File

@@ -1,10 +1,10 @@
from pydantic import Field, TypeAdapter
from typing import Optional, Dict, Any, List
from datetime import datetime
from typing import Any, Dict, List, Optional
from orm.tables.events import EventState, EventStatus
from pydantic import Field, TypeAdapter
from api.schemas.base import Base
from api.db.tables.events import EventState, EventStatus
class ListEventUpdate(Base):

View File

@@ -1,10 +1,10 @@
from pydantic import Field, TypeAdapter
from typing import Optional, Dict, Any, List
from datetime import datetime
from typing import Any, Dict, List, Optional
from orm.tables.process import ProcessStatus
from pydantic import Field, TypeAdapter
from api.schemas.base import Base
from api.db.tables.process import ProcessStatus
class ProcessSchemaUpdate(Base):

View File

@@ -1,9 +1,10 @@
from pydantic import Field
from typing import Dict, Any
from datetime import datetime
from typing import Any, Dict
from orm.tables.events import EventState, EventStatus
from pydantic import Field
from api.schemas.base import Base
from api.db.tables.events import EventState, EventStatus
class ListEvent(Base):

View File

@@ -1,9 +1,10 @@
from pydantic import Field
from typing import Dict, Any
from datetime import datetime
from typing import Any, Dict
from orm.tables.process import NodeStatus
from pydantic import Field
from api.schemas.base import Base
from api.db.tables.process import NodeStatus
class MyModel(Base):

View File

@@ -1,9 +1,10 @@
from pydantic import Field
from typing import Dict, Any
from datetime import datetime
from typing import Any, Dict
from orm.tables.process import ProcessStatus
from pydantic import Field
from api.schemas.base import Base
from api.db.tables.process import ProcessStatus
class ProcessSchema(Base):

View File

@@ -1,5 +1,5 @@
from typing import Dict, Any
from datetime import datetime
from typing import Any, Dict
from api.schemas.base import Base

View File

@@ -1,8 +1,9 @@
from datetime import datetime
from typing import Dict, Any
from typing import Any, Dict
from orm.tables.process import NodeStatus, NodeType
from api.schemas.base import Base
from api.db.tables.process import NodeType, NodeStatus
class Ps_Node(Base):

View File

@@ -1,10 +1,10 @@
from typing import Optional
from fastapi import HTTPException, Request
from orm.tables.account import AccountStatus
from sqlalchemy.ext.asyncio import AsyncConnection
from api.db.logic.auth import get_user
from api.db.tables.account import AccountStatus
from api.schemas.endpoints.account import AllUser
from api.utils.hasher import hasher

View File

@@ -1,15 +1,15 @@
from fastapi_jwt_auth import AuthJWT
from starlette.middleware.base import BaseHTTPMiddleware
import re
from re import escape
from fastapi import (
Request,
status,
)
from fastapi.responses import JSONResponse
from api.config import get_settings
from fastapi_jwt_auth import AuthJWT
from starlette.middleware.base import BaseHTTPMiddleware
import re
from re import escape
from api.config import get_settings
class MiddlewareAccessTokenValidadtion(BaseHTTPMiddleware):

View File

@@ -2,8 +2,9 @@ from fastapi import (
HTTPException,
status,
)
from orm.tables.account import AccountRole
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):

View File

@@ -1,8 +1,9 @@
import asyncio
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.tables.account import account_keyring_table, account_table, AccountRole, KeyStatus, KeyType
from api.utils.hasher import hasher
from api.utils.key_id_gen import KeyIdGenerator

153
api/poetry.lock generated
View File

@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand.
# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand.
[[package]]
name = "aio-pika"
@@ -227,6 +227,25 @@ files = [
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "core-library"
version = "0.1.0"
description = "Abstract classes library for the Vorkout project"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = []
develop = false
[package.dependencies]
sqlalchemy = ">=2.0.43,<3.0.0"
[package.source]
type = "git"
url = "https://gitea.heado.ru/Vorkout/core.git"
reference = "0.1.0"
resolved_reference = "96ddb52582660600dbacead4919e67f948e96898"
[[package]]
name = "cryptography"
version = "44.0.2"
@@ -1416,83 +1435,83 @@ files = [
[[package]]
name = "sqlalchemy"
version = "2.0.39"
version = "2.0.43"
description = "Database Abstraction Library"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "SQLAlchemy-2.0.39-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:66a40003bc244e4ad86b72abb9965d304726d05a939e8c09ce844d27af9e6d37"},
{file = "SQLAlchemy-2.0.39-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67de057fbcb04a066171bd9ee6bcb58738d89378ee3cabff0bffbf343ae1c787"},
{file = "SQLAlchemy-2.0.39-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:533e0f66c32093a987a30df3ad6ed21170db9d581d0b38e71396c49718fbb1ca"},
{file = "SQLAlchemy-2.0.39-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7399d45b62d755e9ebba94eb89437f80512c08edde8c63716552a3aade61eb42"},
{file = "SQLAlchemy-2.0.39-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:788b6ff6728072b313802be13e88113c33696a9a1f2f6d634a97c20f7ef5ccce"},
{file = "SQLAlchemy-2.0.39-cp37-cp37m-win32.whl", hash = "sha256:01da15490c9df352fbc29859d3c7ba9cd1377791faeeb47c100832004c99472c"},
{file = "SQLAlchemy-2.0.39-cp37-cp37m-win_amd64.whl", hash = "sha256:f2bcb085faffcacf9319b1b1445a7e1cfdc6fb46c03f2dce7bc2d9a4b3c1cdc5"},
{file = "SQLAlchemy-2.0.39-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b761a6847f96fdc2d002e29e9e9ac2439c13b919adfd64e8ef49e75f6355c548"},
{file = "SQLAlchemy-2.0.39-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0d7e3866eb52d914aea50c9be74184a0feb86f9af8aaaa4daefe52b69378db0b"},
{file = "SQLAlchemy-2.0.39-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:995c2bacdddcb640c2ca558e6760383dcdd68830160af92b5c6e6928ffd259b4"},
{file = "SQLAlchemy-2.0.39-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:344cd1ec2b3c6bdd5dfde7ba7e3b879e0f8dd44181f16b895940be9b842fd2b6"},
{file = "SQLAlchemy-2.0.39-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:5dfbc543578058c340360f851ddcecd7a1e26b0d9b5b69259b526da9edfa8875"},
{file = "SQLAlchemy-2.0.39-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3395e7ed89c6d264d38bea3bfb22ffe868f906a7985d03546ec7dc30221ea980"},
{file = "SQLAlchemy-2.0.39-cp38-cp38-win32.whl", hash = "sha256:bf555f3e25ac3a70c67807b2949bfe15f377a40df84b71ab2c58d8593a1e036e"},
{file = "SQLAlchemy-2.0.39-cp38-cp38-win_amd64.whl", hash = "sha256:463ecfb907b256e94bfe7bcb31a6d8c7bc96eca7cbe39803e448a58bb9fcad02"},
{file = "sqlalchemy-2.0.39-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6827f8c1b2f13f1420545bd6d5b3f9e0b85fe750388425be53d23c760dcf176b"},
{file = "sqlalchemy-2.0.39-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9f119e7736967c0ea03aff91ac7d04555ee038caf89bb855d93bbd04ae85b41"},
{file = "sqlalchemy-2.0.39-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4600c7a659d381146e1160235918826c50c80994e07c5b26946a3e7ec6c99249"},
{file = "sqlalchemy-2.0.39-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a06e6c8e31c98ddc770734c63903e39f1947c9e3e5e4bef515c5491b7737dde"},
{file = "sqlalchemy-2.0.39-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4c433f78c2908ae352848f56589c02b982d0e741b7905228fad628999799de4"},
{file = "sqlalchemy-2.0.39-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7bd5c5ee1448b6408734eaa29c0d820d061ae18cb17232ce37848376dcfa3e92"},
{file = "sqlalchemy-2.0.39-cp310-cp310-win32.whl", hash = "sha256:87a1ce1f5e5dc4b6f4e0aac34e7bb535cb23bd4f5d9c799ed1633b65c2bcad8c"},
{file = "sqlalchemy-2.0.39-cp310-cp310-win_amd64.whl", hash = "sha256:871f55e478b5a648c08dd24af44345406d0e636ffe021d64c9b57a4a11518304"},
{file = "sqlalchemy-2.0.39-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a28f9c238f1e143ff42ab3ba27990dfb964e5d413c0eb001b88794c5c4a528a9"},
{file = "sqlalchemy-2.0.39-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:08cf721bbd4391a0e765fe0fe8816e81d9f43cece54fdb5ac465c56efafecb3d"},
{file = "sqlalchemy-2.0.39-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a8517b6d4005facdbd7eb4e8cf54797dbca100a7df459fdaff4c5123265c1cd"},
{file = "sqlalchemy-2.0.39-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b2de1523d46e7016afc7e42db239bd41f2163316935de7c84d0e19af7e69538"},
{file = "sqlalchemy-2.0.39-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:412c6c126369ddae171c13987b38df5122cb92015cba6f9ee1193b867f3f1530"},
{file = "sqlalchemy-2.0.39-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b35e07f1d57b79b86a7de8ecdcefb78485dab9851b9638c2c793c50203b2ae8"},
{file = "sqlalchemy-2.0.39-cp311-cp311-win32.whl", hash = "sha256:3eb14ba1a9d07c88669b7faf8f589be67871d6409305e73e036321d89f1d904e"},
{file = "sqlalchemy-2.0.39-cp311-cp311-win_amd64.whl", hash = "sha256:78f1b79132a69fe8bd6b5d91ef433c8eb40688ba782b26f8c9f3d2d9ca23626f"},
{file = "sqlalchemy-2.0.39-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c457a38351fb6234781d054260c60e531047e4d07beca1889b558ff73dc2014b"},
{file = "sqlalchemy-2.0.39-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:018ee97c558b499b58935c5a152aeabf6d36b3d55d91656abeb6d93d663c0c4c"},
{file = "sqlalchemy-2.0.39-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5493a8120d6fc185f60e7254fc056a6742f1db68c0f849cfc9ab46163c21df47"},
{file = "sqlalchemy-2.0.39-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2cf5b5ddb69142511d5559c427ff00ec8c0919a1e6c09486e9c32636ea2b9dd"},
{file = "sqlalchemy-2.0.39-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f03143f8f851dd8de6b0c10784363712058f38209e926723c80654c1b40327a"},
{file = "sqlalchemy-2.0.39-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06205eb98cb3dd52133ca6818bf5542397f1dd1b69f7ea28aa84413897380b06"},
{file = "sqlalchemy-2.0.39-cp312-cp312-win32.whl", hash = "sha256:7f5243357e6da9a90c56282f64b50d29cba2ee1f745381174caacc50d501b109"},
{file = "sqlalchemy-2.0.39-cp312-cp312-win_amd64.whl", hash = "sha256:2ed107331d188a286611cea9022de0afc437dd2d3c168e368169f27aa0f61338"},
{file = "sqlalchemy-2.0.39-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fe193d3ae297c423e0e567e240b4324d6b6c280a048e64c77a3ea6886cc2aa87"},
{file = "sqlalchemy-2.0.39-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:79f4f502125a41b1b3b34449e747a6abfd52a709d539ea7769101696bdca6716"},
{file = "sqlalchemy-2.0.39-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a10ca7f8a1ea0fd5630f02feb055b0f5cdfcd07bb3715fc1b6f8cb72bf114e4"},
{file = "sqlalchemy-2.0.39-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e6b0a1c7ed54a5361aaebb910c1fa864bae34273662bb4ff788a527eafd6e14d"},
{file = "sqlalchemy-2.0.39-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52607d0ebea43cf214e2ee84a6a76bc774176f97c5a774ce33277514875a718e"},
{file = "sqlalchemy-2.0.39-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c08a972cbac2a14810463aec3a47ff218bb00c1a607e6689b531a7c589c50723"},
{file = "sqlalchemy-2.0.39-cp313-cp313-win32.whl", hash = "sha256:23c5aa33c01bd898f879db158537d7e7568b503b15aad60ea0c8da8109adf3e7"},
{file = "sqlalchemy-2.0.39-cp313-cp313-win_amd64.whl", hash = "sha256:4dabd775fd66cf17f31f8625fc0e4cfc5765f7982f94dc09b9e5868182cb71c0"},
{file = "sqlalchemy-2.0.39-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2600a50d590c22d99c424c394236899ba72f849a02b10e65b4c70149606408b5"},
{file = "sqlalchemy-2.0.39-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4eff9c270afd23e2746e921e80182872058a7a592017b2713f33f96cc5f82e32"},
{file = "sqlalchemy-2.0.39-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7332868ce891eda48896131991f7f2be572d65b41a4050957242f8e935d5d7"},
{file = "sqlalchemy-2.0.39-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:125a7763b263218a80759ad9ae2f3610aaf2c2fbbd78fff088d584edf81f3782"},
{file = "sqlalchemy-2.0.39-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:04545042969833cb92e13b0a3019549d284fd2423f318b6ba10e7aa687690a3c"},
{file = "sqlalchemy-2.0.39-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:805cb481474e111ee3687c9047c5f3286e62496f09c0e82e8853338aaaa348f8"},
{file = "sqlalchemy-2.0.39-cp39-cp39-win32.whl", hash = "sha256:34d5c49f18778a3665d707e6286545a30339ad545950773d43977e504815fa70"},
{file = "sqlalchemy-2.0.39-cp39-cp39-win_amd64.whl", hash = "sha256:35e72518615aa5384ef4fae828e3af1b43102458b74a8c481f69af8abf7e802a"},
{file = "sqlalchemy-2.0.39-py3-none-any.whl", hash = "sha256:a1c6b0a5e3e326a466d809b651c63f278b1256146a377a528b6938a279da334f"},
{file = "sqlalchemy-2.0.39.tar.gz", hash = "sha256:5d2d1fe548def3267b4c70a8568f108d1fed7cbbeccb9cc166e05af2abc25c22"},
{file = "SQLAlchemy-2.0.43-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:21ba7a08a4253c5825d1db389d4299f64a100ef9800e4624c8bf70d8f136e6ed"},
{file = "SQLAlchemy-2.0.43-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11b9503fa6f8721bef9b8567730f664c5a5153d25e247aadc69247c4bc605227"},
{file = "SQLAlchemy-2.0.43-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07097c0a1886c150ef2adba2ff7437e84d40c0f7dcb44a2c2b9c905ccfc6361c"},
{file = "SQLAlchemy-2.0.43-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:cdeff998cb294896a34e5b2f00e383e7c5c4ef3b4bfa375d9104723f15186443"},
{file = "SQLAlchemy-2.0.43-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:bcf0724a62a5670e5718957e05c56ec2d6850267ea859f8ad2481838f889b42c"},
{file = "SQLAlchemy-2.0.43-cp37-cp37m-win32.whl", hash = "sha256:c697575d0e2b0a5f0433f679bda22f63873821d991e95a90e9e52aae517b2e32"},
{file = "SQLAlchemy-2.0.43-cp37-cp37m-win_amd64.whl", hash = "sha256:d34c0f6dbefd2e816e8f341d0df7d4763d382e3f452423e752ffd1e213da2512"},
{file = "sqlalchemy-2.0.43-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:70322986c0c699dca241418fcf18e637a4369e0ec50540a2b907b184c8bca069"},
{file = "sqlalchemy-2.0.43-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:87accdbba88f33efa7b592dc2e8b2a9c2cdbca73db2f9d5c510790428c09c154"},
{file = "sqlalchemy-2.0.43-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c00e7845d2f692ebfc7d5e4ec1a3fd87698e4337d09e58d6749a16aedfdf8612"},
{file = "sqlalchemy-2.0.43-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:022e436a1cb39b13756cf93b48ecce7aa95382b9cfacceb80a7d263129dfd019"},
{file = "sqlalchemy-2.0.43-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c5e73ba0d76eefc82ec0219d2301cb33bfe5205ed7a2602523111e2e56ccbd20"},
{file = "sqlalchemy-2.0.43-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9c2e02f06c68092b875d5cbe4824238ab93a7fa35d9c38052c033f7ca45daa18"},
{file = "sqlalchemy-2.0.43-cp310-cp310-win32.whl", hash = "sha256:e7a903b5b45b0d9fa03ac6a331e1c1d6b7e0ab41c63b6217b3d10357b83c8b00"},
{file = "sqlalchemy-2.0.43-cp310-cp310-win_amd64.whl", hash = "sha256:4bf0edb24c128b7be0c61cd17eef432e4bef507013292415f3fb7023f02b7d4b"},
{file = "sqlalchemy-2.0.43-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:52d9b73b8fb3e9da34c2b31e6d99d60f5f99fd8c1225c9dad24aeb74a91e1d29"},
{file = "sqlalchemy-2.0.43-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f42f23e152e4545157fa367b2435a1ace7571cab016ca26038867eb7df2c3631"},
{file = "sqlalchemy-2.0.43-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fb1a8c5438e0c5ea51afe9c6564f951525795cf432bed0c028c1cb081276685"},
{file = "sqlalchemy-2.0.43-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db691fa174e8f7036afefe3061bc40ac2b770718be2862bfb03aabae09051aca"},
{file = "sqlalchemy-2.0.43-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe2b3b4927d0bc03d02ad883f402d5de201dbc8894ac87d2e981e7d87430e60d"},
{file = "sqlalchemy-2.0.43-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d3d9b904ad4a6b175a2de0738248822f5ac410f52c2fd389ada0b5262d6a1e3"},
{file = "sqlalchemy-2.0.43-cp311-cp311-win32.whl", hash = "sha256:5cda6b51faff2639296e276591808c1726c4a77929cfaa0f514f30a5f6156921"},
{file = "sqlalchemy-2.0.43-cp311-cp311-win_amd64.whl", hash = "sha256:c5d1730b25d9a07727d20ad74bc1039bbbb0a6ca24e6769861c1aa5bf2c4c4a8"},
{file = "sqlalchemy-2.0.43-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:20d81fc2736509d7a2bd33292e489b056cbae543661bb7de7ce9f1c0cd6e7f24"},
{file = "sqlalchemy-2.0.43-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b9fc27650ff5a2c9d490c13c14906b918b0de1f8fcbb4c992712d8caf40e83"},
{file = "sqlalchemy-2.0.43-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6772e3ca8a43a65a37c88e2f3e2adfd511b0b1da37ef11ed78dea16aeae85bd9"},
{file = "sqlalchemy-2.0.43-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a113da919c25f7f641ffbd07fbc9077abd4b3b75097c888ab818f962707eb48"},
{file = "sqlalchemy-2.0.43-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4286a1139f14b7d70141c67a8ae1582fc2b69105f1b09d9573494eb4bb4b2687"},
{file = "sqlalchemy-2.0.43-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:529064085be2f4d8a6e5fab12d36ad44f1909a18848fcfbdb59cc6d4bbe48efe"},
{file = "sqlalchemy-2.0.43-cp312-cp312-win32.whl", hash = "sha256:b535d35dea8bbb8195e7e2b40059e2253acb2b7579b73c1b432a35363694641d"},
{file = "sqlalchemy-2.0.43-cp312-cp312-win_amd64.whl", hash = "sha256:1c6d85327ca688dbae7e2b06d7d84cfe4f3fffa5b5f9e21bb6ce9d0e1a0e0e0a"},
{file = "sqlalchemy-2.0.43-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e7c08f57f75a2bb62d7ee80a89686a5e5669f199235c6d1dac75cd59374091c3"},
{file = "sqlalchemy-2.0.43-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14111d22c29efad445cd5021a70a8b42f7d9152d8ba7f73304c4d82460946aaa"},
{file = "sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21b27b56eb2f82653168cefe6cb8e970cdaf4f3a6cb2c5e3c3c1cf3158968ff9"},
{file = "sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c5a9da957c56e43d72126a3f5845603da00e0293720b03bde0aacffcf2dc04f"},
{file = "sqlalchemy-2.0.43-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d79f9fdc9584ec83d1b3c75e9f4595c49017f5594fee1a2217117647225d738"},
{file = "sqlalchemy-2.0.43-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9df7126fd9db49e3a5a3999442cc67e9ee8971f3cb9644250107d7296cb2a164"},
{file = "sqlalchemy-2.0.43-cp313-cp313-win32.whl", hash = "sha256:7f1ac7828857fcedb0361b48b9ac4821469f7694089d15550bbcf9ab22564a1d"},
{file = "sqlalchemy-2.0.43-cp313-cp313-win_amd64.whl", hash = "sha256:971ba928fcde01869361f504fcff3b7143b47d30de188b11c6357c0505824197"},
{file = "sqlalchemy-2.0.43-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4e6aeb2e0932f32950cf56a8b4813cb15ff792fc0c9b3752eaf067cfe298496a"},
{file = "sqlalchemy-2.0.43-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:61f964a05356f4bca4112e6334ed7c208174511bd56e6b8fc86dad4d024d4185"},
{file = "sqlalchemy-2.0.43-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46293c39252f93ea0910aababa8752ad628bcce3a10d3f260648dd472256983f"},
{file = "sqlalchemy-2.0.43-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:136063a68644eca9339d02e6693932116f6a8591ac013b0014479a1de664e40a"},
{file = "sqlalchemy-2.0.43-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6e2bf13d9256398d037fef09fd8bf9b0bf77876e22647d10761d35593b9ac547"},
{file = "sqlalchemy-2.0.43-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:44337823462291f17f994d64282a71c51d738fc9ef561bf265f1d0fd9116a782"},
{file = "sqlalchemy-2.0.43-cp38-cp38-win32.whl", hash = "sha256:13194276e69bb2af56198fef7909d48fd34820de01d9c92711a5fa45497cc7ed"},
{file = "sqlalchemy-2.0.43-cp38-cp38-win_amd64.whl", hash = "sha256:334f41fa28de9f9be4b78445e68530da3c5fa054c907176460c81494f4ae1f5e"},
{file = "sqlalchemy-2.0.43-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ceb5c832cc30663aeaf5e39657712f4c4241ad1f638d487ef7216258f6d41fe7"},
{file = "sqlalchemy-2.0.43-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11f43c39b4b2ec755573952bbcc58d976779d482f6f832d7f33a8d869ae891bf"},
{file = "sqlalchemy-2.0.43-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:413391b2239db55be14fa4223034d7e13325a1812c8396ecd4f2c08696d5ccad"},
{file = "sqlalchemy-2.0.43-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c379e37b08c6c527181a397212346be39319fb64323741d23e46abd97a400d34"},
{file = "sqlalchemy-2.0.43-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:03d73ab2a37d9e40dec4984d1813d7878e01dbdc742448d44a7341b7a9f408c7"},
{file = "sqlalchemy-2.0.43-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8cee08f15d9e238ede42e9bbc1d6e7158d0ca4f176e4eab21f88ac819ae3bd7b"},
{file = "sqlalchemy-2.0.43-cp39-cp39-win32.whl", hash = "sha256:b3edaec7e8b6dc5cd94523c6df4f294014df67097c8217a89929c99975811414"},
{file = "sqlalchemy-2.0.43-cp39-cp39-win_amd64.whl", hash = "sha256:227119ce0a89e762ecd882dc661e0aa677a690c914e358f0dd8932a2e8b2765b"},
{file = "sqlalchemy-2.0.43-py3-none-any.whl", hash = "sha256:1681c21dd2ccee222c2fe0bef671d1aef7c504087c9c4e800371cfcc8ac966fc"},
{file = "sqlalchemy-2.0.43.tar.gz", hash = "sha256:788bfcef6787a7764169cfe9859fe425bf44559619e1d9f56f5bddf2ebf6f417"},
]
[package.dependencies]
aiomysql = {version = ">=0.2.0", optional = true, markers = "extra == \"aiomysql\""}
greenlet = {version = "!=0.4.17", optional = true, markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") or extra == \"aiomysql\""}
greenlet = {version = ">=1", optional = true, markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") or extra == \"aiomysql\""}
pymysql = {version = "*", optional = true, markers = "extra == \"pymysql\""}
typing-extensions = ">=4.6.0"
[package.extras]
aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"]
aioodbc = ["aioodbc", "greenlet (!=0.4.17)"]
aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"]
asyncio = ["greenlet (!=0.4.17)"]
asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"]
aiomysql = ["aiomysql (>=0.2.0)", "greenlet (>=1)"]
aioodbc = ["aioodbc", "greenlet (>=1)"]
aiosqlite = ["aiosqlite", "greenlet (>=1)", "typing_extensions (!=3.10.0.1)"]
asyncio = ["greenlet (>=1)"]
asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (>=1)"]
mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10)"]
mssql = ["pyodbc"]
mssql-pymssql = ["pymssql"]
@@ -1503,7 +1522,7 @@ mysql-connector = ["mysql-connector-python"]
oracle = ["cx_oracle (>=8)"]
oracle-oracledb = ["oracledb (>=1.0.1)"]
postgresql = ["psycopg2 (>=2.7)"]
postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"]
postgresql-asyncpg = ["asyncpg", "greenlet (>=1)"]
postgresql-pg8000 = ["pg8000 (>=1.29.1)"]
postgresql-psycopg = ["psycopg (>=3.0.7)"]
postgresql-psycopg2binary = ["psycopg2-binary"]
@@ -1933,4 +1952,4 @@ propcache = ">=0.2.0"
[metadata]
lock-version = "2.1"
python-versions = ">=3.11,<4.0"
content-hash = "5ed129fde2c5d7b3518fbb2fe2ce79f0bad2aa1060304d45a7bc26d35f7ab46b"
content-hash = "0ac41110571b3bba38672fceef1c107864b8582378c6afba6418e05f99a75da1"

View File

@@ -17,6 +17,7 @@ dependencies = [
"pydantic[email] (>=2.11.3,<3.0.0)",
"python-multipart (>=0.0.20,<0.0.21)",
"fastapi-jwt-auth @ git+https://github.com/vvpreo/fastapi-jwt-auth",
"core-library @ git+https://gitea.heado.ru/Vorkout/core.git@0.1.0",
]

234
client/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "client",
"version": "0.0.5",
"version": "0.0.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "client",
"version": "0.0.5",
"version": "0.0.3",
"dependencies": {
"@ant-design/icons": "^5.6.1",
"@testing-library/dom": "^10.4.0",
@@ -16,7 +16,6 @@
"@types/jest": "^27.5.2",
"@types/react": "^19.0.11",
"@types/react-dom": "^19.0.4",
"@xyflow/react": "^12.8.1",
"antd": "^5.24.7",
"axios": "^1.9.0",
"axios-retry": "^4.5.0",
@@ -1568,55 +1567,6 @@
"@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": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -1683,66 +1633,6 @@
"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": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -2270,12 +2160,6 @@
"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": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
@@ -2456,111 +2340,6 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"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": {
"version": "1.11.13",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
@@ -5328,15 +5107,6 @@
"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": {
"version": "0.12.5",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",

View File

@@ -11,7 +11,6 @@
"@types/jest": "^27.5.2",
"@types/react": "^19.0.11",
"@types/react-dom": "^19.0.4",
"@xyflow/react": "^12.8.1",
"antd": "^5.24.7",
"axios": "^1.9.0",
"axios-retry": "^4.5.0",

View File

@@ -1,10 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 828 B

View File

@@ -1,3 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 198 B

View File

@@ -1,8 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,175 @@
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>
);
}

View File

@@ -2,8 +2,8 @@ import { useUserSelector } from '@/store/userStore';
import { Avatar } from 'antd';
import Title from 'antd/es/typography/Title';
import { useState } from 'react';
import ContentDrawer from './drawers/ContentDrawer';
import UserEdit from './drawers/users/UserEdit';
import ContentDrawer from './ContentDrawer';
import UserEdit from './UserEdit';
interface HeaderProps {
title: string;
@@ -68,7 +68,7 @@ export default function Header({ title, additionalContent }: HeaderProps) {
email={user?.email}
open={openEdit}
closeDrawer={closeEditDrawer}
type="userEdit"
type="edit"
>
{user?.id && <UserEdit closeDrawer={closeEditDrawer} userId={user?.id} />}
</ContentDrawer>

View File

@@ -1,281 +0,0 @@
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>
);
}

View File

@@ -1,245 +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: "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>
);
}

View File

@@ -1,213 +0,0 @@
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 };

View File

@@ -1,17 +0,0 @@
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;

View File

@@ -1,63 +0,0 @@
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>
);
}

View File

@@ -1,119 +0,0 @@
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>
</>
);
}

View File

@@ -1,83 +0,0 @@
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>
);
}

View File

@@ -1,34 +0,0 @@
// 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,
};

View File

@@ -1,119 +1,99 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: "en",
supportedLngs: ["en", "ru"],
fallbackLng: 'en',
supportedLngs: ['en', 'ru'],
interpolation: { escapeValue: false },
resources: {
en: {
translation: {
accounts: "Accounts",
processDiagrams: "Process diagrams",
runningProcesses: "Running processes",
settings: "Settings",
eventsList: "Events list",
configuration: "Configuration",
selectPhoto: "Select photo",
name: "Name",
login: "Login",
password: "Password",
email: "Email",
tenant: "Tenant",
role: "Role",
status: "Status",
nameMessage: "Enter name",
loginMessage: "Enter login",
passwordMessage: "Enter password",
emailMessage: "Enter email",
emailErrorMessage: "Incorrect email",
tenantMessage: "Enter tenant",
roleMessage: "Choose role",
statusMessage: "Choose status",
addAccount: "Add account",
save: "Save changes",
newAccount: "New account",
ACTIVE: "Active",
DISABLED: "Disabled",
BLOCKED: "Blocked",
DELETED: "Deleted",
OWNER: "Owner",
ADMIN: "Admin",
EDITOR: "Editor",
VIEWER: "Viewer",
nameLogin: "Name, login",
createdAt: "Created",
saving: "Saving...",
createdAccountMessage: "User successfully created!",
editAccountMessage: "User successfully updated!",
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",
accounts: 'Accounts',
processDiagrams: 'Process diagrams',
runningProcesses: 'Running processes',
settings: 'Settings',
eventsList: 'Events list',
configuration: 'Configuration',
selectPhoto: 'Select photo',
name: 'Name',
login: 'Login',
password: 'Password',
email: 'Email',
tenant: 'Tenant',
role: 'Role',
status: 'Status',
nameMessage: 'Enter name',
loginMessage: 'Enter login',
passwordMessage: 'Enter password',
emailMessage: 'Enter email',
emailErrorMessage: 'Incorrect email',
tenantMessage: 'Enter tenant',
roleMessage: 'Choose role',
statusMessage: 'Choose status',
addAccount: 'Add account',
save: 'Save changes',
newAccount: 'New account',
ACTIVE: 'Active',
DISABLED: 'Disabled',
BLOCKED: 'Blocked',
DELETED: 'Deleted',
OWNER: 'Owner',
ADMIN: 'Admin',
EDITOR: 'Editor',
VIEWER: 'Viewer',
nameLogin: 'Name, login',
createdAt: 'Created',
saving: 'Saving...',
createdAccountMessage: 'User successfully created!',
editAccountMessage: 'User successfully updated!',
you: '(You)',
},
},
ru: {
translation: {
accounts: "Учетные записи",
processDiagrams: "Схемы процессов",
runningProcesses: "Запущенные процессы",
settings: "Настройки",
eventsList: "Справочкин событий",
configuration: "Конфигурация",
selectPhoto: "Выбрать фото",
name: "Имя",
login: "Логин",
password: "Пароль",
email: "Имейл",
tenant: "Привязка",
role: "Роль",
status: "Статус",
nameMessage: "Введите имя",
loginMessage: "Введите логин",
passwordMessage: "Введите пароль",
emailMessage: "Введите имейл",
emailErrorMessage: "Некорректный имейл",
tenantMessage: "Введите привязку",
roleMessage: "Выберите роль",
statusMessage: "Выберите статус",
addAccount: "Добавить аккаунт",
save: "Сохранить изменения",
newAccount: "Новая учетная запись",
ACTIVE: "Активен",
DISABLED: "Выключен",
BLOCKED: "Заблокирован",
DELETED: "Удален",
OWNER: "Владелец",
ADMIN: "Админ",
EDITOR: "Редактор",
VIEWER: "Наблюдатель",
nameLogin: "Имя, Логин",
createdAt: "Создано",
saving: "Сохранение...",
createdAccountMessage: "Пользователь успешно создан!",
editAccountMessage: "Пользователь успешно обновлен!",
you: "(Вы)",
// nodes
startNode: "Запуск",
startNodeDescription: "Включение",
connectToExisting: "Подключить к существующей",
editNode: "Редактирование блока",
connectTo: `Подключить к {{nodeId}}`,
ifElseNode: "ЕСЛИ - ТО",
conditionIf: "Если {{condition}}, то",
conditionElse: "Иначе",
appropriationNode: "Присвоение",
accounts: 'Учетные записи',
processDiagrams: 'Схемы процессов',
runningProcesses: 'Запущенные процессы',
settings: 'Настройки',
eventsList: 'Справочкин событий',
configuration: 'Конфигурация',
selectPhoto: 'Выбрать фото',
name: 'Имя',
login: 'Логин',
password: 'Пароль',
email: 'Имейл',
tenant: 'Привязка',
role: 'Роль',
status: 'Статус',
nameMessage: 'Введите имя',
loginMessage: 'Введите логин',
passwordMessage: 'Введите пароль',
emailMessage: 'Введите имейл',
emailErrorMessage: 'Некорректный имейл',
tenantMessage: 'Введите привязку',
roleMessage: 'Выберите роль',
statusMessage: 'Выберите статус',
addAccount: 'Добавить аккаунт',
save: 'Сохранить изменения',
newAccount: 'Новая учетная запись',
ACTIVE: 'Активен',
DISABLED: 'Выключен',
BLOCKED: 'Заблокирован',
DELETED: 'Удален',
OWNER: 'Владелец',
ADMIN: 'Админ',
EDITOR: 'Редактор',
VIEWER: 'Наблюдатель',
nameLogin: 'Имя, Логин',
createdAt: 'Создано',
saving: 'Сохранение...',
createdAccountMessage: 'Пользователь успешно создан!',
editAccountMessage: 'Пользователь успешно обновлен!',
you: '(Вы)',
},
},
},

View File

@@ -1,12 +1,11 @@
import React from "react";
import ReactDOM from "react-dom/client";
import "@/index.css";
import App from "@/App";
import AppWrapper from "@/config/AppWrapper";
import "@xyflow/react/dist/style.css";
import React from 'react';
import ReactDOM from 'react-dom/client';
import '@/index.css';
import App from '@/App';
import AppWrapper from '@/config/AppWrapper';
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
document.getElementById('root') as HTMLElement
);
root.render(

View File

@@ -2,12 +2,12 @@ import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { AccountStatus, AllUser, AllUserResponse } from "@/types/user";
import Header from "@/components/Header";
import ContentDrawer from "@/components/drawers/ContentDrawer";
import UserCreate from "@/components/drawers/users/UserCreate";
import ContentDrawer from "@/components/ContentDrawer";
import UserCreate from "@/components/UserCreate";
import { Avatar, Table } from "antd";
import { TableProps } from "antd/lib";
import { UserService } from "@/services/userService";
import UserEdit from "@/components/drawers/users/UserEdit";
import UserEdit from "@/components/UserEdit";
import { useSearchParams } from "react-router-dom";
export default function AccountsPage() {
@@ -201,7 +201,7 @@ export default function AccountsPage() {
<ContentDrawer
open={openCreate}
closeDrawer={closeCreateDrawer}
type="userCreate"
type="create"
>
<UserCreate getUsers={getUsers} closeDrawer={closeCreateDrawer} />
</ContentDrawer>
@@ -211,7 +211,7 @@ export default function AccountsPage() {
email={activeAccount?.email}
open={openEdit}
closeDrawer={closeEditDrawer}
type="userEdit"
type="edit"
>
<UserEdit userId={activeAccount?.id} closeDrawer={closeEditDrawer} />
</ContentDrawer>

View File

@@ -1,22 +1,22 @@
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useEffect, useState } from "react";
import { Layout } from "antd";
import Sider from "antd/es/layout/Sider";
import { Route, Routes, useLocation, useNavigate } from "react-router-dom";
import SiderMenu from "@/components/SiderMenu";
import ProcessDiagramPage from "./ProcessDiagramPage";
import AccountsPage from "./AccountsPage";
import ConfigurationPage from "./ConfigurationPage";
import EventsListPage from "./EventsListPage";
import RunningProcessesPage from "./RunningProcessesPage";
import React, { useEffect, useState } from 'react';
import { Layout } from 'antd';
import Sider from 'antd/es/layout/Sider';
import { Route, Routes, useLocation, useNavigate } from 'react-router-dom';
import SiderMenu from '@/components/SiderMenu';
import ProcessDiagramPage from './ProcessDiagramPage';
import RunningProcessesPage from './RunningProcessesPage';
import AccountsPage from './AccountsPage';
import EventsListPage from './EventsListPage';
import ConfigurationPage from './ConfigurationPage';
export default function MainLayout() {
const navigate = useNavigate();
const location = useLocation();
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 calculateWidths = () => {
@@ -29,24 +29,24 @@ export default function MainLayout() {
useEffect(() => {
calculateWidths();
window.addEventListener("resize", calculateWidths);
return () => window.removeEventListener("resize", calculateWidths);
window.addEventListener('resize', calculateWidths);
return () => window.removeEventListener('resize', calculateWidths);
}, []);
useEffect(() => {
if (location.pathname === "/") {
navigate("/process-diagram");
if (location.pathname === '/') {
navigate('/process-diagram');
}
setSelectedKey(location.pathname);
}, [location.pathname]);
function hangleMenuClick(e: any) {
const key = e.key;
if (key === "toggle") {
if (key === 'toggle') {
setCollapsed(!collapsed);
return;
}
if (key === "divider") {
if (key === 'divider') {
return;
}
@@ -55,7 +55,7 @@ export default function MainLayout() {
}
return (
<Layout style={{ minHeight: "100vh" }}>
<Layout style={{ minHeight: '100vh' }}>
<Sider
className="sider"
collapsible

View File

@@ -1,33 +1,11 @@
import Header from "@/components/Header";
import { useTranslation } from "react-i18next";
import ReactFlowDrawer from "@/components/ReactFlowDrawer";
import { useState } from "react";
import ContentDrawer from "@/components/drawers/ContentDrawer";
import Header from '@/components/Header';
import { useTranslation } from 'react-i18next';
export default function ProcessDiagramPage() {
const { t } = useTranslation();
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const showDrawer = () => setIsDrawerOpen(true);
const closeDrawer = () => {
setIsDrawerOpen(false);
};
return (
<>
<Header title={t("processDiagrams")} />
<div style={{ width: "100%", height: "100%" }}>
<ReactFlowDrawer showDrawer={showDrawer} />
</div>
<ContentDrawer
open={isDrawerOpen}
closeDrawer={closeDrawer}
children={undefined}
type={"nodeEdit"}
></ContentDrawer>
<Header title={t('processDiagrams')} />
</>
);
}

View File

@@ -1,25 +0,0 @@
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: [],
}))
);

View File

@@ -1,8 +0,0 @@
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 };