chore(frontend,backend): add migrations; file cleanup; add redux reducer for dashboard state
This commit is contained in:
73
.env.example
73
.env.example
@@ -1,73 +0,0 @@
|
||||
PROJECT_NAME=osintbuddy
|
||||
ENVIRONMENT=production
|
||||
BASE_URL=http://localhost:8000
|
||||
|
||||
##########################################
|
||||
# Error reporting! (TODO: Wait for sentry to finish implementing their fixes for Python 3.11.4)
|
||||
# Remove if you don't want application errors
|
||||
# reported to the project maintainers :(
|
||||
##########################################
|
||||
SENTRY_DSN=https://c5f217ca357c468cbb7cfe663318018f@o567628.ingest.sentry.io/4505363615711232
|
||||
|
||||
##########################################
|
||||
# Port configuration
|
||||
##########################################
|
||||
FRONTEND_PORT=3000
|
||||
BACKEND_PORT=8000
|
||||
REDIS_PORT=6379
|
||||
FLOWER_PORT=5555
|
||||
|
||||
##########################################
|
||||
# S3/minio configuration (todo: add option for seaweedfs for a prod version eventually)
|
||||
##########################################
|
||||
MINIO_ROOT_USER=minio
|
||||
MINIO_ROOT_PASSWORD=minio
|
||||
|
||||
##########################################
|
||||
# PostgreSQL configuration
|
||||
##########################################
|
||||
POSTGRES_IMAGE=postgres:15.4
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_SERVER=db
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=password
|
||||
POSTGRES_DB=app
|
||||
PGDATA=/var/lib/postgresql/data/pgdata
|
||||
|
||||
##########################################
|
||||
# Scylla,JanusGraph,Elastic configuration
|
||||
##########################################
|
||||
SCYLLADATA=/var/lib/scylla
|
||||
|
||||
SDB_REST_PORT=10000
|
||||
CQL_PORT=9042
|
||||
THRIFT_PORT=9160
|
||||
INTERNODE_PORT=7000
|
||||
INTERNODE_ONE_PORT=7001
|
||||
JMX_PORT=7199
|
||||
SOLR_PORT=8983
|
||||
|
||||
JANUS_IMAGE=janusgraph/janusgraph:1.0.0-rc2
|
||||
JANUSGRAPH_URL=ws://janus:8182
|
||||
|
||||
##########################################
|
||||
# Worker configuration
|
||||
##########################################
|
||||
CELERY_BROKER_URL=redis://redis:6379/0
|
||||
# CELERY_BROKER_URL=redis://redis:6379//
|
||||
CELERY_BACKEND=redis://redis:6379/1
|
||||
CELERY_ENABLE_UTC=true
|
||||
FLOWER_BASIC_AUTH=admin:password
|
||||
CLEARLY_PORT=12223
|
||||
|
||||
##########################################
|
||||
# FastAPI configuration
|
||||
##########################################
|
||||
BACKEND_CORS_ORIGINS=["https://api.YOUR_WEBSITE.com","https://YOUR_WEBSITE.com","http://YOUR_WEBSITE.com","http://localhost:3000","http://localhost","http://local.host:3000","*"]
|
||||
# Run: `openssl rand -hex 32` and replace the below with your output
|
||||
SECRET_KEY=d7c612d43c083f72f98a5ed6d3dc275516f0ac6ebdcc5fdb8c563a29cd114199
|
||||
|
||||
##########################################
|
||||
# Development
|
||||
##########################################
|
||||
VENV_PATH=venv/bin
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,7 +1,4 @@
|
||||
.vscode
|
||||
.env
|
||||
venv
|
||||
.venv
|
||||
docs.osintbuddy.com
|
||||
data
|
||||
elastic
|
||||
.venv
|
||||
@@ -29,11 +29,13 @@
|
||||
can be explored step-by-step. An easy-to-use plugin system allows any
|
||||
Python developer to quickly integrate new data sources.
|
||||
</p>
|
||||
|
||||
> 🚧 *Work in progress*
|
||||
|
||||
<br/>
|
||||
|
||||

|
||||
|
||||
|
||||
---
|
||||
</p>
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
"""projects to graphs
|
||||
|
||||
Revision ID: 5260b5f19250
|
||||
Revises: d51c30bacade
|
||||
Create Date: 2023-10-24 18:08:13.190355
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '5260b5f19250'
|
||||
down_revision = 'd51c30bacade'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table('graphs',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('uuid', sa.UUID(), nullable=True),
|
||||
sa.Column('name', sa.String(), nullable=False),
|
||||
sa.Column('description', sa.String(length=512), nullable=True),
|
||||
sa.Column('is_favorite', sa.Boolean(), nullable=False),
|
||||
sa.Column('last_seen', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('updated', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('created', sa.DateTime(), server_default=sa.text('now()'), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_graphs_uuid'), 'graphs', ['uuid'], unique=False)
|
||||
op.drop_index('ix_projects_uuid', table_name='projects')
|
||||
op.drop_table('project_entities')
|
||||
op.drop_table('projects')
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.create_table('project_entities',
|
||||
sa.Column('project_id', sa.INTEGER(), autoincrement=False, nullable=False),
|
||||
sa.Column('entity_id', sa.INTEGER(), autoincrement=False, nullable=False),
|
||||
sa.ForeignKeyConstraint(['entity_id'], ['entities.id'], name='project_entities_entity_id_fkey'),
|
||||
sa.ForeignKeyConstraint(['project_id'], ['projects.id'], name='project_entities_project_id_fkey'),
|
||||
sa.PrimaryKeyConstraint('project_id', 'entity_id', name='project_entities_pkey')
|
||||
)
|
||||
op.create_table('projects',
|
||||
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
|
||||
sa.Column('uuid', sa.UUID(), autoincrement=False, nullable=True),
|
||||
sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=False),
|
||||
sa.Column('description', sa.VARCHAR(length=512), autoincrement=False, nullable=True),
|
||||
sa.Column('updated', postgresql.TIMESTAMP(), server_default=sa.text('now()'), autoincrement=False, nullable=False),
|
||||
sa.Column('created', postgresql.TIMESTAMP(), server_default=sa.text('now()'), autoincrement=False, nullable=True),
|
||||
sa.Column('is_favorite', sa.BOOLEAN(), autoincrement=False, nullable=False),
|
||||
sa.Column('last_seen', postgresql.TIMESTAMP(), server_default=sa.text('now()'), autoincrement=False, nullable=False),
|
||||
sa.PrimaryKeyConstraint('id', name='projects_pkey')
|
||||
)
|
||||
op.create_index('ix_projects_uuid', 'projects', ['uuid'], unique=False)
|
||||
op.drop_index(op.f('ix_graphs_uuid'), table_name='graphs')
|
||||
op.drop_table('graphs')
|
||||
@@ -0,0 +1,62 @@
|
||||
"""add username user
|
||||
|
||||
Revision ID: 9a20076792f7
|
||||
Revises: 5260b5f19250
|
||||
Create Date: 2023-10-28 01:03:22.221486
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '9a20076792f7'
|
||||
down_revision = '5260b5f19250'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column('user', sa.Column('username', sa.String(length=80), nullable=False))
|
||||
op.alter_column('user', 'full_name',
|
||||
existing_type=sa.VARCHAR(),
|
||||
nullable=False)
|
||||
op.alter_column('user', 'is_active',
|
||||
existing_type=sa.BOOLEAN(),
|
||||
nullable=False)
|
||||
op.alter_column('user', 'is_superuser',
|
||||
existing_type=sa.BOOLEAN(),
|
||||
nullable=False)
|
||||
op.alter_column('user', 'modified',
|
||||
existing_type=postgresql.TIMESTAMP(timezone=True),
|
||||
nullable=False,
|
||||
existing_server_default=sa.text('now()'))
|
||||
op.alter_column('user', 'created',
|
||||
existing_type=postgresql.TIMESTAMP(timezone=True),
|
||||
nullable=False,
|
||||
existing_server_default=sa.text('now()'))
|
||||
op.drop_index('ix_user_id', table_name='user')
|
||||
op.create_index(op.f('ix_user_username'), 'user', ['username'], unique=False)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_index(op.f('ix_user_username'), table_name='user')
|
||||
op.create_index('ix_user_id', 'user', ['id'], unique=False)
|
||||
op.alter_column('user', 'created',
|
||||
existing_type=postgresql.TIMESTAMP(timezone=True),
|
||||
nullable=True,
|
||||
existing_server_default=sa.text('now()'))
|
||||
op.alter_column('user', 'modified',
|
||||
existing_type=postgresql.TIMESTAMP(timezone=True),
|
||||
nullable=True,
|
||||
existing_server_default=sa.text('now()'))
|
||||
op.alter_column('user', 'is_superuser',
|
||||
existing_type=sa.BOOLEAN(),
|
||||
nullable=True)
|
||||
op.alter_column('user', 'is_active',
|
||||
existing_type=sa.BOOLEAN(),
|
||||
nullable=True)
|
||||
op.alter_column('user', 'full_name',
|
||||
existing_type=sa.VARCHAR(),
|
||||
nullable=True)
|
||||
op.drop_column('user', 'username')
|
||||
@@ -0,0 +1,51 @@
|
||||
"""add disabled user
|
||||
|
||||
Revision ID: fab5d9c099df
|
||||
Revises: 9a20076792f7
|
||||
Create Date: 2023-10-28 01:13:44.024665
|
||||
|
||||
"""
|
||||
import os
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
from sqlalchemy_utils import database_exists, create_database, drop_database
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'fab5d9c099df'
|
||||
down_revision = '9a20076792f7'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
def get_url(db_name: str = None):
|
||||
user = os.getenv("POSTGRES_USER", "postgres")
|
||||
password = os.getenv("POSTGRES_PASSWORD", "password")
|
||||
server = os.getenv("POSTGRES_SERVER", "db")
|
||||
db = os.getenv("POSTGRES_DB", "app")
|
||||
if isinstance(db_name, str) and db_name is not None:
|
||||
db = db_name
|
||||
return f"postgresql://{user}:{password}@{server}/{db}"
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column('user', sa.Column('disabled', sa.Boolean(), nullable=False))
|
||||
# Casdoor supports PostgreSQL...
|
||||
engine = sa.create_engine(get_url(db_name='casdoor'))
|
||||
if not database_exists(engine.url):
|
||||
create_database(engine.url)
|
||||
create_user_sql = sa.text((
|
||||
f"CREATE USER {sa.quoted_name(os.getenv('CASDOOR_PG_USER', 'casdoor'), False)} "
|
||||
"WITH SUPERUSER PASSWORD :database_password")).bindparams(
|
||||
database_password=os.getenv("CASDOOR_PG_PASSWORD", "casdoorpassword")
|
||||
).compile(compile_kwargs={"literal_binds": True})
|
||||
conn = engine.connect()
|
||||
conn.execute(create_user_sql)
|
||||
conn.commit()
|
||||
|
||||
def downgrade():
|
||||
op.drop_column('user', 'disabled')
|
||||
# https://casdoor.org/docs/basic/server-installation/#configure-database
|
||||
engine = sa.create_engine(get_url(db_name='casdoor'))
|
||||
if database_exists(engine.url):
|
||||
drop_database(engine.url)
|
||||
@@ -1,6 +1,6 @@
|
||||
from fastapi import APIRouter, Header, Depends
|
||||
|
||||
from app.api.api_v1.endpoints import graphs, nodes, entities, users, login, scans
|
||||
from app.api.api_v1.endpoints import graphs, nodes, entities, login, scans
|
||||
|
||||
api_router = APIRouter()
|
||||
|
||||
@@ -9,4 +9,3 @@ api_router.include_router(scans.router, tags=["Scans"])
|
||||
api_router.include_router(entities.router, tags=["Entities"])
|
||||
api_router.include_router(graphs.router, tags=["Graphs"])
|
||||
api_router.include_router(login.router, tags=["login"])
|
||||
api_router.include_router(users.router, tags=["users"])
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import datetime
|
||||
from uuid import UUID
|
||||
from typing import Annotated
|
||||
import json
|
||||
import ujson
|
||||
from fastapi import (
|
||||
@@ -21,8 +22,9 @@ log = get_logger("api_v1.endpoints.entities")
|
||||
router = APIRouter(prefix="/entities")
|
||||
|
||||
|
||||
@router.get('/{entity_uuid}', operation_id="get_entity")
|
||||
@router.get("/{entity_uuid}")
|
||||
async def get_entity(
|
||||
user: Annotated[schemas.CasdoorUser, Depends(deps.get_user_from_session)],
|
||||
entity_uuid: str,
|
||||
db: Session = Depends(deps.get_db),
|
||||
):
|
||||
@@ -30,11 +32,9 @@ async def get_entity(
|
||||
return entities
|
||||
|
||||
|
||||
@router.post(
|
||||
'',
|
||||
operation_id="create_entity"
|
||||
)
|
||||
@router.post("")
|
||||
async def create_entity(
|
||||
user: Annotated[schemas.CasdoorUser, Depends(deps.get_user_from_session)],
|
||||
entity: schemas.PostEntityCreate,
|
||||
db: Session = Depends(deps.get_db)
|
||||
):
|
||||
@@ -45,11 +45,9 @@ async def create_entity(
|
||||
))
|
||||
|
||||
|
||||
@router.put(
|
||||
'/{entity_id}',
|
||||
operation_id="update_entity_by_uuid"
|
||||
)
|
||||
async def update_entity(
|
||||
@router.put("/{entity_id}")
|
||||
async def update_entity_by_uuid(
|
||||
user: Annotated[schemas.CasdoorUser, Depends(deps.get_user_from_session)],
|
||||
entity_id: str,
|
||||
obj_in: schemas.EntityBase,
|
||||
db: Session = Depends(deps.get_db)
|
||||
@@ -59,11 +57,9 @@ async def update_entity(
|
||||
return entity
|
||||
|
||||
|
||||
@router.get(
|
||||
'',
|
||||
operation_id="get_entities",
|
||||
)
|
||||
async def get_many_entites_by_favorite(
|
||||
@router.get("")
|
||||
async def get_entities(
|
||||
user: Annotated[schemas.CasdoorUser, Depends(deps.get_user_from_session)],
|
||||
db: Session = Depends(deps.get_db),
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
@@ -87,10 +83,10 @@ async def get_many_entites_by_favorite(
|
||||
|
||||
|
||||
@router.delete(
|
||||
'/{entity_id}',
|
||||
operation_id="delete_entity"
|
||||
"/{entity_id}",
|
||||
)
|
||||
async def delete_entity(
|
||||
user: Annotated[schemas.CasdoorUser, Depends(deps.get_user_from_session)],
|
||||
entity_id: str,
|
||||
db: Session = Depends(deps.get_db),
|
||||
):
|
||||
@@ -100,8 +96,9 @@ async def delete_entity(
|
||||
raise HTTPException(status_code=422, detail='entity_id is a required field')
|
||||
|
||||
|
||||
@router.put('/{entity_id}/favorite', operation_id="update_favorite_entity_uuid")
|
||||
async def update_entity_favorite(
|
||||
@router.put("/{entity_id}/favorite")
|
||||
async def update_favorite_entity_uuid(
|
||||
user: Annotated[schemas.CasdoorUser, Depends(deps.get_user_from_session)],
|
||||
entity_id: str,
|
||||
is_favorite: bool = False,
|
||||
db: Session = Depends(deps.get_db),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import uuid
|
||||
import asyncio
|
||||
|
||||
from typing import List, Any, Annotated
|
||||
import boto3
|
||||
from fastapi import (
|
||||
APIRouter,
|
||||
@@ -14,108 +14,105 @@ from sqlalchemy.orm import Session
|
||||
from app.api import deps
|
||||
from app import crud, schemas
|
||||
from app.core.logger import get_logger
|
||||
from app.db.janus import ProjectGraphConnection
|
||||
from app.db.janus import ProjectGraphConnection, janus_create_db
|
||||
|
||||
log = get_logger("api_v1.endpoints.graphs")
|
||||
router = APIRouter(prefix="/graphs")
|
||||
|
||||
|
||||
@router.get('/{graph_id}', operation_id="get_graph")
|
||||
async def get_project(
|
||||
@router.get(
|
||||
'/{graph_id}',
|
||||
response_model=schemas.Graph
|
||||
)
|
||||
async def get_graph(
|
||||
user: Annotated[schemas.CasdoorUser, Depends(deps.get_user_from_session)],
|
||||
graph_id: str,
|
||||
db: Session = Depends(deps.get_db),
|
||||
):
|
||||
graph_project = crud.projects.get_by_uuid(
|
||||
graph = crud.graphs.get_by_uuid(
|
||||
db=db,
|
||||
uuid=graph_id
|
||||
)
|
||||
return {"graph": graph_project}
|
||||
return graph
|
||||
|
||||
|
||||
@router.put('/{graph_id}/favorite', operation_id="update_favorite_graph_uuid")
|
||||
async def get_project(
|
||||
@router.put('/{graph_id}/favorite')
|
||||
async def update_favorite_graph_uuid(
|
||||
user: Annotated[schemas.CasdoorUser, Depends(deps.get_user_from_session)],
|
||||
graph_id: str,
|
||||
is_favorite: bool = False,
|
||||
db: Session = Depends(deps.get_db),
|
||||
):
|
||||
db_obj = crud.projects.get_by_uuid(
|
||||
db_obj = crud.graphs.get_by_uuid(
|
||||
db=db,
|
||||
uuid=graph_id
|
||||
)
|
||||
updated_graph = crud.projects.update_favorite_by_uuid(db, db_obj=db_obj, is_favorite=is_favorite)
|
||||
updated_graph = crud.graphs.update_favorite_by_uuid(db, db_obj=db_obj, is_favorite=is_favorite)
|
||||
return updated_graph
|
||||
|
||||
@router.get('', operation_id="get_graphs")
|
||||
async def get_project(
|
||||
@router.get(
|
||||
"",
|
||||
response_model=schemas.GraphsList
|
||||
)
|
||||
async def get_graphs(
|
||||
user: Annotated[schemas.CasdoorUser, Depends(deps.get_user_from_session)],
|
||||
db: Session = Depends(deps.get_db),
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
is_favorite: bool = False
|
||||
is_favorite: bool = False,
|
||||
):
|
||||
if limit > 50:
|
||||
limit = 50
|
||||
db_projects = crud.projects.get_many_by_favorites(
|
||||
db_graphs = crud.graphs.get_many_by_favorites(
|
||||
db=db,
|
||||
skip=skip,
|
||||
limit=limit,
|
||||
is_favorite=is_favorite
|
||||
)
|
||||
return {"projects": db_projects, "count": crud.projects.count_by_favorites(db, is_favorite)[0][0]}
|
||||
return {
|
||||
"graphs": db_graphs,
|
||||
"count": crud.graphs.count_by_favorites(db, is_favorite)[0][0]
|
||||
}
|
||||
|
||||
|
||||
@router.post('', operation_id="create_graph")
|
||||
async def create_project(
|
||||
name: str,
|
||||
@router.post(
|
||||
'',
|
||||
response_model=schemas.Graph
|
||||
)
|
||||
async def create_graph(
|
||||
user: Annotated[schemas.CasdoorUser, Depends(deps.get_user_from_session)],
|
||||
obj_in: schemas.GraphCreate,
|
||||
db: Session = Depends(deps.get_db),
|
||||
s3 = Depends(deps.get_s3),
|
||||
description: str = '',
|
||||
):
|
||||
project_uuid = uuid.uuid4().hex
|
||||
create_project_graph = f"""
|
||||
map = new HashMap<>()
|
||||
map.put('storage.backend', 'cql')
|
||||
map.put('storage.hostname', 'sdb:9042')
|
||||
map.put('index.search.backend', 'solr')
|
||||
map.put('index.search.solr.mode', 'http')
|
||||
map.put('index.search.solr.http-urls', 'http://index:8983/solr')
|
||||
map.put('graph.graphname', 'project_{project_uuid}')
|
||||
ConfiguredGraphFactory.createConfiguration(new MapConfiguration(map))
|
||||
ConfiguredGraphFactory.open('project_{project_uuid}')
|
||||
"""
|
||||
obj_in = schemas.ProjectCreate(
|
||||
name=name,
|
||||
description=description,
|
||||
uuid=project_uuid
|
||||
)
|
||||
new_project = crud.projects.create(db=db, obj_in=obj_in)
|
||||
cluster = await Cluster.open(
|
||||
asyncio.get_event_loop(),
|
||||
**{'hosts': ['janus'], 'port': 8182}
|
||||
)
|
||||
try:
|
||||
log.info(s3.list_buckets())
|
||||
obj_out = crud.graphs.create(db=db, obj_in=obj_in)
|
||||
cluster = await Cluster.open(
|
||||
asyncio.get_event_loop(),
|
||||
**{'hosts': ['janus'], 'port': 8182}
|
||||
)
|
||||
client = await cluster.connect(hostname='janus')
|
||||
await client.submit(create_project_graph)
|
||||
except GremlinServerError as e:
|
||||
log.error(e)
|
||||
finally:
|
||||
await cluster.close()
|
||||
return new_project
|
||||
await client.submit(janus_create_db(obj_out.uuid.hex))
|
||||
return obj_out
|
||||
except (Exception, GremlinServerError) as error:
|
||||
log.error(error)
|
||||
raise HTTPException(status_code=422, detail='error')
|
||||
|
||||
|
||||
@router.delete('', operation_id = "delete_graph")
|
||||
async def delete_project(
|
||||
@router.delete('')
|
||||
async def delete_graph(
|
||||
user: Annotated[schemas.CasdoorUser, Depends(deps.get_user_from_session)],
|
||||
uuid: str,
|
||||
db: Session = Depends(deps.get_db),
|
||||
):
|
||||
if uuid:
|
||||
crud.projects.remove_by_uuid(db=db, uuid=uuid)
|
||||
crud.graphs.remove_by_uuid(db=db, uuid=uuid)
|
||||
else:
|
||||
raise HTTPException(status_code=422, detail='UUID is a required field')
|
||||
|
||||
|
||||
@router.get('/{graph_id}/stats', operation_id="get_graph_stats")
|
||||
async def get_unique_graph_labels(
|
||||
@router.get('/{graph_id}/stats')
|
||||
async def get_graph_stats(
|
||||
user: Annotated[schemas.CasdoorUser, Depends(deps.get_user_from_session)],
|
||||
graph_id: str,
|
||||
db: Session = Depends(deps.get_db)
|
||||
):
|
||||
@@ -123,25 +120,25 @@ async def get_unique_graph_labels(
|
||||
raise HTTPException(status_code=422, detail='graph_id is a required field')
|
||||
async with ProjectGraphConnection(graph_id) as g:
|
||||
unique_entities = await g.V().label().dedup().toList()
|
||||
total_entites = await g.V().count().toList()
|
||||
total_entities = await g.V().count().toList()
|
||||
total_relations = await g.E().count().toList()
|
||||
|
||||
unique_entity_counts = {"series": [], "labels": []}
|
||||
unique_oute_counts = {"series": [], "labels": []}
|
||||
unique_out_edge_counts = {"series": [], "labels": []}
|
||||
|
||||
for entity in unique_entities:
|
||||
entity_count = await g.V().hasLabel(entity).count().toList()
|
||||
unique_entity_counts["series"].append(entity_count[0])
|
||||
unique_entity_counts["labels"].append(entity)
|
||||
|
||||
oute_count = await g.V().hasLabel(entity).outE().count().toList()
|
||||
unique_oute_counts['series'].append(oute_count[0])
|
||||
unique_oute_counts['labels'].append(entity)
|
||||
out_edge_count = await g.V().hasLabel(entity).outE().count().toList()
|
||||
unique_out_edge_counts['series'].append(out_edge_count[0])
|
||||
unique_out_edge_counts['labels'].append(entity)
|
||||
|
||||
return {
|
||||
"entities": unique_entities,
|
||||
"total_entities": total_entites[0],
|
||||
"total_entities": total_entities[0],
|
||||
"total_relations": total_relations[0],
|
||||
"unique_entity_counts": unique_entity_counts,
|
||||
"entity_oute_counts": unique_oute_counts
|
||||
"entity_oute_counts": unique_out_edge_counts
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException
|
||||
from fastapi import APIRouter, Body, Request, Depends, HTTPException
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from fastapi.responses import JSONResponse
|
||||
from app import crud, schemas
|
||||
from app.api import deps
|
||||
from app.core import security
|
||||
@@ -17,101 +17,29 @@ from app.utils import (
|
||||
verify_password_reset_token,
|
||||
)
|
||||
|
||||
|
||||
log = get_logger("api_v1.endpoints.login")
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def get_login_token(db: Session, username: str, password: str) -> schemas.Token or HTTPException:
|
||||
"""
|
||||
OAuth2 compatible token login, get an access token for future requests
|
||||
"""
|
||||
user = crud.user.authenticate(
|
||||
db, email=username, password=password
|
||||
)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Incorrect email or password, please try again",
|
||||
)
|
||||
elif not crud.user.is_active(user):
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
access_token_expires = timedelta(
|
||||
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
|
||||
)
|
||||
return {
|
||||
"token": security.create_access_token(
|
||||
user.id, expires_delta=access_token_expires
|
||||
),
|
||||
"token_type": "bearer",
|
||||
}
|
||||
@router.get("/get-account", response_class=JSONResponse)
|
||||
async def get_account(request: Request, user=Depends(deps.get_user_from_session)):
|
||||
sdk = request.app.state.CASDOOR_SDK
|
||||
user_data = await sdk.get_user(user["name"])
|
||||
return user_data
|
||||
|
||||
|
||||
@router.post(
|
||||
"/login/access-token",
|
||||
response_model=schemas.Token,
|
||||
operation_id="login_access_token"
|
||||
)
|
||||
def login_access_token(
|
||||
db: Session = Depends(deps.get_db),
|
||||
form_data: OAuth2PasswordRequestForm = Depends()
|
||||
) -> Any:
|
||||
token: dict or HTTPException = get_login_token(
|
||||
db,
|
||||
form_data.username,
|
||||
form_data.password
|
||||
)
|
||||
return token
|
||||
@router.post("/sign-in", response_class=JSONResponse)
|
||||
async def post_signin(code: str, request: Request):
|
||||
state = request.query_params.get("state")
|
||||
sdk = request.app.state.CASDOOR_SDK
|
||||
token = await sdk.get_oauth_token(code)
|
||||
user = sdk.parse_jwt_token(token.get("access_token", None))
|
||||
request.session["obUser"] = user
|
||||
return {"success": True}
|
||||
|
||||
|
||||
@router.post(
|
||||
"/password-recovery/{email}",
|
||||
response_model=schemas.Msg,
|
||||
operation_id="password_recovery_email"
|
||||
)
|
||||
def recover_password(email: str, db: Session = Depends(deps.get_db)) -> Any:
|
||||
"""
|
||||
Password Recovery
|
||||
"""
|
||||
user = crud.user.get_by_email(db, email=email)
|
||||
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="The user with this username does not exist in the system.",
|
||||
)
|
||||
password_reset_token = generate_password_reset_token(email=email)
|
||||
send_reset_password_email(
|
||||
email_to=user.email, email=email, token=password_reset_token
|
||||
)
|
||||
return {"msg": "Password recovery email sent"}
|
||||
|
||||
|
||||
@router.post(
|
||||
"/reset-password/",
|
||||
response_model=schemas.Msg,
|
||||
operation_id="reset_password"
|
||||
)
|
||||
def reset_password(
|
||||
token: str = Body(...),
|
||||
new_password: str = Body(...),
|
||||
db: Session = Depends(deps.get_db),
|
||||
) -> Any:
|
||||
"""
|
||||
Reset password
|
||||
"""
|
||||
email = verify_password_reset_token(token)
|
||||
if not email:
|
||||
raise HTTPException(status_code=400, detail="Invalid token")
|
||||
user = crud.user.get_by_email(db, email=email)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="The user with this username does not exist in the system.",
|
||||
)
|
||||
elif not crud.user.is_active(user):
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
hashed_password = get_password_hash(new_password)
|
||||
user.hashed_password = hashed_password
|
||||
db.add(user)
|
||||
db.commit()
|
||||
return {"msg": "Password updated successfully"}
|
||||
@router.post("/sign-out", response_class=JSONResponse)
|
||||
async def post_signout(request: Request):
|
||||
del request.session["obUser"]
|
||||
return {"success": True}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import asyncio
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import List, Callable, Tuple, Any, AsyncIterator
|
||||
from typing import List, Callable, Tuple, Any, AsyncIterator, Annotated
|
||||
from fastapi import (
|
||||
APIRouter,
|
||||
WebSocket,
|
||||
@@ -38,9 +38,9 @@ async def fetch_node_transforms(plugin_label):
|
||||
|
||||
@router.get(
|
||||
"/refresh",
|
||||
operation_id="refresh_plugins"
|
||||
)
|
||||
async def refresh_plugins(
|
||||
user: Annotated[schemas.CasdoorUser, Depends(deps.get_user_from_session)],
|
||||
db: Session = Depends(deps.get_db)
|
||||
):
|
||||
Registry.plugins = []
|
||||
@@ -53,9 +53,11 @@ async def refresh_plugins(
|
||||
|
||||
@router.get(
|
||||
"/transforms",
|
||||
operation_id="get_entity_transforms"
|
||||
)
|
||||
async def get_node_transforms(label: str):
|
||||
async def get_entity_transforms(
|
||||
user: Annotated[schemas.CasdoorUser, Depends(deps.get_user_from_session)],
|
||||
label: str
|
||||
):
|
||||
if transforms := await fetch_node_transforms(label):
|
||||
return {
|
||||
"type": label,
|
||||
@@ -69,9 +71,11 @@ async def get_node_transforms(label: str):
|
||||
|
||||
@router.post(
|
||||
'/',
|
||||
operation_id="create_graph_entity"
|
||||
)
|
||||
async def get_entity_from_drop(node: schemas.CreateNode):
|
||||
async def create_graph_entity(
|
||||
user: Annotated[schemas.CasdoorUser, Depends(deps.get_user_from_session)],
|
||||
node: schemas.CreateNode
|
||||
):
|
||||
plugin = await Registry.get_plugin(plugin_label=node.label)
|
||||
if plugin:
|
||||
blueprint = plugin.blueprint()
|
||||
|
||||
@@ -34,7 +34,7 @@ async def get_scan_machines(
|
||||
|
||||
|
||||
@router.delete("")
|
||||
async def delete_project(
|
||||
async def delete_scan_project(
|
||||
id: int,
|
||||
db: Session = Depends(deps.get_db),
|
||||
):
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
from typing import Any, List
|
||||
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from pydantic.networks import EmailStr
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app import crud, models, schemas
|
||||
from app.api import deps
|
||||
from app.core.config import settings
|
||||
from app.utils import send_new_account_email
|
||||
|
||||
router = APIRouter(prefix="/users")
|
||||
|
||||
|
||||
@router.get("/", response_model=List[schemas.User], operation_id="get_users")
|
||||
def read_users(
|
||||
db: Session = Depends(deps.get_db),
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
current_user: models.User = Depends(deps.get_current_active_superuser),
|
||||
) -> Any:
|
||||
"""
|
||||
Retrieve users.
|
||||
"""
|
||||
users = crud.user.get_multi(db, skip=skip, limit=limit)
|
||||
return users
|
||||
|
||||
|
||||
@router.post("/", response_model=schemas.User, operation_id="create_user")
|
||||
def create_user(
|
||||
*,
|
||||
db: Session = Depends(deps.get_db),
|
||||
user_in: schemas.UserCreate,
|
||||
current_user: models.User = Depends(deps.get_current_active_superuser),
|
||||
) -> Any:
|
||||
"""
|
||||
Create new user.
|
||||
"""
|
||||
user = crud.user.get_by_email(db, email=user_in.email)
|
||||
if user:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="The user with this email already exists in the system.",
|
||||
)
|
||||
user = crud.user.create(db, obj_in=user_in)
|
||||
if settings.EMAILS_ENABLED and user_in.email:
|
||||
send_new_account_email(
|
||||
email_to=user_in.email,
|
||||
username=user_in.full_name,
|
||||
password=user_in.password,
|
||||
)
|
||||
return user
|
||||
|
||||
|
||||
@router.put("/me", response_model=schemas.User, operation_id="update_user_me")
|
||||
def update_user_me(
|
||||
*,
|
||||
db: Session = Depends(deps.get_db),
|
||||
password: str = Body(None),
|
||||
full_name: str = Body(None),
|
||||
email: EmailStr = Body(None),
|
||||
current_user: models.User = Depends(deps.get_current_active_user),
|
||||
) -> Any:
|
||||
"""
|
||||
Update own user.
|
||||
"""
|
||||
current_user_data = jsonable_encoder(current_user)
|
||||
user_in = schemas.UserUpdate(**current_user_data)
|
||||
if password is not None:
|
||||
user_in.password = password
|
||||
if full_name is not None:
|
||||
user_in.full_name = full_name
|
||||
if email is not None:
|
||||
user_in.email = email
|
||||
user = crud.user.update(db, db_obj=current_user, obj_in=user_in)
|
||||
return user
|
||||
|
||||
|
||||
@router.get("/me", response_model=schemas.User, operation_id="read_user")
|
||||
def read_user_me(
|
||||
db: Session = Depends(deps.get_db),
|
||||
current_user: models.User = Depends(deps.get_current_active_user),
|
||||
) -> Any:
|
||||
"""
|
||||
Get current user.
|
||||
"""
|
||||
return current_user
|
||||
|
||||
|
||||
@router.post("/open", response_model=schemas.User, operation_id="create_user_open")
|
||||
def create_user_open(
|
||||
*,
|
||||
db: Session = Depends(deps.get_db),
|
||||
password: str = Body(...),
|
||||
email: EmailStr = Body(...),
|
||||
full_name: str = Body(...),
|
||||
) -> Any:
|
||||
"""
|
||||
Create new user without the need to be logged in.
|
||||
"""
|
||||
if not settings.USERS_OPEN_REGISTRATION:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="Open user registration is forbidden on this server",
|
||||
)
|
||||
user = crud.user.get_by_email(db, email=email)
|
||||
if user:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="The user with this username already exists in the system",
|
||||
)
|
||||
user_in = schemas.UserCreate(password=password, email=email, full_name=full_name)
|
||||
user = crud.user.create(db, obj_in=user_in)
|
||||
return user
|
||||
|
||||
|
||||
@router.get("/{user_id}", response_model=schemas.User, operation_id="read_user_by_id")
|
||||
def read_user_by_id(
|
||||
user_id: int,
|
||||
current_user: models.User = Depends(deps.get_current_active_user),
|
||||
db: Session = Depends(deps.get_db),
|
||||
) -> Any:
|
||||
"""
|
||||
Get a specific user by id.
|
||||
"""
|
||||
user = crud.user.count(db, id=user_id)
|
||||
if user == current_user:
|
||||
return user
|
||||
if not crud.user.is_superuser(current_user):
|
||||
raise HTTPException(
|
||||
status_code=400, detail="The user doesn't have enough privileges"
|
||||
)
|
||||
return user
|
||||
|
||||
|
||||
@router.put("/{user_id}", response_model=schemas.User, operation_id="update_user")
|
||||
def update_user(
|
||||
*,
|
||||
db: Session = Depends(deps.get_db),
|
||||
user_id: int,
|
||||
user_in: schemas.UserUpdate,
|
||||
current_user: models.User = Depends(deps.get_current_active_superuser),
|
||||
) -> Any:
|
||||
"""
|
||||
Update a user.
|
||||
"""
|
||||
user = crud.user.count(db, id=user_id)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="The user with this username does not exist in the system",
|
||||
)
|
||||
user = crud.user.update(db, db_obj=user, obj_in=user_in)
|
||||
return user
|
||||
@@ -1,9 +1,10 @@
|
||||
from typing import Generator
|
||||
from typing import Generator, Annotated
|
||||
from contextlib import contextmanager
|
||||
import boto3
|
||||
from jose import jwt
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from fastapi import Depends, HTTPException, status, Security
|
||||
from fastapi.security import OAuth2PasswordBearer, SecurityScopes
|
||||
from starlette.requests import Request
|
||||
from sqlalchemy.orm import Session
|
||||
from pydantic import ValidationError
|
||||
import undetected_chromedriver as uc
|
||||
@@ -14,12 +15,24 @@ from app.core import security
|
||||
from app.db.session import SessionLocal
|
||||
from app import crud, models, schemas
|
||||
from app.core.config import settings
|
||||
from app.core.logger import get_logger
|
||||
|
||||
log = get_logger("api.deps")
|
||||
|
||||
reusable_oauth2 = OAuth2PasswordBearer(
|
||||
tokenUrl=f"{settings.API_V1_STR}/login/access-token"
|
||||
oauth2_scheme = OAuth2PasswordBearer(
|
||||
tokenUrl=f"{settings.API_V1_STR}/signin",
|
||||
)
|
||||
|
||||
async def get_user_from_session(request: Request):
|
||||
try:
|
||||
sdk = request.app.state.CASDOOR_SDK
|
||||
user_jwt = request.headers.get("authorization", "").replace("Bearer ", "")
|
||||
user = sdk.parse_jwt_token(user_jwt)
|
||||
return schemas.CasdoorUser.validate(user)
|
||||
except Exception as e:
|
||||
log.error(e)
|
||||
raise HTTPException(status_code=401, detail="Unauthorized")
|
||||
|
||||
|
||||
def get_db() -> Generator:
|
||||
try:
|
||||
@@ -29,9 +42,7 @@ def get_db() -> Generator:
|
||||
db.close()
|
||||
|
||||
|
||||
def get_s3(
|
||||
# current_user: models.User = Depends(get_current_active_user),
|
||||
):
|
||||
def get_s3():
|
||||
kwargs = {
|
||||
"endpoint_url": "http://s3:8333",
|
||||
"aws_access_key_id": "accessKey1",
|
||||
@@ -41,43 +52,6 @@ def get_s3(
|
||||
return s3
|
||||
|
||||
|
||||
def get_current_user(
|
||||
db: Session = Depends(get_db), token: str = Depends(reusable_oauth2)
|
||||
) -> models.User:
|
||||
try:
|
||||
payload = jwt.decode(
|
||||
token, settings.SECRET_KEY, algorithms=[security.ALGORITHM]
|
||||
)
|
||||
token_data = schemas.TokenPayload(**payload)
|
||||
except (jwt.JWTError, ValidationError):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Could not validate credentials",
|
||||
)
|
||||
user = crud.user.get(db, id=token_data.sub)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
return user
|
||||
|
||||
|
||||
def get_current_active_user(
|
||||
current_user: models.User = Depends(get_current_user),
|
||||
) -> models.User:
|
||||
if not crud.user.is_active(current_user):
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
return current_user
|
||||
|
||||
|
||||
def get_current_active_superuser(
|
||||
current_user: models.User = Depends(get_current_user),
|
||||
) -> models.User:
|
||||
if not crud.user.is_superuser(current_user):
|
||||
raise HTTPException(
|
||||
status_code=400, detail="The user doesn't have enough privileges"
|
||||
)
|
||||
return current_user
|
||||
|
||||
|
||||
@contextmanager
|
||||
def get_driver() -> Generator[Session, None, None]:
|
||||
"""
|
||||
|
||||
46
backend/backend/app/app/core/casdoor.py
Normal file
46
backend/backend/app/app/core/casdoor.py
Normal file
@@ -0,0 +1,46 @@
|
||||
|
||||
import os
|
||||
|
||||
from casdoor import AsyncCasdoorSDK
|
||||
|
||||
certificate = '''-----BEGIN CERTIFICATE-----
|
||||
MIIE2TCCAsGgAwIBAgIDAeJAMA0GCSqGSIb3DQEBCwUAMCYxDjAMBgNVBAoTBWFk
|
||||
bWluMRQwEgYDVQQDDAtjZXJ0X3Q4MDVwdzAeFw0yMzEwMjgwNTAzMTNaFw00MzEw
|
||||
MjgwNTAzMTNaMCYxDjAMBgNVBAoTBWFkbWluMRQwEgYDVQQDDAtjZXJ0X3Q4MDVw
|
||||
dzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALuJO1TnSbUua1DgA9rW
|
||||
1iJSSSsZCcBcrUEe46DY9EZH63K7d4gJdrT4lOPr/5cRrp7YWJ3/F0DnPMDlC4RH
|
||||
aU8T2UQLNbP0DIqZdO8Y1QBueiuBPLdxj3k8TSn1RL+7r3ACm4g2m0VVldMAXzaX
|
||||
RRqmuRetBz4RdfwAq7NZFs/mYmoLF5iFoWoniaG26Rsntk5Fxo5L0fgqjMKf6oQo
|
||||
7WQ/hdtxmqrWggabON2oUdoi+guB1kGlJky6FXHmFKqLNsPjaXfwqRLqGSAkowGl
|
||||
vJJYLQvLN9JuYxqcyyDT/wE7FiuLU/CT3mH9QTBPhzHrsklXGYdZdoG0UGimcTUG
|
||||
0wubHaGDsarYdC9vFGFywyEe41ZBHLZt7RhzO/Mu3jKrP7KK31MYXwZo4bsKmgj1
|
||||
UwirO4YSbw9ODko8RLSyR3Om5t3/XNH3VSla8VnkzDriHicCOTKmAnf+XdNgRiBd
|
||||
fFb8BQdrVZ/Ua+lOQl0QlOsqzCMP2tjqmdQ4z1kwAT7QggA+iE8GC5GvOxw4BA4p
|
||||
h6kQHPOX66c7NspvC++UdpxTm9NJxITU6ZMcAS9ZwSd8bk/jkMrHbjLggH1FIm6T
|
||||
oJ7FYm/v31ZxXjr/TiM66lMHAfwmxoxirbOsu66IMWHCNbIMxYySmTNLE9vGSN9F
|
||||
+x4PbBxyZtFfgtUZmoWrKNY9AgMBAAGjEDAOMAwGA1UdEwEB/wQCMAAwDQYJKoZI
|
||||
hvcNAQELBQADggIBALt9bWjQgstTLcQSMUIRacKZ0/bP6OD6d4pk2WPYjrYJHWfp
|
||||
BoRN7zW/zu7ztr4SpGD/CwC4Nib/vplcMDXNi2xDxK6sAOcwzSUD5IaAZpoFdluT
|
||||
cYAXJ75ezXRhycl+aI2/R0effQiUDXp+9FmT6ENQ2NAw6JL29ih+ugDXh1/JJ+oV
|
||||
gnZYEk47vyJLxOOCGNww4u75iRMC73DBO2rHkwXfR9IQxx1bPK+8CwqtO7ZQKV8v
|
||||
THxlcrEbMWDRLOLrIuAOUwVOq725dLscFXPJuMbCNUjgsxFCL+MA5yFot8nf4DTW
|
||||
yHfnd+lio0g83c6Xb3QaJtW5NJvIeeZ7Q66fh24kM7XiZZs1kzNYT2wTWTrJGyvm
|
||||
6fT8SM5hy6xuoGmzEj6SiKiQHSzjd/gv4K2Wv3F4I+Zigxvx8LCHT3ETH18xPOB6
|
||||
IJyOJjUDGY3o/T8DhVj0Fw5NHa/ujjx893rcdiYdiQnWZ9oBwDtlHDDtdEaGKrAr
|
||||
OvJDjhG2J2X5IbXWc5fuTVoxhnjSuT/x2+3IBuWETKvjbno9/fTcUl9o2Y0ejTvf
|
||||
iKFQ3TJfwBi51Pd3skzEJCE/C5Vzw03YrwO2Clx9iZRAv+drNn/vM++ch9+Ph3jQ
|
||||
tyCMlYmLO/0DCB/PgEjqQyScFRH8mcMU2DYM5Z+PThlUvTtTMrXlZcIShcgi
|
||||
-----END CERTIFICATE-----'''
|
||||
|
||||
class Config:
|
||||
CASDOOR_SDK = AsyncCasdoorSDK(
|
||||
endpoint='http://casdoor:8080',
|
||||
client_id='057e00c722b1f415196b',
|
||||
client_secret='17e86c4cd14e97ea968e3a08c9c8efcb8fdebf02',
|
||||
certificate=certificate,
|
||||
org_name='org_ob',
|
||||
application_name='application_ob',
|
||||
)
|
||||
REDIRECT_URI = 'http://localhost:3000/callback'
|
||||
SECRET_TYPE = 'filesystem'
|
||||
SECRET_KEY = os.urandom(24)
|
||||
@@ -2,13 +2,14 @@ import os
|
||||
import secrets
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.routing import APIRoute
|
||||
from pydantic import (
|
||||
AnyHttpUrl,
|
||||
HttpUrl,
|
||||
PostgresDsn,
|
||||
validator
|
||||
)
|
||||
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
@@ -22,8 +23,8 @@ class Settings(BaseSettings):
|
||||
API_V1_STR: str = "/api/v1"
|
||||
SECRET_KEY: str = secrets.token_urlsafe(32)
|
||||
ADMIN_BACKEND_SECRET_KEY: str = secrets.token_urlsafe(32)
|
||||
# 60 minutes * 24 hours * 8 days = 8 days
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8
|
||||
# 60 minutes * 6 hours
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 6
|
||||
BACKEND_CORS_ORIGINS: List[str] = os.getenv('BACKEND_CORS_ORIGINS')
|
||||
|
||||
@validator("BACKEND_CORS_ORIGINS", pre=True)
|
||||
@@ -60,9 +61,29 @@ class Settings(BaseSettings):
|
||||
JANUSGRAPH_PORT: int = 8182
|
||||
|
||||
SENTRY_DSN: str = None
|
||||
|
||||
SUPERUSER_EMAIL: str = "admin@example.com"
|
||||
SUPERUSER_PASSWORD: str = "password"
|
||||
SUPERUSER_USERNAME: str = "sudo"
|
||||
SUPERUSER_FULL_NAME: str = "Super Admin"
|
||||
|
||||
BACKEND_LOG_LEVEL: str = "info"
|
||||
UVICORN_HOST: str = "0.0.0.0"
|
||||
|
||||
class Config:
|
||||
case_sensitive = True
|
||||
|
||||
|
||||
settings = Settings()
|
||||
|
||||
|
||||
def use_route_names_as_operation_ids(app: FastAPI) -> None:
|
||||
"""
|
||||
Simplify operation IDs so that generated API clients have simpler function
|
||||
names.
|
||||
|
||||
Should be called only after all routes have been added.
|
||||
"""
|
||||
for route in app.routes:
|
||||
if isinstance(route, APIRoute):
|
||||
route.operation_id = route.name
|
||||
@@ -13,15 +13,16 @@ ALGORITHM = "HS256"
|
||||
|
||||
|
||||
def create_access_token(
|
||||
subject: Union[str, Any], expires_delta: timedelta = None
|
||||
data: dict, expires_delta: timedelta = None
|
||||
) -> str:
|
||||
to_encode = data.copy()
|
||||
if expires_delta:
|
||||
expire = datetime.utcnow() + expires_delta
|
||||
else:
|
||||
expire = datetime.utcnow() + timedelta(
|
||||
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
|
||||
)
|
||||
to_encode = {"exp": expire, "sub": str(subject)}
|
||||
to_encode.update({"exp": expire})
|
||||
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=ALGORITHM)
|
||||
return encoded_jwt
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from app.crud.crud_projects import projects # noqa
|
||||
from app.crud.crud_graphs import graphs # noqa
|
||||
from app.crud.crud_entities import entities # noqa
|
||||
from app.crud.crud_user import user # noqa
|
||||
from app.crud.crud_scan_machines import scan_machine
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
from typing import List
|
||||
from app.crud.base import CRUDBase, ModelType
|
||||
from app.models.projects import Projects
|
||||
from app.schemas.projects import ProjectCreate, ProjectUpdate
|
||||
from app.models.graphs import Graphs
|
||||
from app.schemas.graphs import GraphCreate, GraphUpdate
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func
|
||||
|
||||
|
||||
class CRUDProjects(CRUDBase[
|
||||
Projects,
|
||||
ProjectCreate,
|
||||
ProjectUpdate
|
||||
class CRUDGraphs(CRUDBase[
|
||||
Graphs,
|
||||
GraphCreate,
|
||||
GraphUpdate
|
||||
]):
|
||||
def get_multi_by_user(
|
||||
self, db: Session, *, skip: int = 0, limit: int = 100
|
||||
@@ -35,11 +35,11 @@ class CRUDProjects(CRUDBase[
|
||||
def count_by_favorites(self, db: Session, is_favorite: bool = False) -> int:
|
||||
return db.query(func.count(self.model.id)).where(self.model.is_favorite == is_favorite).all()
|
||||
|
||||
def update_favorite_by_uuid(self, db: Session, db_obj: Projects, is_favorite: bool = False):
|
||||
def update_favorite_by_uuid(self, db: Session, db_obj: Graphs, is_favorite: bool = False):
|
||||
setattr(db_obj, 'is_favorite', is_favorite)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
projects = CRUDProjects(Projects)
|
||||
graphs = CRUDGraphs(Graphs)
|
||||
@@ -30,6 +30,23 @@ class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]):
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def create_superuser(self, db: Session, *, obj_in: UserCreate) -> User:
|
||||
if isinstance(obj_in, dict):
|
||||
user_data = obj_in
|
||||
else:
|
||||
user_data = obj_in.dict(exclude_unset=True)
|
||||
|
||||
if user_data["password"]:
|
||||
hashed_password = get_password_hash(user_data["password"])
|
||||
del user_data["password"]
|
||||
user_data["hashed_password"] = hashed_password
|
||||
user_data['is_superuser'] = True
|
||||
db_obj = self.model(**user_data)
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def update(
|
||||
self, db: Session, *, db_obj: User, obj_in: Union[UserUpdate, Dict[str, Any]]
|
||||
) -> User:
|
||||
@@ -61,5 +78,8 @@ class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]):
|
||||
def is_superuser(self, user: User) -> bool:
|
||||
return user.is_superuser
|
||||
|
||||
def is_disabled(self, user: User) -> bool:
|
||||
return user.disabled
|
||||
|
||||
|
||||
user = CRUDUser(User)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from app.db.base_class import Base # noqa
|
||||
from app.models import ( # noqa
|
||||
User,
|
||||
Projects,
|
||||
Graphs,
|
||||
Proxy_Type,
|
||||
Proxies,
|
||||
Entities,
|
||||
|
||||
@@ -4,6 +4,7 @@ from osintbuddy import load_plugin, Registry
|
||||
|
||||
from app import crud, schemas
|
||||
from app.core.logger import get_logger
|
||||
from app.core.config import settings
|
||||
# make sure all SQL Alchemy models are imported (app.db.base) before initializing DB
|
||||
from .base import * # noqa
|
||||
|
||||
@@ -29,12 +30,10 @@ core_plugins = {
|
||||
'Whois': 'whois'
|
||||
}
|
||||
|
||||
|
||||
def load_initial_plugin(db, plugin_mod, plugin_code):
|
||||
load_plugin(plugin_mod, plugin_code)
|
||||
|
||||
plugin = Registry.get_plug(plugin_mod)
|
||||
print(plugin)
|
||||
obj_in = schemas.EntityCreate(
|
||||
label=plugin.label,
|
||||
author=plugin.author,
|
||||
@@ -45,7 +44,15 @@ def load_initial_plugin(db, plugin_mod, plugin_code):
|
||||
return crud.entities.create(db=db, obj_in=obj_in)
|
||||
|
||||
def init_db(db: Session) -> None:
|
||||
|
||||
users_count = crud.user.count_all(db)[0][0]
|
||||
if users_count == 0:
|
||||
log.info('Creating initial system user (superuser)...')
|
||||
crud.user.create_superuser(db, obj_in=schemas.UserCreate(
|
||||
email=settings.SUPERUSER_EMAIL,
|
||||
username=settings.SUPERUSER_USERNAME,
|
||||
full_name=settings.SUPERUSER_FULL_NAME,
|
||||
password=settings.SUPERUSER_PASSWORD
|
||||
))
|
||||
entity_count = crud.entities.count_all(db)[0][0]
|
||||
if entity_count < 14:
|
||||
for plugin_label, plugin_mod in core_plugins.items():
|
||||
|
||||
@@ -15,7 +15,7 @@ from app.core.config import settings
|
||||
|
||||
@asynccontextmanager
|
||||
async def ProjectGraphConnection(
|
||||
project_uuid: str,
|
||||
graph_uuid: str,
|
||||
host: str = settings.JANUSGRAPH_HOST,
|
||||
port: int = settings.JANUSGRAPH_PORT
|
||||
) -> AsyncIterator[AsyncGraphTraversal]:
|
||||
@@ -24,11 +24,22 @@ async def ProjectGraphConnection(
|
||||
**{'hosts': [host], 'port': port}
|
||||
)
|
||||
client = await cluster.connect(hostname='janus')
|
||||
# await client.submit(f'project_{project_uuid}.io(IoCore.graphson()).writeGraph("data.json")')
|
||||
print(f'connecting traversal: project_{project_uuid}_traversal')
|
||||
print(f'connecting traversal: graph_{graph_uuid}_traversal')
|
||||
async with await DriverRemoteConnection.using(
|
||||
cluster,
|
||||
{'g': f'project_{project_uuid.replace("-", "")}_traversal'}
|
||||
{'g': f'graph_{graph_uuid.replace("-", "")}_traversal'}
|
||||
) as connection:
|
||||
yield Graph().traversal().withRemote(connection)
|
||||
|
||||
|
||||
janus_create_db = lambda graph_uuid: f"""
|
||||
map = new HashMap<>()
|
||||
map.put('storage.backend', 'cql')
|
||||
map.put('storage.hostname', 'sdb:9042')
|
||||
map.put('index.search.backend', 'solr')
|
||||
map.put('index.search.solr.mode', 'http')
|
||||
map.put('index.search.solr.http-urls', 'http://index:8983/solr')
|
||||
map.put('graph.graphname', 'graph_{graph_uuid}')
|
||||
ConfiguredGraphFactory.createConfiguration(new MapConfiguration(map))
|
||||
ConfiguredGraphFactory.open('graph_{graph_uuid}')
|
||||
"""
|
||||
@@ -2,9 +2,12 @@ import logging
|
||||
|
||||
from app.db.init_db import init_db
|
||||
from app.db.session import SessionLocal
|
||||
from app.core.logger import get_logger
|
||||
from app import schemas, crud
|
||||
from app.core.config import settings
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
log = get_logger(__name__)
|
||||
|
||||
|
||||
def init() -> None:
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.routing import APIRoute
|
||||
from fastapi.responses import UJSONResponse
|
||||
from fastapi.openapi.utils import get_openapi
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from starlette.middleware.sessions import SessionMiddleware
|
||||
|
||||
from fastapi_cache import caches, close_caches
|
||||
from fastapi_cache.backends.redis import CACHE_KEY, RedisCacheBackend
|
||||
# import sentry_sdk
|
||||
# from sentry_sdk.integrations.asyncio import AsyncioIntegration
|
||||
from osintbuddy import discover_plugins
|
||||
from app.api.api_v1.api import api_router
|
||||
from app.core.config import settings
|
||||
from app.core.config import settings, use_route_names_as_operation_ids
|
||||
from app.core.casdoor import Config as CasdoorConfig
|
||||
|
||||
# if settings.SENTRY_DSN:
|
||||
# sentry_sdk.init(
|
||||
@@ -35,6 +39,12 @@ app = FastAPI(
|
||||
on_shutdown=[on_shutdown]
|
||||
)
|
||||
|
||||
app.include_router(api_router, prefix=settings.API_V1_STR)
|
||||
|
||||
@app.get("/status")
|
||||
def get_status():
|
||||
return {"status": "ok"}
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=settings.BACKEND_CORS_ORIGINS,
|
||||
@@ -54,6 +64,19 @@ app.add_middleware(
|
||||
],
|
||||
)
|
||||
|
||||
app.add_middleware(
|
||||
SessionMiddleware,
|
||||
secret_key=CasdoorConfig.SECRET_KEY,
|
||||
)
|
||||
app.state.CASDOOR_SDK = CasdoorConfig().CASDOOR_SDK
|
||||
app.state.REDIRECT_URI = CasdoorConfig().REDIRECT_URI
|
||||
app.state.SECRET_TYPE = CasdoorConfig().SECRET_TYPE
|
||||
app.state.SECRET_KEY = CasdoorConfig().SECRET_KEY
|
||||
|
||||
|
||||
|
||||
|
||||
use_route_names_as_operation_ids(app)
|
||||
|
||||
def app_openapi_schema(app):
|
||||
"""Return openapi_schema. cached."""
|
||||
@@ -71,11 +94,5 @@ def app_openapi_schema(app):
|
||||
|
||||
return openapi_schema
|
||||
|
||||
|
||||
app.include_router(api_router, prefix=settings.API_V1_STR)
|
||||
app.openapi_schema = app_openapi_schema(app)
|
||||
|
||||
# @app.get("/sentry-debug")
|
||||
# async def trigger_error():
|
||||
# division_by_zero = 1 / 0
|
||||
# return division_by_zero
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
from .proxy import Proxy_Type, Proxies # noqa
|
||||
from .projects import ( # noqa
|
||||
Projects,
|
||||
)
|
||||
from .entities import Entities, ProjectEntities # noqa
|
||||
from .graphs import Graphs # noqa
|
||||
from .entities import Entities # noqa
|
||||
from .user import User # noqa
|
||||
from .scans import Scan_Machines, Scans
|
||||
|
||||
@@ -8,14 +8,6 @@ from sqlalchemy.orm import Mapped
|
||||
from sqlalchemy.orm import mapped_column
|
||||
|
||||
|
||||
# http://localhost:3000/app/projects - entities are at the second table on the ui
|
||||
ProjectEntities = Table(
|
||||
"project_entities",
|
||||
Base.metadata,
|
||||
Column('project_id', ForeignKey('projects.id'), primary_key=True),
|
||||
Column('entity_id', ForeignKey('entities.id'), primary_key=True)
|
||||
)
|
||||
|
||||
# Endpoints that use entities can be found here:
|
||||
#
|
||||
# backend/backend/app/app/api/api_v1/endpoints/entities.py
|
||||
|
||||
@@ -5,22 +5,16 @@ from sqlalchemy import Column, String, DateTime, Boolean, func
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from app.db.base_class import Base
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from app.models.entities import Entities, ProjectEntities
|
||||
from app.models.entities import Entities
|
||||
|
||||
|
||||
# http://localhost:3000/app/projects - projects are the first table on the ui
|
||||
# Endpoints for a users projects and their graphs can be found here:
|
||||
# backend/backend/app/app/api/api_v1/endpoints/projects.py
|
||||
class Projects(Base):
|
||||
class Graphs(Base):
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
uuid: Mapped[UUID] = Column(UUID(as_uuid=True), default=uuid.uuid4, index=True)
|
||||
|
||||
name: Mapped[str] = mapped_column(String, nullable=False)
|
||||
description: Mapped[str] = mapped_column(String(512), nullable=True)
|
||||
|
||||
# A project can have many entities and each entity can be for many projects
|
||||
entities: Mapped[List[Entities]] = relationship(secondary=ProjectEntities)
|
||||
|
||||
is_favorite: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
|
||||
last_seen: Mapped[DateTime] = mapped_column(
|
||||
@@ -33,10 +27,12 @@ class Projects(Base):
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"Projects(id={self.id!r}, "
|
||||
f"Graphs(id={self.id!r}, "
|
||||
f"uuid={self.uuid!r}, "
|
||||
f"name={self.name!r}, "
|
||||
f"description={self.description[64:]!r}, "
|
||||
f"is_favorite={self.is_favorite!r}, "
|
||||
f"updated={self.updated!r}, "
|
||||
f"last_seen={self.last_seen!r}, "
|
||||
f"created={self.created!r})"
|
||||
)
|
||||
@@ -1,18 +1,21 @@
|
||||
from sqlalchemy import Boolean, Column, Integer, String, DateTime, func
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from app.db.base_class import Base
|
||||
|
||||
|
||||
class User(Base):
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
full_name = Column(String, index=True)
|
||||
email = Column(String, unique=True, index=True, nullable=False)
|
||||
hashed_password = Column(String, nullable=False)
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
username: Mapped[str] = mapped_column(String(80), index=True)
|
||||
full_name: Mapped[str] = mapped_column(String, index=True)
|
||||
email: Mapped[str] = mapped_column(String, unique=True, index=True, nullable=False)
|
||||
hashed_password: Mapped[str] = mapped_column(String, nullable=False)
|
||||
|
||||
is_active = Column(Boolean(), default=True)
|
||||
is_superuser = Column(Boolean(), default=False)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
is_superuser: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
|
||||
modified = Column(
|
||||
disabled: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
|
||||
modified: Mapped[DateTime] = mapped_column(
|
||||
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
||||
)
|
||||
created = Column(DateTime(timezone=True), server_default=func.now())
|
||||
created: Mapped[DateTime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
from .msg import Msg # noqa
|
||||
from .api_errors import ErrorDetail, HTTPError # noqa
|
||||
from .projects import ( # noqa
|
||||
ProjectCreate,
|
||||
ProjectUpdate,
|
||||
Project,
|
||||
ProjectsListInDB,
|
||||
ProjectInDB,
|
||||
from .graphs import ( # noqa
|
||||
GraphCreate,
|
||||
GraphUpdate,
|
||||
Graph,
|
||||
GraphInDB,
|
||||
GraphInDBBase,
|
||||
GraphsList
|
||||
)
|
||||
from .node import CreateNode # noqa
|
||||
from .entities import ( # noqa
|
||||
@@ -18,12 +19,12 @@ from .entities import ( # noqa
|
||||
EntityInDB,
|
||||
PostEntityCreate,
|
||||
)
|
||||
from .user import User, UserUpdate, UserCreate, UserBase, UserInDB, UserInDBBase # noqa
|
||||
from .token import Token, TokenPayload # noqa
|
||||
from .user import User, UserUpdate, UserCreate, UserBase, UserInDB, UserInDBBase, CasdoorUser # noqa
|
||||
from .token import Token, TokenPayload, TokenData # noqa
|
||||
from .scan_machines import (
|
||||
ScanMachine,
|
||||
ScanMachineBase,
|
||||
ScanMachineCreate,
|
||||
ScanMachineUpdate,
|
||||
ScanMachineInDB,
|
||||
)
|
||||
)
|
||||
47
backend/backend/app/app/schemas/graphs.py
Executable file
47
backend/backend/app/app/schemas/graphs.py
Executable file
@@ -0,0 +1,47 @@
|
||||
import datetime
|
||||
from uuid import UUID
|
||||
from typing import Optional, List, Union
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
# Shared properties
|
||||
class GraphsBase(BaseModel):
|
||||
name: str
|
||||
description: Optional[str]
|
||||
|
||||
is_favorite: bool = False
|
||||
|
||||
|
||||
# Properties to receive via API on creation
|
||||
class GraphCreate(GraphsBase):
|
||||
pass
|
||||
|
||||
|
||||
# Properties to receive via API on update
|
||||
class GraphUpdate(GraphsBase):
|
||||
pass
|
||||
|
||||
|
||||
class GraphInDBBase(GraphsBase):
|
||||
uuid: UUID
|
||||
|
||||
class Config:
|
||||
from_orm = True
|
||||
|
||||
|
||||
# Additional properties to return via API
|
||||
class Graph(GraphInDBBase):
|
||||
updated: datetime.datetime
|
||||
created: datetime.datetime
|
||||
last_seen: datetime.datetime
|
||||
|
||||
|
||||
# Additional properties stored in DB
|
||||
class GraphInDB(GraphInDBBase):
|
||||
id: int
|
||||
|
||||
|
||||
class GraphsList(BaseModel):
|
||||
graphs: List[Graph]
|
||||
count: int
|
||||
@@ -1,45 +0,0 @@
|
||||
import datetime
|
||||
from typing import Optional, List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
# Cases
|
||||
# Shared properties
|
||||
class ProjectsBase(BaseModel):
|
||||
name: str
|
||||
description: str
|
||||
uuid: str
|
||||
|
||||
# Properties to receive via API on creation
|
||||
class ProjectCreate(ProjectsBase):
|
||||
pass
|
||||
# is_superuser: bool = False
|
||||
|
||||
|
||||
# Properties to receive via API on update
|
||||
class ProjectUpdate(ProjectsBase):
|
||||
pass
|
||||
|
||||
|
||||
class ProjectInDBBase(ProjectsBase):
|
||||
id: Optional[int] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# Additional properties to return via API
|
||||
class Project(ProjectInDBBase):
|
||||
updated: datetime.datetime
|
||||
created: datetime.datetime
|
||||
|
||||
|
||||
# Additional properties stored in DB
|
||||
class ProjectInDB(ProjectInDBBase):
|
||||
pass
|
||||
|
||||
|
||||
class ProjectsListInDB(ProjectInDB):
|
||||
cases: List[ProjectInDB]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Optional
|
||||
from typing import Optional, List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -10,3 +10,8 @@ class Token(BaseModel):
|
||||
|
||||
class TokenPayload(BaseModel):
|
||||
sub: Optional[int] = None
|
||||
|
||||
|
||||
class TokenData(BaseModel):
|
||||
username: str | None = None
|
||||
scopes: List[str] = []
|
||||
|
||||
@@ -1,9 +1,44 @@
|
||||
from uuid import UUID
|
||||
import datetime
|
||||
from typing import Optional
|
||||
from typing import Optional, List
|
||||
|
||||
from pydantic import BaseModel, EmailStr
|
||||
|
||||
|
||||
class CasdoorUser(BaseModel):
|
||||
id: UUID
|
||||
name: str
|
||||
displayName: str
|
||||
createdTime: datetime.datetime
|
||||
updatedTime: datetime.datetime
|
||||
sub: UUID
|
||||
firstName: str
|
||||
lastName: str
|
||||
avatar: str
|
||||
avatarType: str
|
||||
permanentAvatar: str
|
||||
email: str
|
||||
emailVerified: bool
|
||||
phone: str
|
||||
countryCode: str
|
||||
region: str
|
||||
location: str
|
||||
bio: str
|
||||
language: str
|
||||
isOnline: bool
|
||||
isAdmin: bool
|
||||
isForbidden: bool
|
||||
isDeleted: bool
|
||||
owner: str = "org_ob"
|
||||
type: str = "normal-user"
|
||||
signupApplication: str = "application_ob"
|
||||
aud: List[str] = []
|
||||
exp: int
|
||||
nbf: int
|
||||
iat: int
|
||||
jti: str
|
||||
|
||||
|
||||
# Shared properties
|
||||
class UserBase(BaseModel):
|
||||
email: Optional[EmailStr] = None
|
||||
@@ -17,7 +52,7 @@ class UserCreate(UserBase):
|
||||
email: EmailStr
|
||||
full_name: str
|
||||
password: str
|
||||
# is_superuser: bool = False
|
||||
username: str = ""
|
||||
|
||||
|
||||
# Properties to receive via API on update
|
||||
|
||||
@@ -1,8 +1,42 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import uvicorn
|
||||
from colorama import just_fix_windows_console
|
||||
from termcolor import colored
|
||||
from pyfiglet import figlet_format
|
||||
just_fix_windows_console()
|
||||
from app.core.config import settings
|
||||
|
||||
|
||||
def print_startup_message():
|
||||
print(colored("Introducing...", color="red", attrs=["bold"]))
|
||||
time.sleep(1)
|
||||
for line in figlet_format("the", font="small").split('\n'):
|
||||
print(colored(line, color="blue"))
|
||||
for line in figlet_format("OSINTBuddy", font='doom',).split('\n'):
|
||||
print(colored(line, color="blue", attrs=["bold"]))
|
||||
for line in figlet_format(" project", font="small").split('\n'):
|
||||
print(colored(line, color="blue"))
|
||||
print(
|
||||
colored("Created by:", color="red"),
|
||||
colored("jerlendds\n\n\n", color="red", attrs=["bold", "underline"]),
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
print_startup_message()
|
||||
uvicorn.run(
|
||||
"app.main:app",
|
||||
host="0.0.0.0",
|
||||
port=80,
|
||||
loop="asyncio",
|
||||
reload=True,
|
||||
workers=4,
|
||||
headers=[("server", "app")],
|
||||
log_level=settings.BACKEND_LOG_LEVEL
|
||||
)
|
||||
|
||||
print(sys.argv[0])
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run("app.main:app", host="0.0.0.0", port=80, loop='asyncio', reload=True, workers=4, headers=[('server', 'app')], log_level='info')
|
||||
|
||||
main()
|
||||
|
||||
@@ -10,7 +10,7 @@ def test_get_access_token(client: TestClient) -> None:
|
||||
"username": settings.FIRST_SUPERUSER,
|
||||
"password": settings.FIRST_SUPERUSER_PASSWORD,
|
||||
}
|
||||
r = client.post(f"{settings.API_V1_STR}/login/access-token", data=login_data)
|
||||
r = client.post(f"{settings.API_V1_STR}/login/token", data=login_data)
|
||||
tokens = r.json()
|
||||
assert r.status_code == 200
|
||||
assert "access_token" in tokens
|
||||
|
||||
@@ -15,7 +15,7 @@ def user_authentication_headers(
|
||||
) -> Dict[str, str]:
|
||||
data = {"username": email, "password": password}
|
||||
|
||||
r = client.post(f"{settings.API_V1_STR}/login/access-token", data=data)
|
||||
r = client.post(f"{settings.API_V1_STR}/login/token", data=data)
|
||||
response = r.json()
|
||||
auth_token = response["access_token"]
|
||||
headers = {"Authorization": f"Bearer {auth_token}"}
|
||||
|
||||
@@ -20,7 +20,7 @@ def get_superuser_token_headers(client: TestClient) -> Dict[str, str]:
|
||||
"username": settings.FIRST_SUPERUSER,
|
||||
"password": settings.FIRST_SUPERUSER_PASSWORD,
|
||||
}
|
||||
r = client.post(f"{settings.API_V1_STR}/login/access-token", data=login_data)
|
||||
r = client.post(f"{settings.API_V1_STR}/login/token", data=login_data)
|
||||
tokens = r.json()
|
||||
a_token = tokens["access_token"]
|
||||
headers = {"Authorization": f"Bearer {a_token}"}
|
||||
|
||||
@@ -26,13 +26,5 @@ else
|
||||
echo "There is no script $PRE_START_PATH"
|
||||
fi
|
||||
|
||||
# Start Uvicorn with live reload
|
||||
# uvicorn service:app --host 0.0.0.0 --port 2222 --header server:app \
|
||||
# --reload \
|
||||
# --reload-dir /service/
|
||||
# uvicorn service:app --host 0.0.0.0 --port 2222 --no-server-header \
|
||||
# --reload \
|
||||
# --reload-dir /service/
|
||||
|
||||
python3 ./app/start.py "app.main:app"
|
||||
# exec uvicorn --loop asyncio --workers 4 --reload --reload-dir app --header server:app --host $HOST --port $PORT --log-level $LOG_LEVEL "$APP_MODULE"
|
||||
python3 ./app/start.py "app.main:app"
|
||||
@@ -4,11 +4,10 @@ LABEL maintainer="jerlendds <support@forum.osintbuddy.com>"
|
||||
WORKDIR /app/
|
||||
ENV PYTHONPATH=/app/
|
||||
|
||||
RUN apt-get -y update && apt-get -y install apt-transport-https nmap git wget gnupg curl chromium chromium-driver && \
|
||||
apt-get clean;
|
||||
RUN apt-get -y update && apt-get -y install git wget gnupg curl && \
|
||||
apt-get clean && pip3 install --no-cache-dir --upgrade pip;
|
||||
COPY requirements.txt /app/requirements.txt
|
||||
RUN pip3 install --no-cache-dir --upgrade pip && \
|
||||
pip3 install --no-cache-dir -r /app/requirements.txt
|
||||
RUN pip3 install --no-cache-dir -r /app/requirements.txt
|
||||
|
||||
COPY app/ /app/
|
||||
COPY osintbuddy-plugins /app/osintbuddy-plugins/
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
alembic==1.10.4
|
||||
casdoor
|
||||
itsdangerous==2.1.2
|
||||
sqlalchemy_utils==0.41.1
|
||||
beautifulsoup4==4.12.2
|
||||
celery==5.2.7
|
||||
colorama==0.4.6
|
||||
|
||||
@@ -1,40 +1,30 @@
|
||||
alembic==1.10.4
|
||||
beautifulsoup4==4.12.2
|
||||
boto3==1.28.64
|
||||
casdoor==1.17.0
|
||||
celery==5.2.7
|
||||
colorama==0.4.6
|
||||
cryptography==41.0.4
|
||||
dataclasses-json==0.5.7
|
||||
email-validator==2.0.0.post2
|
||||
emails==0.6
|
||||
fastapi==0.103.2
|
||||
fastapi-cache==0.1.0
|
||||
grequests==0.6.0
|
||||
httptools==0.5.0
|
||||
langdetect==1.0.9
|
||||
numexpr==2.8.4
|
||||
openai==0.27.6
|
||||
openapi-schema-pydantic==1.2.4
|
||||
osintbuddy==0.0.4rc46.post1
|
||||
# osintbuddy==0.0.4
|
||||
itsdangerous==2.1.2
|
||||
passlib==1.7.4
|
||||
pathspec==0.11.1
|
||||
pip==23.3
|
||||
pipdeptree==2.13.0
|
||||
psycopg2-binary==2.9.6
|
||||
pydantic-settings==2.0.3
|
||||
pyfiglet==0.8.post1
|
||||
PySocks==1.7.1
|
||||
pytest==7.3.1
|
||||
python-jose==3.3.0
|
||||
python-multipart==0.0.6
|
||||
python3-nmap==1.6.0
|
||||
redis==4.5.4
|
||||
sqlalchemy-json==0.7.0
|
||||
SQLAlchemy-Utils==0.41.1
|
||||
tenacity==8.2.2
|
||||
termcolor==2.3.0
|
||||
ujson==5.8.0
|
||||
undetected-chromedriver==3.4.7
|
||||
uvicorn==0.22.0
|
||||
validators==0.20.0
|
||||
watchfiles==0.19.0
|
||||
boto3==1.28.64
|
||||
|
||||
gremlinpy @ git+https://github.com/jerlendds/gremlinpy.git@7d3033e6a55ed9cb1f982ec3b58ca233e01c58e3
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# This package is auto generated please don't edit manually
|
||||
export VERSION="v0.3.0"
|
||||
@@ -1,2 +0,0 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
@@ -1,24 +1,9 @@
|
||||
FROM node:18-alpine as build-stage
|
||||
FROM node:18-alpine
|
||||
LABEL maintainer="jerlendds <support@forum.osintbuddy.com>"
|
||||
|
||||
WORKDIR /app/
|
||||
|
||||
COPY package.json yarn.lock ./
|
||||
|
||||
RUN yarn
|
||||
|
||||
RUN yarn install && ls && pwd
|
||||
COPY . .
|
||||
|
||||
ARG NODE_ENV=${ENVIRONMENT}
|
||||
ARG REACT_APP_BASE_URL=${BASE_URL}
|
||||
# TODO: Improve docker file for dev...
|
||||
# if [ $NODE_ENV=='production' ]; then yarn build
|
||||
# if [ $NODE_ENV=='development' ]; then yarn start
|
||||
RUN yarn build
|
||||
|
||||
FROM nginx:1.25.0-alpine
|
||||
|
||||
COPY --from=build-stage /app/build/ /usr/share/nginx/html
|
||||
COPY --from=build-stage /app/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
EXPOSE 80
|
||||
CMD [ "yarn", "start" ]
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
"version": "0.3.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@axa-fr/oidc-client": "^7.7.2",
|
||||
"@axa-fr/react-oidc": "^7.7.2",
|
||||
"@codemirror/lang-python": "^6.1.3",
|
||||
"@excalidraw/excalidraw": "^0.16.1",
|
||||
"@headlessui/react": "^1.7.7",
|
||||
@@ -11,12 +13,13 @@
|
||||
"@lexical/headless": "^0.12.2",
|
||||
"@lexical/mark": "^0.12.2",
|
||||
"@locker/near-membrane-dom": "^0.12.14",
|
||||
"@open-rpc/client-js": "^1.8.1",
|
||||
"@reactour/tour": "^3.4.0",
|
||||
"@reduxjs/toolkit": "^1.9.1",
|
||||
"@types/katex": "^0.16.3",
|
||||
"@types/lodash-es": "^4.17.9",
|
||||
"@uiw/codemirror-theme-tokyo-night": "^4.21.8",
|
||||
"@uiw/react-codemirror": "^4.21.8",
|
||||
"@uiw/codemirror-theme-tokyo-night": "^4.21.20",
|
||||
"@uiw/react-codemirror": "^4.21.20",
|
||||
"@yaireo/dragsort": "^1.3.1",
|
||||
"@yaireo/tagify": "^4.17.8",
|
||||
"axios": "^1.2.1",
|
||||
@@ -39,6 +42,8 @@
|
||||
"react-use-websocket": "^4.3.1",
|
||||
"reactflow": "^11.5.3",
|
||||
"sigma": "^3.0.0-alpha3",
|
||||
"swr": "^2.2.4",
|
||||
"vscode-languageserver-protocol": "^3.17.5",
|
||||
"y-websocket": "^1.5.0",
|
||||
"yjs": "^13.6.8",
|
||||
"yup": "^0.32.11"
|
||||
@@ -47,8 +52,7 @@
|
||||
"start": "craco start",
|
||||
"build": "craco build",
|
||||
"test": "craco test",
|
||||
"eject": "react-scripts eject",
|
||||
"swagger:codegen": "rm -f -dr ./src/app/openapi && mkdir ./src/app/openapi && openapi --name obSDK -i http://localhost:8000/api/v1/openapi.json --output ./src/app/openapi --client axios --exportModels true --exportSchemas true --indent 2 --postfixServices v1"
|
||||
"swagger:codegen": "rm -f -dr ./src/app/openapi && mkdir ./src/app/openapi && openapi --name obSDK --client axios -i http://localhost:8000/api/v1/openapi.json --output ./src/app/openapi --exportModels true --exportSchemas true --indent 2 --postfixServices v1"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
|
||||
@@ -6,12 +6,6 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#0F172A" />
|
||||
<meta name="description" content="Mine, map, and merge data for novel insights" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&family=Lexend:wght@100;200;300;400;500;600;700;800;900&display=swap&family=Fira+Code&family=Crimson+Text:ital,wght@0,400;0,600;0,700;1,400;1,600;1,700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<title>OSINTBuddy</title>
|
||||
|
||||
1
frontend/src/@types/api.d.ts
vendored
1
frontend/src/@types/api.d.ts
vendored
@@ -22,3 +22,4 @@ interface ApiTransforms {
|
||||
type: string;
|
||||
transforms: { label: string; icon: TablerIcon };
|
||||
}
|
||||
|
||||
|
||||
8
frontend/src/@types/casdoor.d.ts
vendored
Executable file
8
frontend/src/@types/casdoor.d.ts
vendored
Executable file
@@ -0,0 +1,8 @@
|
||||
import SDK from 'casdoor-js-sdk';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
sdk: SDK
|
||||
}
|
||||
}
|
||||
|
||||
3
frontend/src/@types/globals.d.ts
vendored
Executable file → Normal file
3
frontend/src/@types/globals.d.ts
vendored
Executable file → Normal file
@@ -3,5 +3,4 @@ declare module '@yaireo/dragsort';
|
||||
declare type JSONObject = {
|
||||
[any: string]: any
|
||||
};
|
||||
|
||||
type HexColor = `#${string}`;
|
||||
type HexColor = `#${string}`;
|
||||
|
||||
1
frontend/src/@types/nodes.d.ts
vendored
1
frontend/src/@types/nodes.d.ts
vendored
@@ -19,7 +19,6 @@ interface EditState {
|
||||
|
||||
|
||||
|
||||
|
||||
interface ActiveProjectGraph {
|
||||
id: number;
|
||||
uuid: string;
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import AppRoutes from '@/routes/AppRoutes';
|
||||
import SDK from 'casdoor-js-sdk'
|
||||
import { SdkConfig } from 'casdoor-js-sdk/lib/esm/sdk'
|
||||
|
||||
const config: SdkConfig = {
|
||||
serverUrl: "http://localhost:8080",
|
||||
clientId: "057e00c722b1f415196b",
|
||||
organizationName: "org_ob",
|
||||
appName: "application_ob",
|
||||
redirectPath: "/callback",
|
||||
signinPath: "/api/v1/sign-in",
|
||||
}
|
||||
window.sdk = new SDK(config)
|
||||
|
||||
function App() {
|
||||
return (
|
||||
|
||||
@@ -18,19 +18,16 @@ const api = axios.create({
|
||||
});
|
||||
|
||||
const setHeader = () => {
|
||||
const user = JSON.parse(localStorage.getItem(LS_USER_AUTH_KEY) || '{}');
|
||||
if (user && user.token) {
|
||||
api.defaults.headers.common['Authorization'] = `Bearer ${user.token}`;
|
||||
return `Bearer ${user.token}`;
|
||||
const accessToken = sessionStorage.getItem('accessToken')
|
||||
if (accessToken) {
|
||||
api.defaults.headers.common['Authorization'] = `Bearer ${sessionStorage.getItem('accessToken')}`;
|
||||
return accessToken;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
setHeader();
|
||||
|
||||
// TODO: Move all api calls to new SDK
|
||||
export const sdk = new obSDK({
|
||||
BASE: BASE_URL
|
||||
BASE: BASE_URL,
|
||||
TOKEN: setHeader(),
|
||||
})
|
||||
|
||||
export { BASE_URL, LS_USER_AUTH_KEY, WS_URL, API_PREFIX, setHeader };
|
||||
|
||||
@@ -10,43 +10,31 @@ export { CancelablePromise, CancelError } from './core/CancelablePromise';
|
||||
export { OpenAPI } from './core/OpenAPI';
|
||||
export type { OpenAPIConfig } from './core/OpenAPI';
|
||||
|
||||
export type { Body_create_user_open } from './models/Body_create_user_open';
|
||||
export type { Body_login_access_token } from './models/Body_login_access_token';
|
||||
export type { Body_reset_password } from './models/Body_reset_password';
|
||||
export type { Body_update_user_me } from './models/Body_update_user_me';
|
||||
export type { CreateNode } from './models/CreateNode';
|
||||
export type { EntityBase } from './models/EntityBase';
|
||||
export type { Graph } from './models/Graph';
|
||||
export type { GraphCreate } from './models/GraphCreate';
|
||||
export type { GraphsList } from './models/GraphsList';
|
||||
export type { HTTPValidationError } from './models/HTTPValidationError';
|
||||
export type { Msg } from './models/Msg';
|
||||
export type { PostEntityCreate } from './models/PostEntityCreate';
|
||||
export type { ScanMachineCreate } from './models/ScanMachineCreate';
|
||||
export type { Token } from './models/Token';
|
||||
export type { User } from './models/User';
|
||||
export type { UserCreate } from './models/UserCreate';
|
||||
export type { UserUpdate } from './models/UserUpdate';
|
||||
export type { ValidationError } from './models/ValidationError';
|
||||
export type { XYPosition } from './models/XYPosition';
|
||||
|
||||
export { $Body_create_user_open } from './schemas/$Body_create_user_open';
|
||||
export { $Body_login_access_token } from './schemas/$Body_login_access_token';
|
||||
export { $Body_reset_password } from './schemas/$Body_reset_password';
|
||||
export { $Body_update_user_me } from './schemas/$Body_update_user_me';
|
||||
export { $CreateNode } from './schemas/$CreateNode';
|
||||
export { $EntityBase } from './schemas/$EntityBase';
|
||||
export { $Graph } from './schemas/$Graph';
|
||||
export { $GraphCreate } from './schemas/$GraphCreate';
|
||||
export { $GraphsList } from './schemas/$GraphsList';
|
||||
export { $HTTPValidationError } from './schemas/$HTTPValidationError';
|
||||
export { $Msg } from './schemas/$Msg';
|
||||
export { $PostEntityCreate } from './schemas/$PostEntityCreate';
|
||||
export { $ScanMachineCreate } from './schemas/$ScanMachineCreate';
|
||||
export { $Token } from './schemas/$Token';
|
||||
export { $User } from './schemas/$User';
|
||||
export { $UserCreate } from './schemas/$UserCreate';
|
||||
export { $UserUpdate } from './schemas/$UserUpdate';
|
||||
export { $ValidationError } from './schemas/$ValidationError';
|
||||
export { $XYPosition } from './schemas/$XYPosition';
|
||||
|
||||
export { Defaultv1 } from './services/Defaultv1';
|
||||
export { Entitiesv1 } from './services/Entitiesv1';
|
||||
export { Graphsv1 } from './services/Graphsv1';
|
||||
export { Loginv1 } from './services/Loginv1';
|
||||
export { Nodesv1 } from './services/Nodesv1';
|
||||
export { Scansv1 } from './services/Scansv1';
|
||||
export { Usersv1 } from './services/Usersv1';
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
/* generated using openapi-typescript-codegen -- do no edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
export type Body_login_access_token = {
|
||||
grant_type?: (string | null);
|
||||
username: string;
|
||||
password: string;
|
||||
scope?: string;
|
||||
client_id?: (string | null);
|
||||
client_secret?: (string | null);
|
||||
};
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
/* generated using openapi-typescript-codegen -- do no edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
export type Body_reset_password = {
|
||||
token: string;
|
||||
new_password: string;
|
||||
};
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
/* generated using openapi-typescript-codegen -- do no edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
export type Body_update_user_me = {
|
||||
password?: string;
|
||||
full_name?: string;
|
||||
email?: string;
|
||||
};
|
||||
|
||||
15
frontend/src/app/openapi/models/Graph.ts
Normal file
15
frontend/src/app/openapi/models/Graph.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/* generated using openapi-typescript-codegen -- do no edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
export type Graph = {
|
||||
name: string;
|
||||
description: (string | null);
|
||||
is_favorite?: boolean;
|
||||
uuid: string;
|
||||
updated: string;
|
||||
created: string;
|
||||
last_seen: string;
|
||||
};
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
export type Body_create_user_open = {
|
||||
password: string;
|
||||
email: string;
|
||||
full_name: string;
|
||||
export type GraphCreate = {
|
||||
name: string;
|
||||
description: (string | null);
|
||||
is_favorite?: boolean;
|
||||
};
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
export type Msg = {
|
||||
status: string;
|
||||
import type { Graph } from './Graph';
|
||||
|
||||
export type GraphsList = {
|
||||
graphs: Array<Graph>;
|
||||
count: number;
|
||||
};
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
/* generated using openapi-typescript-codegen -- do no edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
export type Token = {
|
||||
token: string;
|
||||
token_type: string;
|
||||
};
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
/* generated using openapi-typescript-codegen -- do no edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
export type User = {
|
||||
email?: (string | null);
|
||||
is_active?: (boolean | null);
|
||||
is_superuser?: boolean;
|
||||
full_name?: (string | null);
|
||||
id?: (number | null);
|
||||
modified: string;
|
||||
created: string;
|
||||
};
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
/* generated using openapi-typescript-codegen -- do no edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
export type UserCreate = {
|
||||
email: string;
|
||||
is_active?: (boolean | null);
|
||||
is_superuser?: boolean;
|
||||
full_name: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
/* generated using openapi-typescript-codegen -- do no edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
export type UserUpdate = {
|
||||
email?: (string | null);
|
||||
is_active?: (boolean | null);
|
||||
is_superuser?: boolean;
|
||||
full_name?: (string | null);
|
||||
hashed_password?: (string | null);
|
||||
};
|
||||
|
||||
@@ -6,23 +6,23 @@ import type { BaseHttpRequest } from './core/BaseHttpRequest';
|
||||
import type { OpenAPIConfig } from './core/OpenAPI';
|
||||
import { AxiosHttpRequest } from './core/AxiosHttpRequest';
|
||||
|
||||
import { Defaultv1 } from './services/Defaultv1';
|
||||
import { Entitiesv1 } from './services/Entitiesv1';
|
||||
import { Graphsv1 } from './services/Graphsv1';
|
||||
import { Loginv1 } from './services/Loginv1';
|
||||
import { Nodesv1 } from './services/Nodesv1';
|
||||
import { Scansv1 } from './services/Scansv1';
|
||||
import { Usersv1 } from './services/Usersv1';
|
||||
|
||||
type HttpRequestConstructor = new (config: OpenAPIConfig) => BaseHttpRequest;
|
||||
|
||||
export class obSDK {
|
||||
|
||||
public readonly default: Defaultv1;
|
||||
public readonly entities: Entitiesv1;
|
||||
public readonly graphs: Graphsv1;
|
||||
public readonly login: Loginv1;
|
||||
public readonly nodes: Nodesv1;
|
||||
public readonly scans: Scansv1;
|
||||
public readonly users: Usersv1;
|
||||
|
||||
public readonly request: BaseHttpRequest;
|
||||
|
||||
@@ -39,12 +39,12 @@ export class obSDK {
|
||||
ENCODE_PATH: config?.ENCODE_PATH,
|
||||
});
|
||||
|
||||
this.default = new Defaultv1(this.request);
|
||||
this.entities = new Entitiesv1(this.request);
|
||||
this.graphs = new Graphsv1(this.request);
|
||||
this.login = new Loginv1(this.request);
|
||||
this.nodes = new Nodesv1(this.request);
|
||||
this.scans = new Scansv1(this.request);
|
||||
this.users = new Usersv1(this.request);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
/* generated using openapi-typescript-codegen -- do no edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export const $Body_create_user_open = {
|
||||
properties: {
|
||||
password: {
|
||||
type: 'string',
|
||||
isRequired: true,
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
isRequired: true,
|
||||
format: 'email',
|
||||
},
|
||||
full_name: {
|
||||
type: 'string',
|
||||
isRequired: true,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
@@ -1,44 +0,0 @@
|
||||
/* generated using openapi-typescript-codegen -- do no edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export const $Body_login_access_token = {
|
||||
properties: {
|
||||
grant_type: {
|
||||
type: 'any-of',
|
||||
contains: [{
|
||||
type: 'string',
|
||||
pattern: 'password',
|
||||
}, {
|
||||
type: 'null',
|
||||
}],
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
isRequired: true,
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
isRequired: true,
|
||||
},
|
||||
scope: {
|
||||
type: 'string',
|
||||
},
|
||||
client_id: {
|
||||
type: 'any-of',
|
||||
contains: [{
|
||||
type: 'string',
|
||||
}, {
|
||||
type: 'null',
|
||||
}],
|
||||
},
|
||||
client_secret: {
|
||||
type: 'any-of',
|
||||
contains: [{
|
||||
type: 'string',
|
||||
}, {
|
||||
type: 'null',
|
||||
}],
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
@@ -1,18 +0,0 @@
|
||||
/* generated using openapi-typescript-codegen -- do no edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export const $Body_update_user_me = {
|
||||
properties: {
|
||||
password: {
|
||||
type: 'string',
|
||||
},
|
||||
full_name: {
|
||||
type: 'string',
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
format: 'email',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
@@ -2,45 +2,30 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export const $User = {
|
||||
export const $Graph = {
|
||||
properties: {
|
||||
email: {
|
||||
name: {
|
||||
type: 'string',
|
||||
isRequired: true,
|
||||
},
|
||||
description: {
|
||||
type: 'any-of',
|
||||
contains: [{
|
||||
type: 'string',
|
||||
format: 'email',
|
||||
}, {
|
||||
type: 'null',
|
||||
}],
|
||||
isRequired: true,
|
||||
},
|
||||
is_active: {
|
||||
type: 'any-of',
|
||||
contains: [{
|
||||
type: 'boolean',
|
||||
}, {
|
||||
type: 'null',
|
||||
}],
|
||||
},
|
||||
is_superuser: {
|
||||
is_favorite: {
|
||||
type: 'boolean',
|
||||
},
|
||||
full_name: {
|
||||
type: 'any-of',
|
||||
contains: [{
|
||||
type: 'string',
|
||||
}, {
|
||||
type: 'null',
|
||||
}],
|
||||
uuid: {
|
||||
type: 'string',
|
||||
isRequired: true,
|
||||
format: 'uuid',
|
||||
},
|
||||
id: {
|
||||
type: 'any-of',
|
||||
contains: [{
|
||||
type: 'number',
|
||||
}, {
|
||||
type: 'null',
|
||||
}],
|
||||
},
|
||||
modified: {
|
||||
updated: {
|
||||
type: 'string',
|
||||
isRequired: true,
|
||||
format: 'date-time',
|
||||
@@ -50,5 +35,10 @@ export const $User = {
|
||||
isRequired: true,
|
||||
format: 'date-time',
|
||||
},
|
||||
last_seen: {
|
||||
type: 'string',
|
||||
isRequired: true,
|
||||
format: 'date-time',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
@@ -2,31 +2,23 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export const $UserCreate = {
|
||||
export const $GraphCreate = {
|
||||
properties: {
|
||||
email: {
|
||||
name: {
|
||||
type: 'string',
|
||||
isRequired: true,
|
||||
format: 'email',
|
||||
},
|
||||
is_active: {
|
||||
description: {
|
||||
type: 'any-of',
|
||||
contains: [{
|
||||
type: 'boolean',
|
||||
type: 'string',
|
||||
}, {
|
||||
type: 'null',
|
||||
}],
|
||||
isRequired: true,
|
||||
},
|
||||
is_superuser: {
|
||||
is_favorite: {
|
||||
type: 'boolean',
|
||||
},
|
||||
full_name: {
|
||||
type: 'string',
|
||||
isRequired: true,
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
isRequired: true,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
@@ -2,14 +2,17 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export const $Body_reset_password = {
|
||||
export const $GraphsList = {
|
||||
properties: {
|
||||
token: {
|
||||
type: 'string',
|
||||
graphs: {
|
||||
type: 'array',
|
||||
contains: {
|
||||
type: 'Graph',
|
||||
},
|
||||
isRequired: true,
|
||||
},
|
||||
new_password: {
|
||||
type: 'string',
|
||||
count: {
|
||||
type: 'number',
|
||||
isRequired: true,
|
||||
},
|
||||
},
|
||||
@@ -1,12 +0,0 @@
|
||||
/* generated using openapi-typescript-codegen -- do no edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export const $Msg = {
|
||||
properties: {
|
||||
status: {
|
||||
type: 'string',
|
||||
isRequired: true,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
@@ -1,16 +0,0 @@
|
||||
/* generated using openapi-typescript-codegen -- do no edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export const $Token = {
|
||||
properties: {
|
||||
token: {
|
||||
type: 'string',
|
||||
isRequired: true,
|
||||
},
|
||||
token_type: {
|
||||
type: 'string',
|
||||
isRequired: true,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
@@ -1,44 +0,0 @@
|
||||
/* generated using openapi-typescript-codegen -- do no edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export const $UserUpdate = {
|
||||
properties: {
|
||||
email: {
|
||||
type: 'any-of',
|
||||
contains: [{
|
||||
type: 'string',
|
||||
format: 'email',
|
||||
}, {
|
||||
type: 'null',
|
||||
}],
|
||||
},
|
||||
is_active: {
|
||||
type: 'any-of',
|
||||
contains: [{
|
||||
type: 'boolean',
|
||||
}, {
|
||||
type: 'null',
|
||||
}],
|
||||
},
|
||||
is_superuser: {
|
||||
type: 'boolean',
|
||||
},
|
||||
full_name: {
|
||||
type: 'any-of',
|
||||
contains: [{
|
||||
type: 'string',
|
||||
}, {
|
||||
type: 'null',
|
||||
}],
|
||||
},
|
||||
hashed_password: {
|
||||
type: 'any-of',
|
||||
contains: [{
|
||||
type: 'string',
|
||||
}, {
|
||||
type: 'null',
|
||||
}],
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
24
frontend/src/app/openapi/services/Defaultv1.ts
Normal file
24
frontend/src/app/openapi/services/Defaultv1.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/* generated using openapi-typescript-codegen -- do no edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { CancelablePromise } from '../core/CancelablePromise';
|
||||
import type { BaseHttpRequest } from '../core/BaseHttpRequest';
|
||||
|
||||
export class Defaultv1 {
|
||||
|
||||
constructor(public readonly httpRequest: BaseHttpRequest) {}
|
||||
|
||||
/**
|
||||
* Get Status
|
||||
* @returns any Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public getStatus(): CancelablePromise<any> {
|
||||
return this.httpRequest.request({
|
||||
method: 'GET',
|
||||
url: '/status',
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -54,7 +54,7 @@ export class Entitiesv1 {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Many Entites By Favorite
|
||||
* Get Entities
|
||||
* @param skip
|
||||
* @param limit
|
||||
* @param isFavorite
|
||||
@@ -81,7 +81,7 @@ export class Entitiesv1 {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Entity
|
||||
* Update Entity By Uuid
|
||||
* @param entityId
|
||||
* @param requestBody
|
||||
* @returns any Successful Response
|
||||
@@ -127,7 +127,7 @@ export class Entitiesv1 {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Entity Favorite
|
||||
* Update Favorite Entity Uuid
|
||||
* @param entityId
|
||||
* @param isFavorite
|
||||
* @returns any Successful Response
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { Graph } from '../models/Graph';
|
||||
import type { GraphCreate } from '../models/GraphCreate';
|
||||
import type { GraphsList } from '../models/GraphsList';
|
||||
|
||||
import type { CancelablePromise } from '../core/CancelablePromise';
|
||||
import type { BaseHttpRequest } from '../core/BaseHttpRequest';
|
||||
|
||||
@@ -10,14 +14,14 @@ export class Graphsv1 {
|
||||
constructor(public readonly httpRequest: BaseHttpRequest) {}
|
||||
|
||||
/**
|
||||
* Get Project
|
||||
* Get Graph
|
||||
* @param graphId
|
||||
* @returns any Successful Response
|
||||
* @returns Graph Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public getGraph(
|
||||
graphId: string,
|
||||
): CancelablePromise<any> {
|
||||
): CancelablePromise<Graph> {
|
||||
return this.httpRequest.request({
|
||||
method: 'GET',
|
||||
url: '/api/v1/graphs/{graph_id}',
|
||||
@@ -31,7 +35,7 @@ export class Graphsv1 {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Project
|
||||
* Update Favorite Graph Uuid
|
||||
* @param graphId
|
||||
* @param isFavorite
|
||||
* @returns any Successful Response
|
||||
@@ -57,18 +61,18 @@ export class Graphsv1 {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Project
|
||||
* Get Graphs
|
||||
* @param skip
|
||||
* @param limit
|
||||
* @param isFavorite
|
||||
* @returns any Successful Response
|
||||
* @returns GraphsList Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public getGraphs(
|
||||
skip?: number,
|
||||
limit: number = 100,
|
||||
isFavorite: boolean = false,
|
||||
): CancelablePromise<any> {
|
||||
): CancelablePromise<GraphsList> {
|
||||
return this.httpRequest.request({
|
||||
method: 'GET',
|
||||
url: '/api/v1/graphs',
|
||||
@@ -84,23 +88,19 @@ export class Graphsv1 {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Project
|
||||
* @param name
|
||||
* @param description
|
||||
* @returns any Successful Response
|
||||
* Create Graph
|
||||
* @param requestBody
|
||||
* @returns Graph Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public createGraph(
|
||||
name: string,
|
||||
description: string = '',
|
||||
): CancelablePromise<any> {
|
||||
requestBody: GraphCreate,
|
||||
): CancelablePromise<Graph> {
|
||||
return this.httpRequest.request({
|
||||
method: 'POST',
|
||||
url: '/api/v1/graphs',
|
||||
query: {
|
||||
'name': name,
|
||||
'description': description,
|
||||
},
|
||||
body: requestBody,
|
||||
mediaType: 'application/json',
|
||||
errors: {
|
||||
422: `Validation Error`,
|
||||
},
|
||||
@@ -108,7 +108,7 @@ export class Graphsv1 {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Project
|
||||
* Delete Graph
|
||||
* @param uuid
|
||||
* @returns any Successful Response
|
||||
* @throws ApiError
|
||||
@@ -129,7 +129,7 @@ export class Graphsv1 {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Unique Graph Labels
|
||||
* Get Graph Stats
|
||||
* @param graphId
|
||||
* @returns any Successful Response
|
||||
* @throws ApiError
|
||||
|
||||
@@ -2,11 +2,6 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { Body_login_access_token } from '../models/Body_login_access_token';
|
||||
import type { Body_reset_password } from '../models/Body_reset_password';
|
||||
import type { Msg } from '../models/Msg';
|
||||
import type { Token } from '../models/Token';
|
||||
|
||||
import type { CancelablePromise } from '../core/CancelablePromise';
|
||||
import type { BaseHttpRequest } from '../core/BaseHttpRequest';
|
||||
|
||||
@@ -15,40 +10,31 @@ export class Loginv1 {
|
||||
constructor(public readonly httpRequest: BaseHttpRequest) {}
|
||||
|
||||
/**
|
||||
* Login Access Token
|
||||
* @param formData
|
||||
* @returns Token Successful Response
|
||||
* Get Account
|
||||
* @returns any Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public loginAccessToken(
|
||||
formData: Body_login_access_token,
|
||||
): CancelablePromise<Token> {
|
||||
public getAccount(): CancelablePromise<any> {
|
||||
return this.httpRequest.request({
|
||||
method: 'POST',
|
||||
url: '/api/v1/login/access-token',
|
||||
formData: formData,
|
||||
mediaType: 'application/x-www-form-urlencoded',
|
||||
errors: {
|
||||
422: `Validation Error`,
|
||||
},
|
||||
method: 'GET',
|
||||
url: '/api/v1/get-account',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Recover Password
|
||||
* Password Recovery
|
||||
* @param email
|
||||
* @returns Msg Successful Response
|
||||
* Post Signin
|
||||
* @param code
|
||||
* @returns any Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public passwordRecoveryEmail(
|
||||
email: string,
|
||||
): CancelablePromise<Msg> {
|
||||
public postSignin(
|
||||
code: string,
|
||||
): CancelablePromise<any> {
|
||||
return this.httpRequest.request({
|
||||
method: 'POST',
|
||||
url: '/api/v1/password-recovery/{email}',
|
||||
path: {
|
||||
'email': email,
|
||||
url: '/api/v1/sign-in',
|
||||
query: {
|
||||
'code': code,
|
||||
},
|
||||
errors: {
|
||||
422: `Validation Error`,
|
||||
@@ -57,23 +43,14 @@ export class Loginv1 {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset Password
|
||||
* Reset password
|
||||
* @param requestBody
|
||||
* @returns Msg Successful Response
|
||||
* Post Signout
|
||||
* @returns any Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public resetPassword(
|
||||
requestBody: Body_reset_password,
|
||||
): CancelablePromise<Msg> {
|
||||
public postSignout(): CancelablePromise<any> {
|
||||
return this.httpRequest.request({
|
||||
method: 'POST',
|
||||
url: '/api/v1/reset-password/',
|
||||
body: requestBody,
|
||||
mediaType: 'application/json',
|
||||
errors: {
|
||||
422: `Validation Error`,
|
||||
},
|
||||
url: '/api/v1/sign-out',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export class Nodesv1 {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Node Transforms
|
||||
* Get Entity Transforms
|
||||
* @param label
|
||||
* @returns any Successful Response
|
||||
* @throws ApiError
|
||||
@@ -45,7 +45,7 @@ export class Nodesv1 {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Entity From Drop
|
||||
* Create Graph Entity
|
||||
* @param requestBody
|
||||
* @returns any Successful Response
|
||||
* @throws ApiError
|
||||
|
||||
@@ -17,7 +17,7 @@ export class Scansv1 {
|
||||
* @returns any Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public createScanMachineApiV1ScansMachinesPost(
|
||||
public createScanMachine(
|
||||
requestBody: ScanMachineCreate,
|
||||
): CancelablePromise<any> {
|
||||
return this.httpRequest.request({
|
||||
@@ -38,7 +38,7 @@ export class Scansv1 {
|
||||
* @returns any Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public getScanMachinesApiV1ScansMachinesGet(
|
||||
public getScanMachines(
|
||||
skip?: number,
|
||||
limit: number = 50,
|
||||
): CancelablePromise<any> {
|
||||
@@ -56,12 +56,12 @@ export class Scansv1 {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Project
|
||||
* Delete Scan Project
|
||||
* @param id
|
||||
* @returns any Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public deleteProjectApiV1ScansDelete(
|
||||
public deleteScanProject(
|
||||
id: number,
|
||||
): CancelablePromise<any> {
|
||||
return this.httpRequest.request({
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
/* generated using openapi-typescript-codegen -- do no edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { Body_create_user_open } from '../models/Body_create_user_open';
|
||||
import type { Body_update_user_me } from '../models/Body_update_user_me';
|
||||
import type { User } from '../models/User';
|
||||
import type { UserCreate } from '../models/UserCreate';
|
||||
import type { UserUpdate } from '../models/UserUpdate';
|
||||
|
||||
import type { CancelablePromise } from '../core/CancelablePromise';
|
||||
import type { BaseHttpRequest } from '../core/BaseHttpRequest';
|
||||
|
||||
export class Usersv1 {
|
||||
|
||||
constructor(public readonly httpRequest: BaseHttpRequest) {}
|
||||
|
||||
/**
|
||||
* Read Users
|
||||
* Retrieve users.
|
||||
* @param skip
|
||||
* @param limit
|
||||
* @returns User Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public getUsers(
|
||||
skip?: number,
|
||||
limit: number = 100,
|
||||
): CancelablePromise<Array<User>> {
|
||||
return this.httpRequest.request({
|
||||
method: 'GET',
|
||||
url: '/api/v1/users/',
|
||||
query: {
|
||||
'skip': skip,
|
||||
'limit': limit,
|
||||
},
|
||||
errors: {
|
||||
422: `Validation Error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create User
|
||||
* Create new user.
|
||||
* @param requestBody
|
||||
* @returns User Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public createUser(
|
||||
requestBody: UserCreate,
|
||||
): CancelablePromise<User> {
|
||||
return this.httpRequest.request({
|
||||
method: 'POST',
|
||||
url: '/api/v1/users/',
|
||||
body: requestBody,
|
||||
mediaType: 'application/json',
|
||||
errors: {
|
||||
422: `Validation Error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Read User Me
|
||||
* Get current user.
|
||||
* @returns User Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public readUser(): CancelablePromise<User> {
|
||||
return this.httpRequest.request({
|
||||
method: 'GET',
|
||||
url: '/api/v1/users/me',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update User Me
|
||||
* Update own user.
|
||||
* @param requestBody
|
||||
* @returns User Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public updateUserMe(
|
||||
requestBody?: Body_update_user_me,
|
||||
): CancelablePromise<User> {
|
||||
return this.httpRequest.request({
|
||||
method: 'PUT',
|
||||
url: '/api/v1/users/me',
|
||||
body: requestBody,
|
||||
mediaType: 'application/json',
|
||||
errors: {
|
||||
422: `Validation Error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create User Open
|
||||
* Create new user without the need to be logged in.
|
||||
* @param requestBody
|
||||
* @returns User Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public createUserOpen(
|
||||
requestBody: Body_create_user_open,
|
||||
): CancelablePromise<User> {
|
||||
return this.httpRequest.request({
|
||||
method: 'POST',
|
||||
url: '/api/v1/users/open',
|
||||
body: requestBody,
|
||||
mediaType: 'application/json',
|
||||
errors: {
|
||||
422: `Validation Error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Read User By Id
|
||||
* Get a specific user by id.
|
||||
* @param userId
|
||||
* @returns User Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public readUserById(
|
||||
userId: number,
|
||||
): CancelablePromise<User> {
|
||||
return this.httpRequest.request({
|
||||
method: 'GET',
|
||||
url: '/api/v1/users/{user_id}',
|
||||
path: {
|
||||
'user_id': userId,
|
||||
},
|
||||
errors: {
|
||||
422: `Validation Error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update User
|
||||
* Update a user.
|
||||
* @param userId
|
||||
* @param requestBody
|
||||
* @returns User Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public updateUser(
|
||||
userId: number,
|
||||
requestBody: UserUpdate,
|
||||
): CancelablePromise<User> {
|
||||
return this.httpRequest.request({
|
||||
method: 'PUT',
|
||||
url: '/api/v1/users/{user_id}',
|
||||
path: {
|
||||
'user_id': userId,
|
||||
},
|
||||
body: requestBody,
|
||||
mediaType: 'application/json',
|
||||
errors: {
|
||||
422: `Validation Error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import { configureStore, ThunkAction, Action, combineReducers } from '@reduxjs/toolkit';
|
||||
import settings from '@/features/settings/settingsSlice';
|
||||
import graph from '@/features/graph/graphSlice';
|
||||
import dashboard from '@/features/dashboard/dashboardSlice';
|
||||
|
||||
const reducer = combineReducers({
|
||||
dashboard,
|
||||
settings,
|
||||
graph,
|
||||
});
|
||||
|
||||
BIN
frontend/src/assets/fonts/Inter-Regular.woff
Normal file
BIN
frontend/src/assets/fonts/Inter-Regular.woff
Normal file
Binary file not shown.
BIN
frontend/src/assets/fonts/Inter-Regular.woff2
Normal file
BIN
frontend/src/assets/fonts/Inter-Regular.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/fonts/Lexend-Regular.woff
Normal file
BIN
frontend/src/assets/fonts/Lexend-Regular.woff
Normal file
Binary file not shown.
BIN
frontend/src/assets/fonts/Lexend-Regular.woff2
Normal file
BIN
frontend/src/assets/fonts/Lexend-Regular.woff2
Normal file
Binary file not shown.
@@ -5,6 +5,24 @@
|
||||
@import './tables.css';
|
||||
@import './charts.css';
|
||||
|
||||
@font-face {
|
||||
font-family: 'Lexend';
|
||||
src: url('@/assets/fonts/Lexend-Regular.woff2') format('woff2'),
|
||||
url('@/assets/fonts/Lexend-Regular.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src: url('@/assets/fonts/Inter-Regular.woff2') format('woff2'),
|
||||
url('@/assets/fonts/Inter-Regular.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
*,
|
||||
:after,
|
||||
:before {
|
||||
@@ -249,6 +267,6 @@ input[type='file'] {
|
||||
}
|
||||
|
||||
.cm-line span {
|
||||
@apply !font-code text-sm;
|
||||
@apply text-sm;
|
||||
}
|
||||
/* end react-grid-layout and codemirror */
|
||||
|
||||
@@ -1,35 +1,67 @@
|
||||
// @ts-nocheck
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
import "react-grid-layout/css/styles.css";
|
||||
import { Responsive, WidthProvider } from "react-grid-layout";
|
||||
import CodeMirror from "@uiw/react-codemirror";
|
||||
import { LockClosedIcon, LockOpenIcon } from "@heroicons/react/24/outline";
|
||||
import { tokyoNightInit } from "@uiw/codemirror-theme-tokyo-night";
|
||||
import CodeMirror, { Extension } from "@uiw/react-codemirror";
|
||||
import { tags as t } from "@lezer/highlight";
|
||||
import { python } from "@codemirror/lang-python";
|
||||
import { Icon } from "@/components/Icons";
|
||||
import { useParams } from "react-router-dom"
|
||||
import { useEffectOnce } from "@/components/utils";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useEffectOnce } from "../utils";
|
||||
import { useAppDispatch, useAppSelector } from "@/app/hooks";
|
||||
import { selectActiveEntity, setActiveEntityId } from "@/features/dashboard/dashboardSlice";
|
||||
import { LockClosedIcon, LockOpenIcon } from "@heroicons/react/24/outline";
|
||||
import { Responsive, WidthProvider } from "react-grid-layout";
|
||||
import "react-grid-layout/css/styles.css";
|
||||
import { Icon } from "../Icons";
|
||||
import { sdk } from "@/app/api";
|
||||
|
||||
export const tokyoNightTheme = tokyoNightInit({
|
||||
settings: {
|
||||
caret: "#c6c6c6",
|
||||
background: '#1a1b26',
|
||||
fontFamily: 'monospace'
|
||||
},
|
||||
styles: [{ tag: t.comment, color: "#6272a4" }],
|
||||
})
|
||||
|
||||
export function CodeEditor({ code, setCode, lsp }: JSONObject) {
|
||||
return (
|
||||
<>{lsp ? (
|
||||
<CodeMirror
|
||||
theme={tokyoNightTheme}
|
||||
value={code}
|
||||
onChange={(value) => setCode(value)}
|
||||
extensions={[python(), lsp]}
|
||||
/>
|
||||
) : (
|
||||
<CodeMirror
|
||||
theme={tokyoNightTheme}
|
||||
value={code}
|
||||
onChange={(value) => setCode(value)}
|
||||
extensions={[python()]}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
const ResponsiveGridLayout = WidthProvider(Responsive);
|
||||
|
||||
export function ResizableHandles({ activeEntity }: JSONObject) {
|
||||
const [pythonCode, setPythonCode] = useState(activeEntity.source);
|
||||
|
||||
export default function EntityEditor({ activeEntity }: JSONObject) {
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const [isEntityDraggable, setEntityDraggable] = useState(false);
|
||||
const [isElementsDraggable, setElementsDraggable] = useState(false);
|
||||
const [code, setCode] = useState(activeEntity?.source)
|
||||
|
||||
useEffect(() => {
|
||||
setPythonCode(activeEntity.source)
|
||||
}, [activeEntity])
|
||||
if (activeEntity?.source) setCode(activeEntity.source)
|
||||
}, [activeEntity?.source])
|
||||
|
||||
return (
|
||||
<>
|
||||
<ResponsiveGridLayout
|
||||
compactType={null}
|
||||
className="w-auto flex h-full z-[99] absolute"
|
||||
rowHeight={60}
|
||||
rowHeight={56}
|
||||
maxRows={50}
|
||||
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
|
||||
cols={{ lg: 20, md: 20, sm: 20, xs: 18, xxs: 16 }}
|
||||
@@ -37,7 +69,7 @@ export function ResizableHandles({ activeEntity }: JSONObject) {
|
||||
isResizable={true}
|
||||
>
|
||||
<div
|
||||
className=" overflow-hidden rounded-md z-10 border border-dark-300 flex flex-col h-full"
|
||||
className=" overflow-hidden rounded-md z-10 bg-dark-600 border border-dark-300 flex flex-col h-full"
|
||||
key="b"
|
||||
data-grid={{
|
||||
x: 0,
|
||||
@@ -54,7 +86,7 @@ export function ResizableHandles({ activeEntity }: JSONObject) {
|
||||
<li className="flex items-start">
|
||||
<div className="flex items-center">
|
||||
<span className="text-slate-500 font-display truncate">
|
||||
Entity Editor{" "}
|
||||
Entity Editor
|
||||
<span className="font-medium font-display">/ </span>
|
||||
</span>
|
||||
</div>
|
||||
@@ -64,9 +96,8 @@ export function ResizableHandles({ activeEntity }: JSONObject) {
|
||||
<span
|
||||
className="text-slate-500 text-inherit whitespace-nowrap font-display"
|
||||
title={"placeholder"}
|
||||
aria-current={"placeholder"}
|
||||
>
|
||||
{activeEntity.label}
|
||||
{activeEntity && activeEntity.label}
|
||||
<span className="font-medium font-display "> /</span>
|
||||
</span>
|
||||
</div>
|
||||
@@ -75,16 +106,16 @@ export function ResizableHandles({ activeEntity }: JSONObject) {
|
||||
<div className="flex justify-between items-center w-full text-slate-400 ">
|
||||
<button
|
||||
onClick={() => {
|
||||
sdk.entities.updateEntityByUuid(activeEntity.uuid, {
|
||||
source: pythonCode,
|
||||
label: activeEntity.label,
|
||||
description: activeEntity.description,
|
||||
author: activeEntity.author
|
||||
}).then(() => {
|
||||
toast.info(
|
||||
`The ${activeEntity.label} entity has been saved.`
|
||||
);
|
||||
}).catch(error => console.error(error));
|
||||
// sdk.entities.updateEntityByUuid(activeEntity.uuid, {
|
||||
// source: pythonCode,
|
||||
// label: activeEntity.label,
|
||||
// description: activeEntity.description,
|
||||
// author: activeEntity.author
|
||||
// }).then(() => {
|
||||
// toast.info(
|
||||
// `The ${activeEntity.label} entity has been saved.`
|
||||
// );
|
||||
// }).catch(error => console.error(error));
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
@@ -105,28 +136,9 @@ export function ResizableHandles({ activeEntity }: JSONObject) {
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
<div className="container overflow-y-scroll h-full !bg-[#1a1b26]">
|
||||
<div className="container bg-[#1A1B26] overflow-y-scroll h-full ">
|
||||
<div className="editor ">
|
||||
<CodeMirror
|
||||
|
||||
theme={tokyoNightInit({
|
||||
settings: {
|
||||
caret: "#c6c6c6",
|
||||
fontFamily: "FiraCode",
|
||||
background: '#1a1b26'
|
||||
},
|
||||
|
||||
styles: [{ tag: t.comment, color: "#6272a4" }],
|
||||
})}
|
||||
value={pythonCode}
|
||||
onChange={(value) => setPythonCode(value)}
|
||||
extensions={[python({ jsx: true })]}
|
||||
options={{
|
||||
mode: "python",
|
||||
theme: "default",
|
||||
lineNumbers: true,
|
||||
}}
|
||||
/>
|
||||
<CodeEditor code={code} setCode={setCode} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -134,30 +146,3 @@ export function ResizableHandles({ activeEntity }: JSONObject) {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default function EntityDetails() {
|
||||
const params: any = useParams()
|
||||
const [activeGraph, setActiveGraph] = useState<any>(null);
|
||||
const [graphStats, setGraphStats] = useState<any>(null);
|
||||
const [activeEntity, setActiveEntity] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
if (params?.entityId) {
|
||||
sdk.entities.getEntity(params.entityId).then(data => {
|
||||
setActiveEntity(data)
|
||||
})
|
||||
}
|
||||
}, [params?.entityId])
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="flex flex-col h-screen w-full">
|
||||
{/* <GraphHeader stats={graphStats} graph={activeGraph} /> */}
|
||||
<section className="flex w-full h-full relative">
|
||||
<ResizableHandles activeEntity={activeEntity} />
|
||||
</section>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
558
frontend/src/components/EntityEditor/exts/language.ts
Normal file
558
frontend/src/components/EntityEditor/exts/language.ts
Normal file
@@ -0,0 +1,558 @@
|
||||
// @ts-nocheck
|
||||
import { setDiagnostics } from '@codemirror/lint';
|
||||
import { Facet } from '@codemirror/state';
|
||||
import { EditorView,Tooltip} from '@codemirror/view';
|
||||
import {
|
||||
RequestManager,
|
||||
Client,
|
||||
WebSocketTransport,
|
||||
} from '@open-rpc/client-js';
|
||||
import {
|
||||
DiagnosticSeverity,
|
||||
CompletionItemKind,
|
||||
CompletionTriggerKind,
|
||||
} from 'vscode-languageserver-protocol';
|
||||
|
||||
import type {
|
||||
Completion,
|
||||
CompletionContext,
|
||||
CompletionResult,
|
||||
} from '@codemirror/autocomplete';
|
||||
import type { PublishDiagnosticsParams } from 'vscode-languageserver-protocol';
|
||||
import type { ViewUpdate, PluginValue } from '@codemirror/view';
|
||||
import type { Text } from '@codemirror/state';
|
||||
import type * as LSP from 'vscode-languageserver-protocol';
|
||||
import { Transport } from '@open-rpc/client-js/build/transports/Transport';
|
||||
|
||||
const timeout = 10000;
|
||||
const changesDelay = 500;
|
||||
|
||||
const CompletionItemKindMap = Object.fromEntries(
|
||||
Object.entries(CompletionItemKind).map(([key, value]) => [value, key])
|
||||
) as Record<CompletionItemKind, string>;
|
||||
|
||||
|
||||
// https://microsoft.github.io/language-server-protocol/specifications/specification-current/
|
||||
|
||||
// Client to server then server to client
|
||||
export interface LSPRequestMap {
|
||||
initialize: [LSP.InitializeParams, LSP.InitializeResult];
|
||||
'textDocument/hover': [LSP.HoverParams, LSP.Hover];
|
||||
'textDocument/completion': [
|
||||
LSP.CompletionParams,
|
||||
LSP.CompletionItem[] | LSP.CompletionList | null
|
||||
];
|
||||
}
|
||||
|
||||
// Client to server
|
||||
export interface LSPNotifyMap {
|
||||
initialized: LSP.InitializedParams;
|
||||
'textDocument/didChange': LSP.DidChangeTextDocumentParams;
|
||||
'textDocument/didOpen': LSP.DidOpenTextDocumentParams;
|
||||
}
|
||||
|
||||
// Server to client
|
||||
export interface LSPEventMap {
|
||||
'textDocument/publishDiagnostics': LSP.PublishDiagnosticsParams;
|
||||
}
|
||||
|
||||
export type Notification = {
|
||||
[key in keyof LSPEventMap]: {
|
||||
jsonrpc: '2.0';
|
||||
id?: null | undefined;
|
||||
method: key;
|
||||
params: LSPEventMap[key];
|
||||
};
|
||||
}[keyof LSPEventMap];
|
||||
|
||||
export class LanguageServerClient {
|
||||
private rootUri: string;
|
||||
private workspaceFolders: LSP.WorkspaceFolder[];
|
||||
private autoClose?: boolean;
|
||||
|
||||
private transport: Transport;
|
||||
private requestManager: RequestManager;
|
||||
private client: Client;
|
||||
|
||||
public ready: boolean;
|
||||
public capabilities: LSP.ServerCapabilities<any>;
|
||||
|
||||
private plugins: LanguageServerPlugin[];
|
||||
|
||||
public initializePromise: Promise<void>;
|
||||
|
||||
constructor(options: LanguageServerClientOptions) {
|
||||
this.rootUri = options.rootUri as string;
|
||||
this.workspaceFolders = options.workspaceFolders as LSP.WorkspaceFolder[];
|
||||
this.autoClose = options.autoClose;
|
||||
this.plugins = [];
|
||||
this.transport = options.transport;
|
||||
|
||||
this.requestManager = new RequestManager([this.transport]);
|
||||
this.client = new Client(this.requestManager);
|
||||
|
||||
this.client.onNotification((data) => {
|
||||
console.log('on notification constructor', data)
|
||||
this.processNotification(data as any);
|
||||
});
|
||||
|
||||
const webSocketTransport = <WebSocketTransport>this.transport
|
||||
if (webSocketTransport && webSocketTransport.connection) {
|
||||
// XXX(hjr265): Need a better way to do this. Relevant issue:
|
||||
// https://github.com/FurqanSoftware/codemirror-languageserver/issues/9
|
||||
webSocketTransport.connection.addEventListener('message', (message: JSONObject) => {
|
||||
const data = JSON.parse(message.data);
|
||||
if (data.method && data.id) {
|
||||
webSocketTransport.connection.send(JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
id: data.id,
|
||||
result: null
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.initializePromise = this.initialize();
|
||||
|
||||
this.transport.connection.addEventListener('message', (message) => {
|
||||
const data = JSON.parse(message.data);
|
||||
if (data.method && data.id)
|
||||
console.log('BEFORE process request', data, message)
|
||||
this.processRequest(data);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
processRequest({ id }: { id: string }) {
|
||||
this.transport.connection.send(JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
id,
|
||||
result: null
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
async initialize() {
|
||||
const { capabilities } = await this.request('initialize', {
|
||||
capabilities: {
|
||||
textDocument: {
|
||||
hover: {
|
||||
dynamicRegistration: true,
|
||||
contentFormat: ['plaintext', 'markdown'],
|
||||
},
|
||||
moniker: {},
|
||||
synchronization: {
|
||||
dynamicRegistration: true,
|
||||
willSave: false,
|
||||
didSave: false,
|
||||
willSaveWaitUntil: false,
|
||||
},
|
||||
completion: {
|
||||
dynamicRegistration: true,
|
||||
completionItem: {
|
||||
snippetSupport: false,
|
||||
commitCharactersSupport: true,
|
||||
documentationFormat: ['plaintext', 'markdown'],
|
||||
deprecatedSupport: false,
|
||||
preselectSupport: false,
|
||||
},
|
||||
contextSupport: false,
|
||||
},
|
||||
signatureHelp: {
|
||||
dynamicRegistration: true,
|
||||
signatureInformation: {
|
||||
documentationFormat: ['plaintext', 'markdown'],
|
||||
},
|
||||
},
|
||||
declaration: {
|
||||
dynamicRegistration: true,
|
||||
linkSupport: true,
|
||||
},
|
||||
definition: {
|
||||
dynamicRegistration: true,
|
||||
linkSupport: true,
|
||||
},
|
||||
typeDefinition: {
|
||||
dynamicRegistration: true,
|
||||
linkSupport: true,
|
||||
},
|
||||
implementation: {
|
||||
dynamicRegistration: true,
|
||||
linkSupport: true,
|
||||
},
|
||||
},
|
||||
workspace: {
|
||||
didChangeConfiguration: {
|
||||
dynamicRegistration: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
initializationOptions: null,
|
||||
processId: null,
|
||||
rootUri: this.rootUri,
|
||||
workspaceFolders: this.workspaceFolders,
|
||||
}, timeout * 3);
|
||||
this.capabilities = capabilities;
|
||||
this.notify('initialized', {});
|
||||
this.ready = true;
|
||||
}
|
||||
|
||||
close() {
|
||||
this.client.close();
|
||||
}
|
||||
|
||||
textDocumentDidOpen(params: LSP.DidOpenTextDocumentParams) {
|
||||
return this.notify('textDocument/didOpen', params);
|
||||
}
|
||||
|
||||
textDocumentDidChange(params: LSP.DidChangeTextDocumentParams) {
|
||||
return this.notify('textDocument/didChange', params)
|
||||
}
|
||||
|
||||
async textDocumentHover(params: LSP.HoverParams) {
|
||||
return await this.request('textDocument/hover', params, timeout)
|
||||
}
|
||||
|
||||
async textDocumentCompletion(params: LSP.CompletionParams) {
|
||||
return await this.request('textDocument/completion', params, timeout)
|
||||
}
|
||||
|
||||
attachPlugin(plugin: LanguageServerPlugin) {
|
||||
this.plugins.push(plugin);
|
||||
}
|
||||
|
||||
detachPlugin(plugin: LanguageServerPlugin) {
|
||||
const i = this.plugins.indexOf(plugin);
|
||||
if (i === -1) return;
|
||||
this.plugins.splice(i, 1);
|
||||
if (this.autoClose) this.close();
|
||||
}
|
||||
|
||||
private request<K extends keyof LSPRequestMap>(
|
||||
method: K,
|
||||
params: LSPRequestMap[K][0],
|
||||
timeout: number
|
||||
): Promise<LSPRequestMap[K][1]> {
|
||||
return this.client.request({ method, params }, timeout);
|
||||
}
|
||||
|
||||
private notify<K extends keyof LSPNotifyMap>(
|
||||
method: K,
|
||||
params: LSPNotifyMap[K]
|
||||
): Promise<LSPNotifyMap[K]> {
|
||||
return this.client.notify({ method, params });
|
||||
}
|
||||
|
||||
private processNotification(notification: Notification) {
|
||||
for (const plugin of this.plugins)
|
||||
plugin.processNotification(notification);
|
||||
}
|
||||
}
|
||||
|
||||
export class LanguageServerPlugin implements PluginValue {
|
||||
public client: LanguageServerClient;
|
||||
|
||||
private documentUri: string;
|
||||
private languageId: string;
|
||||
private documentVersion: number;
|
||||
|
||||
private changesTimeout: number;
|
||||
|
||||
constructor(private view: EditorView, private allowHTMLContent: boolean) {
|
||||
this.client = this.view.state.facet(client);
|
||||
this.documentUri = this.view.state.facet(documentUri);
|
||||
this.languageId = this.view.state.facet(languageId);
|
||||
this.documentVersion = 0;
|
||||
this.changesTimeout = 0;
|
||||
|
||||
this.client.attachPlugin(this);
|
||||
|
||||
try {
|
||||
this.initialize({
|
||||
documentText: this.view.state.doc.toString(),
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('wtfd', e)
|
||||
}
|
||||
}
|
||||
|
||||
update({ docChanged }: ViewUpdate) {
|
||||
if (!docChanged) return;
|
||||
if (this.changesTimeout) clearTimeout(this.changesTimeout);
|
||||
this.changesTimeout = self.setTimeout(() => {
|
||||
this.sendChange({
|
||||
documentText: this.view.state.doc.toString(),
|
||||
});
|
||||
}, changesDelay);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.client.detachPlugin(this);
|
||||
}
|
||||
|
||||
async initialize({ documentText }: { documentText: string }) {
|
||||
if (this.client.initializePromise) {
|
||||
try {
|
||||
await this.client.initializePromise;
|
||||
} catch (e) {
|
||||
console.log('catching init')
|
||||
console.warn(e)
|
||||
}
|
||||
}
|
||||
this.client.textDocumentDidOpen({
|
||||
textDocument: {
|
||||
uri: this.documentUri,
|
||||
languageId: this.languageId,
|
||||
text: documentText,
|
||||
version: this.documentVersion,
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
async sendChange({ documentText }: { documentText: string }) {
|
||||
if (!this.client.ready) return;
|
||||
try {
|
||||
await this.client.textDocumentDidChange({
|
||||
textDocument: {
|
||||
uri: this.documentUri,
|
||||
version: this.documentVersion++,
|
||||
},
|
||||
contentChanges: [{ text: documentText }],
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
requestDiagnostics(view: EditorView) {
|
||||
this.sendChange({ documentText: view.state.doc.toString() });
|
||||
}
|
||||
|
||||
async requestHoverTooltip(
|
||||
view: EditorView,
|
||||
{ line, character }: { line: number; character: number }
|
||||
): Promise<Tooltip | null> {
|
||||
if (!this.client.ready || !this.client.capabilities!.hoverProvider) return null;
|
||||
|
||||
this.sendChange({ documentText: view.state.doc.toString() });
|
||||
const result = await this.client.textDocumentHover({
|
||||
textDocument: { uri: this.documentUri },
|
||||
position: { line, character },
|
||||
});
|
||||
if (!result) return null;
|
||||
const { contents, range } = result;
|
||||
let pos = posToOffset(view.state.doc, { line, character })!;
|
||||
let end: number = 0;
|
||||
if (range) {
|
||||
pos = posToOffset(view.state.doc, range.start)!;
|
||||
end = posToOffset(view.state.doc, range.end)!;
|
||||
}
|
||||
if (pos === null) return null;
|
||||
const dom = document.createElement('div');
|
||||
dom.classList.add('documentation');
|
||||
if (this.allowHTMLContent) dom.innerHTML = formatContents(contents);
|
||||
else dom.textContent = formatContents(contents);
|
||||
return { pos, end, create: (view) => ({ dom }), above: true };
|
||||
}
|
||||
|
||||
async requestCompletion(
|
||||
context: CompletionContext,
|
||||
{ line, character }: { line: number; character: number },
|
||||
{
|
||||
triggerKind,
|
||||
triggerCharacter,
|
||||
}: {
|
||||
triggerKind: CompletionTriggerKind;
|
||||
triggerCharacter: string | undefined;
|
||||
}
|
||||
): Promise<CompletionResult | null> {
|
||||
if (!this.client.ready || !this.client.capabilities!.completionProvider) return null;
|
||||
this.sendChange({
|
||||
documentText: context.state.doc.toString(),
|
||||
});
|
||||
|
||||
const result = await this.client.textDocumentCompletion({
|
||||
textDocument: { uri: this.documentUri },
|
||||
position: { line, character },
|
||||
context: {
|
||||
triggerKind,
|
||||
triggerCharacter,
|
||||
}
|
||||
});
|
||||
|
||||
if (!result) return null;
|
||||
|
||||
const items = 'items' in result ? result.items : result;
|
||||
|
||||
let options = items.map(
|
||||
({
|
||||
detail,
|
||||
label,
|
||||
kind,
|
||||
textEdit,
|
||||
documentation,
|
||||
sortText,
|
||||
filterText,
|
||||
}) => {
|
||||
const completion: Completion & {
|
||||
filterText: string;
|
||||
sortText?: string;
|
||||
apply: string;
|
||||
} = {
|
||||
label,
|
||||
detail,
|
||||
apply: textEdit?.newText ?? label,
|
||||
type: kind && CompletionItemKindMap[kind].toLowerCase(),
|
||||
sortText: sortText ?? label,
|
||||
filterText: filterText ?? label,
|
||||
};
|
||||
if (documentation) {
|
||||
completion.info = formatContents(documentation);
|
||||
}
|
||||
return completion;
|
||||
}
|
||||
);
|
||||
|
||||
const [span, match] = prefixMatch(options);
|
||||
const token = context.matchBefore(match);
|
||||
let { pos } = context;
|
||||
|
||||
if (token) {
|
||||
pos = token.from;
|
||||
const word = token.text.toLowerCase();
|
||||
if (/^\w+$/.test(word)) {
|
||||
options = options
|
||||
.filter(({ filterText }) =>
|
||||
filterText.toLowerCase().startsWith(word)
|
||||
)
|
||||
.sort(({ apply: a }, { apply: b }) => {
|
||||
switch (true) {
|
||||
case a.startsWith(token.text) &&
|
||||
!b.startsWith(token.text):
|
||||
return -1;
|
||||
case !a.startsWith(token.text) &&
|
||||
b.startsWith(token.text):
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
return {
|
||||
from: pos,
|
||||
options,
|
||||
};
|
||||
}
|
||||
|
||||
processNotification(notification: Notification) {
|
||||
try {
|
||||
console.log('processing notification')
|
||||
switch (notification.method) {
|
||||
case 'textDocument/publishDiagnostics':
|
||||
this.processDiagnostics(notification.params);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
processDiagnostics(params: PublishDiagnosticsParams) {
|
||||
if (params.uri !== this.documentUri) return;
|
||||
|
||||
const diagnostics = params.diagnostics
|
||||
.map(({ range, message, severity }) => ({
|
||||
from: posToOffset(this.view.state.doc, range.start)!,
|
||||
to: posToOffset(this.view.state.doc, range.end)!,
|
||||
severity: ({
|
||||
[DiagnosticSeverity.Error]: 'error',
|
||||
[DiagnosticSeverity.Warning]: 'warning',
|
||||
[DiagnosticSeverity.Information]: 'info',
|
||||
[DiagnosticSeverity.Hint]: 'info',
|
||||
} as const)[severity!],
|
||||
message,
|
||||
}))
|
||||
.filter(({ from, to }) => from !== null && to !== null && from !== undefined && to !== undefined)
|
||||
.sort((a, b) => {
|
||||
switch (true) {
|
||||
case a.from < b.from:
|
||||
return -1;
|
||||
case a.from > b.from:
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
this.view.dispatch(setDiagnostics(this.view.state, diagnostics));
|
||||
}
|
||||
}
|
||||
|
||||
export interface LanguageServerBaseOptions {
|
||||
rootUri: string | null;
|
||||
workspaceFolders?: LSP.WorkspaceFolder[] | null;
|
||||
documentUri: string;
|
||||
languageId: string;
|
||||
}
|
||||
|
||||
export interface LanguageServerClientOptions extends LanguageServerBaseOptions {
|
||||
transport: Transport,
|
||||
autoClose?: boolean;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function posToOffset(doc: Text, pos: { line: number; character: number }) {
|
||||
if (pos.line >= doc.lines) return;
|
||||
const offset = doc.line(pos.line + 1).from + pos.character;
|
||||
if (offset > doc.length) return;
|
||||
return offset;
|
||||
}
|
||||
|
||||
export function offsetToPos(doc: Text, offset: number) {
|
||||
const line = doc.lineAt(offset);
|
||||
return {
|
||||
line: line.number - 1,
|
||||
character: offset - line.from,
|
||||
};
|
||||
}
|
||||
|
||||
export function formatContents(
|
||||
contents: LSP.MarkupContent | LSP.MarkedString | LSP.MarkedString[]
|
||||
): string {
|
||||
if (Array.isArray(contents)) {
|
||||
return contents.map((c) => formatContents(c) + '\n\n').join('');
|
||||
} else if (typeof contents === 'string') {
|
||||
return contents;
|
||||
} else {
|
||||
return contents.value;
|
||||
}
|
||||
}
|
||||
|
||||
export function toSet(chars: Set<string>) {
|
||||
let preamble = '';
|
||||
let flat = Array.from(chars).join('');
|
||||
const words = /\w/.test(flat);
|
||||
if (words) {
|
||||
preamble += '\\w';
|
||||
flat = flat.replace(/\w/g, '');
|
||||
}
|
||||
return `[${preamble}${flat.replace(/[^\w\s]/g, '\\$&')}]`;
|
||||
}
|
||||
|
||||
export function prefixMatch(options: Completion[]) {
|
||||
const first = new Set<string>();
|
||||
const rest = new Set<string>();
|
||||
|
||||
for (const { apply } of options) {
|
||||
const [initial, ...restStr] = apply as string;
|
||||
first.add(initial);
|
||||
for (const char of restStr) {
|
||||
rest.add(char);
|
||||
}
|
||||
}
|
||||
|
||||
const source = toSet(first) + toSet(rest) + '*$';
|
||||
return [new RegExp('^' + source), new RegExp(source)];
|
||||
}
|
||||
89
frontend/src/components/EntityEditor/exts/languageServer.tsx
Normal file
89
frontend/src/components/EntityEditor/exts/languageServer.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import { Facet, ViewPlugin, hoverTooltip } from "@uiw/react-codemirror";
|
||||
import { LanguageServerBaseOptions, LanguageServerClient, LanguageServerClientOptions, LanguageServerPlugin, offsetToPos } from "./language";
|
||||
import { autocompletion } from '@codemirror/autocomplete';
|
||||
import {
|
||||
CompletionTriggerKind,
|
||||
} from 'vscode-languageserver-protocol';
|
||||
import {
|
||||
WebSocketTransport,
|
||||
} from '@open-rpc/client-js';
|
||||
|
||||
interface LanguageServerOptions extends LanguageServerClientOptions {
|
||||
client?: LanguageServerClient;
|
||||
allowHTMLContent?: boolean;
|
||||
}
|
||||
|
||||
const useLast = (values: readonly any[]) => values.reduce((_, v) => v, '');
|
||||
|
||||
const client = Facet.define<LanguageServerClient, LanguageServerClient>({ combine: useLast });
|
||||
const documentUri = Facet.define<string, string>({ combine: useLast });
|
||||
const languageId = Facet.define<string, string>({ combine: useLast });
|
||||
|
||||
export function languageServerWithTransport(options: LanguageServerOptions) {
|
||||
let plugin: LanguageServerPlugin | null = null;
|
||||
|
||||
return [
|
||||
client.of(options.client || new LanguageServerClient({ ...options, autoClose: true })),
|
||||
documentUri.of(options.documentUri),
|
||||
languageId.of(options.languageId),
|
||||
ViewPlugin.define((view) => (plugin = new LanguageServerPlugin(view, options.allowHTMLContent as boolean))),
|
||||
hoverTooltip(
|
||||
(view, pos) =>
|
||||
plugin?.requestHoverTooltip(
|
||||
view,
|
||||
offsetToPos(view.state.doc, pos)
|
||||
) ?? null
|
||||
),
|
||||
autocompletion({
|
||||
override: [
|
||||
async (context) => {
|
||||
if (plugin == null) return null;
|
||||
|
||||
const { state, pos, explicit } = context;
|
||||
const line = state.doc.lineAt(pos);
|
||||
let trigKind: CompletionTriggerKind =
|
||||
CompletionTriggerKind.Invoked;
|
||||
let trigChar: string | undefined;
|
||||
if (
|
||||
!explicit &&
|
||||
plugin.client.capabilities?.completionProvider?.triggerCharacters?.includes(
|
||||
line.text[pos - line.from - 1]
|
||||
)
|
||||
) {
|
||||
trigKind = CompletionTriggerKind.TriggerCharacter;
|
||||
trigChar = line.text[pos - line.from - 1];
|
||||
}
|
||||
if (
|
||||
trigKind === CompletionTriggerKind.Invoked &&
|
||||
!context.matchBefore(/\w+$/)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return await plugin.requestCompletion(
|
||||
context,
|
||||
offsetToPos(state.doc, pos),
|
||||
{
|
||||
triggerKind: trigKind,
|
||||
triggerCharacter: trigChar,
|
||||
}
|
||||
);
|
||||
},
|
||||
],
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
export interface LanguageServerWebsocketOptions extends LanguageServerBaseOptions {
|
||||
serverUri?: `ws://${string}` | `wss://${string}`;
|
||||
}
|
||||
|
||||
|
||||
export function languageServer(options: LanguageServerWebsocketOptions) {
|
||||
const serverUri = options.serverUri;
|
||||
delete options.serverUri;
|
||||
return languageServerWithTransport({
|
||||
...options,
|
||||
transport: new WebSocketTransport(serverUri as string)
|
||||
})
|
||||
}
|
||||
@@ -17,11 +17,6 @@ export default function IncidentCard({ closeModal }: JSONObject) {
|
||||
return option.label.toLowerCase().includes(query.toLowerCase());
|
||||
}) ?? [];
|
||||
|
||||
useEffect(() => {
|
||||
sdk.graphs.getGraphs(0, 1000).then((data) => {
|
||||
setOptions(data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className='bg-dark-600 w-full shadow sm:rounded-lg'>
|
||||
|
||||
@@ -72,12 +72,12 @@ export function formatAMPM(date: Date) {
|
||||
return strTime;
|
||||
}
|
||||
|
||||
export function formatPGDate(date: string, showAt: boolean = false) {
|
||||
export function formatPGDate(date: string, showAt: boolean = false): string {
|
||||
if (date) {
|
||||
const dateStr = date.replace(' ', 'T')
|
||||
return `${new Date(dateStr).toDateString()}
|
||||
return `${new Date(dateStr).toLocaleDateString()}
|
||||
${showAt ? 'at' : ''}
|
||||
${formatAMPM(new Date(dateStr))}`
|
||||
}
|
||||
return null
|
||||
return ''
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
import { LS_USER_AUTH_KEY, sdk } from '@/app/api';
|
||||
import { LoginFormValues } from '@/routes/public/SigninPage';
|
||||
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
export interface User {
|
||||
email: string;
|
||||
isActive: boolean;
|
||||
isSuperuser: boolean;
|
||||
fullName: string;
|
||||
id: number;
|
||||
modified: Date;
|
||||
created: Date;
|
||||
}
|
||||
|
||||
export interface AuthState {
|
||||
auth: {
|
||||
isAuthenticated: boolean;
|
||||
token?: string;
|
||||
user?: User;
|
||||
};
|
||||
}
|
||||
|
||||
const rememberState = JSON.parse(localStorage.getItem(LS_USER_AUTH_KEY) || '{}');
|
||||
|
||||
let initialState;
|
||||
|
||||
if (rememberState) {
|
||||
initialState = {
|
||||
isAuthenticated: true,
|
||||
token: rememberState.token,
|
||||
user: {
|
||||
...rememberState.user,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
initialState = {
|
||||
isAuthenticated: false,
|
||||
user: null,
|
||||
token: null,
|
||||
};
|
||||
}
|
||||
|
||||
export const login = createAsyncThunk('auth/login', async (user: LoginFormValues, thunkAPI) => {
|
||||
try {
|
||||
const data = await sdk.login.loginAccessToken({
|
||||
username: user.email,
|
||||
password: user.password
|
||||
})
|
||||
if (data && data.token) {
|
||||
localStorage.setItem(LS_USER_AUTH_KEY, JSON.stringify(data));
|
||||
return data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return thunkAPI.rejectWithValue(error);
|
||||
}
|
||||
});
|
||||
|
||||
export const authSlice = createSlice({
|
||||
name: 'auth',
|
||||
initialState,
|
||||
reducers: {
|
||||
logout: (state) => {
|
||||
state.isAuthenticated = false;
|
||||
state.user = null;
|
||||
state.token = null;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(login.fulfilled, (state, action) => {
|
||||
const user = action?.payload;
|
||||
state.isAuthenticated = true;
|
||||
state.token = action?.payload?.token;
|
||||
state.user = {
|
||||
...user,
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { logout } = authSlice.actions;
|
||||
|
||||
export const selectAuthenticated = (state: AuthState) => state.auth.isAuthenticated;
|
||||
export const selectToken = (state: AuthState) => {
|
||||
if (state.auth.user) return state.auth.token;
|
||||
return null;
|
||||
};
|
||||
|
||||
export default authSlice.reducer;
|
||||
225
frontend/src/features/dashboard/dashboardSlice.tsx
Normal file
225
frontend/src/features/dashboard/dashboardSlice.tsx
Normal file
@@ -0,0 +1,225 @@
|
||||
import { createSlice, PayloadAction, createAsyncThunk } from '@reduxjs/toolkit';
|
||||
|
||||
import { RootState } from '@/app/store';
|
||||
import { sdk } from '@/app/api';
|
||||
import { Entitiesv1, Graph, GraphCreate } from '@/app/openapi';
|
||||
import Entities from '../../routes/dashboard/Entities';
|
||||
|
||||
export interface Graphs {
|
||||
graphs: Graph[];
|
||||
favoriteGraphs: Graph[];
|
||||
isLoadingGraphs: boolean;
|
||||
isGraphsError: boolean;
|
||||
isLoadingFavoriteGraphs: boolean;
|
||||
isFavoriteGraphsError: boolean;
|
||||
isLoadingEntities: boolean;
|
||||
isLoadingFavoriteEntities: boolean;
|
||||
isEntitiesError: boolean;
|
||||
isFavoriteEntitesError: boolean;
|
||||
favoriteEntities: JSONObject[];
|
||||
entities: JSONObject[];
|
||||
activeGraph: null | Graph;
|
||||
selectedEntity: null | JSONObject;
|
||||
activeTab: "graphs" | "entities" | "market";
|
||||
graphsCount: number;
|
||||
favoriteGraphsCount: number
|
||||
favoriteEntitiesCount: number;
|
||||
entitiesCount: number;
|
||||
activeEntityId: JSONObject | null;
|
||||
}
|
||||
|
||||
const initialState: Graphs = {
|
||||
isLoadingGraphs: false,
|
||||
isGraphsError: false,
|
||||
favoriteGraphs: [],
|
||||
graphs: [],
|
||||
graphsCount: 0,
|
||||
favoriteGraphsCount: 0,
|
||||
activeGraph: null,
|
||||
favoriteEntitiesCount: 0,
|
||||
entitiesCount: 0,
|
||||
selectedEntity: null,
|
||||
entities: [],
|
||||
favoriteEntities: [],
|
||||
isLoadingFavoriteGraphs: false,
|
||||
isFavoriteGraphsError: false,
|
||||
isLoadingEntities: false,
|
||||
isLoadingFavoriteEntities: false,
|
||||
isEntitiesError: false,
|
||||
isFavoriteEntitesError: false,
|
||||
activeTab: "graphs",
|
||||
activeEntityId: null
|
||||
};
|
||||
|
||||
|
||||
interface UpdateGraphFavorite {
|
||||
uuid: string
|
||||
isFavorite: boolean
|
||||
}
|
||||
|
||||
interface DataFavorite {
|
||||
data: Graph
|
||||
isFavorite: boolean
|
||||
}
|
||||
|
||||
interface ErrorFavorite {
|
||||
data: Graph;
|
||||
isFavorite: boolean;
|
||||
}
|
||||
|
||||
// TODO: Fix these stupid fucking types in the builder...
|
||||
export const updateGraphFavorite = createAsyncThunk("graphs/updateGraphFavorite", async ({ uuid, isFavorite }: UpdateGraphFavorite, thunkAPI): Promise<DataFavorite | ErrorFavorite> => await sdk.graphs.updateFavoriteGraphUuid(uuid, isFavorite)
|
||||
.then((data: Graph) => ({ data, isFavorite }))
|
||||
.catch((error) => ({ data: error, isFavorite }))
|
||||
);
|
||||
|
||||
|
||||
export const createGraph = createAsyncThunk("graphs/createGraph", async (createGraph: GraphCreate, thunkAPI) => await sdk.graphs.createGraph(createGraph)
|
||||
.then(graph => graph)
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
return thunkAPI.rejectWithValue(error)
|
||||
})
|
||||
);
|
||||
|
||||
export const deleteGraph = createAsyncThunk("graphs/deleteGraph", async (uuid: string, thunkAPI) => await sdk.graphs.deleteGraph(uuid)
|
||||
.then(() => uuid)
|
||||
.catch((error: Error) => {
|
||||
console.error(error);
|
||||
return thunkAPI.rejectWithValue(error);
|
||||
})
|
||||
);
|
||||
|
||||
interface GetGraphs {
|
||||
pageSize: number, pageIndex: number, isFavorite: boolean
|
||||
}
|
||||
|
||||
export const getGraphs = createAsyncThunk("graphs/getGraphs", async ({
|
||||
pageSize,
|
||||
pageIndex,
|
||||
isFavorite }: GetGraphs,
|
||||
thunkAPI
|
||||
) => await sdk.graphs.getGraphs(pageSize * pageIndex, pageSize, isFavorite)
|
||||
.then((data) => {
|
||||
return { data, isFavorite }
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
return thunkAPI.rejectWithValue({ error, isFavorite });
|
||||
}))
|
||||
|
||||
|
||||
export const getEntities = createAsyncThunk("entities/getEntities", async ({
|
||||
pageSize,
|
||||
pageIndex,
|
||||
isFavorite }: GetGraphs,
|
||||
thunkAPI
|
||||
) => await sdk.entities.getEntities(pageSize * pageIndex, pageSize, isFavorite)
|
||||
.then((data) => {
|
||||
return { data, isFavorite }
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
return thunkAPI.rejectWithValue({ error, isFavorite });
|
||||
}))
|
||||
|
||||
|
||||
export const dashboard = createSlice({
|
||||
name: "dashboard",
|
||||
initialState,
|
||||
reducers: {
|
||||
setActiveTab: (state, action) => {
|
||||
state.activeTab = action.payload;
|
||||
},
|
||||
setActiveEntityId: (state, action) => {
|
||||
state.activeEntityId = action.payload
|
||||
}
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(createGraph.fulfilled, (state, action) => {
|
||||
state.graphs.push(action.payload)
|
||||
})
|
||||
.addCase(deleteGraph.fulfilled, (state, action) => {
|
||||
state.graphs = state.graphs.filter((graph) => graph.uuid !== action.payload)
|
||||
state.favoriteGraphs = state.favoriteGraphs.filter((graph) => graph.uuid !== action.payload)
|
||||
})
|
||||
.addCase(getGraphs.fulfilled, (state, action) => {
|
||||
if (action.payload.isFavorite) {
|
||||
state.favoriteGraphs = action.payload.data.graphs
|
||||
state.favoriteGraphsCount = action.payload.data.count
|
||||
} else {
|
||||
state.graphs = action.payload.data.graphs
|
||||
state.graphsCount = action.payload.data.count
|
||||
}
|
||||
})
|
||||
.addCase(updateGraphFavorite.fulfilled, (state, action) => {
|
||||
if (action.payload.isFavorite) {
|
||||
state.graphs = state.graphs.filter((graph) => graph.uuid !== action.payload.data.uuid)
|
||||
state.favoriteGraphsCount += 1
|
||||
state.graphsCount -= 1
|
||||
state.favoriteGraphs.push(action.payload.data)
|
||||
} else {
|
||||
state.favoriteGraphs = state.favoriteGraphs.filter((graph) => graph.uuid !== action.payload.data.uuid)
|
||||
state.graphsCount += 1
|
||||
state.favoriteGraphsCount -= 1
|
||||
state.graphs.push(action.payload.data)
|
||||
}
|
||||
state.isLoadingFavoriteGraphs = false
|
||||
state.isLoadingGraphs = false
|
||||
})
|
||||
.addCase(updateGraphFavorite.pending, (state, action) => {
|
||||
state.isLoadingFavoriteGraphs = true
|
||||
state.isLoadingGraphs = true
|
||||
})
|
||||
|
||||
.addCase(updateGraphFavorite.rejected, (state, action: JSONObject) => {
|
||||
if (action.payload.isFavorite) {
|
||||
state.isFavoriteGraphsError = true
|
||||
state.isLoadingFavoriteGraphs = false
|
||||
}
|
||||
if (!action.payload.isFavorite) {
|
||||
state.isGraphsError = true
|
||||
state.isLoadingGraphs = false
|
||||
}
|
||||
})
|
||||
.addCase(getEntities.fulfilled, (state, action) => {
|
||||
if (action.payload.isFavorite) {
|
||||
state.favoriteEntities = action.payload.data.entities
|
||||
state.favoriteEntitiesCount = action.payload.data.count
|
||||
} else {
|
||||
state.entities = action.payload.data.entities
|
||||
state.entitiesCount = action.payload.data.count
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
export const { setActiveTab, setActiveEntityId } = dashboard.actions;
|
||||
|
||||
export const selectDashboardGraphs = (state: RootState) => ({
|
||||
graphs: state.dashboard.graphs,
|
||||
graphsCount: state.dashboard.graphsCount,
|
||||
activeGraph: state.dashboard.activeGraph,
|
||||
isLoadingGraphs: state.dashboard.isLoadingGraphs,
|
||||
isLoadingFavoriteGraphs: state.dashboard.isLoadingFavoriteGraphs,
|
||||
isGraphsError: state.dashboard.isGraphsError,
|
||||
isFavoriteGraphsError: state.dashboard.isFavoriteGraphsError,
|
||||
favoriteGraphs: state.dashboard.favoriteGraphs,
|
||||
})
|
||||
export const selectDashboardEntities = (state: RootState) => ({
|
||||
entities: state.dashboard.entities,
|
||||
favoriteEntities: state.dashboard.favoriteEntities,
|
||||
entitiesCount: state.dashboard.entitiesCount,
|
||||
favoriteEntitiesCount: state.dashboard.favoriteEntitiesCount,
|
||||
isLoadingEntities: state.dashboard.isLoadingEntities,
|
||||
isLoadingFavoriteEntities: state.dashboard.isLoadingFavoriteEntities,
|
||||
isEntitiesError: state.dashboard.isEntitiesError,
|
||||
isFavoriteEntitiesError: state.dashboard.isFavoriteEntitesError
|
||||
});
|
||||
export const selectActiveTab = (state: RootState) => state.dashboard.activeTab
|
||||
export const selectActiveEntity = (state: RootState) => state.dashboard.entities.filter((entity) => entity.uuid === state.dashboard.activeEntityId)[0]
|
||||
|
||||
export default dashboard.reducer;
|
||||
@@ -23,7 +23,7 @@ import { isSidebarOpen, setSidebar } from "@/features/settings/settingsSlice";
|
||||
import IncidentCard from "@/components/IncidentCard";
|
||||
|
||||
const navigation = [
|
||||
{ name: "Inquiries", to: "/app/inquiries", icon: InboxIcon },
|
||||
{ name: "Dashboard", to: "/app/dashboard/graphs", icon: InboxIcon },
|
||||
{ name: "Incidents *", to: "/app/incidents", icon: FolderOpenIcon },
|
||||
{ name: "Scans", to: "/app/scans", icon: DocumentMagnifyingGlassIcon },
|
||||
];
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
import React, { ReactElement, Suspense, lazy } from "react";
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import { Route, Routes, useParams } from "react-router-dom";
|
||||
import AppLayout from "./AppLayout";
|
||||
import NotFound from "./NotFound";
|
||||
import PublicLayout from "./PublicLayout";
|
||||
import ScansCreatePage from "./scans-create";
|
||||
import GraphDetails from "./graphs/GraphDetails";
|
||||
import Graphs from "./graphs/Graphs";
|
||||
import Entities from "./graphs/Entities";
|
||||
import Market from "./graphs/Market";
|
||||
import EntityDetails from "./graphs/EntityDetails";
|
||||
const SigninPage = lazy(() => import("./public/SigninPage"));
|
||||
const DashboardPage = lazy(() => import("./graphs"));
|
||||
import GraphDetails from "./dashboard/GraphDetailsPage";
|
||||
import Graphs from "./dashboard/_components/Graphs";
|
||||
import Entities from "./dashboard/Entities";
|
||||
import Market from "./dashboard/MarketPage";
|
||||
import EntityDetails from "./dashboard/EntityDetailsPage";
|
||||
const DashboardPage = lazy(() => import("./dashboard"));
|
||||
const AboutPage = lazy(() => import("@routes/public/AboutPage"));
|
||||
const LandingPage = lazy(() => import("@routes/public/LandingPage"));
|
||||
const InquiryGraph = lazy(() => import("./inquiry-graph"));
|
||||
const EntityGraphPage = lazy(() => import("./entity-graph"));
|
||||
const SettingsPage = lazy(() => import("./settings"));
|
||||
const IncidentsPage = lazy(() => import("./incidents"));
|
||||
const ScansPage = lazy(() => import("./scans"));
|
||||
import CallbackPage from './public/CallbackPage';
|
||||
|
||||
|
||||
export default function AppRoutes(): ReactElement {
|
||||
return (
|
||||
|
||||
<Routes>
|
||||
<Route path="/callback" element={<><CallbackPage /></>} />
|
||||
<Route path="/" element={<PublicLayout />}>
|
||||
<Route
|
||||
index
|
||||
@@ -39,18 +41,10 @@ export default function AppRoutes(): ReactElement {
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="sign-in"
|
||||
element={
|
||||
<Suspense>
|
||||
<SigninPage />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
<Route path="/app" element={<AppLayout />}>
|
||||
<Route
|
||||
path="inquiries"
|
||||
path="dashboard"
|
||||
element={
|
||||
<Suspense>
|
||||
<DashboardPage />
|
||||
@@ -101,21 +95,14 @@ export default function AppRoutes(): ReactElement {
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="inquiries/graph/:graphId"
|
||||
path="dashboard/graph/:graphId"
|
||||
element={
|
||||
<Suspense>
|
||||
<InquiryGraph />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="entity/:entityId"
|
||||
element={
|
||||
<Suspense>
|
||||
<EntityGraphPage />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="incidents"
|
||||
element={
|
||||
@@ -153,5 +140,6 @@ export default function AppRoutes(): ReactElement {
|
||||
</Route>
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
32
frontend/src/routes/dashboard/EntityDetailsPage.tsx
Normal file
32
frontend/src/routes/dashboard/EntityDetailsPage.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
// @ts-nocheck
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams } from "react-router-dom"
|
||||
import { useAppDispatch, useAppSelector } from "@/app/hooks";
|
||||
import { selectActiveEntity, setActiveEntityId } from "@/features/dashboard/dashboardSlice";
|
||||
import EntityEditor from "../../components/EntityEditor/EntityEditor";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export default function EntityDetailsPage() {
|
||||
const params: { entityId: string | undefined } = useParams()
|
||||
const [activeGraph, setActiveGraph] = useState<any>(null);
|
||||
const [graphStats, setGraphStats] = useState<any>(null);
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
useEffect(() => {
|
||||
params?.entityId && dispatch(setActiveEntityId(params.entityId))
|
||||
}, [params])
|
||||
const activeEntity = useAppSelector((state) => selectActiveEntity(state))
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col h-screen w-full">
|
||||
<section className="flex w-full h-full relative">
|
||||
<EntityEditor activeEntity={activeEntity} />
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -15,7 +15,7 @@ export default function GraphDetails() {
|
||||
useEffect(() => {
|
||||
sdk.graphs.getGraph(params.graphId)
|
||||
.then((data) => {
|
||||
setActiveGraph(data.graph)
|
||||
setActiveGraph(data)
|
||||
})
|
||||
sdk.graphs.getGraphStats(params.graphId)
|
||||
.then((data) => setGraphStats(data))
|
||||
@@ -54,7 +54,6 @@ export default function GraphDetails() {
|
||||
uniqueOuteChartRef.current,
|
||||
data,
|
||||
{
|
||||
|
||||
distributeSeries: true,
|
||||
reverseData: true,
|
||||
horizontalBars: true,
|
||||
@@ -70,7 +69,7 @@ export default function GraphDetails() {
|
||||
return (
|
||||
<>
|
||||
<section className="flex flex-col w-full">
|
||||
<GraphHeader stats={graphStats} graph={activeGraph} />
|
||||
<GraphHeader stats={graphStats} graph={activeGraph} setActiveGraph={setActiveGraph} />
|
||||
<section className="flex w-full h-full relative">
|
||||
<div className="flex flex-col w-2/5">
|
||||
<div className="flex flex-col pl-4 mx-4 mt-4 bg-dark-600 rounded-md ">
|
||||
@@ -90,7 +89,6 @@ export default function GraphDetails() {
|
||||
<CaseNotes />
|
||||
</section>
|
||||
</section>
|
||||
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user