Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1320fe4916 | ||
|
|
9f25d38f9b | ||
|
|
4eb60fa019 | ||
|
|
622fc581f9 |
@@ -1,46 +0,0 @@
|
||||
---
|
||||
title: Architecture guide
|
||||
description: The overall design and architecture of OSINTBuddy.
|
||||
---
|
||||
|
||||
This document describes the high-level architecture of OSINTBuddy. Please be aware the project is in a very early beta state and major changes may occur!
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
On the highest level, OSINTBuddy is a thing which lets users build interactive node based project graphs on the client, which can be expanded through data transforms that pull from: custom plugin based sources, open data sources, search engines, or for whatever you decide to implement.
|
||||
|
||||
### Project Structure
|
||||
|
||||
```js
|
||||
frontend // react frontend
|
||||
backend
|
||||
├── backend
|
||||
│ ├── app
|
||||
│ │ ├── alembic // migrations
|
||||
│ │ ├── app
|
||||
│ │ │ ├── admin // sqladmin models
|
||||
│ │ │ ├── api // controllers/api endpoints
|
||||
│ │ │ │ └── api_v1
|
||||
│ │ │ ├── core // env settings/celery app/logger/hashing
|
||||
│ │ │ ├── crud // views/crud functions
|
||||
│ │ │ ├── db // db initialization and base model classes
|
||||
│ │ │ ├── models // relational data models
|
||||
│ │ │ ├── schemas // pydantic/validation schemas
|
||||
│ │ │ ├── plugins // data sources/user plugins
|
||||
│ │ │ └── tests
|
||||
│ │ └── scripts // startup/testing scripts used by docker
|
||||
├── microservice
|
||||
└ └── google-service // golang google crawlers
|
||||
```
|
||||
|
||||
|
||||
### Core Services
|
||||
- **Frontend - [React](https://legacy.reactjs.org/docs/getting-started.html)**
|
||||
- **Backend - [FastAPI](https://fastapi.tiangolo.com/)**
|
||||
- **Caching - [Redis](https://redis.io/docs/)**
|
||||
- **Graph database - [JanusGraph](https://janusgraph.org/) + [ScyllaDB](https://www.scylladb.com/)**
|
||||
- **Task queue - [Redis & Celery](https://docs.celeryq.dev/en/stable/getting-started/backends-and-brokers/redis.html)**
|
||||
- **Relational database - [PostgreSQL](https://www.postgresql.org/docs/)**
|
||||
|
||||
7
.github/osintbuddy.com/docs/entities-api.md
vendored
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Entities API reference
|
||||
pageTitle: Entities API reference
|
||||
description:
|
||||
---
|
||||
|
||||
Coming soon. ETA 12-24 weeks from June 10th 2023.
|
||||
7
.github/osintbuddy.com/docs/entities.md
vendored
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Entities
|
||||
pageTitle: Entities
|
||||
description:
|
||||
---
|
||||
|
||||
Coming soon. ETA 12-24 weeks from June 10th 2023.
|
||||
41
.github/osintbuddy.com/docs/how-to-contribute.md
vendored
@@ -1,41 +0,0 @@
|
||||
---
|
||||
title: How to contribute
|
||||
description: Contributing to OSINTBuddy development.
|
||||
---
|
||||
We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's:
|
||||
|
||||
- Reporting a bug
|
||||
- Discussing the current state of the code
|
||||
- Submitting a fix
|
||||
- Proposing new features
|
||||
- Becoming a maintainer
|
||||
|
||||
## We Develop with Github
|
||||
We use github to host code, to track issues and feature requests, as well as accept pull requests.
|
||||
|
||||
Pull requests are the best way to propose changes to the codebase. We actively welcome your pull requests:
|
||||
|
||||
1. Fork the repo and create your branch from `main`.
|
||||
2. If you've added code that should be tested, add tests.
|
||||
3. If you've changed APIs, update the documentation.
|
||||
4. Ensure the test suite passes.
|
||||
5. Make sure your code lints.
|
||||
6. Issue that pull request!
|
||||
|
||||
## Any contributions you make will be under the GNU AGPLv3 Software License
|
||||
In short, when you submit code changes, your submissions are understood to be under the same [GNU AGPLv3 License](https://choosealicense.com/licenses/agpl-3.0/) that covers the project. Feel free to contact the maintainers if that's a concern.
|
||||
|
||||
## Report bugs using Github's [issues](https://github.com/briandk/transcriptase-atom/issues)
|
||||
We use GitHub issues to track public bugs. Report a bug by [opening a new issue](); it's that easy! **Great Bug Reports** tend to have:
|
||||
|
||||
- A quick summary and/or background
|
||||
- Steps to reproduce
|
||||
- Be specific!
|
||||
- What you expected would happen
|
||||
- What actually happens
|
||||
- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)
|
||||
|
||||
People *love* thorough bug reports. I'm not even kidding.
|
||||
|
||||
## License
|
||||
By contributing, you agree that your contributions will be licensed under its GNU AGPLv3 License.
|
||||
@@ -1,43 +0,0 @@
|
||||
---
|
||||
title: Getting involved with OSINTBuddy
|
||||
pageTitle: "OSINTBuddy - Getting Started"
|
||||
description: "OSINTBuddy: The Open-Source Alternative to Maltego. Fetch data from different sources and returns the results as visual entities that you can explore step-by-step."
|
||||
---
|
||||
|
||||
Learn all the ways you can get involved with the OSINTBuddy community and the projects development. {% .lead %}
|
||||
|
||||
{% quick-links %}
|
||||
|
||||
|
||||
|
||||
|
||||
{% quick-link title="Plugin System" icon="plug" href="/docs/plugin-system" description="Learn how to build powerful plugins that can pull from any data source." /%}
|
||||
|
||||
{% quick-link title="OSINTBuddy Membership" icon="coin" href="https://opencollective.com/openinfolabs/projects/osintbuddy#contribute" description="Join the OSINTBuddy Society for unique benefits. This helps us stay free for everyone" /%}
|
||||
|
||||
|
||||
|
||||
{% /quick-links %}
|
||||
|
||||
|
||||
## Joining the community
|
||||
|
||||
[Join us on Discord](https://discord.gg/gsbbYHA3K3) where you can connect with fellow OSINT enthusiasts, exchange ideas, get help, and discuss the OSINTBuddy application. Our Discord server offers dedicated channels for various topics, including general discussions, bug reporting, and feature requests. People are encouraged to ask questions, share their experiences, and or contribute to the development of the application.
|
||||
|
||||
By joining the OSINTBuddy community, you'll not only gain access to insider dev logs and support but also have the opportunity to contribute to the development and growth of this platform.
|
||||
|
||||
## Bug reporting
|
||||
We use GitHub issues to track public bugs. Report a bug by [opening a new issue](https://github.com/jerlendds/osintbuddy/issues/new?assignees=jerlendds&labels=Type%3A+Bug&projects=&template=bug_report.md&title=%5BBUG%5D+); it's that easy! **Great Bug Reports** tend to have:
|
||||
|
||||
- Error logs wrapped in code blocks
|
||||
- A quick summary and/or background
|
||||
- Steps to reproduce
|
||||
- What you expected would happen
|
||||
- What actually happens
|
||||
- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)
|
||||
|
||||
People *love* thorough bug reports! I'm not even kidding.
|
||||
|
||||
## Reporting Security Issues
|
||||
Please email the core contributors directly at [theosintbuddyproject@openinfolabs.com](mailto:theosintbuddyproject@openinfolabs.com) directly if you encounter a serious security issue (e.g. in a package we use, etc)
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
---
|
||||
title: Installation
|
||||
description: Fetch data from different sources and returns the results as visual entities that you can explore step-by-step
|
||||
---
|
||||
|
||||
Learn how to get OSINTBuddy set up in about 30 minutes. To start using the alpha version of OSINTBuddy, ensure you have the prerequisites then follow the installation steps.
|
||||
|
||||
|
||||
**Prerequisites:**
|
||||
- Git
|
||||
- Docker & Docker Compose
|
||||
- _Windows only_:
|
||||
- WSL2
|
||||
- Tabby.sh _(optional)_
|
||||
|
||||
*Note that if you're on **Windows** and want this project to work you need unix line endings [(context)](https://stackoverflow.com/a/13154031). Before cloning, run: `git config --global core.autocrlf false`*
|
||||
|
||||
|
||||
*Note that if you're running on an **Apple** device you will need to open your Docker app, select the **features in development** tab on the left hand side of the docker app, and enable/checkmark the `Use Rosetta for x86/64 emulation on Apple Silicon` option if you want this project to work*
|
||||
|
||||
|
||||
## OSINTBuddy Install Instructions
|
||||
1. Clone the repo and submodules
|
||||
```bash
|
||||
git clone --recurse-submodules https://github.com/jerlendds/osintbuddy.git
|
||||
cd osintbuddy
|
||||
# using ssh?
|
||||
# git clone --recurse-submodules git@github.com:jerlendds/osintbuddy.git
|
||||
```
|
||||
|
||||
2. Install Docker & Compose
|
||||
- [Install Guide for Mac](https://docs.docker.com/desktop/install/mac-install/)
|
||||
- [Install Guide for Windows](https://docs.docker.com/desktop/install/windows-install/)
|
||||
- [Install Guide for Linux](https://docs.docker.com/desktop/install/linux-install/)
|
||||
|
||||
3. Start the stack with Docker:
|
||||
```bash
|
||||
# ./launcher # display usage information.
|
||||
# ./launcher bootstrap # first time developing osintbuddy? read .github/CONTRIBUTING.md then run
|
||||
# bootstrap to setup the development environments for the stack.
|
||||
./launcher start # start the osintbuddy app.
|
||||
# ./launcher stop # stop the osintbuddy app.
|
||||
```
|
||||
- **Note:** the stack will take a few minutes to startup while Solr and ScyllaDB configure themselves for JanusGraph. If you try to connect before all the databases are ready you will encounter errors.
|
||||
|
||||
|
||||
You can now access OSINTBuddy through the URLs provided for the frontend, backend, and documentation.
|
||||
- **Default login**:
|
||||
- username: `osintbuddy`
|
||||
- password: `osintbuddy`
|
||||
- **URLs**
|
||||
- Frontend: [localhost:3000](http://localhost:3000)
|
||||
- Casdoor: [localhost:45910](http://localhost:45910)
|
||||
- Backend: [localhost:48997/api](http://localhost:48997/api)
|
||||
- Documentation: [localhost:48997/docs](http://localhost:48997/docs)
|
||||
16
.github/osintbuddy.com/docs/intro/roadmap.md
vendored
@@ -1,16 +0,0 @@
|
||||
---
|
||||
title: "OSINTBuddy: The Roadmap"
|
||||
pageTitle: "OSINTBuddy - Feature Roadmap"
|
||||
description: "Learn more about the long term plans for the OSINTBuddy project."
|
||||
---
|
||||
|
||||
|
||||
{% timelines %}
|
||||
|
||||
{% timeline title="Prototype Graph and Entity Editor (v. Alpha)" description="Edit your entities sources directly from the OSINTBuddy Web UI then visit the Graph to transform and interact with your entities" releaseDate="Released on August 12th, 2023" /%}
|
||||
|
||||
{% timeline title="UI/UX Improvements" downloadLink="https://github.com/jerlendds/osintbuddy/archive/refs/heads/main.zip" description="Download the OSINTBuddy source code (AGPLv3 license) from the latest stable changes on Github." releaseDate="Released Recently" /%}
|
||||
|
||||
{% timeline title="Rest of the System" description="Join the Discord or become a member for the latest news on the OSINTBuddy project" releaseDate="Unreleased" /%}
|
||||
|
||||
{% /timelines %}
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Plugin recipes
|
||||
pageTitle: Plugin recipes
|
||||
description:
|
||||
---
|
||||
|
||||
Coming soon. ETA 12-24 weeks from June 10th 2023.
|
||||
88
.github/osintbuddy.com/docs/plugin-system.md
vendored
@@ -1,88 +0,0 @@
|
||||
---
|
||||
title: Plugin System
|
||||
description: Fetch data from different sources and returns the results as visual entities that you can explore step-by-step
|
||||
---
|
||||
|
||||
***Please note:** [OSINTBuddy plugins](https://github.com/jerlendds/osintbuddy-plugins) are still in early alpha and breaking changes may occasionally occur in the API. That said, if you remain on the `main` branch and avoid accessing protected methods we will try our best to avoid introducing breaking changes.*
|
||||
|
||||
The osintbuddy plugin system at its core is very simple. An `OBRegistry` class holds all registered `OBPlugin` classes within the application. This registry is loaded into the [osintbuddy application](https://github.com/jerlendds/osintbuddy/) where it is then used to load the available entities for the user when they access a project graph, load transforms when a user opens the context menu of a node, and perform transformations which expect a `Plugin.blueprint()` to be returned. The returned data of a transform decorated method will be automatically mapped to a [JanusGraph](https://janusgraph.org/) database through [gremlin](https://tinkerpop.apache.org/) according to the labels *(as snakecase)* you previously set in the classes `node` for whatever `Plugin.blueprint()`
|
||||
you return.
|
||||
|
||||
To make this a bit more clear please see the below example...
|
||||
|
||||
```py
|
||||
from pydantic import BaseModel
|
||||
import osintbuddy import transform, Plugin
|
||||
from osintbuddy.elements import TextInput, DropdownInput, Title, CopyText
|
||||
from osintbuddy.errors import OBPluginError
|
||||
|
||||
|
||||
class CSESearchResults(Plugin):
|
||||
label = "CSE Result"
|
||||
name = "CSE result"
|
||||
show_label = False # do not show this on the entities dialog
|
||||
# the user sees on the left of the project graph screen
|
||||
color = "#058F63"
|
||||
node = [
|
||||
Title(label="Result"),
|
||||
CopyText(label="URL"),
|
||||
CopyText(label="Cache URL"),
|
||||
]
|
||||
|
||||
|
||||
class CSESearchPlugin(Plugin):
|
||||
label = "CSE Search"
|
||||
name = "CSE search"
|
||||
color = "#2C7237"
|
||||
node = [
|
||||
[
|
||||
TextInput(label="Query", icon="search"),
|
||||
TextInput(label="Pages", icon="123", default="1"),
|
||||
],
|
||||
DropdownInput(label="CSE Categories", options=cse_link_options)
|
||||
]
|
||||
|
||||
@transform(label="To cse results", icon="search")
|
||||
async def transform_to_cse_results(
|
||||
self,
|
||||
node: BaseModel, # dynamically generated pydantic model
|
||||
# that is mapped from the above labels contained within `node`
|
||||
use # a pydantic model allowing you to access a selenium instance
|
||||
# (and eventually a gremlin graph and settings api)
|
||||
):
|
||||
results = []
|
||||
|
||||
if not node.query:
|
||||
raise OBPluginError((
|
||||
'You can send error messages to the user here'
|
||||
'if they forget to submit data or if some other error occurs'
|
||||
))
|
||||
|
||||
# notice how you can access data returned from the context menu
|
||||
# of this node; using the label name in snake case
|
||||
print(node.cse_categories, node.query, node.pages)
|
||||
|
||||
... # (removed code for clarity)
|
||||
|
||||
if resp:
|
||||
for result in resp["results"]:
|
||||
url = result.get("breadcrumbUrl", {})
|
||||
# some elements you can store more than just a string,
|
||||
# (these elements storing dicts are mapped
|
||||
# to janusgraph as properties with the names
|
||||
# result_title, result_subtitle, and result_text)
|
||||
blueprint = CSESearchResults.blueprint(
|
||||
result={
|
||||
"title": result.get("titleNoFormatting"),
|
||||
"subtitle": url.get("host") + url.get("crumbs"),
|
||||
"text": result.get("contentNoFormatting"),
|
||||
},
|
||||
url=result.get("unescapedUrl"),
|
||||
cache_url=result.get("cacheUrl"),
|
||||
)
|
||||
results.append(blueprint)
|
||||
# here we return a list of blueprints (blueprints are dicts)
|
||||
# but you can also return a single blueprint without a list
|
||||
return results
|
||||
|
||||
```
|
||||
7
.github/osintbuddy.com/docs/plugins-api.md
vendored
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Plugins API reference
|
||||
pageTitle: Plugins API reference
|
||||
description:
|
||||
---
|
||||
|
||||
Coming soon. ETA 12-24 weeks from June 10th 2023.
|
||||
12
.github/osintbuddy.com/docs/project-graphs.md
vendored
@@ -1,12 +0,0 @@
|
||||
---
|
||||
title: Project graphs
|
||||
pageTitle: Project graphs
|
||||
description:
|
||||
---
|
||||
|
||||
Project graphs allow you to visualize complicated data sets and the connections between your stored data.
|
||||
In addition, you can transform existing data into new insights through a customizable plugin system that's easy to use.
|
||||
|
||||

|
||||
|
||||
|
||||
8
.github/osintbuddy.com/docs/registry-api.md
vendored
@@ -1,8 +0,0 @@
|
||||
---
|
||||
title: Registry API reference
|
||||
pageTitle: Registry API reference
|
||||
description:
|
||||
---
|
||||
## Share your plugins through the registry
|
||||
|
||||
Coming soon. ETA 12-18 weeks from July 10th 2023.
|
||||
7
.github/osintbuddy.com/docs/settings-api.md
vendored
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Settings API reference
|
||||
pageTitle: Settings API reference
|
||||
description:
|
||||
---
|
||||
|
||||
Coming soon. ETA 12-24 weeks from June 10th 2023.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Transforms API reference
|
||||
pageTitle: Transforms API reference
|
||||
description:
|
||||
---
|
||||
|
||||
Coming soon. ETA 12-24 weeks from June 10th 2023.
|
||||
6
.github/osintbuddy.com/docs/transforms.md
vendored
@@ -1,6 +0,0 @@
|
||||
---
|
||||
title: Transforms
|
||||
pageTitle: Transforms
|
||||
description:
|
||||
---
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
title: "Understanding OSINTBuddy"
|
||||
pageTitle: "OSINTBuddy - Overview"
|
||||
description: "Understand the OSINTBuddy platform, its features, and how it can help enhance your open-source intelligence investigations."
|
||||
---
|
||||
|
||||
## An overview
|
||||
|
||||
OSINTBuddy is an open-source platform designed to assist with intelligence investigations. It offers a range of tools and features that enable investigators to collect, analyze, and visualize data from various sources. Below we will explore the key components of OSINTBuddy, their benefits, and how it can help enhance your OSINT capabilities.
|
||||
|
||||
### Data Collection
|
||||
|
||||
OSINTBuddy supports a wide range of data sources, including network information, search engines, and custom APIs. Users can easily configure and connect to these sources, pulling relevant data into their investigations.
|
||||
|
||||
### Plugin System
|
||||
|
||||
The plugin system is a core feature of OSINTBuddy that allows users to create, modify, and share plugins for specific tasks. This provides a flexible and customizable approach to data gathering and analysis, ensuring that OSINTBuddy remains adaptable to the ever-changing landscape of open-source intelligence.
|
||||
|
||||
### Visualization
|
||||
|
||||
To aid in the understanding of complex data sets, OSINTBuddy offers an intuitive visualization system that enables users to create interactive graphs of information that can be explored and transformed into new types of data. This helps investigators to better comprehend the relationships between data points, entities, and networks, making it easier to identify leads or potential targets.
|
||||
|
||||
|
||||
### Analytics & Inference
|
||||
|
||||
A whitepaper focused on the OSINT domain will be here eventually...
|
||||
72
.github/osintbuddy.com/docs/writing-plugins.md
vendored
@@ -1,72 +0,0 @@
|
||||
---
|
||||
title: Writing plugins
|
||||
pageTitle: OSINTBuddy - Creating your first plugin
|
||||
description:
|
||||
---
|
||||
|
||||
## Creating your first plugin
|
||||
|
||||
In this guide, we will dive into creating a custom plugin for OSINTBuddy that extends its capabilities. This will enable you to incorporate additional sources and transformations into the OSINTBuddy toolbox. We will use the provided code example as a reference to build our plugin.
|
||||
|
||||
### Step-by-step guide to create a new plugin
|
||||
|
||||
Start by importing the necessary modules:
|
||||
|
||||
```py
|
||||
import socket
|
||||
import osintbuddy as ob
|
||||
```
|
||||
|
||||
Define a class for your plugin, inheriting from `ob.Plugin`. Set the required attributes such as `label`, and optionally set an `icon` like `world-www` (using [tabler-icon](https://tabler-icons.io/) names), and a `node` which contains a list of elements used as a blueprint for creating the node displayed on the OSINTBuddy UI.
|
||||
|
||||
```py
|
||||
class WebsitePlugin(ob.Plugin):
|
||||
label = 'Website'
|
||||
name = 'Website'
|
||||
color = '#1D1DB8'
|
||||
icon = 'world-www'
|
||||
node = [
|
||||
ob.elements.TextInput(label='Domain', icon='world-www'),
|
||||
]
|
||||
```
|
||||
|
||||
Now, define a transformation method to gather and transform the data from the input node. In this case, the transform_to_ip function uses the `socket.gethostbyname()` Python module to obtain the IP address for a given domain. Decorate the method with `@transform()` and set the metadata attributes such as `label` and `icon`.
|
||||
|
||||
```py
|
||||
@ob.transform(label='To IP', icon='building-broadcast-tower')
|
||||
def transform_to_ip(self, node, **kwargs):
|
||||
blueprint = IPAddressPlugin.blueprint(
|
||||
ip_address=socket.gethostbyname(node.domain)
|
||||
)
|
||||
return blueprint
|
||||
```
|
||||
## Using the new plugin
|
||||
|
||||
To use the new plugin, simply add it to the existing OSINTBuddy plugins folder inside the application, in development mode it will be detected automatically and loaded by the platform. Once added, you can access your 'Website' plugin and use the 'To IP' transformation.
|
||||
|
||||
Creating a custom plugin for OSINTBuddy is an easy and effective way to enhance the capabilities of the tool, allowing you to fetch information from additional sources or transform the data in new ways. By following this guide and using the provided code example as a reference, you'll be able to create your own unique plugins to extend the functionality of OSINTBuddy to fit your specific needs.
|
||||
|
||||
|
||||
### Full example
|
||||
|
||||
```py
|
||||
import socket
|
||||
import osintbuddy as ob
|
||||
|
||||
class WebsitePlugin(ob.Plugin):
|
||||
label = 'Website'
|
||||
name = 'Website'
|
||||
color = '#1D1DB8'
|
||||
icon = 'world-www'
|
||||
node = [
|
||||
ob.elements.TextInput(label='Domain', icon='world-www'),
|
||||
]
|
||||
|
||||
@ob.transform(label='To IP', icon='building-broadcast-tower')
|
||||
def transform_to_ip(self, node, use):
|
||||
blueprint = IPAddressPlugin.blueprint(
|
||||
ip_address=socket.gethostbyname(node.domain)
|
||||
)
|
||||
return blueprint
|
||||
```
|
||||
|
||||
37
.github/osintbuddy.com/index.md
vendored
@@ -1,37 +0,0 @@
|
||||
---
|
||||
title: "OSINTBuddy: Transformative Link Analysis"
|
||||
pageTitle: "OSINTBuddy: Transformative Link Analysis, a Maltego Alternative"
|
||||
description: "OSINTBuddy: Fetch data from different sources and return the results as visual entities that you can explore step-by-step. The Open-Source Link Analysis Solution."
|
||||
---
|
||||
|
||||
|
||||
|
||||
OSINTBuddy provides a range of tools for performing OSINT research. One of the key features of OSINTBuddy is its plugin system which allows researchers to easily add or modify Python plugins _(also referred to as "entities")_ to suit their specific needs, or they can choose from any community-developed entities. This system provides a flexible and customizable approach to data gathering and analysis, making it an open-source alternative to popular proprietary tools such as Maltego.
|
||||
|
||||
{% quick-links %}
|
||||
|
||||
{% quick-link title="Installation" icon="installation" href="/docs/installation" description="Step-by-step guides for setting up your system and installing OSINTBuddy." /%}
|
||||
[Title](index.md) [Title](docs/writing-plugins.md) [Title](docs/understanding-osintbuddy.md) [Title](docs/transforms-api.md) [Title](docs/transforms.md) [Title](docs/settings-api.md) [Title](docs/registry-api.md) [Title](docs/project-graphs.md) [Title](docs/plugins-api.md) [Title](docs/plugin-system.md) [Title](docs/plugin-recipes.md) [Title](docs/how-to-contribute.md) [Title](docs/entities.md) [Title](docs/entities-api.md) [Title](docs/architecture-guide.md) [Title](docs/intro/roadmap.md) [Title](docs/intro/getting-started.md) [Title](docs/intro) [Title](docs/intro/installation.md)
|
||||
{% quick-link title="Architecture overview" icon="presets" href="/docs/architecture-guide" description="Learn how the internals work and start contributing to OSINTBuddy." /%}
|
||||
|
||||
{% quick-link title="Plugin System" icon="plug" href="/docs/plugin-system" description="Learn how to build powerful plugins that can pull from any data source." /%}
|
||||
|
||||
{% quick-link title="OSINTBuddy Membership" icon="coin" href="https://opencollective.com/openinfolabs/projects/osintbuddy#contribute" description="Join the OSINTBuddy Society for unique benefits. This helps us stay free for everyone" /%}
|
||||
|
||||
|
||||
{% /quick-links %}
|
||||
|
||||
|
||||
## How the Plugin System Works
|
||||
|
||||
The plugin system works by allowing users to create and share small, focused modules that perform specific tasks, such as extracting information from a social media profile, analyzing email headers, or mapping IP addresses to geographical locations. These individual plugins can then be combined and connected in various ways to build more complex data-mining workflows.
|
||||
|
||||
|
||||
## The Benefits of an Open-Source Alternative
|
||||
|
||||
As an open-source project, OSINTBuddy offers several advantages over proprietary tools like [Maltego](https://www.maltego.com/). The most significant of these is the ability for users to actively participate in the development process, suggesting improvements, reporting bugs, and [contributing their own plugins and enhancements](/docs/registry-api).
|
||||
|
||||
This collaborative approach ensures that OSINTBuddy remains responsive to the needs of its users, continually evolving to address new challenges and opportunities in the rapidly changing world of open-source intelligence.
|
||||
|
||||
Moreover, as an open-source project, OSINTBuddy is freely available to all, lowering the barriers to entry for individuals and organizations seeking to leverage the power of OSINT in their work. This democratizes access to powerful intelligence-gathering tools and fosters a more diverse and inclusive community of practitioners.
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
# Our Code of Conduct
|
||||
|
||||
## Hacker Ethics and Our Pledge
|
||||
|
||||
In the interest of _fostering an respectful, libre, and open source environment,_ we as hackers,
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for anyone. Respect has great importance in everyday life. Calls to respect this or that are increasingly part of public life:
|
||||
|
||||
- environmentalists exhort us to respect nature.
|
||||
- foes of abortion and capital punishment insist on respect for human life.
|
||||
- members of racial and ethnic minorities and those discriminated against because of their gender, sexual orientation, age, religious beliefs.
|
||||
- economic status demand respect both as social and moral equals and for their cultural differences.
|
||||
|
||||
It is widely acknowledged that **public debates about such demands should take place under terms of mutual respect**. We may learn both that our lives together go better when we respect the things that deserve to be respected.[[1]](https://plato.stanford.edu/entries/respect/) Hackers can come in many forms, many undertakings might now be considered variants of hacking, from modding cars to crafting to do-it-yourself projects.
|
||||
|
||||
**We don’t care if you’re liberal or conservative, Black or white, straight or gay, or anything in between!** In fact, we won’t bring it up, or ask. **We simply do not care.** **This is a place to experiment with and then practice leaving behind categories of social difference while remaining respectful.** We all make mistakes, overstep ourselves at times, introduce bugs, bring down production, lie in our documentation, get frustrated, implement dark UI patterns, or any number of other mistakes, but when we get called out, we will make the choice to try and understand the others point of view rather than attacking or trying to ruin someones life. This is an open-technology community that aims to be a space of refuge for any participants, a space of sanctuary. From programmers, to translators, designers, writers and beyond, we will all seek to understand how and why things are the way they are before catalyzing or proposing a transition to a future state.
|
||||
|
||||
We believe that from ethics, enlightened self-interest, and the commonwealth, our governance will emerge. Our identities may be distributed across many of your jurisdictions. The only law that all our constituent cultures would generally recognize is the [Golden Rule](https://en.wikipedia.org/wiki/Golden_Rule).
|
||||
|
||||
## Our Standards - The ethical principles of hacking - motivation and limits
|
||||
|
||||
* Accept constructive criticism and be respectful of differing viewpoints and experiences while showing empathy towards other.
|
||||
* Access to computers - and anything which might teach you something about the way the world really works - should be unlimited and total. Always yield to the Hands-On Imperative!
|
||||
* All information should be free.
|
||||
* Mistrust authority - promote decentralization.
|
||||
* Hackers should be judged by their acting, not bogus criteria such as degrees, age, gender, race, or position.
|
||||
* You can create art and beauty on a computer.
|
||||
* Computers can change your life for the better.
|
||||
* Don't litter other people's data.
|
||||
* Make public data available, protect private data.
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of any mistakes in behavior.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
194
README.md
@@ -20,55 +20,32 @@
|
||||
|
||||
|
||||
-------
|
||||
| [osintbuddy-plugins](https://github.com/jerlendds/osintbuddy-plugins) | [osintbuddy.com](https://osintbuddy.com) | [osintbuddy discord](https://discord.gg/gsbbYHA3K3) | [osintbuddy-core-plugins](https://github.com/jerlendds/osintbuddy-core-plugins) |
|
||||
<span style="display: inline-block; width:830px"> </span>
|
||||
| [plugins-system](https://github.com/osintbuddy/plugins) | [osintbuddy.com](https://osintbuddy.com) | [osintbuddy discord](https://discord.gg/b8vW4J4skv) | [entities](https://github.com/osintbuddy/entities) | [pypi package](https://pypi.org/project/osintbuddy/) |
|
||||
|
||||
## Introducing OSINTBuddy
|
||||
|
||||
**Notice** - *No, this project is not "dead"*, we are currently undergoing a big rewrite (Python plugins will still be supported!). The rewrite can be found at [github.com/osintbuddy/osintbuddy](https://github.com/osintbuddy/osintbuddy)
|
||||
|
||||
|
||||
## Introducing OSINTBuddy
|
||||
<p>
|
||||
Welcome to the OSINTBuddy project where you can connect, combine,
|
||||
and get insight from unstructured and public data as results that
|
||||
can be explored step-by-step. An easy-to-use plugin system allows any
|
||||
Python developer to quickly integrate new data sources so you can focus
|
||||
on discovering, interacting, and visualizing what's important to you
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Welcome to the OSINTBuddy project where you can connect, combine,
|
||||
and get insight from unstructured and public data as results that
|
||||
can be explored step-by-step. An easy-to-use plugin system allows any
|
||||
Python developer to quickly integrate new data sources so you can focus
|
||||
on discovering, interacting, and visualizing what's important to you
|
||||
</p>
|
||||
|
||||
🚧 <ins>Work in progress</ins> 🚧
|
||||
🚧 <ins>This repo of osintbuddy is archived and will recieve no further attention. Please see [osintbuddy/osintbuddy](https://github.com/osintbuddy/osintbuddy) for the version under active development</ins> 🚧
|
||||
<br/>
|
||||
|
||||
[2024-01-09 20-55.webm](https://github.com/jerlendds/osintbuddy/assets/29207058/fb07fa95-30af-45aa-a75b-2fa1473bc37b)
|
||||
[osib-demo-2025-04-12T21-13.webm](https://github.com/user-attachments/assets/4a7e21f1-1e80-42b0-a4a6-91477e53ba4c)
|
||||
|
||||
|
||||
⚠️ **Searching for contributors**, please contact me on the **[forum (work-in-progress)](https://forum.osintbuddy.com)**
|
||||
or on the **[discord](https://discord.gg/gsbbYHA3K3)** ⚠️
|
||||
⚠️ The above demo is from the new version of osintbuddy found at [github.com/osintbuddy/osintbuddy](https://github.com/osintbuddy/osintbuddy) ⚠️
|
||||
|
||||
To get started please see [CONTRIBUTING.md](./.github/CONTRIBUTING.md), and [CONTRIBUTOR_AGREEMENT.md](./.github/CONTRIBUTOR_AGREEMENT.md) to agree with the AGPLv3
|
||||
|
||||
---
|
||||
</p>
|
||||
|
||||
<details open="open">
|
||||
<summary>
|
||||
<b>Table of Contents</b>
|
||||
</summary>
|
||||
<ol>
|
||||
<li>
|
||||
<a href="#what-is-osintbuddycom">What is OSINTBuddy</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#getting-started">Getting Started</a>
|
||||
<ul>
|
||||
<li><a href="#installation">Installation</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#usage">Usage</a></li>
|
||||
<li><a href="#roadmap">Roadmap</a></li>
|
||||
<li><a href="#contributing">Contributing</a></li>
|
||||
<li><a href="#contact">Contact</a></li>
|
||||
<li><a href="#sponsor-osintbuddy">Sponsor OSINTBuddy</a></li>
|
||||
</ol>
|
||||
</details>
|
||||
|
||||
### What is <a referrerpolicy="unsafe-url" target="_blank" href="https://osintbuddy.com">osintbuddy.com</a>?
|
||||
|
||||
@@ -95,149 +72,10 @@ OSINT is a method of working with, assessing and ranking information — We are
|
||||
We've decided to do something about it. The rapid developments in technologies such as AI and big data analytics have opened new horizons for OSINT which weren't previously available. We want to put all of that information at your fingertips. We want actionable intelligence quickly and to the point, that's why we're building an open-source OSINT tool that's free. Free to use, free to modify, free to do with as you wish, and built with plain old web technologies anyone can learn. But this isn't really the project. This is a free new community. A community for OSINT enthusiasts around the world and we need your help to design it, to program it, and to build it. We want to hear your suggestions, your ideas, and we're going to build it right in front of your eyes. The notion of a “needle in a haystack” is taken to the extreme on the internet. Let's build a magnet.
|
||||
|
||||
|
||||
### Key Alpha Features
|
||||
- Visual representation and layout modes for fetched data for easy understanding and editing.
|
||||
- Simplified data fetching/transformations from custom sources using Python plugins, custom plugin/entity field layouts, check out the [osintbuddy](https://pypi.org/project/osintbuddy/) PyPi package for more details and expect many more input fields like files and checkboxes/toggles to come in the future.
|
||||
- A development platform that is open for contributions.
|
||||
- Works most of the time :)
|
||||
|
||||
|
||||
### The future
|
||||
|
||||
At the core of this project lies a far more ambitious vision than the mere creation of an all-in-one data aggregation and analysis tool. What we aspire to start is the birth of a knowledge-driven community, passionately dedicated to the development of an evolving intelligence tool - a system and platform designed for discovering, interacting, and visualizing information to derive actionable insights. We imagine a system that not only learns from and evolves with your skills but also augments them. Here are some of our ideas for the future in no particular order:
|
||||
|
||||
|
||||
#### Intuitive search and discovery tools
|
||||
|
||||
- We want you to be able to interact with our algorithms, filter, segment, search for particular data, apply advanced query filters, data layouts, build queries visually, and more so you can uncover insights that matter via an easy-to-navigate web-based interface.
|
||||
|
||||
|
||||
#### Collective intelligence through collaborative workspaces
|
||||
|
||||
- We want to create a platform that promotes collective intelligence. Think of the currently popular collaborative workspaces such as [AFFiNE](https://github.com/toeverything/AFFiNE), Logseq, or Obsidian but built into our data workspace. A system where each individual user contributes to the pool of knowledge, where you can share your graph environments, where you can track changes with annotations and comments, where insights from your workspaces can be optionally published and engaged with by a global community. Where every connection made, every data source integrated, every plugin created and shared by members of our community contributes to advancing insights around the world.
|
||||
|
||||
|
||||
#### Real-time monitoring
|
||||
|
||||
- We want functionality that can provide real-time updates for a set of entities or scans, notifying you when theres new data, changes, and or emerging patterns in your field of interest. Automatically retrieve insights as time goes on and learn from history with snapshots of your past data.
|
||||
|
||||
|
||||
#### History graphs
|
||||
|
||||
- We want to create a browser extension that allows you to seamlessly browse the web while also populating your OSINTBuddy graph. Each website you visit is mapped as an entity, with lines connecting them in the sequence you’ve traveled. Not just a bookmark list or a history tab, the history graph could maintain the context, showing not just where you’ve been, but also how and when you got there, and with the ability to extract a comment or piece of data out of a website you visit into an entity, you'll rarely have to leave your favorite web browser.
|
||||
|
||||
|
||||
#### AI, swarm intelligence, and evolutionary algorithms
|
||||
|
||||
- Integrating AI, swarm intelligence, and evolutionary algorithms could let us create a tool that can continually evolve and optimize your operations over time. From NLP and sentiment analysis to search and anomaly detection with swarm intelligence, bringing data science tools and techniques to OSINTBuddy could let use create a powerful tool that reveals deep insights among a "haystack" of noisy information.
|
||||
|
||||
|
||||
This is a project that will most likely not be finished for many years if ever, and that will require collaboration among experts in many fields. We're always looking for help, from writing documentation, researching feature ideas, designing the UX/UI, donating, to simply marketing and sharing the project, anything you contribute helps realize a vision for what could turn into a cross-disciplinary toolkit for working with information. Will you join me?
|
||||
|
||||
|
||||
## Getting Started
|
||||
|
||||
To start using OSINTBuddy, follow these simple installation steps:
|
||||
|
||||
*Note that if you're on windows and want this project to work you need unix line endings [(context)](https://stackoverflow.com/a/13154031). Before cloning, run: `git config --global core.autocrlf false`*
|
||||
|
||||
|
||||
*Note that if you're running on an **Apple** device you will need to open your Docker app, select the **features in development** tab on the left hand side of the docker app, and enable/checkmark the `Use Rosetta for x86/64 emulation on Apple Silicon` option if you want this application to work*
|
||||
|
||||
|
||||
### Installation
|
||||
1. Clone the repo and submodules
|
||||
```sh
|
||||
git clone --recurse-submodules https://github.com/jerlendds/osintbuddy.git
|
||||
cd osintbuddy
|
||||
# using ssh?
|
||||
# git clone --recurse-submodules git@github.com:jerlendds/osintbuddy.git
|
||||
```
|
||||
|
||||
2. Install Docker & Compose
|
||||
- [Install Guide for Mac](https://docs.docker.com/desktop/install/mac-install/)
|
||||
- [Install Guide for Windows](https://docs.docker.com/desktop/install/windows-install/)
|
||||
- [Install Guide for Linux](https://docs.docker.com/desktop/install/linux-install/)
|
||||
|
||||
3. Start the stack with Docker:
|
||||
```sh
|
||||
# ./launcher # display usage information.
|
||||
# ./launcher bootstrap # first time developing osintbuddy? read .github/CONTRIBUTING.md then run
|
||||
# bootstrap to setup the development environments for the stack.
|
||||
./launcher start # start the osintbuddy app.
|
||||
# ./launcher stop # stop the osintbuddy app.
|
||||
```
|
||||
- **Note:** the stack will take a few minutes to startup while Solr and ScyllaDB configure themselves for JanusGraph. If you try to connect before all the databases are ready you will encounter errors.
|
||||
|
||||
- **URLs**
|
||||
- Frontend: http://localhost:3000
|
||||
- Casdoor: http://localhost:45910
|
||||
- Backend: http://localhost:48997/api
|
||||
- Documentation: http://localhost:48997/docs
|
||||
- Access OSINTBuddy through the URLs provided for the frontend, backend, and documentation.
|
||||
- Default login:
|
||||
- *username:* osintbuddy
|
||||
- *password:* osintbuddy
|
||||
## [↑](#introducing-osintbuddy)Roadmap
|
||||
|
||||
~~See the [trello board](https://trello.com/b/99Q70frX/) for a list of our upcoming features.~~
|
||||
|
||||
See the forum: [forum.osintbuddy.com](https://forum.osintbuddy.com), for a list of our upcoming features, to contribute feedback, ideas, and more (*Note: We're currently in the process of theming and setting up the forum so we can move trello issues over. We're not sure when this will be completed. For the time being you can count on dev log updates being on the forum or on discord from now on*).
|
||||
|
||||
|
||||
See the [open issues](https://github.com/jerlendds/osintbuddy/issues)
|
||||
for a list of requested features (and known issues).
|
||||
|
||||
|
||||
## [↑](#introducing-osintbuddy)Contributing
|
||||
|
||||
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. But if you'd like to make a significant change to this project or the `osintbuddy-plugins` project, please first [create an issue](https://github.com/jerlendds/osintbuddy/issues/new?assignees=jerlendds&labels=Type%3A+Suggestion&projects=&template=feature.md&title=%5BFEATURE+REQUEST%5D+Your_feature_request_here) or open a contributor post on the [forum](https://forum.osintbuddy.com/c/osintbuddy-contributors/11) to get feedback before spending too much time. We don't want you to invest your time on changes we are already working on. Also, for details on how to get up and running with the project you can check out [CONTRIBUTING.md](./.github/CONTRIBUTING.md). If you want to contribute directly please ensure you agree with the [CONTRIBUTOR_AGREEMENT.md](./.github/CONTRIBUTOR_AGREEMENT.md), in short, we want to ensure you're okay with your changes being licensed under the AGPLv3.
|
||||
|
||||
1. Fork the Project
|
||||
2. Create your Feature Branch (`git checkout -b feature/add-core-feature`)
|
||||
3. Commit your Changes (`git commit -m 'feat: add core feature to osintbuddy app'`)
|
||||
4. Push to the Branch (`git push origin feature/add-core-feature`)
|
||||
5. Open a Pull Request
|
||||
|
||||
*Note: If you're working on the `develop` branch you want to ensure the `osintbuddy-plugins` submodule is also on the develop branch.*
|
||||
|
||||
## [↑](#introducing-osintbuddy)License
|
||||
|
||||
We are using the [GNU Affero General Public License v3.0](https://choosealicense.com/licenses/agpl-3.0/)
|
||||
|
||||
[Why the AGPLv3?](https://ploum.net/2023-06-19-more-rms.html)
|
||||
|
||||
> In the early eighties, RMS realised that software was transformed from "a way to use a machine" to a product or a commodity. He foresaw that this would put an end to collective intelligence and to knowledge sharing.
|
||||
> For the last 30 years, businesses and proponents of Open Source, including Linus Torvalds, have been decrying the GPL because of the essential right of "doing business" aka "privatising the common". Microsoft, through Github, Google and Apple pushed for MIT/BSD licensed software as the open source standard.
|
||||
|
||||
> Our freedoms were taken away by proprietary software which is mostly coded by ourselves. For free. We spent our free time developing, debugging, testing software before handing them to corporations... Without Non-copyleft Open Source, there would be no proprietary MacOS, OSX nor Android. There would be no Facebook, no Amazon. We created all the components.
|
||||
|
||||
### The four freedoms and one obligation of free software
|
||||
- The right to use the software at your own discretion
|
||||
- The right to study the software
|
||||
- The right to modify the software
|
||||
- The right to redistribute the software, including with modifications
|
||||
- The obligation to keep those four rights, effectively keeping the software in the commons.
|
||||
|
||||
> We need to realize that any software without that last obligation will, sooner or later, become an oppression tool against ourselves. And that maintaining the commons is not only about software. It’s about everything we are as a society and everything we are losing against individual greed.
|
||||
|
||||
|
||||
*Note: the [OSINTBuddy PyPi package](https://github.com/jerlendds/osintbuddy-plugins) is MIT licensed. We understand some individuals and businesses may not want to share their plugins developed in-house.*
|
||||
|
||||
Patched `aiogremlin` library: [jerlendds/gremlinpy](https://github.com/jerlendds/gremlinpy/)
|
||||
|
||||
|
||||
## [↑](#introducing-osintbuddy)Contact
|
||||
|
||||
[Open an issue](https://github.com/jerlendds/osintbuddy/issues/new?assignees=jerlendds&labels=Type%3A+Suggestion&projects=&template=feature.md&title=%5BFEATURE+REQUEST%5D+Your_feature_request_here) if you need to get in touch with me or send an email to <a href="mailto:jerlendds@osintbuddy.com">jerlendds@osintbuddy.com</a>.
|
||||
|
||||
|
||||
## [↑](#introducing-osintbuddy)Sponsor OSINTBuddy
|
||||
Help us keep the project free and maintained. Sponsoring the project makes it possible for us to cover our server costs and allows us to make investments into new areas of development.
|
||||
Depending on your level of sponsorship you may get unique benefits. Learn more on the [OSINTBuddy OpenCollective](https://opencollective.com/openinfolabs/projects/osintbuddy#category-CONTRIBUTE)
|
||||
|
||||
[](https://opencollective.com/openinfolabs/projects/osintbuddy#category-CONTRIBUTE)
|
||||
[](https://opencollective.com/openinfolabs/projects/osintbuddy#category-CONTRIBUTE)
|
||||
[AGPL](https://choosealicense.com/licenses/agpl-3.0/)
|
||||
|
||||
|
||||
[contributors-shield]: https://img.shields.io/github/contributors/jerlendds/osintbuddy.svg?style=for-the-badge
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
from fastapi import APIRouter
|
||||
from app.core.config import settings
|
||||
|
||||
from .endpoints import entity, graph, graph_inquiry, login, scan, account, config
|
||||
from .endpoints import entity, graph, node, login, scan, account, config
|
||||
|
||||
api_router = APIRouter(prefix=settings.API_V1_STR)
|
||||
|
||||
api_router.include_router(graph_inquiry.router, tags=["Nodes"])
|
||||
api_router.include_router(node.router, tags=["Nodes"])
|
||||
api_router.include_router(config.router, tags=["Config"])
|
||||
api_router.include_router(login.router, tags=["Login"])
|
||||
api_router.include_router(account.router, tags=["Accounts"])
|
||||
|
||||
@@ -14,12 +14,12 @@ router = APIRouter(prefix="/account")
|
||||
|
||||
@router.get(
|
||||
"/",
|
||||
response_model=Union[schemas.UserInDBBase, schemas.HTTPError],
|
||||
response_model=Union[schemas.User, schemas.HTTPError],
|
||||
response_model_exclude_none=True
|
||||
)
|
||||
async def get_account(
|
||||
request: APIRequest,
|
||||
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
|
||||
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
|
||||
) -> models.User:
|
||||
try:
|
||||
return user
|
||||
|
||||
@@ -15,7 +15,7 @@ router = APIRouter(prefix="/config")
|
||||
@router.get("/casdoor", response_model=Any)
|
||||
async def get_casdoor_config(
|
||||
request: APIRequest,
|
||||
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)]
|
||||
user: Annotated[schemas.User, Depends(deps.get_user_from_session)]
|
||||
):
|
||||
sdk = request.app.state.CASDOOR_SDK
|
||||
casdoor_apps = sdk.get_applications()
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import os, importlib, inspect
|
||||
from uuid import UUID
|
||||
from typing import Annotated
|
||||
from app.schemas.entities import ENTITY_NAMESPACE
|
||||
|
||||
from app.schemas.entities import ENTITY_NAMESPACE, Entity
|
||||
from app.api.utils import get_hid
|
||||
from osintbuddy.plugins import EntityRegistry, load_plugin_source
|
||||
from osintbuddy import Registry
|
||||
from osintbuddy.templates.default import plugin_source_template
|
||||
from osintbuddy.utils.generic import to_snake_case
|
||||
from fastapi import APIRouter, HTTPException, Depends
|
||||
@@ -12,6 +10,7 @@ from sqlalchemy.orm import Session
|
||||
from starlette import status
|
||||
|
||||
from app.api import deps
|
||||
from app.api.api_v1.endpoints.node import refresh_local_entities
|
||||
from app import crud, schemas
|
||||
from app.core.logger import get_logger
|
||||
|
||||
@@ -20,14 +19,15 @@ router = APIRouter(prefix="/entity")
|
||||
|
||||
|
||||
async def fetch_node_transforms(plugin_label):
|
||||
plugin = await EntityRegistry.get_plugin(plugin_label=to_snake_case(plugin_label))
|
||||
plugin = await Registry.get_plugin(plugin_label=to_snake_case(plugin_label))
|
||||
if plugin is not None:
|
||||
return plugin().transform_labels
|
||||
labels = plugin().transform_labels
|
||||
return labels
|
||||
|
||||
|
||||
@router.get("/plugins/transform/")
|
||||
async def get_entity_transforms(
|
||||
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
|
||||
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
|
||||
label: str
|
||||
):
|
||||
try:
|
||||
@@ -41,8 +41,8 @@ async def get_entity_transforms(
|
||||
detail="Invalid transform error. Please file an issue if this occurs."
|
||||
)
|
||||
except Exception as e:
|
||||
log.error(e)
|
||||
log.error("Error inside entity.get_entity_transforms")
|
||||
log.error(e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="There was an error fetching transforms, please try again."
|
||||
@@ -51,12 +51,13 @@ async def get_entity_transforms(
|
||||
|
||||
@router.get("/details/{hid}", response_model=schemas.Entity)
|
||||
async def get_entity(
|
||||
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
|
||||
hid: Annotated[int, Depends(deps.get_entity_id)],
|
||||
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
|
||||
hid: Annotated[str, Depends(deps.get_entity_id)],
|
||||
db: Annotated[Session, Depends(deps.get_db)],
|
||||
):
|
||||
try:
|
||||
return crud.entities.get(db=db, id=hid)
|
||||
entities = crud.entities.get(db=db, id=hid)
|
||||
return entities
|
||||
except Exception as e:
|
||||
log.error('Error inside entity.get_entity:')
|
||||
log.error(e)
|
||||
@@ -71,7 +72,7 @@ async def get_entity(
|
||||
response_model=schemas.AllEntitiesList
|
||||
)
|
||||
async def get_entities(
|
||||
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
|
||||
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
|
||||
db: Annotated[Session, Depends(deps.get_db)],
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
@@ -79,22 +80,6 @@ async def get_entities(
|
||||
# try:
|
||||
if limit > 50:
|
||||
limit = 50
|
||||
|
||||
EntityRegistry.discover_plugins()
|
||||
for e in EntityRegistry.entities:
|
||||
entity_file = open(inspect.getsourcefile(e), "r")
|
||||
file_entity = schemas.EntityCreate(
|
||||
label=e.label,
|
||||
author=e.author,
|
||||
description=e.description,
|
||||
source=entity_file.read()
|
||||
)
|
||||
entity_obj = crud.entities.get_by_label(db=db, label=file_entity.label)
|
||||
if entity_obj:
|
||||
crud.entities.update(db, db_obj=entity_obj, obj_in=file_entity)
|
||||
else:
|
||||
crud.entities.create(db, obj_in=file_entity)
|
||||
|
||||
db_entities, entities_count = crud.entities.get_many_by_favorites(
|
||||
db=db,
|
||||
skip=skip,
|
||||
@@ -109,17 +94,16 @@ async def get_entities(
|
||||
)
|
||||
|
||||
entities = []
|
||||
favorite_entities = []
|
||||
|
||||
for entity in db_entities:
|
||||
entity = entity._asdict()
|
||||
entity["id"] = deps.get_hid(db_id=entity.get("id"), ns=ENTITY_NAMESPACE)
|
||||
entity["id"] = deps.hid(db_id=entity.get("id"), ns=ENTITY_NAMESPACE)
|
||||
entities.append(entity)
|
||||
favorite_entities = []
|
||||
for entity in db_favorite_entities:
|
||||
entity = entity._asdict()
|
||||
entity["id"] = deps.get_hid(db_id=entity.get("id"), ns=ENTITY_NAMESPACE)
|
||||
entity["id"] = deps.hid(db_id=entity.get("id"), ns=ENTITY_NAMESPACE)
|
||||
favorite_entities.append(entity)
|
||||
|
||||
|
||||
return {
|
||||
"entities": entities,
|
||||
"count": entities_count,
|
||||
@@ -136,12 +120,12 @@ async def get_entities(
|
||||
|
||||
@router.post("")
|
||||
async def create_entity(
|
||||
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
|
||||
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
|
||||
entity: schemas.PostEntityCreate,
|
||||
db: Annotated[Session, Depends(deps.get_db)],
|
||||
):
|
||||
try:
|
||||
obj_in = schemas.EntityCreate(
|
||||
return crud.entities.create(db, obj_in=schemas.EntityCreate(
|
||||
label=entity.label,
|
||||
author=entity.author,
|
||||
description=entity.description,
|
||||
@@ -150,8 +134,7 @@ async def create_entity(
|
||||
description=entity.description,
|
||||
author=entity.author
|
||||
)
|
||||
)
|
||||
return crud.entities.create(db, obj_in)
|
||||
))
|
||||
except Exception as e:
|
||||
log.error('Error inside entity.create_entity:')
|
||||
log.error(e)
|
||||
@@ -163,17 +146,19 @@ async def create_entity(
|
||||
|
||||
@router.put("/{hid}")
|
||||
async def update_entity_by_id(
|
||||
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
|
||||
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
|
||||
hid: Annotated[str, Depends(deps.get_entity_id)],
|
||||
obj_in: schemas.EntityUpdate,
|
||||
obj_in: schemas.EntityBase,
|
||||
db: Annotated[Session, Depends(deps.get_db)],
|
||||
):
|
||||
try:
|
||||
db_obj = crud.entities.get(db=db, id=hid)
|
||||
entity = crud.entities.update(db=db, db_obj=db_obj, obj_in=obj_in)
|
||||
refresh_local_entities(db)
|
||||
|
||||
return entity
|
||||
except Exception as e:
|
||||
log.error('Error inside entity.update_entity_by_id:')
|
||||
log.error('Error inside entity.update_entity_by_uuid:')
|
||||
log.error(e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
@@ -183,7 +168,7 @@ async def update_entity_by_id(
|
||||
|
||||
@router.delete("/{hid}")
|
||||
async def delete_entity(
|
||||
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
|
||||
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
|
||||
hid: Annotated[str, Depends(deps.get_entity_id)],
|
||||
db: Annotated[Session, Depends(deps.get_db)],
|
||||
):
|
||||
@@ -200,7 +185,7 @@ async def delete_entity(
|
||||
|
||||
@router.put("/{hid}/favorite")
|
||||
async def update_entity_favorite_id(
|
||||
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
|
||||
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
|
||||
hid: Annotated[str, Depends(deps.get_entity_id)],
|
||||
db: Annotated[Session, Depends(deps.get_db)],
|
||||
):
|
||||
|
||||
@@ -8,7 +8,7 @@ from app.api import deps
|
||||
from app import crud, schemas
|
||||
from app.core.logger import get_logger
|
||||
from app.db.janus import ProjectGraphConnection, janus_create_db
|
||||
from app.api.utils import get_hid
|
||||
from app.api.utils import hid
|
||||
|
||||
log = get_logger("api_v1.endpoints.graph")
|
||||
router = APIRouter(prefix="/graph")
|
||||
@@ -16,7 +16,7 @@ router = APIRouter(prefix="/graph")
|
||||
|
||||
@router.get("/{hid}", response_model=schemas.Graph)
|
||||
async def get_graph(
|
||||
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
|
||||
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
|
||||
hid: Annotated[str, Depends(deps.get_graph_id)],
|
||||
db: Annotated[Session, Depends(deps.get_db)],
|
||||
):
|
||||
@@ -37,7 +37,7 @@ async def get_graph(
|
||||
|
||||
@router.patch('/{hid}/favorite/')
|
||||
async def update_graph_favorite_id(
|
||||
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
|
||||
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
|
||||
hid: Annotated[str, Depends(deps.get_graph_id)],
|
||||
db: Annotated[Session, Depends(deps.get_db)],
|
||||
):
|
||||
@@ -66,7 +66,7 @@ async def update_graph_favorite_id(
|
||||
response_model=schemas.AllGraphsList,
|
||||
)
|
||||
async def get_graphs(
|
||||
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
|
||||
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
|
||||
db: Annotated[Session, Depends(deps.get_db)],
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
@@ -112,7 +112,7 @@ async def get_graphs(
|
||||
response_model=schemas.GraphsList
|
||||
)
|
||||
async def get_graphs_by_favorite(
|
||||
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
|
||||
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
|
||||
db: Annotated[Session, Depends(deps.get_db)],
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
@@ -139,7 +139,7 @@ async def get_graphs_by_favorite(
|
||||
response_model=schemas.Graph
|
||||
)
|
||||
async def create_graph(
|
||||
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
|
||||
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
|
||||
obj_in: schemas.GraphCreate,
|
||||
db: Annotated[Session, Depends(deps.get_db)],
|
||||
):
|
||||
@@ -162,7 +162,7 @@ async def create_graph(
|
||||
|
||||
@router.delete('')
|
||||
async def delete_graph(
|
||||
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
|
||||
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
|
||||
hid: Annotated[str, Depends(deps.get_graph_id)],
|
||||
db: Annotated[Session, Depends(deps.get_db)],
|
||||
):
|
||||
@@ -180,7 +180,7 @@ async def delete_graph(
|
||||
|
||||
@router.get('/{hid}/stats')
|
||||
async def get_graph_stats(
|
||||
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
|
||||
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
|
||||
hid: Annotated[str, Depends(deps.get_graph_id)],
|
||||
db: Annotated[Session, Depends(deps.get_db)],
|
||||
):
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
from uuid import UUID
|
||||
from typing import List, Callable, Tuple, Any, Annotated, Dict
|
||||
from typing import List, Callable, Tuple, Any, Annotated
|
||||
from fastapi import APIRouter, WebSocket, WebSocketException, WebSocketDisconnect, HTTPException, Depends
|
||||
from sqlalchemy import or_
|
||||
from websockets.exceptions import ConnectionClosedError
|
||||
from gremlinpy.process.graph_traversal import AsyncGraphTraversal
|
||||
from gremlin_python.process.graph_traversal import __ as _g
|
||||
from gremlin_python.process.traversal import P
|
||||
from gremlin_python.process.traversal import T, Cardinality
|
||||
from osintbuddy.utils import to_snake_case, MAP_KEY, chunks
|
||||
from osintbuddy import EntityRegistry, TransformUse, EntityConfig
|
||||
from osintbuddy import Registry, PluginUse, load_plugins
|
||||
from osintbuddy.errors import OBPluginError
|
||||
from sqlalchemy.orm import Session
|
||||
from starlette import status
|
||||
@@ -22,19 +20,30 @@ from app.core.config import settings
|
||||
log = get_logger("api_v1.endpoints.nodes")
|
||||
router = APIRouter(prefix="/node")
|
||||
|
||||
# TODO: Refactor to somewhere that makes sense...
|
||||
def refresh_local_entities(db: Session):
|
||||
Registry.plugins = []
|
||||
Registry.labels = []
|
||||
Registry.ui_labels = []
|
||||
entities = crud.entities.get_many(db, skip=0, limit=100)
|
||||
load_plugins(entities)
|
||||
|
||||
|
||||
async def fetch_node_transforms(plugin_label):
|
||||
plugin = await EntityRegistry.get_plugin(plugin_label=plugin_label)
|
||||
plugin = await Registry.get_plugin(plugin_label=plugin_label)
|
||||
if plugin is not None:
|
||||
labels = plugin().transform_labels
|
||||
return labels
|
||||
|
||||
|
||||
|
||||
def add_node_element(vertex, element: dict or List[dict], data_labels: List[str]):
|
||||
# Remove stylistic values unrelated to element data
|
||||
# Some osintbuddy.elements of type displays dont have an icon or options
|
||||
|
||||
icon = element.pop('icon', None)
|
||||
options = element.pop('options', None)
|
||||
|
||||
label = element.pop('label')
|
||||
elm_type = element.pop('type')
|
||||
|
||||
@@ -47,6 +56,7 @@ def add_node_element(vertex, element: dict or List[dict], data_labels: List[str]
|
||||
vertex.property(f'{to_snake_case(label)}_{to_snake_case(k)}', v)
|
||||
# Save the data labels so we can assign these as meta properties later
|
||||
data_labels.append(to_snake_case(label))
|
||||
|
||||
element['type'] = elm_type
|
||||
element['icon'] = icon
|
||||
element['label'] = label
|
||||
@@ -107,11 +117,7 @@ async def save_node_on_drop(
|
||||
}
|
||||
}
|
||||
|
||||
async def load_nodes_from_db(uuid: UUID, viewport=None) -> tuple[list, list]:
|
||||
zoom_scale = viewport.get('zoom')
|
||||
# x = viewport.get('x')
|
||||
# y = viewport.get('y')
|
||||
|
||||
async def load_initial_graph(uuid: UUID) -> tuple[list, list]:
|
||||
async with ProjectGraphConnection(uuid) as graph:
|
||||
edges: list = await graph.V().outE().project('from', 'edge', 'to')\
|
||||
.by(_g.outV()).by(_g.valueMap(True)).by(_g.inV()).union(
|
||||
@@ -119,20 +125,8 @@ async def load_nodes_from_db(uuid: UUID, viewport=None) -> tuple[list, list]:
|
||||
_g.project('from').by(_g.select('from')).unfold(),
|
||||
_g.project('to').by(_g.select('to')).unfold()
|
||||
).fold().toList()
|
||||
# TODO: Load/update UI nodes on far drag
|
||||
# TODO: unload nodes UI side when off screen offset by max drag distance
|
||||
# y = viewport.get('y')
|
||||
nodes: list = await graph.V()\
|
||||
.valueMap(True).toList()
|
||||
# .has('y', P.between(y, y2))\
|
||||
# \
|
||||
# .has('y', P.lt(y)).has('y', P.gt(y_offset))\
|
||||
# .has('x', P.gt(x))\
|
||||
# .has('x', P.between(x1, x2))\
|
||||
# .has('y', P.between(y1, y2))\
|
||||
# .or_(_g.has('x', P.gt(x1)), _g.has('x', P.lt(x2)))
|
||||
# maybe something like the above?!
|
||||
|
||||
nodes: list = await graph.V().valueMap(True).toList()
|
||||
|
||||
return nodes, edges
|
||||
|
||||
|
||||
@@ -155,13 +149,10 @@ def node_to_blueprint_entity(ui_entity_element, node) -> None:
|
||||
if entity_label not in k:
|
||||
ui_entity_element[k] = node[v][0]
|
||||
|
||||
|
||||
async def read_graph(viewport_event, send_json, project_uuid, is_initial_read: bool = False):
|
||||
async def read_graph(action_type, send_json, project_uuid):
|
||||
nodes = []
|
||||
edges_data = []
|
||||
data_nodes, edges = await load_nodes_from_db(project_uuid, viewport_event)
|
||||
if is_initial_read:
|
||||
EntityRegistry.discover_plugins()
|
||||
data_nodes, edges = await load_initial_graph(project_uuid)
|
||||
tmp_invalid_fix = []
|
||||
tmp_invalid_id_fix = []
|
||||
for node in data_nodes:
|
||||
@@ -171,9 +162,9 @@ async def read_graph(viewport_event, send_json, project_uuid, is_initial_read: b
|
||||
}
|
||||
entity_id = node.pop(T.id)
|
||||
entity_type = node.pop(T.label)
|
||||
plugin = await EntityRegistry.get_plugin(to_snake_case(entity_type))
|
||||
plugin = await Registry.get_plugin(to_snake_case(entity_type))
|
||||
if plugin:
|
||||
ui_entity = plugin.create()
|
||||
ui_entity = plugin.blueprint()
|
||||
for element in ui_entity['elements']:
|
||||
if isinstance(element, list):
|
||||
[node_to_blueprint_entity(
|
||||
@@ -196,8 +187,8 @@ async def read_graph(viewport_event, send_json, project_uuid, is_initial_read: b
|
||||
}
|
||||
nodes.append(ui_entity)
|
||||
else:
|
||||
# TODO detect invalid/renamed entities/plugins on any plugin label updates and fix graph
|
||||
tmp_invalid_id_fix.append(entity_id)
|
||||
# TODO detect invalid/renamed entities/plugins on any plugin label updates and fix graph
|
||||
tmp_invalid_fix.append(entity_type)
|
||||
print(f'(TODO: on entity code update and label change rename nodes in janusgraph) Error Invalid Plugin: {entity_type}')
|
||||
if len(edges[0]) > 0:
|
||||
@@ -216,24 +207,22 @@ async def read_graph(viewport_event, send_json, project_uuid, is_initial_read: b
|
||||
'type': 'float'
|
||||
})
|
||||
await send_json({
|
||||
'action': 'isInitialRead' if is_initial_read else 'read',
|
||||
'action': 'isInitialRead',
|
||||
'nodes': nodes,
|
||||
'edges': edges_data
|
||||
})
|
||||
|
||||
|
||||
async def update_node(node, send_json, uuid: UUID):
|
||||
async def update_node(node, action_type, send_json, uuid: UUID):
|
||||
if updateTargetId := node.pop('id', None):
|
||||
async with ProjectGraphConnection(uuid) as graph:
|
||||
updateTarget = graph.V(updateTargetId)
|
||||
for k, v in node.items():
|
||||
# if k == 'x' or k == 'y':
|
||||
# print(to_snake_case(k), v)
|
||||
updateTarget.property(Cardinality.single, to_snake_case(k), v)
|
||||
await updateTarget.next()
|
||||
|
||||
|
||||
async def remove_nodes(node, send_json, uuid: UUID):
|
||||
async def remove_nodes(node, action_type, send_json, uuid: UUID):
|
||||
async with ProjectGraphConnection(uuid) as graph:
|
||||
if targetNode := node.get('id'):
|
||||
await graph.V(targetNode).drop().next()
|
||||
@@ -254,20 +243,23 @@ async def get_transform_notification(transform_output, transform_type):
|
||||
|
||||
|
||||
async def nodes_transform(
|
||||
entity_context: dict,
|
||||
node: dict,
|
||||
send_json: Callable[[dict], None],
|
||||
uuid: UUID
|
||||
):
|
||||
transform_result = {}
|
||||
transform_type = entity_context.get("transform")
|
||||
entity_data = entity_context.get("data", {})
|
||||
entity_type = entity_data.get("label")
|
||||
plugin = await EntityRegistry.get_plugin(entity_type)
|
||||
entity_type = node.get('data', {}).get('label')
|
||||
transform_type = None
|
||||
plugin = await Registry.get_plugin(entity_type)
|
||||
if plugin := plugin():
|
||||
transform_result = await plugin.run_transform(
|
||||
transform_type = node["transform"]
|
||||
transform_result = await plugin.get_transform(
|
||||
transform_type=transform_type,
|
||||
transform_context=entity_context,
|
||||
use=TransformUse(get_driver=deps.get_driver, config=EntityConfig(enabled=False, test=1, test2="two"))
|
||||
entity=node,
|
||||
use=PluginUse(
|
||||
get_driver=deps.get_driver,
|
||||
get_graph=lambda: None
|
||||
)
|
||||
)
|
||||
async def create_entity_data(
|
||||
graph: AsyncGraphTraversal,
|
||||
@@ -306,15 +298,13 @@ async def nodes_transform(
|
||||
|
||||
async with ProjectGraphConnection(uuid) as graph:
|
||||
if isinstance(transform_result, list):
|
||||
out_result = [await create_entity_data(graph, result, entity_context) for result in transform_result]
|
||||
out_result = [await create_entity_data(graph, result, node) for result in transform_result]
|
||||
else:
|
||||
out_result = list(await create_entity_data(graph, transform_result, entity_context))
|
||||
transform_notification = await get_transform_notification(transform_result, transform_type)
|
||||
|
||||
out_result = list(await create_entity_data(graph, transform_result, node))
|
||||
await send_json({
|
||||
"action": "isLoading",
|
||||
"detail": False,
|
||||
"message": transform_notification
|
||||
"message": await get_transform_notification(transform_result, transform_type)
|
||||
})
|
||||
await send_json({
|
||||
"action": "createEntity",
|
||||
@@ -324,115 +314,101 @@ async def nodes_transform(
|
||||
await send_json({'error': 'noPluginFound'})
|
||||
|
||||
|
||||
async def run_user_graph_event(event: dict, send_json: Callable, uuid: UUID) -> None:
|
||||
async def get_command_type(event):
|
||||
user_event: list = event["action"].split(":")
|
||||
action = user_event[0]
|
||||
IS_READ: bool = action == "read"
|
||||
IS_UPDATE: bool = action == "update"
|
||||
IS_DELETE: bool = action == "delete"
|
||||
IS_TRANSFORM: bool = action == "transform"
|
||||
IS_INITIAL: bool = 'initial' in action
|
||||
USER_ACTION = None
|
||||
if len(user_event) >= 2:
|
||||
USER_ACTION = user_event[1]
|
||||
IS_READ = action == "read"
|
||||
IS_UPDATE = action == "update"
|
||||
IS_DELETE = action == "delete"
|
||||
IS_TRANSFORM = action == "transform"
|
||||
return USER_ACTION, IS_READ, IS_UPDATE, IS_DELETE, IS_TRANSFORM
|
||||
|
||||
ACTION_TARGET = user_event[1]
|
||||
|
||||
if ACTION_TARGET == 'node':
|
||||
async def run_user_graph_event(event: dict, send_json: Callable, uuid: UUID) -> None:
|
||||
USER_ACTION, IS_READ, IS_UPDATE, IS_DELETE, IS_TRANSFORM = await get_command_type(event)
|
||||
if USER_ACTION == 'node':
|
||||
if IS_READ:
|
||||
await send_json({"action": "isLoading", "detail": True })
|
||||
await read_graph(USER_ACTION, send_json, uuid)
|
||||
await send_json({ "action": "isLoading", "detail": False, "message": "Success! Your graph environment has loaded!" })
|
||||
if IS_UPDATE:
|
||||
await update_node(event["node"], send_json, uuid)
|
||||
await update_node(event["node"], USER_ACTION, send_json, uuid)
|
||||
if IS_DELETE:
|
||||
await remove_nodes(event["node"], send_json, uuid)
|
||||
await remove_nodes(event["node"], USER_ACTION, send_json, uuid)
|
||||
if IS_TRANSFORM:
|
||||
await send_json({"action": "isLoading", "detail": True })
|
||||
await nodes_transform(event["node"], send_json, uuid)
|
||||
if ACTION_TARGET == 'graph':
|
||||
if IS_READ:
|
||||
await read_graph(event["viewport"], send_json, uuid)
|
||||
await send_json({ "action": "isLoading", "detail": False, "message": "Success! You've been reconnected!" })
|
||||
if IS_INITIAL:
|
||||
await send_json({"action": "isLoading", "detail": True })
|
||||
await read_graph(event["viewport"], send_json, uuid, True)
|
||||
await send_json({ "action": "isLoading", "detail": False, "message": "Success! Your graph environment has loaded!" })
|
||||
|
||||
|
||||
# TODO: Finish implementing 'multiplayer' logic
|
||||
graph_users: Dict[str, WebSocket] = {}
|
||||
|
||||
@router.websocket("/graph/{hid}")
|
||||
async def active_graph_inquiry(
|
||||
websocket: WebSocket,
|
||||
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_ws)],
|
||||
user: Annotated[schemas.User, Depends(deps.get_user_from_ws)],
|
||||
hid: Annotated[int, Depends(deps.get_graph_id)],
|
||||
db: Session = Depends(deps.get_db)
|
||||
):
|
||||
EntityRegistry.discover_plugins()
|
||||
graph_users[user.cid.hex] = websocket
|
||||
active_inquiry = crud.graphs.get(db, id=hid)
|
||||
|
||||
await websocket.accept()
|
||||
if active_inquiry is not None:
|
||||
await graph_users[user.cid.hex].send_json({"action": "isLoading", "detail": True })
|
||||
is_project_active = True
|
||||
active_inquiry = crud.graphs.get(db, id=hid)
|
||||
if active_inquiry is None:
|
||||
is_project_active = False
|
||||
else:
|
||||
websocket.send({"action": "error"})
|
||||
await websocket.send_json({"action": "isLoading", "detail": True })
|
||||
|
||||
for user_cid, ws in graph_users.items():
|
||||
while is_project_active:
|
||||
try:
|
||||
async for event in ws.iter_json():
|
||||
await run_user_graph_event(
|
||||
event=event,
|
||||
send_json=ws.send_json,
|
||||
uuid=active_inquiry.uuid
|
||||
)
|
||||
event: dict = await websocket.receive_json()
|
||||
await run_user_graph_event(
|
||||
event=event,
|
||||
send_json=websocket.send_json,
|
||||
uuid=active_inquiry.uuid
|
||||
)
|
||||
except OBPluginError as e:
|
||||
await ws.send_json({"action": "error", "detail": f"{e}"})
|
||||
await ws.send_json({"action": "isLoading", "detail": False })
|
||||
await websocket.send_json({"action": "error", "detail": f"{e}"})
|
||||
await websocket.send_json({"action": "isLoading", "detail": False })
|
||||
log.error(e)
|
||||
except (WebSocketException, ConnectionClosedError) as e:
|
||||
except (WebSocketException, BufferError, ConnectionClosedError) as e:
|
||||
await websocket.send_json({"action": "isLoading", "detail": False })
|
||||
log.error("Exception inside node.active_project")
|
||||
log.error(e)
|
||||
await ws.send_json({"action": "isLoading", "detail": False })
|
||||
await ws.close()
|
||||
# del graph_users[user_cid]
|
||||
except (WebSocketDisconnect, RuntimeError) as e:
|
||||
if isinstance(e, RuntimeError):
|
||||
log.error(e)
|
||||
else:
|
||||
log.info((
|
||||
f"disconnect user -> {user_cid} from all"
|
||||
f"http://localhost:3000/graph/inquiry/{hid} users: {graph_users}"
|
||||
))
|
||||
del graph_users[user_cid]
|
||||
except KeyError:
|
||||
pass
|
||||
is_project_active = False
|
||||
await websocket.close()
|
||||
except WebSocketDisconnect as e:
|
||||
log.info(f"disconnected!")
|
||||
|
||||
@router.get("/refresh")
|
||||
async def refresh_entity_plugins(
|
||||
async def refresh_plugins(
|
||||
hid: Annotated[str, Depends(deps.get_graph_id)],
|
||||
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
|
||||
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
|
||||
db: Session = Depends(deps.get_db)
|
||||
):
|
||||
# try:
|
||||
EntityRegistry.discover_plugins()
|
||||
return {"status": "success", "plugins": EntityRegistry._visible_entities}
|
||||
# except Exception as e:
|
||||
# log.error("Error inside node.refresh_plugins")
|
||||
# log.error(e)
|
||||
# raise HTTPException(
|
||||
# status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
# detail="There was an error refreshing, please try again."
|
||||
# )
|
||||
try:
|
||||
refresh_local_entities(db)
|
||||
return {"status": "success", "plugins": Registry.ui_labels}
|
||||
except Exception as e:
|
||||
log.error("Error inside node.refresh_plugins")
|
||||
log.error(e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="There was an error refreshing, please try again."
|
||||
)
|
||||
|
||||
|
||||
@router.post("/")
|
||||
async def create_entity_on_drop(
|
||||
hid: Annotated[str, Depends(deps.get_graph_id)],
|
||||
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
|
||||
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
|
||||
create_node: schemas.CreateNode,
|
||||
db: Session = Depends(deps.get_db)
|
||||
):
|
||||
try:
|
||||
active_inquiry = crud.graphs.get(db, id=hid)
|
||||
plugin = await EntityRegistry.get_plugin(plugin_label=to_snake_case(create_node.label))
|
||||
plugin = await Registry.get_plugin(plugin_label=to_snake_case(create_node.label))
|
||||
if plugin:
|
||||
blueprint = plugin.create()
|
||||
blueprint = plugin.blueprint()
|
||||
blueprint["position"] = create_node.position.model_dump()
|
||||
return await save_node_on_drop(
|
||||
create_node.label,
|
||||
@@ -1,12 +1,9 @@
|
||||
import datetime
|
||||
from typing import Generator, Any
|
||||
from contextlib import contextmanager
|
||||
import uuid
|
||||
|
||||
import boto3
|
||||
from botocore.exceptions import BotoCoreError
|
||||
from fastapi import Depends, HTTPException, WebSocket, WebSocketException
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from sqlalchemy.orm import Session
|
||||
import undetected_chromedriver as uc
|
||||
from selenium import webdriver
|
||||
@@ -16,16 +13,13 @@ from selenium.webdriver.chrome.webdriver import WebDriver
|
||||
from app import schemas, crud
|
||||
from app.db.session import SessionLocal
|
||||
from app.core.logger import get_logger
|
||||
from app.api.utils import APIRequest, HidChecker, get_hid
|
||||
from app.api.utils import APIRequest, HidChecker, hid
|
||||
from app.core.config import settings
|
||||
|
||||
|
||||
log = get_logger("api.deps")
|
||||
|
||||
|
||||
if "dev" in settings.ENVIRONMENT:
|
||||
___OB_DEV_CID___ = uuid.uuid4()
|
||||
|
||||
def get_db() -> Generator[Any, Session, Any]:
|
||||
try:
|
||||
db: Session = SessionLocal()
|
||||
@@ -37,56 +31,26 @@ def get_db() -> Generator[Any, Session, Any]:
|
||||
async def get_user_from_session(
|
||||
request: APIRequest,
|
||||
db: Session = Depends(get_db)
|
||||
) -> schemas.UserInDBBase:
|
||||
|
||||
) -> schemas.User:
|
||||
if 'dev' in settings.ENVIRONMENT:
|
||||
return {}
|
||||
if user := request.session.get("member"):
|
||||
ob_user: schemas.UserInDBBase | None = crud.user.get_by_cid(db=db, cid=user.get("id"))
|
||||
ob_user: schemas.User | None = crud.user.get_by_cid(db=db, cid=user.get("id"))
|
||||
if ob_user:
|
||||
ob_user = jsonable_encoder(ob_user)
|
||||
return schemas.UserInDBBase(**ob_user)
|
||||
if "dev" in settings.ENVIRONMENT:
|
||||
return schemas.UserCreate(
|
||||
cid=___OB_DEV_CID___,
|
||||
name="DEV",
|
||||
username="DEV",
|
||||
email="dev@osintbuddy.com",
|
||||
avatar="",
|
||||
phone="",
|
||||
display_name="Dev Account",
|
||||
first_name="Dev",
|
||||
last_name="eloper",
|
||||
is_admin=True,
|
||||
created_time=datetime.datetime.now(),
|
||||
updated_time=datetime.datetime.now(),
|
||||
)
|
||||
return ob_user
|
||||
raise HTTPException(status_code=401, detail="Unauthorized")
|
||||
|
||||
|
||||
async def get_user_from_ws(
|
||||
websocket: WebSocket,
|
||||
db: Session = Depends(get_db)
|
||||
) -> schemas.UserInDBBase:
|
||||
|
||||
) -> schemas.User:
|
||||
if 'dev' in settings.ENVIRONMENT:
|
||||
return {}
|
||||
if user := websocket.session.get("member"):
|
||||
ob_user: schemas.UserInDBBase | None = crud.user.get_by_cid(db=db, cid=user.get("id"))
|
||||
ob_user: schemas.User | None = crud.user.get_by_cid(db=db, cid=user.get("id"))
|
||||
if ob_user:
|
||||
ob_user = jsonable_encoder(ob_user)
|
||||
return schemas.UserInDBBase(**ob_user)
|
||||
if "dev" in settings.ENVIRONMENT:
|
||||
return schemas.UserInDBBase(
|
||||
cid=___OB_DEV_CID___,
|
||||
name="DEV",
|
||||
username="DEV",
|
||||
email="dev@osintbuddy.com",
|
||||
avatar="",
|
||||
phone="",
|
||||
display_name="Dev Account",
|
||||
first_name="Dev",
|
||||
last_name="eloper",
|
||||
is_admin=True,
|
||||
created_time=datetime.datetime.now(),
|
||||
updated_time=datetime.datetime.now(),
|
||||
)
|
||||
return ob_user
|
||||
raise WebSocketException(code=401)
|
||||
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ if TYPE_CHECKING:
|
||||
sqids = Sqids(alphabet=settings.SQIDS_ALPHABET, min_length=4)
|
||||
|
||||
|
||||
def get_hid(db_id: int, ns: int = settings.SQIDS_NAMESPACE) -> str:
|
||||
def hid(db_id: int, ns: int = settings.SQIDS_NAMESPACE) -> str:
|
||||
"""Generate an ID thats easy for humans to work with"""
|
||||
return sqids.encode([ns, db_id])
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from typing import List
|
||||
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func, select
|
||||
|
||||
@@ -32,11 +31,6 @@ class CRUDEntities(CRUDBase[
|
||||
) -> List[ModelType]:
|
||||
return db.query(self.model).where(self.model.uuid == uuid).first()
|
||||
|
||||
def get_by_label(
|
||||
self, db: Session, *, label: str = None
|
||||
) -> List[ModelType]:
|
||||
return db.query(self.model).where(self.model.label == label).first()
|
||||
|
||||
def update_favorite_by_id(self, db: Session, db_obj: Entities, is_favorite: bool = False):
|
||||
setattr(db_obj, 'is_favorite', is_favorite)
|
||||
db.add(db_obj)
|
||||
|
||||
@@ -45,7 +45,7 @@ class CRUDGraphs(CRUDBase[
|
||||
def get_many_user_graphs(
|
||||
self,
|
||||
db: Session,
|
||||
user: schemas.UserInDBBase,
|
||||
user: schemas.User,
|
||||
skip: int = 0,
|
||||
limit: int = 50,
|
||||
is_favorite: bool = True
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import inspect, time, importlib, imp
|
||||
|
||||
import requests
|
||||
from sqlalchemy.orm import Session
|
||||
from osintbuddy.plugins import EntityRegistry
|
||||
from osintbuddy import load_plugin, Registry
|
||||
|
||||
from app import crud, schemas
|
||||
from app.core.logger import get_logger
|
||||
@@ -11,61 +9,56 @@ from .base import * # noqa
|
||||
|
||||
log = get_logger(__name__)
|
||||
|
||||
core_ob_url = 'https://raw.githubusercontent.com/jerlendds/osintbuddy-core-plugins/develop/plugins/'
|
||||
core_entity_files: list[str] = [
|
||||
'google_cse_result',
|
||||
'google_cse_search',
|
||||
'dns',
|
||||
'google_cache_result',
|
||||
'google_cache_search',
|
||||
'google_result',
|
||||
'google_search',
|
||||
'ip',
|
||||
'ip_geolocation',
|
||||
'subdomain',
|
||||
'telegram_cse_search',
|
||||
'url',
|
||||
'username',
|
||||
'username_profile',
|
||||
'website',
|
||||
'whois'
|
||||
]
|
||||
core_ob_url = 'https://raw.githubusercontent.com/jerlendds/osintbuddy-core-plugins/main/plugins/'
|
||||
core_plugins = {
|
||||
'CSE Result': 'cse_result',
|
||||
'CSE Search': 'cse_search',
|
||||
'DNS': 'dns',
|
||||
'Google Cache Result': 'google_cache_result',
|
||||
'Google Cache Search': 'google_cache_search',
|
||||
'Google Result': 'google_result',
|
||||
'Google Search': 'google_search',
|
||||
'IP': 'ip',
|
||||
'IP Geolocation': 'ip_geolocation',
|
||||
'Subdomain': 'subdomain',
|
||||
'Telegram Websearch': 'telegram_websearch',
|
||||
'URL': 'url',
|
||||
'Username': 'username',
|
||||
'Username Profile': 'username_profile',
|
||||
'Website': 'website',
|
||||
'Whois': 'whois'
|
||||
}
|
||||
|
||||
def load_initial_plugin(db, plugin_mod, plugin_code):
|
||||
load_plugin(plugin_mod, plugin_code)
|
||||
|
||||
plugin = Registry.get_plug(plugin_mod)
|
||||
obj_in = schemas.EntityCreate(
|
||||
label=plugin.label,
|
||||
author=plugin.author,
|
||||
description=plugin.description,
|
||||
source=plugin_code,
|
||||
is_favorite=False
|
||||
)
|
||||
return crud.entities.create(db=db, obj_in=obj_in)
|
||||
|
||||
def init_db(db: Session) -> None:
|
||||
entity_count = crud.entities.count_all(db)[0][0]
|
||||
if entity_count < 14:
|
||||
for file_endpoint in core_entity_files:
|
||||
log.info(f"Saving core plugin/entity file: {file_endpoint}")
|
||||
for plugin_label, plugin_mod in core_plugins.items():
|
||||
log.info(f'Loading core plugins: {plugin_label}')
|
||||
try:
|
||||
resp = requests.get(core_ob_url + file_endpoint + ".py")
|
||||
source_file = open(f"/app/app/plugins/{file_endpoint}.py", "w+")
|
||||
source_file.write(resp.text)
|
||||
source_file.close()
|
||||
resp = requests.get(core_ob_url + plugin_mod + '.py')
|
||||
load_initial_plugin(db=db, plugin_mod=plugin_mod, plugin_code=resp.text)
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
# TODO: Use tenacity lib retry logic instead
|
||||
log.error("Error loading core plugin! ", e)
|
||||
|
||||
EntityRegistry.discover_plugins()
|
||||
for e in EntityRegistry.entities:
|
||||
log.info(f"Loading core plugin/entity: {e.label}")
|
||||
entity_file = open(inspect.getsourcefile(e), "r")
|
||||
obj_in = schemas.EntityCreate(
|
||||
label=e.label,
|
||||
author=e.author,
|
||||
description=e.description,
|
||||
source=entity_file.read(),
|
||||
is_favorite=False
|
||||
)
|
||||
entity_file.close()
|
||||
entity_obj = crud.entities.get_by_label(db=db, label=obj_in.label)
|
||||
if entity_obj:
|
||||
crud.entities.update(db, db_obj=entity_obj, obj_in=obj_in.model_dump())
|
||||
else:
|
||||
crud.entities.create(db, obj_in=obj_in)
|
||||
log.error(e)
|
||||
resp = requests.get(core_ob_url + plugin_mod + '.py')
|
||||
load_initial_plugin(db=db, plugin_mod=plugin_mod, plugin_code=resp.text)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"service": "[Entities: Create]",
|
||||
"message": f"{len(EntityRegistry.entities)} initial entities (plugins) loaded",
|
||||
"message": f"{len(Registry.plugins)} initial entities (plugins) loaded",
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import os
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
|
||||
DB_POOL_SIZE = int(os.getenv("DB_POOL_SIZE", "100"))
|
||||
WEB_CONCURRENCY = int(os.getenv("WEB_CONCURRENCY", "4"))
|
||||
POOL_SIZE = max(DB_POOL_SIZE // WEB_CONCURRENCY, 5)
|
||||
|
||||
engine = create_engine(settings.SQLALCHEMY_DATABASE_URI, pool_size=POOL_SIZE, max_overflow=0, pool_pre_ping=True)
|
||||
engine = create_engine(settings.SQLALCHEMY_DATABASE_URI, pool_pre_ping=True)
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
|
||||
15
backend/app/app/email-templates/src/new_account.mjml
Executable file
@@ -0,0 +1,15 @@
|
||||
<mjml>
|
||||
<mj-body background-color="#fff">
|
||||
<mj-section>
|
||||
<mj-column>
|
||||
<mj-divider border-color="#555"></mj-divider>
|
||||
<mj-text font-size="20px" color="#555" font-family="helvetica">{{ project_name }} - New Account</mj-text>
|
||||
<mj-text font-size="16px" color="#555">You have a new account:</mj-text>
|
||||
<mj-text font-size="16px" color="#555">Username: {{ username }}</mj-text>
|
||||
<mj-text font-size="16px" color="#555">Password: {{ password }}</mj-text>
|
||||
<mj-button padding="50px 0px" href="{{ link }}">Go to Dashboard</mj-button>
|
||||
<mj-divider border-color="#555" border-width="2px" />
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
</mj-body>
|
||||
</mjml>
|
||||
19
backend/app/app/email-templates/src/reset_password.mjml
Executable file
@@ -0,0 +1,19 @@
|
||||
<mjml>
|
||||
<mj-body background-color="#fff">
|
||||
<mj-section>
|
||||
<mj-column>
|
||||
<mj-divider border-color="#555"></mj-divider>
|
||||
<mj-text font-size="20px" color="#555" font-family="helvetica">{{ project_name }} - Password Recovery</mj-text>
|
||||
<mj-text font-size="16px" color="#555">We received a request to recover the password for user {{ username }}
|
||||
with email {{ email }}</mj-text>
|
||||
<mj-text font-size="16px" color="#555">Reset your password by clicking the button below:</mj-text>
|
||||
<mj-button padding="50px 0px" href="{{ link }}">Reset Password</mj-button>
|
||||
<mj-text font-size="16px" color="#555">Or open the following link:</mj-text>
|
||||
<mj-text font-size="16px" color="#555"><a href="{{ link }}">{{ link }}</a></mj-text>
|
||||
<mj-divider border-color="#555" border-width="2px" />
|
||||
<mj-text font-size="14px" color="#555">The reset password link / button will expire in {{ valid_hours }} hours.</mj-text>
|
||||
<mj-text font-size="14px" color="#555">If you didn't request a password recovery you can disregard this email.</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
</mj-body>
|
||||
</mjml>
|
||||
11
backend/app/app/email-templates/src/test_email.mjml
Executable file
@@ -0,0 +1,11 @@
|
||||
<mjml>
|
||||
<mj-body background-color="#fff">
|
||||
<mj-section>
|
||||
<mj-column>
|
||||
<mj-divider border-color="#555"></mj-divider>
|
||||
<mj-text font-size="20px" color="#555" font-family="helvetica">{{ project_name }}</mj-text>
|
||||
<mj-text font-size="16px" color="#555">Test email for: {{ email }}</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
</mj-body>
|
||||
</mjml>
|
||||
@@ -14,12 +14,12 @@ def init() -> None:
|
||||
|
||||
|
||||
def main() -> None:
|
||||
# try:
|
||||
log.info("Running initial data check...")
|
||||
init()
|
||||
log.info("Initial data check completed.")
|
||||
# except Exception as e:
|
||||
# log.error(e)
|
||||
try:
|
||||
log.info("Running initial data check...")
|
||||
init()
|
||||
log.info("Initial data check completed.")
|
||||
except Exception as e:
|
||||
log.error(e)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -8,7 +8,7 @@ from starlette.middleware.sessions import SessionMiddleware
|
||||
# from fastapi_cache.backends.redis import CACHE_KEY, RedisCacheBackend
|
||||
# import sentry_sdk
|
||||
# from sentry_sdk.integrations.asyncio import AsyncioIntegration
|
||||
from app.api.api_v1.api import api_router, graph_inquiry
|
||||
from app.api.api_v1.api import api_router, node
|
||||
from app.core.config import settings, use_route_names_as_operation_ids
|
||||
from app.core.casdoor import Config as CasdoorConfig
|
||||
# if settings.SENTRY_DSN:
|
||||
|
||||
@@ -3,7 +3,7 @@ from uuid import UUID
|
||||
from typing import Optional, List
|
||||
|
||||
from pydantic import BaseModel, validator, ConfigDict
|
||||
from app.api.utils import get_hid
|
||||
from app.api.utils import hid
|
||||
from osintbuddy.templates import plugin_source_template
|
||||
|
||||
ENTITY_NAMESPACE = 1510
|
||||
@@ -35,10 +35,10 @@ class PostEntityCreate(BaseModel):
|
||||
|
||||
|
||||
class EntityUpdate(EntityBase):
|
||||
label: Optional[str] = ""
|
||||
author: Optional[str] = ""
|
||||
description: Optional[str] = ""
|
||||
source: Optional[str]
|
||||
label: Optional[str]
|
||||
author: Optional[str]
|
||||
description: Optional[str]
|
||||
source: Optional[str]
|
||||
|
||||
|
||||
class EntityInDBBase(EntityBase):
|
||||
@@ -47,7 +47,7 @@ class EntityInDBBase(EntityBase):
|
||||
'id',
|
||||
pre=True,
|
||||
allow_reuse=True
|
||||
)(lambda v: get_hid(v, ENTITY_NAMESPACE))
|
||||
)(lambda v: hid(v, ENTITY_NAMESPACE))
|
||||
|
||||
last_edited: datetime.datetime
|
||||
updated: datetime.datetime
|
||||
@@ -55,8 +55,7 @@ class EntityInDBBase(EntityBase):
|
||||
|
||||
|
||||
class Entity(EntityInDBBase):
|
||||
# model_config = ConfigDict(from_attributes=True)
|
||||
pass
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class EntityInList(PostEntityCreate):
|
||||
|
||||
@@ -3,7 +3,7 @@ from uuid import UUID
|
||||
from typing import Optional, List
|
||||
|
||||
from pydantic import BaseModel, validator
|
||||
from app.api.utils import get_hid, sqids
|
||||
from app.api.utils import hid, sqids
|
||||
|
||||
GRAPH_NAMESPACE = 5172
|
||||
|
||||
@@ -31,7 +31,7 @@ class GraphInDBBase(GraphsBase):
|
||||
'id',
|
||||
pre=True,
|
||||
allow_reuse=True
|
||||
)(lambda v: get_hid(v, GRAPH_NAMESPACE))
|
||||
)(lambda v: hid(v, GRAPH_NAMESPACE))
|
||||
|
||||
class Config:
|
||||
from_orm = True
|
||||
|
||||
@@ -72,6 +72,9 @@ class User(UserCreate):
|
||||
|
||||
|
||||
class UserInDBBase(UserCreate):
|
||||
is_admin: bool
|
||||
created_time: datetime.datetime
|
||||
updated_time: datetime.datetime
|
||||
cid: UUID
|
||||
|
||||
class Config:
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
import osintbuddy as ob
|
||||
from osintbuddy import load_local_plugins
|
||||
import osintbuddy
|
||||
from app import crud, schemas, models
|
||||
from app.core.logger import get_logger
|
||||
from app.db.session import SessionLocal
|
||||
|
||||
from termcolor import colored
|
||||
from pyfiglet import figlet_format
|
||||
|
||||
db = SessionLocal()
|
||||
entities = crud.entities.get_many(db, skip=0, limit=100)
|
||||
load_local_plugins(entities, "plugins")
|
||||
|
||||
print(
|
||||
colored(figlet_format("osintbuddy py >", font='smslant',), color="green"),
|
||||
colored("Created by jerlendds", color="blue"),
|
||||
@@ -18,11 +12,6 @@ print(
|
||||
###############################################################################
|
||||
# The following imports and variables have been included in your current shell:
|
||||
###############################################################################
|
||||
import osintbuddy as ob
|
||||
from osintbuddy import load_local_plugins
|
||||
import osintbuddy
|
||||
from app import crud, schemas, models
|
||||
from app.db.session import SessionLocal
|
||||
db = SessionLocal()
|
||||
entities = crud.entities.get_many(db, skip=0, limit=100)
|
||||
load_local_plugins(entities, 'plugins')
|
||||
""", color="green"))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM python:3.11.7-slim-bullseye
|
||||
FROM python:3.11.4-slim-bullseye
|
||||
LABEL maintainer="jerlendds <support@forum.osintbuddy.com>"
|
||||
|
||||
WORKDIR /app/
|
||||
|
||||
@@ -28,8 +28,7 @@ watchfiles==0.19.0
|
||||
|
||||
gremlinpy @ git+https://github.com/jerlendds/gremlinpy.git@eaba7dca12ad0156eb0d6d8ba2eb5751551c6a6d
|
||||
|
||||
warcio==1.7.4
|
||||
sqids==0.4.0
|
||||
sentry-sdk==1.39.1
|
||||
sentry-sdk==1.33.1
|
||||
boto3-stubs==1.28.75
|
||||
|
||||
|
||||
1
backend/plugins/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
*.py
|
||||
@@ -28,6 +28,5 @@ watchfiles==0.19.0
|
||||
|
||||
gremlinpy @ git+https://github.com/jerlendds/gremlinpy.git@eaba7dca12ad0156eb0d6d8ba2eb5751551c6a6d
|
||||
|
||||
warcio==1.7.4
|
||||
sqids==0.4.0
|
||||
sentry-sdk==1.39.1
|
||||
sentry-sdk==1.33.1
|
||||
@@ -3,6 +3,9 @@
|
||||
"version": "0.3.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@blocksuite/blocks": "^0.10.0",
|
||||
"@blocksuite/editor": "^0.0.0-20231129200023-14f7d22b-nightly",
|
||||
"@blocksuite/store": "^0.10.0",
|
||||
"@codemirror/lang-python": "^6.1.3",
|
||||
"@headlessui/react": "^1.7.7",
|
||||
"@heroicons/react": "^2.0.13",
|
||||
@@ -10,7 +13,6 @@
|
||||
"@reactour/tour": "^3.6.1",
|
||||
"@reduxjs/toolkit": "^1.9.1",
|
||||
"@tabler/icons-react": "^2.42.0",
|
||||
"@tarii/3d-earth": "^1.0.3",
|
||||
"@uiw/codemirror-theme-aura": "^4.21.20",
|
||||
"@uiw/react-codemirror": "^4.21.20",
|
||||
"casdoor-js-sdk": "^0.11.0",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { emptyApi as api } from "./baseApi";
|
||||
const injectedRtkApi = api.injectEndpoints({
|
||||
endpoints: (build) => ({
|
||||
refreshEntityPlugins: build.query<
|
||||
RefreshEntityPluginsApiResponse,
|
||||
RefreshEntityPluginsApiArg
|
||||
refreshPlugins: build.query<
|
||||
RefreshPluginsApiResponse,
|
||||
RefreshPluginsApiArg
|
||||
>({
|
||||
query: (queryArg) => ({
|
||||
url: `/api/v1/node/refresh`,
|
||||
@@ -125,7 +125,7 @@ const injectedRtkApi = api.injectEndpoints({
|
||||
query: (queryArg) => ({
|
||||
url: `/api/v1/entity/${queryArg.hid}`,
|
||||
method: "PUT",
|
||||
body: queryArg.entityUpdate,
|
||||
body: queryArg.entityBase,
|
||||
}),
|
||||
}),
|
||||
deleteEntity: build.mutation<DeleteEntityApiResponse, DeleteEntityApiArg>({
|
||||
@@ -176,9 +176,9 @@ const injectedRtkApi = api.injectEndpoints({
|
||||
overrideExisting: false,
|
||||
});
|
||||
export { injectedRtkApi as api };
|
||||
export type RefreshEntityPluginsApiResponse =
|
||||
export type RefreshPluginsApiResponse =
|
||||
/** status 200 Successful Response */ any;
|
||||
export type RefreshEntityPluginsApiArg = {
|
||||
export type RefreshPluginsApiArg = {
|
||||
hid: string;
|
||||
};
|
||||
export type CreateEntityOnDropApiResponse =
|
||||
@@ -200,7 +200,7 @@ export type PostSignoutApiResponse =
|
||||
/** status 200 Successful Response */ Status;
|
||||
export type PostSignoutApiArg = void;
|
||||
export type GetAccountApiResponse = /** status 200 Successful Response */
|
||||
| UserInDbBase
|
||||
| User
|
||||
| HttpError;
|
||||
export type GetAccountApiArg = void;
|
||||
export type GetGraphApiResponse = /** status 200 Successful Response */ Graph;
|
||||
@@ -264,7 +264,7 @@ export type UpdateEntityByIdApiResponse =
|
||||
/** status 200 Successful Response */ any;
|
||||
export type UpdateEntityByIdApiArg = {
|
||||
hid: string;
|
||||
entityUpdate: EntityUpdate;
|
||||
entityBase: EntityBase;
|
||||
};
|
||||
export type DeleteEntityApiResponse = /** status 200 Successful Response */ any;
|
||||
export type DeleteEntityApiArg = {
|
||||
@@ -313,7 +313,7 @@ export type Status = {
|
||||
export type HttpError = {
|
||||
detail: string;
|
||||
};
|
||||
export type UserInDbBase = {
|
||||
export type User = {
|
||||
name: string;
|
||||
username?: string | null;
|
||||
email?: string | null;
|
||||
@@ -325,7 +325,7 @@ export type UserInDbBase = {
|
||||
is_admin: boolean;
|
||||
created_time: string;
|
||||
updated_time: string;
|
||||
cid: string;
|
||||
uuid: string;
|
||||
};
|
||||
export type Graph = {
|
||||
label: string;
|
||||
@@ -381,11 +381,11 @@ export type PostEntityCreate = {
|
||||
author: string;
|
||||
description: string;
|
||||
};
|
||||
export type EntityUpdate = {
|
||||
label?: string | null;
|
||||
author?: string | null;
|
||||
description?: string | null;
|
||||
source: string | null;
|
||||
export type EntityBase = {
|
||||
label?: string;
|
||||
author?: string;
|
||||
description?: string;
|
||||
source?: string;
|
||||
is_favorite?: boolean;
|
||||
};
|
||||
export type ScanMachineCreate = {
|
||||
@@ -393,7 +393,7 @@ export type ScanMachineCreate = {
|
||||
description: string;
|
||||
};
|
||||
export const {
|
||||
useRefreshEntityPluginsQuery,
|
||||
useRefreshPluginsQuery,
|
||||
useCreateEntityOnDropMutation,
|
||||
useGetCasdoorConfigQuery,
|
||||
usePostSigninMutation,
|
||||
|
||||
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 19 KiB |
@@ -1,254 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="69.776581mm"
|
||||
viewBox="0 0 218.55358 69.776582"
|
||||
width="218.55359mm"
|
||||
version="1.1"
|
||||
id="svg30"
|
||||
sodipodi:docname="OSINTBuddy.svg"
|
||||
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs30" />
|
||||
<sodipodi:namedview
|
||||
id="namedview30"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="0.93338092"
|
||||
inkscape:cx="413.01466"
|
||||
inkscape:cy="131.779"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1024"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg30" />
|
||||
<linearGradient
|
||||
id="a">
|
||||
<stop
|
||||
offset="0"
|
||||
stop-color="#b0bccf"
|
||||
id="stop1" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#b0bccf"
|
||||
stop-opacity="0"
|
||||
id="stop2" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="b"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="230.540335"
|
||||
x2="369.067145"
|
||||
xlink:href="#a"
|
||||
y1="409.54838"
|
||||
y2="409.54838" />
|
||||
<linearGradient
|
||||
id="c"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="248.126665"
|
||||
x2="386.653475"
|
||||
xlink:href="#a"
|
||||
y1="367.26637"
|
||||
y2="367.26637" />
|
||||
<path
|
||||
d="m155.10919 269.94146h51.14959v39.91076c0 33.58458-51.84829 32.48924-51.84829 0z"
|
||||
fill="none"
|
||||
stroke="#b0bccf"
|
||||
stroke-width="8.39786"
|
||||
id="path2" />
|
||||
<g
|
||||
fill="#b0bccf"
|
||||
transform="translate(150.21155 265.74253)"
|
||||
id="g17">
|
||||
<g
|
||||
transform="matrix(2.000607 1.1550509 -1.1550509 2.000607 -114.54424 -361.12059)"
|
||||
id="g6">
|
||||
<rect
|
||||
height="6.677884"
|
||||
ry=".917437"
|
||||
width="1.109652"
|
||||
x="133.26457"
|
||||
y="113.53527"
|
||||
id="rect2" />
|
||||
<rect
|
||||
height="6.677884"
|
||||
ry=".917437"
|
||||
width="1.109652"
|
||||
x="145.20386"
|
||||
y="113.53527"
|
||||
id="rect3" />
|
||||
<rect
|
||||
height="14.584357"
|
||||
ry="1.550303"
|
||||
width="1.621357"
|
||||
x="135.81224"
|
||||
y="109.58203"
|
||||
id="rect4" />
|
||||
<rect
|
||||
height="14.584357"
|
||||
ry="1.550303"
|
||||
width="1.621357"
|
||||
x="142.14449"
|
||||
y="109.58203"
|
||||
id="rect5" />
|
||||
<rect
|
||||
height="20.411003"
|
||||
ry="2.16967"
|
||||
width="1.834875"
|
||||
x="138.87161"
|
||||
y="106.66871"
|
||||
id="rect6" />
|
||||
</g>
|
||||
<g
|
||||
stroke-width=".715877"
|
||||
id="g16">
|
||||
<g
|
||||
transform="matrix(1.3467588 0 0 1.3467588 -106.93441 30.218159)"
|
||||
id="g10">
|
||||
<path
|
||||
d="m149.74457.19113632q-2.17627 0-4.03756-.801776-1.83265-.83042902-3.20713-2.23354302-1.34584-1.431739-2.11899-3.37894-.74452-1.947173-.74452-4.2093413 0-2.290823.74452-4.180744.77315-1.889893 2.11899-3.207128 1.37448-1.345835 3.20713-2.061706 1.86129-.744523 4.03756-.744523h5.0684q2.26217 0 4.12346.744523 1.88989.744496 3.23576 2.090358 1.34583 1.345834 2.09036 3.235755.74449 1.861296.74449 4.123465 0 2.2621683-.77312 4.2093413-.74452 1.947201-2.11901 3.37894-1.34584 1.403114-3.20713 2.23354302-1.86127.801776-4.09481.801776zm5.0684-3.63665702q1.43176 0 2.63441-.515434 1.20268-.515432 2.06174-1.403113.88768-.916334 1.37448-2.176263.48681-1.259957.48681-2.7203223 0-1.460394-.48681-2.691697-.4868-1.259956-1.37448-2.147638-.85906-.887681-2.06174-1.374488-1.20265-.515432-2.63441-.515432h-5.0684q-1.40314 0-2.60579.515432-1.17406.486807-2.03311 1.374488-.85906.887682-1.34584 2.118984-.4868 1.231304-.4868 2.720351 0 1.4603653.4868 2.7203223.48678 1.259929 1.34584 2.176263.85905.887681 2.03311 1.403113 1.20265.515434 2.60579.515434z"
|
||||
id="path6" />
|
||||
<path
|
||||
d="m167.95643.19113632v-3.63665702h15.60612q1.46039 0 2.23351-.744497.80181-.744522.80181-1.975826 0-1.345861-.80181-2.004452-.77312-.687243-2.23351-.687243h-9.56411q-1.4604 0-2.66307-.429529-1.17405-.458153-2.00445-1.2313023-.80178-.801803-1.25996-1.889921-.42953-1.088145-.42953-2.376728 0-1.259929.4009-2.319421.42953-1.088145 1.23131-1.861295.83042-.773149 2.0331-1.202676 1.20265-.458154 2.74895-.458154h14.9475v3.636657h-14.9475q-1.25993 0-1.94717.687243-.68725.658618-.68725 1.861269 0 1.202675.68725 1.861294.71587.658591 1.91854.658591h9.53549q3.17847 0 4.81068 1.489047 1.66083 1.4890193 1.66083 4.5243373 0 1.317209-.40088 2.433981-.4009 1.11677-1.20267 1.947173-.80178.80180302-2.03311 1.25995502-1.20265.458154-2.83485.458154z"
|
||||
id="path7" />
|
||||
<path
|
||||
d="m193.35572.19113632v-20.81770132h3.63666v20.81770132z"
|
||||
id="path8" />
|
||||
<path
|
||||
d="m204.17973-15.042736v15.23387232h-3.46485v-18.89915432q0-1.002213.48678-1.574925.51544-.601338 1.37449-.601338.4009 0 .77315.171811.37225.171811.77315.572712l14.68985 14.5752533v-15.2338713h3.46487v19.0709653q0 1.03086602-.51544 1.60357902-.48678.572685-1.28859.572685-.88768 0-1.66084-.77315z"
|
||||
id="path9" />
|
||||
<path
|
||||
d="m232.47127.19113632v-17.18104432h-7.9033v-3.636657h19.44324v3.636657h-7.90327v17.18104432z"
|
||||
id="path10" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(1.3467588 0 0 1.3467588 -108.04477 30.218159)"
|
||||
id="g15">
|
||||
<path
|
||||
d="m140.46084 26.68477v-20.817701h15.23379q1.51767 0 2.74897.3436219 1.25993.343622 2.14764 1.030866.88768.6585907 1.37448 1.6894568.48678 1.0022394.48678 2.3194473 0 1.002214-.22917 1.746736-.22917.744497-.62999 1.288583-.40087.515433-.94496.887682-.51542.343621-1.08811.515432 1.43173.458153 2.34808 1.660831.91629 1.202676.91629 3.035319 0 1.431766-.48676 2.60579-.45817 1.145398-1.34588 1.975828-.85904.830401-2.11897 1.288581-1.23131.429527-2.74898.429527zm6.27104-9.048703v-3.063945h8.9914q1.54627 0 2.29079-.572711.74454-.572685.74454-1.947174 0-.830428-.31501-1.317208-.31499-.515432-.91633-.7731493-.57269-.2863435-1.40311-.3722476-.80178-.086037-1.80401-.086037h-10.22267v13.5730129h10.53765q.9736 0 1.80402-.143134.8304-.143133 1.43174-.458151.60135-.343624.91633-.859057.34363-.515433.34363-1.288581 0-1.288556-.80177-1.975801-.80181-.715897-2.34809-.715897z"
|
||||
id="path11" />
|
||||
<path
|
||||
d="m184.61602 5.867069h3.55074v11.62584q0 2.405326-.62994 4.209343-.62997 1.775389-1.97584 2.949412-1.34584 1.174052-3.49348 1.746737-2.11897.572712-5.15429.572712-3.0067 0-5.15433-.572712-2.11898-.572685-3.46485-1.746737-1.34583-1.174023-1.97582-2.949412-.62997-1.804017-.62997-4.209343v-11.62584h3.75119v11.62584q0 1.574925.37227 2.691697.40087 1.116772 1.25993 1.832641.88768.715871 2.34807 1.059492 1.4604.314996 3.60804.314996 2.14763 0 3.608-.314996 1.4604-.343621 2.31946-1.059492.88767-.715869 1.25994-1.832641.40088-1.116772.40088-2.691697z"
|
||||
id="path12" />
|
||||
<path
|
||||
d="m191.45981 26.68477v-20.817701h12.05537q2.20488 0 4.06618.7444961 1.86126.7445236 3.20713 2.0903583 1.34584 1.3458616 2.09036 3.2357826.77315 1.861267.77315 4.123437 0 2.262169-.77315 4.209369-.74452 1.947172-2.09036 3.37894-1.34587 1.403114-3.20713 2.233516-1.8613.801802-4.06618.801802zm12.05537-3.636657q1.40312 0 2.57716-.515433 1.20267-.515433 2.06173-1.403113.88768-.916334 1.37446-2.176263.48681-1.259957.48681-2.720351 0-1.460393-.48681-2.691697-.48678-1.259929-1.37446-2.147636-.85906-.887681-2.06173-1.374461-1.17404-.5154326-2.57716-.5154326h-8.44734v13.5443866z"
|
||||
id="path13" />
|
||||
<path
|
||||
d="m216.63004 26.68477v-20.817701h12.05537q2.20489 0 4.06618.7444961 1.86128.7445236 3.20713 2.0903583 1.34582 1.3458616 2.09036 3.2357826.77314 1.861267.77314 4.123437 0 2.262169-.77314 4.209369-.74454 1.947172-2.09036 3.37894-1.34585 1.403114-3.20713 2.233516-1.86129.801802-4.06618.801802zm12.05537-3.636657q1.4031 0 2.57712-.515433 1.2027-.515433 2.06175-1.403113.88767-.916334 1.37448-2.176263.48681-1.259957.48681-2.720351 0-1.460393-.48681-2.691697-.48681-1.259929-1.37448-2.147636-.85905-.887681-2.06175-1.374461-1.17402-.5154326-2.57712-.5154326h-8.44737v13.5443866z"
|
||||
id="path14" />
|
||||
<path
|
||||
d="m248.41493 26.68477v-7.903279l-11.05315-12.914422h4.89661l8.10371 9.592735 8.13238-9.592735h4.63887l-11.08179 12.914422v7.903279z"
|
||||
id="path15" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<path
|
||||
d="m216.25711 396.16259c1.19733.32192 1.97921 1.53061 1.76391 2.72691-.0967.53749-.31684.94222-.73572 1.35272l-.36157.35439.60185 2.36734c.33104 1.30207.60189 2.38306.60189 2.40228 0 .0185.12077.0352.26843.0358 1.09307.005 2.31469.79757 2.8096 1.82649.35373.73545.44281 1.40894.28541 2.15841-.23476 1.11741-1.09438 2.0875-2.18548 2.46594-.74754.25916-1.80496.19151-2.50205-.16061l-.27799-.14024-.76785.80922c-.42237.44506-1.66381 1.74812-2.75879 2.89567l-1.99092 2.08648.0877.16959c.1529.29559.22734.80689.18379 1.26276-.10533 1.10229-.91509 1.9393-2.05982 2.129-.67836.11247-1.42364-.12265-1.94988-.61482-1.43298-1.3404-.80486-3.67401 1.10972-4.12289.43849-.10292.81212-.0845 1.27881.0628l.30487.0961.67993-.69922c.73073-.75151 3.74385-3.91076 4.41561-4.62975l.40828-.43701-.19583-.38861c-.1183-.23474-.23351-.58734-.29128-.8906-.16185-.8494.0247-1.69565.53722-2.45199.24834-.36555.64624-.74515.98441-.93885.11644-.0667.2122-.14671.21282-.17761.00092-.0311-.26935-1.12825-.60047-2.43816l-.60186-2.38161-.28294-.0333c-.96418-.11429-1.81011-.98551-1.9728-2.03204-.2607-1.67638 1.35142-3.10758 3.0007-2.66415zm-1.23618 1.1525c-.42796.2184-.69165.66813-.6961 1.18726-.009 1.1681 1.41302 1.7423 2.25596.91029.69023-.68131.41286-1.8382-.52408-2.18585-.27398-.10154-.74144-.0617-1.03578.0883zm2.49362 9.67176c-.39528.17483-.70527.46369-.91726.85472-.1807.33321-.19398.39342-.19306.86691.001.60302.12881.92389.52351 1.31892.36092.36114.78416.53611 1.31095.54197.3745.005.47454-.0185.80535-.181.45661-.22455.78479-.57647.95269-1.0214.16742-.44376.13468-1.05572-.0803-1.49514-.42629-.87126-1.52094-1.27461-2.40181-.88493zm-8.98863 10.26085c-.58756.17452-.93798.64869-.93829 1.26964-.00019.39706.0939.64162.3558.92386.6093.65689 1.72076.49351 2.12212-.31192.39833-.79936-.0571-1.74029-.92721-1.91585-.2956-.0596-.29622-.0596-.61242.0342zm-.91658-16.19663c.54336.11091 1.04535.59268 1.19529 1.14691.0945.34981.0321.82901-.15228 1.16802-.28449.52263-.77487.8081-1.3796.80306-.61906-.006-1.09481-.28385-1.38803-.81317-.14548-.26285-.1668-.35817-.1668-.75325 0-.39672.0216-.48994.16958-.75888.33861-.61206 1.03551-.93293 1.72178-.79269zm19.26734 1.91417c.40507.18968.62428.39557.80352.75428.61165 1.224-.51975 2.60369-1.8242 2.22448-1.12115-.32594-1.53008-1.67829-.7689-2.543.32544-.36975.60637-.50142 1.12492-.5273.33687-.0154.47346.003.66466.0917zm-15.95747.93349c.90716.52398 1.05221.71182.85069 1.10151-.0985.19088-.36855.31468-.57368.26317-.20912-.0526-1.44332-.75804-1.5632-.89364-.14301-.16184-.1288-.51107.0278-.69175.24865-.28633.43756-.25327 1.2579.22056zm12.92266 1.55461c.21035.21035.22703.51401.038.70032-.22672.22363-1.48218.887-1.67878.887-.39015 0-.65144-.51066-.42598-.83254.12973-.18533 1.50426-.92004 1.72104-.92004.12416 0 .23196.0516.34567.16526zm-9.81811.22239c.85277.48906.93771.56403.93771.82743 0 .11333-.0278.25731-.0611.32001-.0883.16496-.39802.28078-.59651.22302-.17823-.0519-1.41738-.75874-1.53747-.87698-.0414-.0404-.089-.17792-.10595-.30487-.0278-.20326-.009-.25205.14115-.40353.26534-.26562.43652-.23537 1.22207.21528zm6.74571 6.69353c.17328.1875.80665 1.37499.80665 1.5127 0 .33734-.33981.61109-.66883.53884-.21931-.0482-.27552-.11831-.65851-.82191-.39978-.73452-.43725-.92751-.22826-1.17585.12726-.15104.19181-.181.39182-.181.17575 0 .27089.034.35716.12726zm1.40847 2.78008c.14363.0636.24927.21344.55121.78257.41535.78288.45479.98288.23691 1.20074-.18409.1841-.40105.23135-.60952.1325-.23074-.10932-.91219-1.33277-.9164-1.64515-.006-.3818.38135-.62839.73777-.47072zm2.18267 2.48771c1.15377.41173 1.42086 1.91182.48327 2.71437-.62249.53282-1.5209.48566-2.11776-.11114-.6359-.63589-.63367-1.56802.006-2.20697.4396-.43963 1.07473-.59407 1.62917-.39621z"
|
||||
fill="#b0bccf"
|
||||
stroke-width=".213676"
|
||||
id="path17" />
|
||||
<path
|
||||
d="m137.05192 400.96156h31.22083v31.22083h-31.22083z"
|
||||
fill="none"
|
||||
stroke="#b0bccf"
|
||||
stroke-width="2.427"
|
||||
id="path18" />
|
||||
<text
|
||||
style="font-size:25.4;font-family:Good Times;letter-spacing:-.134937;word-spacing:-.690562;stroke:#b0bccf;stroke-width:1.3;fill:url(#b)"
|
||||
x="229.26233"
|
||||
y="418.78128"
|
||||
id="text21"><tspan
|
||||
fill="url(#b)"
|
||||
stroke-width="1.3"
|
||||
x="229.26233"
|
||||
y="418.78128"
|
||||
id="tspan21"><tspan
|
||||
fill="url(#b)"
|
||||
stroke-width="1.3"
|
||||
id="tspan19">i<tspan
|
||||
fill="url(#b)"
|
||||
stroke-width="1.3"
|
||||
id="tspan18">c</tspan></tspan><tspan
|
||||
fill="url(#b)"
|
||||
stroke="none"
|
||||
stroke-width="1.3"
|
||||
id="tspan20">GRAPH</tspan></tspan></text>
|
||||
<path
|
||||
d="m233.84345 353.88056c1.19732.32192 1.9792 1.53061 1.76391 2.72691-.0967.53749-.31685.94222-.73573 1.35272l-.36158.35439.60186 2.36734c.33103 1.30207.60189 2.38306.60189 2.40228 0 .0185.12077.0352.26842.0358 1.09307.005 2.31471.79757 2.8096 1.82649.35373.73545.44282 1.40893.28541 2.1584-.23475 1.11741-1.09437 2.0875-2.18547 2.46594-.74753.25916-1.80496.19151-2.50205-.16061l-.278-.14024-.76785.80922c-.42237.44506-1.6638 1.74812-2.75879 2.89567l-1.99091 2.08648.0877.16959c.1529.29559.22734.80689.18379 1.26276-.10534 1.10229-.9151 1.9393-2.05982 2.129-.67837.11247-1.42364-.12265-1.94989-.61482-1.43297-1.3404-.80485-3.67401 1.10973-4.12289.43849-.10292.81211-.0845 1.27881.0628l.30486.0961.67995-.69922c.73072-.75151 3.74385-3.91076 4.41561-4.62975l.40828-.43701-.19583-.38861c-.11831-.23474-.23352-.58734-.29128-.8906-.16185-.8494.0247-1.69564.53721-2.45198.24834-.36555.64625-.74515.98441-.93885.11645-.0667.2122-.14671.21282-.17761.00092-.0311-.26934-1.12825-.60047-2.43816l-.60186-2.38161-.28293-.0333c-.96418-.11429-1.81012-.98551-1.97281-2.03204-.2607-1.67638 1.35143-3.10758 3.00071-2.66415zm-1.23618 1.1525c-.42796.2184-.69165.66813-.6961 1.18726-.009 1.1681 1.41301 1.7423 2.25596.91029.69023-.68131.41285-1.8382-.52408-2.18585-.27398-.10154-.74145-.0617-1.03578.0883zm2.49361 9.67176c-.39528.17483-.70527.46369-.91726.85472-.1807.3332-.19398.39341-.19305.8669.001.60302.1288.92389.5235 1.31892.36093.36114.78416.53611 1.31096.54197.37449.005.47454-.0185.80535-.181.45659-.22455.78478-.57647.95269-1.0214.16741-.44376.13467-1.05572-.0803-1.49513-.42629-.87126-1.52094-1.27461-2.40181-.88493zm-8.98863 10.26084c-.58755.17452-.93798.64869-.93829 1.26964-.00018.39706.0939.64162.3558.92386.60931.65689 1.72076.49351 2.12212-.31192.39834-.79936-.0571-1.74029-.9272-1.91585-.2956-.0596-.29622-.0596-.61243.0342zm-.91658-16.19662c.54336.11091 1.04536.59268 1.19529 1.14691.0945.34981.0321.82901-.15228 1.16802-.28448.52263-.77486.8081-1.3796.80306-.61906-.006-1.0948-.28385-1.38802-.81317-.14549-.26285-.1668-.35817-.1668-.75325 0-.39672.0216-.48994.16958-.75888.33859-.61206 1.03549-.93293 1.72177-.79269zm19.26734 1.91417c.40507.18968.62428.39557.80353.75428.61165 1.224-.51976 2.60369-1.8242 2.22448-1.12116-.32594-1.53009-1.67829-.76891-2.543.32544-.36975.60637-.50142 1.12493-.5273.33686-.0154.47345.003.66465.0917zm-15.95746.93349c.90715.52398 1.05221.71182.85069 1.10151-.0985.19088-.36856.31468-.57369.26317-.20911-.0526-1.44332-.75804-1.56319-.89364-.14302-.16184-.12881-.51107.0278-.69175.24865-.28633.43756-.25327 1.25789.22056zm12.92265 1.55461c.21035.21035.22703.51401.038.70032-.22672.22363-1.48217.887-1.67878.887-.39015 0-.65143-.51066-.42598-.83254.12973-.18533 1.50426-.92004 1.72104-.92004.12417 0 .23197.0516.34567.16526zm-9.8181.22239c.85276.48906.93771.56403.93771.82743 0 .11333-.0278.25731-.0611.32001-.0883.16496-.39803.28078-.59652.22302-.17822-.0519-1.41737-.75874-1.53746-.87698-.0414-.0404-.0889-.17792-.10596-.30487-.0278-.20326-.009-.25205.14116-.40353.26533-.26562.43651-.23537 1.22207.21528zm6.7457 6.69352c.17329.1875.80665 1.37499.80665 1.5127 0 .33734-.3398.61109-.66883.53884-.2193-.0482-.27552-.11831-.6585-.82191-.39979-.73452-.43726-.92751-.22827-1.17585.12726-.15104.19182-.181.39182-.181.17575 0 .27089.034.35716.12726zm1.40848 2.78008c.14363.0636.24927.21344.5512.78257.41536.78288.4548.98288.23691 1.20074-.18409.1841-.40105.23135-.60952.1325-.23073-.10932-.91219-1.33277-.91639-1.64515-.006-.3818.38135-.62839.73777-.47072zm2.18266 2.48771c1.15377.41173 1.42086 1.91182.48328 2.71437-.62249.53282-1.52091.48566-2.11777-.11114-.63589-.63589-.63367-1.56802.006-2.20697.4396-.43963 1.07473-.59407 1.62917-.39621z"
|
||||
fill="#b0bccf"
|
||||
stroke-width=".213676"
|
||||
id="path21" />
|
||||
<text
|
||||
style="font-size:25.4;font-family:Good Times;letter-spacing:-.134937;word-spacing:-.690562;stroke:#b0bccf;stroke-width:1.3;fill:url(#c)"
|
||||
x="246.84866"
|
||||
y="376.49924"
|
||||
id="text25"><tspan
|
||||
fill="url(#c)"
|
||||
stroke-width="1.3"
|
||||
x="246.84866"
|
||||
y="376.49924"
|
||||
id="tspan25"><tspan
|
||||
fill="url(#c)"
|
||||
stroke-width="1.3"
|
||||
id="tspan23">i<tspan
|
||||
fill="url(#c)"
|
||||
stroke-width="1.3"
|
||||
id="tspan22">c</tspan></tspan><tspan
|
||||
fill="url(#c)"
|
||||
stroke="none"
|
||||
stroke-width="1.3"
|
||||
id="tspan24">GRAPH</tspan></tspan></text>
|
||||
<path
|
||||
d="m160.08378-106.54659c-1.65246 0-3.18532-.3044-4.59861-.91319-1.39153-.63054-2.60913-1.47851-3.65277-2.54391-1.02193-1.08714-1.8264-2.36996-2.41346-3.84847-.56531-1.47851-.84797-3.0766-.84797-4.79428 0-1.73943.28266-3.32665.84797-4.76167.58706-1.43502 1.39153-2.65262 2.41346-3.65279 1.04364-1.02191 2.26124-1.80465 3.65277-2.34822 1.41329-.56531 2.94615-.84797 4.59861-.84797h12.84998v4.142h-12.84998c-1.06539 0-2.0547.19569-2.96789.58706-.89146.36963-1.66333.89145-2.31561 1.56548-.65227.67402-1.16323 1.47851-1.53287 2.41345-.36962.93494-.55443 1.96772-.55443 3.09835 0 1.10888.18481 2.14166.55443 3.09834.36964.95668.8806 1.78291 1.53287 2.47868.65228.67403 1.42415 1.20673 2.31561 1.59809.91319.39138 1.9025.58706 2.96789.58706h10.143v-6.16408h-10.69745v-3.48971h14.87206v13.79579zm39.97478 0-6.001-6.58806h-8.87106v-3.65279h9.8821c1.50025 0 2.63087-.40224 3.39187-1.20671.761-.82624 1.14149-2.01122 1.14149-3.55495 0-1.54374-.40223-2.68524-1.2067-3.42449-.78274-.761-1.89162-1.1415-3.32665-1.1415h-12.91521v19.5685h-4.10939v-23.7105h17.0246c1.34805 0 2.55476.20656 3.62018.61966 1.06539.39137 1.96772.96756 2.70697 1.72856.73925.73925 1.30457 1.64158 1.69592 2.70697.39137 1.06541.58707 2.26126.58707 3.58756 0 1.9786-.43486 3.64192-1.30457 4.98998-.84798 1.3263-2.04382 2.28298-3.58755 2.87004l7.1425 7.20773zm32.57136 0-3.55494-5.9684h-15.54795l1.95684-3.35925h11.60163l-5.28349-8.87106-10.76267 18.19871h-4.72905l13.63271-22.69947c.23918-.41311.52184-.73925.84798-.97842s.71751-.35876 1.1741-.35876c.45661 0 .8371.11959 1.14151.35876.32614.23917.60879.56531.84796.97842l13.66533 22.69947zm14.50312-6.58806v-4.01154h9.52333c1.45677 0 2.5874-.41312 3.39188-1.23934.82622-.82622 1.23934-1.91336 1.23934-3.26141 0-1.3698-.41311-2.45694-1.23934-3.26142-.80448-.80449-1.93511-1.20673-3.39188-1.20673h-12.55646v19.56851h-4.10939v-23.71051h16.66585c1.3698 0 2.5874.20656 3.6528.61967 1.08713.39137 2.00033.95669 2.73957 1.69594.73927.71751 1.30458 1.5981 1.69595 2.64175s.58706 2.21776.58706 3.52233c0 1.28283-.19569 2.45693-.58706 3.52233-.39137 1.04365-.95668 1.94598-1.69595 2.70698-.73924.76099-1.65244 1.35892-2.73957 1.79377-1.0654.41312-2.283.61967-3.6528.61967zm40.04003 6.6859v-10.30607h-16.58472v-3.6854h16.58472v-9.81687h4.142v23.80835zm-19.07929 0v-23.80834h4.142v23.80834z"
|
||||
fill="#b0bccf"
|
||||
stroke-width=".571284"
|
||||
id="path25" />
|
||||
<g
|
||||
fill="#b0bccf"
|
||||
transform="translate(-4.697965 -3.47097)"
|
||||
id="g30">
|
||||
<path
|
||||
d="m34.289873 3.6988367c3.361842.9038753 5.557206 4.2976421 4.952696 7.6566013-.271451 1.509171-.889649 2.645567-2.065774 3.798171l-1.015231.995051 1.689893 6.647017c.92947 3.655948 1.689986 6.691143 1.689986 6.745109 0 .05191.339083.09879.75367.100544 3.069134.01417 6.499228 2.239415 7.888795 5.128425.993211 2.064987 1.243333 3.956016.801379 6.060368-.659145 3.137467-3.0728 5.861284-6.136378 6.923862-2.098914.727665-5.067966.537721-7.025256-.450956l-.780543-.393762-2.155975 2.272123c-1.185921 1.249639-4.671613 4.908362-7.746124 8.130455l-5.590066 5.858412.2463.476172c.429297.829965.638321 2.265586.516031 3.545575-.295768 3.09501-2.569405 5.44516-5.783562 5.977797-1.904716.315796-3.997291-.344367-5.4748908-1.726292-4.0234831-3.763564-2.259866-10.315868 3.1159048-11.576229 1.231192-.288984 2.280245-.237267 3.590625.176327l.856002.269833 1.909158-1.963278c2.051721-2.110081 10.511966-10.980623 12.398129-12.999392l1.146371-1.22704-.549864-1.091124c-.332168-.659121-.655653-1.649145-.817846-2.500637-.454457-2.384943.06938-4.76104 1.508386-6.884683.697291-1.0264 1.814514-2.092234 2.764037-2.636108.326953-.187285.595803-.41193.597548-.498687.0023-.0873-.756272-3.167902-1.686001-6.845858l-1.689893-6.687086-.794443-.09351c-2.707209-.320913-5.082427-2.767113-5.539228-5.705565-.731992-4.7069341 3.794526-8.7254482 8.425377-7.4804036zm-3.470949 3.2359784c-1.201612.6132164-1.942018 1.8759804-1.9545 3.3335879-.02613 3.279784 3.967455 4.892026 6.334271 2.555905 1.938034-1.91298 1.159224-5.1612872-1.471507-6.1374239-.769292-.2851037-2.081828-.1732435-2.908264.247931zm7.001571 27.1563489c-1.109865.490881-1.980271 1.301951-2.575475 2.399875-.507369.935587-.544648 1.104648-.542058 2.434105.0023 1.693162.361653 2.594109 1.469876 3.70327 1.013415 1.014001 2.201761 1.50529 3.68091 1.521733 1.051489.01418 1.332411-.05191 2.261271-.508202 1.282007-.630491 2.203497-1.618617 2.674946-2.867892.47007-1.24598.378132-2.964245-.225524-4.198051-1.196924-2.446319-4.270487-3.578846-6.743794-2.484698zm-25.238248 28.81039c-1.649744.490015-2.6336728 1.821394-2.6345398 3.564889-.000516 1.114856.2636818 1.801541.9990238 2.594015 1.7108 1.84441 4.831532 1.385673 5.958482-.875808 1.118442-2.244443-.160423-4.88639-2.603401-5.379321-.83-.16735-.831723-.16735-1.719565.09597zm-2.573577-45.476866c1.525647.311415 2.935148 1.664123 3.356123 3.2203.265428.982195.09023 2.327685-.427561 3.279562-.798767 1.467439-2.175664 2.268971-3.8736418 2.254827-1.738199-.01688-3.0739845-.796997-3.8972918-2.283211-.408484-.738025-.4683334-1.005668-.4683334-2.114982 0-1.113907.0607-1.375654.4761389-2.130778.9507168-1.718534 2.9074673-2.619481 4.8344011-2.225718zm54.098787 5.374599c1.137345.532589 1.752859 1.110685 2.256138 2.117865 1.717397 3.436752-1.459377 7.310651-5.121991 6.245895-3.147967-.915173-4.296166-4.712301-2.158918-7.140231.913757-1.038179 1.702562-1.407884 3.158551-1.480542.945855-.04324 1.329376.0082 1.86622.25747zm-44.805333 2.621064c2.547126 1.471223 2.954402 1.998645 2.388577 3.092817-.276679.535951-1.034839.883554-1.610812.738928-.587142-.147709-4.052548-2.128424-4.389133-2.509167-.401546-.454412-.361654-1.434978.07804-1.94229.698157-.803958 1.228589-.711129 3.531924.619289zm36.284207 4.365026c.590612.590622.637442 1.443226.106641 1.966347-.636585.627913-4.161652 2.490534-4.713684 2.490534-1.09546 0-1.829091-1.43384-1.196069-2.33761.364266-.520367 4.22367-2.583292 4.832329-2.583292.348633 0 .651316.144883.97057.464021zm-27.567256.62442c2.394402 1.373182 2.632898 1.583682 2.632898 2.323266 0 .3182-.07804.722464-.171696.89851-.248048.463188-1.117575.788383-1.674893.626202-.500432-.145717-3.979701-2.130381-4.316896-2.462385-.116252-.113436-.249747-.499565-.297492-.856003-.07804-.570724-.02601-.707709.396343-1.133033.745-.745809 1.225638-.66088 3.431313.604451zm18.940566 18.794077c.486545.526472 2.264916 3.860693 2.264916 4.247367 0 .947178-.954093 1.715804-1.877924 1.512956-.615774-.135345-.773619-.332191-1.848957-2.307774-1.122521-2.062373-1.227726-2.604257-.640922-3.301536.357316-.424093.538578-.508212 1.100136-.508212.493495 0 .76061.0954 1.002845.357317zm3.954727 7.805904c.40328.178576.699892.599307 1.54767 2.197309 1.16622 2.198164 1.276978 2.759724.665203 3.371428-.516911.516922-1.126084.649582-1.71141.372026-.647858-.306936-2.56126-3.742142-2.573049-4.619239-.01758-1.072023 1.070746-1.764391 2.071505-1.321689zm6.12847 6.984987c3.239552 1.156062 3.989484 5.368012 1.356949 7.621408-1.747831 1.496047-4.270417 1.363632-5.946259-.312057-1.785474-1.785451-1.779227-4.402692.01758-6.196733 1.234322-1.234391 3.017615-1.668024 4.574379-1.112466z"
|
||||
stroke-width=".599959"
|
||||
id="path26" />
|
||||
<g
|
||||
transform="translate(0 -4.284847)"
|
||||
id="g29">
|
||||
<g
|
||||
transform="matrix(1.8224234 0 0 1.8224234 -68.586524 -59.923346)"
|
||||
id="g28">
|
||||
<path
|
||||
d="m108.86906 44.633785c-1.92434 0-3.7096.329148-5.35541.987459-1.62051.633017-3.0382 1.544208-4.253571 2.734257-1.19005 1.164725-2.126862 2.583067-2.810509 4.254205-.658325 1.671128-.987458 3.519147-.987458 5.544763 0 2.000305.329133 3.861107.987458 5.58289.683647 1.721765 1.620459 3.215674 2.810509 4.48169 1.215371 1.240693 2.633061 2.228732 4.253571 2.963011 1.64581.708975 3.43135 1.096336 5.35541 1.063075l5.6693-.09786v-4.823551l-5.6693.09786c-1.2407 0-2.39266-.227954-3.45611-.683722-1.03813-.455759-1.93716-1.075626-2.69677-1.86054-.75961-.810259-1.35447-1.77267-1.78492-2.886761-.43044-1.114098-.64559-2.316635-.64559-3.607971 0-1.316652.21515-2.519843.64559-3.608609.43045-1.088777 1.02531-2.025574 1.78492-2.810509.75961-.784923 1.65864-1.392603 2.69677-1.823048 1.06345-.455769 2.21559-.704533 3.45611-.683088l5.6693.09786v-4.823554zm10.72224.185546v4.823551l4.24214.07307v-4.823551zm4.24214 22.343633-4.24214.07308v4.823551l4.24214-.07308z"
|
||||
fill="#b0bccf"
|
||||
stroke="#b0bccf"
|
||||
stroke-width="1.22963"
|
||||
id="path27" />
|
||||
<path
|
||||
d="m83.39564 72.86192v-29.234034h5.106903v29.234034z"
|
||||
stroke-width=".692748"
|
||||
id="path28" />
|
||||
</g>
|
||||
<path
|
||||
d="m192.22345 72.48702c-3.58109 0-6.90299-.659674-9.96577-1.979-3.01563-1.366462-5.65432-3.204121-7.91602-5.512979-2.21466-2.355972-3.95805-5.136007-5.23028-8.340127-1.2251-3.204122-1.83766-6.667387-1.83766-10.38982 0-3.769567.61256-7.209276 1.83766-10.319148 1.27223-3.109873 3.01562-5.748567 5.23028-7.916064 2.2617-2.21461 4.90039-3.910907 7.91602-5.088893 3.06278-1.2251 6.38468-1.83766 9.96577-1.83766h27.84756v8.976245h-27.84756c-2.30883 0-4.4528.424085-6.4318 1.272234-1.9319.801035-3.60465 1.931885-5.01822 3.392596-1.41354 1.460688-2.52087 3.20412-3.32192 5.230254-.80101 2.026135-1.20152 4.264301-1.20152 6.714522 0 2.403086.40051 4.641252 1.20152 6.7145.80105 2.073247 1.90838 3.863793 3.32192 5.371617 1.41357 1.460709 3.08632 2.615138 5.01822 3.463266 1.979.84817 4.12297 1.272233 6.4318 1.272233h21.98119v-13.358351h-23.18275v-7.562648h32.22966v29.897244z"
|
||||
stroke-width="1.23805"
|
||||
id="path29" />
|
||||
</g>
|
||||
</g>
|
||||
<path
|
||||
d="m48.054683 215.40426h51.14959v39.91076c0 33.58458-51.84829 32.48924-51.84829 0z"
|
||||
fill="none"
|
||||
stroke="#b0bccf"
|
||||
stroke-width="8.39786"
|
||||
id="path30" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 9.2 KiB |
@@ -1 +0,0 @@
|
||||
<svg height="69.776581mm" viewBox="0 0 62.144792 69.776582" width="62.144794mm" xmlns="http://www.w3.org/2000/svg"><path d="m29.591908.22786659c3.361842.90387531 5.557206 4.29764211 4.952696 7.65660131-.271451 1.509171-.889649 2.6455671-2.065774 3.7981711l-1.015231.995051 1.689893 6.647017c.92947 3.655948 1.689986 6.691143 1.689986 6.745109 0 .05191.339083.09879.75367.100544 3.069134.01417 6.499228 2.239415 7.888795 5.128425.993211 2.064987 1.243333 3.956016.801379 6.060368-.659145 3.137467-3.0728 5.861284-6.136378 6.923862-2.098914.727665-5.067966.537721-7.025256-.450956l-.780543-.393762-2.155975 2.272123c-1.185921 1.249639-4.671613 4.908362-7.746124 8.130455l-5.590066 5.858412.2463.476172c.429297.829965.638321 2.265586.516031 3.545575-.295768 3.09501-2.569405 5.44516-5.783562 5.977797-1.904716.315796-3.997291-.344367-5.4748908-1.726292-4.0234831-3.763564-2.259866-10.315868 3.1159048-11.576229 1.231192-.288984 2.280245-.237267 3.590625.176327l.856002.269833 1.909158-1.963278c2.051721-2.110081 10.511966-10.980623 12.398129-12.999392l1.146371-1.22704-.549864-1.091124c-.332168-.659121-.655653-1.649145-.817846-2.500637-.454457-2.384943.06938-4.76104 1.508386-6.884683.697291-1.0264 1.814514-2.092234 2.764037-2.636108.326953-.187285.595803-.41193.597548-.498687.0023-.0873-.756272-3.167902-1.686001-6.845858l-1.689893-6.687086-.794443-.09351c-2.707209-.320913-5.082427-2.767113-5.539228-5.7055651-.731992-4.7069341 3.794526-8.7254482 8.425377-7.48040361zm-3.470949 3.23597841c-1.201612.6132164-1.942018 1.8759804-1.9545 3.3335879-.02613 3.2797841 3.967455 4.8920261 6.334271 2.555905 1.938034-1.91298 1.159224-5.1612872-1.471507-6.1374239-.769292-.2851037-2.081828-.1732435-2.908264.247931zm7.001571 27.156349c-1.109865.490881-1.980271 1.301951-2.575475 2.399875-.507369.935587-.544648 1.104648-.542058 2.434105.0023 1.693162.361653 2.594109 1.469876 3.70327 1.013415 1.014001 2.201761 1.50529 3.68091 1.521733 1.051489.01418 1.332411-.05191 2.261271-.508202 1.282007-.630491 2.203497-1.618617 2.674946-2.867892.47007-1.24598.378132-2.964245-.225524-4.198051-1.196924-2.446319-4.270487-3.578846-6.743794-2.484698zm-25.238248 28.81039c-1.649744.490015-2.6336728 1.821394-2.6345398 3.564889-.000516 1.114856.2636818 1.801541.9990238 2.594015 1.7108 1.84441 4.831532 1.385673 5.958482-.875808 1.118442-2.244443-.160423-4.88639-2.603401-5.379321-.83-.16735-.831723-.16735-1.719565.09597zm-2.573577-45.476866c1.525647.311415 2.935148 1.664123 3.356123 3.2203.265428.982195.09023 2.327685-.427561 3.279562-.798767 1.467439-2.175664 2.268971-3.8736418 2.254827-1.738199-.01688-3.0739845-.796997-3.8972918-2.283211-.408484-.738025-.4683334-1.005668-.4683334-2.114982 0-1.113907.0607-1.375654.4761389-2.130778.9507168-1.718534 2.9074673-2.619481 4.8344011-2.225718zm54.098787 5.374599c1.137345.532589 1.752859 1.110685 2.256138 2.117865 1.717397 3.436752-1.459377 7.310651-5.121991 6.245895-3.147967-.915173-4.296166-4.712301-2.158918-7.140231.913757-1.038179 1.702562-1.407884 3.158551-1.480542.945855-.04324 1.329376.0082 1.86622.25747zm-44.805333 2.621064c2.547126 1.471223 2.954402 1.998645 2.388577 3.092817-.276679.535951-1.034839.883554-1.610812.738928-.587142-.147709-4.052548-2.128424-4.389133-2.509167-.401546-.454412-.361654-1.434978.07804-1.94229.698157-.803958 1.228589-.711129 3.531924.619289zm36.284207 4.365026c.590612.590622.637442 1.443226.106641 1.966347-.636585.627913-4.161652 2.490534-4.713684 2.490534-1.09546 0-1.829091-1.43384-1.196069-2.33761.364266-.520367 4.22367-2.583292 4.832329-2.583292.348633 0 .651316.144883.97057.464021zm-27.567256.62442c2.394402 1.373182 2.632898 1.583682 2.632898 2.323266 0 .3182-.07804.722464-.171696.89851-.248048.463188-1.117575.788383-1.674893.626202-.500432-.145717-3.979701-2.130381-4.316896-2.462385-.116252-.113436-.249747-.499565-.297492-.856003-.07804-.570724-.02601-.707709.396343-1.133033.745-.745809 1.225638-.66088 3.431313.604451zm18.940566 18.794077c.486545.526472 2.264916 3.860693 2.264916 4.247367 0 .947178-.954093 1.715804-1.877924 1.512956-.615774-.135345-.773619-.332191-1.848957-2.307774-1.122521-2.062373-1.227726-2.604257-.640922-3.301536.357316-.424093.538578-.508212 1.100136-.508212.493495 0 .76061.0954 1.002845.357317zm3.954727 7.805904c.40328.178576.699892.599307 1.54767 2.197309 1.16622 2.198164 1.276978 2.759724.665203 3.371428-.516911.516922-1.126084.649582-1.71141.372026-.647858-.306936-2.56126-3.742142-2.573049-4.619239-.01758-1.072023 1.070746-1.764391 2.071505-1.321689zm6.12847 6.984987c3.239552 1.156062 3.989484 5.368012 1.356949 7.621408-1.747831 1.496047-4.270417 1.363632-5.946259-.312057-1.785474-1.785451-1.779227-4.402692.01758-6.196733 1.234322-1.234391 3.017615-1.668024 4.574379-1.112466z" fill="#2b3bff" stroke-width=".599959"/></svg>
|
||||
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 23 KiB |
1
frontend/src/assets/images/logo-sm.svg
Executable file
@@ -0,0 +1 @@
|
||||
<svg height="12.082338mm" viewBox="0 0 42.157818 12.082338" width="42.157818mm" xmlns="http://www.w3.org/2000/svg"><g fill="#f9f9f9" stroke-width=".264583" transform="translate(-81.813094 -121.379916)"><path d="m97.375169 131.26811q-.804331 0-1.492245-.29633-.677331-.30692-1.18533-.8255-.497415-.52917-.783164-1.24883-.275166-.71967-.275166-1.55575 0-.84666.275166-1.54516.285749-.6985.783164-1.18533.507999-.49741 1.18533-.762.687914-.27516 1.492245-.27516h1.873244q.836077 0 1.523997.27516.6985.27517 1.19592.77259.49741.49741.77257 1.19591.27517.68791.27517 1.52399 0 .83608-.28575 1.55575-.27516.71966-.78316 1.24883-.49741.51858-1.18533.8255-.68792.29633-1.513417.29633zm1.873244-1.34408q.529165 0 .973667-.1905.4445-.1905.762-.51858.32808-.33867.508-.80433.17991-.46567.17991-1.00542 0-.53974-.17991-.99483-.17992-.46566-.508-.79374-.3175-.32809-.762-.508-.444502-.1905-.973667-.1905h-1.873244q-.518581 0-.96308.1905-.433915.17991-.751414.508-.317499.32808-.497415.78316-.179917.45508-.179917 1.00541 0 .53975.179917 1.00542.179916.46566.497415.80433.317499.32808.751414.51858.444499.1905.96308.1905z"/><path d="m104.10613 131.26811v-1.34408h5.7679q.53975 0 .8255-.27517.29633-.27516.29633-.73024 0-.49742-.29633-.74084-.28575-.25399-.8255-.25399h-3.53482q-.53975 0-.98425-.15875-.43391-.16934-.74083-.45509-.29633-.29633-.46566-.69849-.15875-.40217-.15875-.87842 0-.46566.14816-.85724.15875-.40217.45508-.68792.30692-.28575.75142-.4445.4445-.16933 1.016-.16933h5.52448v1.34408h-5.52448q-.46567 0-.71967.254-.254.24341-.254.68791 0 .4445.254.68792.26458.24341.70908.24341h3.52424q1.17475 0 1.778.55033.61383.55034.61383 1.67217 0 .48683-.14817.89958-.14817.41274-.4445.71966-.29633.29633-.75141.46567-.4445.16933-1.04775.16933z"/><path d="m113.49351 131.26811v-7.69406h1.34408v7.69406z"/><path d="m115.7054 131.26811v-7.69406h5.63031q.56092 0 1.016.127.46567.127.79375.381.32808.24341.508.62441.17991.37042.17991.85725 0 .37041-.0847.64558-.0847.27517-.23284.47625-.14816.1905-.34925.32808-.1905.127-.40216.1905.52916.16933.86783.61383.33866.4445.33866 1.12183 0 .52917-.17991.96308-.16933.42333-.49742.73025-.3175.30692-.78316.47625-.45508.15875-1.016.15875zm2.31774-3.34432v-1.13242h3.32316q.57149 0 .84666-.21166.27517-.21167.27517-.71967 0-.30691-.11642-.48683-.11642-.1905-.33867-.28575-.21166-.10583-.51858-.13758-.29633-.0318-.66675-.0318h-3.77823v5.01648h3.89465q.35983 0 .66675-.0529.30691-.0529.52916-.16934.22225-.127.33867-.3175.127-.1905.127-.47624 0-.47625-.29633-.73025-.29634-.26458-.86783-.26458z"/></g><g transform="translate(-124.673515 -121.39826)"><path d="m125.48315 122.09239h8.45564v6.59773c0 5.55194-8.57114 5.37087-8.57114 0z" fill="none" stroke="#f9f9f9" stroke-width="1.38827"/><g fill="#f9f9f9" transform="matrix(.33191332 .19163024 -.19163024 .33191332 105.65201 61.486077)"><rect height="6.677884" ry=".917437" width="1.109652" x="133.26457" y="113.53527"/><rect height="6.677884" ry=".917437" width="1.109652" x="145.20386" y="113.53527"/><rect height="14.584357" ry="1.550303" width="1.621357" x="135.81224" y="109.58203"/><rect height="14.584357" ry="1.550303" width="1.621357" x="142.14449" y="109.58203"/><rect height="20.411003" ry="2.16967" width="1.834875" x="138.87161" y="106.66871"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
1
frontend/src/assets/images/logo-watermark.svg
Executable file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
frontend/src/assets/images/logo.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
2
frontend/src/assets/images/logo.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 5.9 KiB |
@@ -1,29 +1,17 @@
|
||||
.btn-primary {
|
||||
@apply ring-1 ring-primary-400/95 whitespace-nowrap focus:ring-primary-300/90 hover:ring-primary-300/80 text-left text-sm font-semibold hover:text-slate-300/70 text-slate-300/80 scale-100 hover:scale-[99%] flex items-center border hover:ring-inset border-primary-400/95 focus:border-primary-300/90 hover:border-primary-300/80 py-2 px-5 font-display rounded-md hover:shadow justify-center transition-all duration-75 ease-linear hover:stroke-primary-300/80 stroke-primary-400/95 ;
|
||||
@apply ring-1 ring-primary-300/95 whitespace-nowrap hover:ring-primary-200 text-left text-sm font-semibold text-primary-300 hover:text-primary-100/90 flex items-center border hover:ring-inset border-primary-300/95 hover:border-primary-200 py-2 px-3 rounded-md hover:shadow justify-center transition-colors duration-100 ease-in-out;
|
||||
}
|
||||
|
||||
.btn-primary-solid {
|
||||
@apply ring-1 ring-primary-400/95 hover:ring-primary-300/80 whitespace-nowrap text-left text-sm font-semibold hover:text-slate-200/95 flex scale-100 hover:scale-[99%] items-center hover:ring-inset bg-primary-400/95 hover:bg-primary-300/80 focus:bg-primary-300/90 py-2 px-5 font-display rounded-md hover:shadow justify-center transition-all duration-75 ease-linear text-slate-200/90 hover:text-slate-200 hover:stroke-primary-300/80 stroke-primary-400/95
|
||||
}
|
||||
.btn-primary-solid:disabled {
|
||||
@apply !bg-mirage-400 !ring-mirage-400 !text-slate-500 hover:pointer-events-none
|
||||
}
|
||||
.btn-primary-solid svg,
|
||||
.btn-primary svg,
|
||||
.btn-danger svg {
|
||||
@apply ml-4 w-5 h-5 transition-transform duration-150 ease-out
|
||||
}
|
||||
|
||||
.btn-primary-solid:hover svg,
|
||||
.btn-primary:hover svg,
|
||||
.btn-danger:hover svg {
|
||||
@apply ml-4 w-5 h-5 scale-[106%] !ease-linear
|
||||
@apply ml-5 w-5 h-5
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
@apply ring-1 hover:shadow ring-danger-500/70 hover:ring-danger-500 text-left text-sm font-semibold text-danger-500/70 hover:text-danger-500 scale-100 hover:scale-[99%] flex items-center border hover:ring-inset border-danger-500/70 hover:border-danger-500 py-2 px-3 rounded-md font-display justify-center transition-all duration-75 ease-linear
|
||||
@apply ring-1 hover:shadow ring-danger-500/70 hover:ring-danger-500 text-left text-sm font-semibold text-danger-500/70 hover:text-danger-500 flex items-center border hover:ring-inset border-danger-500/70 hover:border-danger-500 py-2 px-3 rounded-md justify-center transition-colors duration-100 ease-in-out
|
||||
}
|
||||
|
||||
|
||||
.btn-form {
|
||||
@apply flex px-2 py-2 hover:ring-2 focus:ring-1 focus:ring-inset outline-none items-center text-slate-400 hover:text-slate-200 rounded-md ring-1
|
||||
}
|
||||
@@ -34,10 +22,11 @@
|
||||
@apply mx-2
|
||||
}
|
||||
|
||||
|
||||
.form-input-wrapper {
|
||||
@apply mt-2.5 hover:border-mirage-200/40 transition-colors duration-200 ease-in-out block justify-between items-center to-mirage-400/50 from-mirage-300/20 bg-gradient-to-br rounded border focus-within:!border-primary/40 px-3.5 py-1 text-slate-100 shadow-sm border-mirage-400/20 focus-within:from-mirage-400/20 focus-within:to-mirage-400/30 focus-within:bg-gradient-to-l
|
||||
}
|
||||
|
||||
.form-input {
|
||||
@apply block w-full placeholder:text-slate-700 bg-transparent focus:outline-none outline-none sm:text-sm
|
||||
@apply block w-full placeholder:text-slate-700 bg-transparent focus:outline-none outline-none sm:text-sm
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
.bar-hz .ct-label {
|
||||
@apply text-slate-400 font-display
|
||||
}
|
||||
.bar-hz {
|
||||
@apply overflow-y-scroll flex flex-col
|
||||
}
|
||||
|
||||
.bar-hz .ct-series-a .ct-bar {
|
||||
@apply stroke-sky-600
|
||||
@@ -36,7 +33,4 @@
|
||||
}
|
||||
.bar-hz .ct-series-j .ct-bar {
|
||||
@apply stroke-slate-200
|
||||
}
|
||||
|
||||
.ct-label.ct-vertical.ct-end {
|
||||
}
|
||||
@@ -66,7 +66,7 @@
|
||||
color: rgb(203, 213, 225);
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
|
||||
font-feature-settings: 'ss01';
|
||||
font-size: 0.8rem;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 24.5px;
|
||||
tab-size: 4;
|
||||
|
||||
@@ -50,28 +50,20 @@ button {
|
||||
::selection {
|
||||
@apply text-slate-200 bg-radiance-500/50
|
||||
}
|
||||
/* 2d6cffb0 */
|
||||
|
||||
html {
|
||||
background-color: rgb(7, 21, 35, 1);
|
||||
background-image: url(../images/image-bg.siteorigin.com-inflicted-overlay.png );
|
||||
}
|
||||
/* 1hmfchzd.png */
|
||||
|
||||
body {
|
||||
background-color: transparent;
|
||||
background-size: 100% 100%;
|
||||
background-position: 0px 0px,0px 0px,0px 0px,0px 0px,0px 0px;
|
||||
background-image: radial-gradient(49% 81% at 45% 47%, rgba(71, 0, 80, 0.093) 0%, #073AFF00 100%),radial-gradient(113% 91% at 17% -2%, rgba(7, 70, 132, 0.168) 1%, #ff000000 99%),radial-gradient(142% 91% at 83% 7%, hsla(234, 100%, 19%, 0.109) 1%, rgba(153, 0, 255, 0.126) 99%),radial-gradient(142% 91% at -6% 74%, rgba(0, 42, 255, 0.116) 1%, rgba(3, 154, 84, 0.116) 99%),radial-gradient(142% 91% at 111% 84%, #1144ed0f 25%, rgba(69, 34, 150, 0.045) 95%);}
|
||||
/* https://gradienta.io/editor */
|
||||
background-position: 0px 0px,0px 0px,0px 0px,0px 0px,0px 0px;
|
||||
background-image: radial-gradient(49% 81% at 45% 47%, rgba(157, 45, 255, 0.689) 0%, #073AFF00 100%),radial-gradient(113% 91% at 17% -2%, rgba(7, 162, 139, 0.482) 1%, #ff000000 99%),radial-gradient(142% 91% at 83% 7%, rgb(50, 13, 143) 1%, #ff00ea00 99%),radial-gradient(142% 91% at -6% 74%, rgba(0, 42, 255, 0.441) 1%, #FF000000 99%),radial-gradient(142% 91% at 111% 84%, rgb(0, 64, 255) 25%, rgb(69, 34, 150) 95%);
|
||||
}
|
||||
|
||||
/* https://gradienta.io/editor */
|
||||
body {
|
||||
@apply min-h-screen from-mirage-800/90 to-mirage-700/50 bg-gradient-to-br;
|
||||
}
|
||||
|
||||
#root {
|
||||
@apply flex flex-col h-full min-h-screen relative bg-mirage-900/20 justify-between;
|
||||
}
|
||||
|
||||
.half-grayscale {
|
||||
--tw-grayscale: grayscale(70%);
|
||||
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
||||
@apply flex flex-col min-h-screen relative bg-mirage-800/60
|
||||
}
|
||||
|
||||
svg.icon {
|
||||
@@ -92,7 +84,6 @@ button,
|
||||
a,
|
||||
figcaption {
|
||||
@apply text-mirage-400 font-display;
|
||||
/* color: #ff9602; */
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -100,23 +91,23 @@ figcaption {
|
||||
Works on Chrome, Edge, and Safari
|
||||
*/
|
||||
*::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
width: 8px;
|
||||
border-radius: none;
|
||||
|
||||
}
|
||||
*::-webkit-scrollbar-track {
|
||||
background: #19233433;
|
||||
width: 10px;
|
||||
width: 8px;
|
||||
border-radius: none;
|
||||
|
||||
}
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background-color: #1a1f2793;
|
||||
border-radius: 2px;
|
||||
border-right: 9px solid #1b243a84;
|
||||
border-right: 10px solid #1b243a84;
|
||||
}
|
||||
* {
|
||||
scrollbar-width: 10px;
|
||||
scrollbar-width: 8px;
|
||||
scrollbar-color: #1d222f62 #202d4333;
|
||||
|
||||
}
|
||||
@@ -209,7 +200,6 @@ input[type='number'] {
|
||||
}
|
||||
.react-flow__attribution a {
|
||||
@apply !text-slate-500;
|
||||
|
||||
}
|
||||
|
||||
.react-flow__edge-path {
|
||||
@@ -303,5 +293,7 @@ input[type='file'] {
|
||||
@apply !border-slate-600 !w-2 !h-2
|
||||
}
|
||||
|
||||
|
||||
.cm-content {
|
||||
@apply font-code text-sm font-semibold
|
||||
}
|
||||
/* end react-grid-layout and codemirror */
|
||||
|
||||
@@ -1,28 +1,25 @@
|
||||
.sidebar-link {
|
||||
@apply flex items-center duration-150 ease-in-out mx-2.5 my-1 py-2 text-base font-medium rounded-md border hover:border-mirage-400/60 border-transparent hover:ring-slate-800/20 text-slate-400/30 hover:text-slate-400/60 shadow hover:translate-x-px z-50 transition-all focus:text-slate-400/40 -translate-x-px ring-slate-900/20 ring-2 ring-inset from-mirage-400/10 pl-3 to-mirage-300/5 bg-gradient-to-tl from-10% pr-1.5 hover:from-mirage-300/20 hover:to-mirage-300/10 hover:bg-gradient-to-tl font-display;
|
||||
@apply flex items-center transition-colors duration-200 ease-in-out px-2 mx-2 my-1 py-2 text-base font-sans font-medium rounded-md text-slate-600 hover:bg-mirage-100/20 hover:text-slate-500 border hover:border-mirage-400/60 border-transparent hover:from-mirage-500/60 hover:to-mirage-500/40 hover:bg-gradient-to-r;
|
||||
}
|
||||
|
||||
.sidebar-link.active {
|
||||
@apply hover:translate-x-px shadow translate-x-px ring-1 ring-primary-300/80 border-primary-300/80 ring-inset from-mirage-300/20 to-mirage-300/10 bg-gradient-to-tl from-10% hover:from-mirage-300/20 hover:to-mirage-300/10 hover:bg-gradient-to-tl focus:from-mirage-300/20 focus:to-mirage-300/10;
|
||||
}
|
||||
.sidebar-link.active span {
|
||||
@apply !text-slate-300/80
|
||||
@apply to-primary-300/30 from-primary-300/40 from-60% border border-mirage-400/60 bg-gradient-to-br shadow-sm bg-mirage-500 text-slate-300 ;
|
||||
}
|
||||
|
||||
.sidebar-link.active button {
|
||||
@apply bg-mirage-500 border-mirage-300/40 ;
|
||||
@apply bg-mirage-500 hover:bg-mirage-600 border-mirage-300/40 ;
|
||||
}
|
||||
.sidebar-link.active svg {
|
||||
@apply text-primary-300 -ml-0.5
|
||||
@apply text-slate-400
|
||||
}
|
||||
.sidebar-link button {
|
||||
@apply border border-mirage-400/40 transition-colors duration-150 from-10% hover:from-mirage-300/20 hover:to-mirage-300/10 hover:bg-gradient-to-tl
|
||||
@apply border border-mirage-400/40 transition-colors duration-200 hover:border-mirage-300 bg-mirage-500
|
||||
}
|
||||
|
||||
.sidebar-link button svg {
|
||||
@apply origin-center rotate-0
|
||||
}
|
||||
.sidebar-link:hover button svg,
|
||||
.sidebar-link.active button svg {
|
||||
@apply !rotate-180
|
||||
@apply text-slate-400
|
||||
}
|
||||
.sidebar-link button:hover svg {
|
||||
@apply text-slate-300;
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/* Start node */
|
||||
.node.container {
|
||||
@apply flex flex-col w-auto max-w-sm justify-between rounded-md transition-all duration-150 ease-in-out ring-1 ring-mirage-400/90 backdrop-blur-xl focus:ring-mirage-300/70 focus-within:ring-mirage-300/70 hover:ring-mirage-300/70 bg-mirage-500/20 hover:bg-slate-900/60 focus:bg-slate-900/60 focus-within:bg-slate-900/60 hover:shadow-md shadow-slate-900/30 shadow cursor-grab target:cursor-grabbing;
|
||||
@apply flex flex-col w-auto max-w-sm justify-between rounded-md transition-all duration-150 ease-in-out ring-1 ring-mirage-400/30 backdrop-blur-lg hover:ring-mirage-400/70 to-mirage-600/50 from-mirage-500/40 from-60% bg-gradient-to-br hover:shadow-md shadow-sm;
|
||||
}
|
||||
|
||||
.node .side-header {
|
||||
@@ -23,6 +23,8 @@
|
||||
@apply leading-4;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.node .node-display h1,
|
||||
.node .node-display h2,
|
||||
.node .node-display p {
|
||||
@@ -36,11 +38,11 @@
|
||||
}
|
||||
|
||||
.node .header {
|
||||
@apply flex h-full w-full items-center justify-between rounded-t-md text-slate-50/70 py-2 px-1 ;
|
||||
@apply flex h-full w-full items-center justify-between rounded-t-md text-slate-50/70 py-2 px-1 cursor-grab focus:cursor-grabbing;
|
||||
}
|
||||
|
||||
.node .header svg {
|
||||
@apply h-6 w-6;
|
||||
@apply h-7 w-7;
|
||||
}
|
||||
|
||||
.text-container {
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import classNames from "classnames";
|
||||
import { ReactComponent as OSINTBuddyLogomark } from "@images/logo-ext.svg"; // OSINTBuddy-text.svg
|
||||
import { ReactComponent as OSINTBuddyLogo } from "@images/logo.svg";
|
||||
import { Link, NavLink } from "react-router-dom";
|
||||
import HamburgerMenu from "./HamburgerMenu";
|
||||
import { toast } from "react-toastify";
|
||||
import { CogIcon, DocumentMagnifyingGlassIcon, FolderOpenIcon, PlusIcon, HomeIcon } from "@heroicons/react/24/outline";
|
||||
import { Icon } from "./Icons";
|
||||
|
||||
|
||||
const navigation = [
|
||||
{ name: <><span>Dashboard</span></>, to: "/dashboard", icon: HomeIcon },
|
||||
{ name: <><span>Workspaces</span> <span className="half-grayscale ml-auto mr-2.5 opacity-30 right-0">🚧</span></>, to: "/workspaces", icon: FolderOpenIcon },
|
||||
{ name: <><span>Scans</span> <span className="half-grayscale ml-auto mr-2.5 opacity-30 right-0">🚧</span></>, to: "/scans", icon: DocumentMagnifyingGlassIcon },
|
||||
{ name: "Dashboard", to: "/dashboard", icon: HomeIcon },
|
||||
{ name: "Incidents *", to: "/incidents", icon: FolderOpenIcon },
|
||||
{ name: "Scans *", to: "/scans", icon: DocumentMagnifyingGlassIcon },
|
||||
];
|
||||
|
||||
interface AppLayoutSidebarProps {
|
||||
@@ -23,7 +22,7 @@ export default function AppLayoutSidebar({ showSidebar, toggleSidebar, setShowIn
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
"fixed inset-y-0 flex border-r from-mirage-600/40 to-mirage-600/50 bg-gradient-to-br shadow-xl border-mirage-600/70 w-64 flex-col transition-transform duration-100",
|
||||
"fixed inset-y-0 flex border-r from-mirage-900/20 to-mirage-600/20 bg-gradient-to-br shadow border-mirage-800/80 w-64 flex-col transition-transform duration-100",
|
||||
showSidebar ? "translate-x-0" : "-translate-x-52 border-r-2"
|
||||
)}
|
||||
>
|
||||
@@ -35,7 +34,7 @@ export default function AppLayoutSidebar({ showSidebar, toggleSidebar, setShowIn
|
||||
)}
|
||||
>
|
||||
<Link to="/" replace>
|
||||
<OSINTBuddyLogomark className="h-7 ml-1 w-auto fill-slate-400" />
|
||||
<OSINTBuddyLogo className="h-7 w-auto fill-slate-400" />
|
||||
</Link>
|
||||
|
||||
<HamburgerMenu
|
||||
@@ -45,75 +44,86 @@ export default function AppLayoutSidebar({ showSidebar, toggleSidebar, setShowIn
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col overflow-y-auto">
|
||||
<nav className="flex-1 flex flex-col">
|
||||
<nav className="flex-1 flex py-4 pt-1 flex-col">
|
||||
{navigation.map((item) => (
|
||||
<NavLink
|
||||
key={item.to}
|
||||
key={item.name}
|
||||
to={item.to}
|
||||
className={({ isActive }) =>
|
||||
classNames(
|
||||
isActive && "active mx-2",
|
||||
"sidebar-link ",
|
||||
!showSidebar ? "mx-0 ml-0 mr-px " : 'mr-2.5'
|
||||
isActive && "active",
|
||||
"sidebar-link",
|
||||
!showSidebar && "mx-0"
|
||||
)
|
||||
}
|
||||
>
|
||||
<item.icon
|
||||
className={classNames(
|
||||
"transition-all mr-2 flex-shrink-0 h-6 w-6 duration-100",
|
||||
location.pathname.includes(item.to) && "text-slate-400/30",
|
||||
"transition-all",
|
||||
location.pathname.includes(item.to)
|
||||
? "text-slate-500"
|
||||
: "",
|
||||
"mr-3 flex-shrink-0 h-6 w-6 duration-100",
|
||||
showSidebar
|
||||
? "translate-x-0"
|
||||
: "translate-x-[12.75rem] "
|
||||
: "translate-x-[13.15rem]"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{item.name}
|
||||
{item.name === "Incidents *" && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setShowIncidentsModal(true)
|
||||
toast.info(
|
||||
<>
|
||||
This feature is currently being planned out and
|
||||
created. You can help shape this feature by
|
||||
contributing to the discussion
|
||||
<a
|
||||
className="text-primary"
|
||||
href="https://github.com/jerlendds/osintbuddy/discussions"
|
||||
target="_blank"
|
||||
>
|
||||
on Github!
|
||||
</a>
|
||||
</>,
|
||||
{ autoClose: 10000 }
|
||||
);
|
||||
}}
|
||||
title="Create new incident"
|
||||
className="ml-auto -mr-0.5 relative bg-dark-400 transition-colors duration-75 hover:bg-dark-500 p-1.5 rounded-full"
|
||||
>
|
||||
<PlusIcon className="text-white w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</NavLink>
|
||||
))}
|
||||
<NavLink
|
||||
to="/settings"
|
||||
replace
|
||||
className={({ isActive, }) =>
|
||||
className={({ isActive }) =>
|
||||
classNames(
|
||||
!showSidebar ? "mx-0 ml-0 mr-px" : 'mr-2.5',
|
||||
"sidebar-link transition-all mt-auto sidebar-link text-slate-400/30 ",
|
||||
isActive && "active mx-2"
|
||||
isActive && "active",
|
||||
"sidebar-link mt-auto",
|
||||
!showSidebar && "mx-0"
|
||||
)
|
||||
}
|
||||
>
|
||||
<CogIcon
|
||||
className={classNames(
|
||||
"transition-all ",
|
||||
location.pathname.includes("settings") && "!mr-2 hover:-ml-0.5 ",
|
||||
"mr-2 flex-shrink-0 h-6 w-6 duration-100",
|
||||
showSidebar
|
||||
? "translate-x-0"
|
||||
: "translate-x-[12.75rem] "
|
||||
)}
|
||||
/>
|
||||
<span>Settings</span> <span className="half-grayscale ml-auto mr-2.5 right-0 opacity-30">🚧</span>
|
||||
</NavLink>
|
||||
<a
|
||||
href="https://discord.gg/gsbbYHA3K3"
|
||||
className={
|
||||
classNames(
|
||||
"sidebar-link ",
|
||||
!showSidebar ? "mx-0 ml-0 mr-px" : 'mr-2.5'
|
||||
)
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
icon="brand-discord"
|
||||
className={classNames(
|
||||
"transition-all",
|
||||
"mr-2 flex-shrink-0 h-6 w-6 duration-100",
|
||||
showSidebar
|
||||
? "translate-x-0"
|
||||
: "translate-x-[12.75rem] "
|
||||
location.pathname.includes("settings")
|
||||
? "text-primary"
|
||||
: "text-slate-600 group-hover:text-slate-300",
|
||||
"mr-3 flex-shrink-0 h-6 w-6 duration-100",
|
||||
showSidebar ? "translate-x-0" : "translate-x-[13.16rem]"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Discord
|
||||
</a>
|
||||
Settings
|
||||
</NavLink>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,36 +12,25 @@ import { UpdateEntityByIdApiArg } from '../../app/api';
|
||||
import { useParams } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
export const auraTheme = auraInit({
|
||||
export const tokyoNightTheme = auraInit({
|
||||
settings: {
|
||||
caret: "#cbd5ef",
|
||||
caret: "#c6c6c6",
|
||||
background: 'rgba(0 0 0 0)',
|
||||
fontFamily: 'Fira Code',
|
||||
gutterBackground: 'rgba(36, 46, 107, .17)',
|
||||
selectionMatch: 'rgba(76, 86, 107, .14)',
|
||||
lineHighlight: 'rgba(36, 46, 77, .20)',
|
||||
fontFamily: 'monospace',
|
||||
gutterBackground: 'rgba(36, 46, 77, .16)',
|
||||
selectionMatch: 'rgba(76, 86, 107, .00)',
|
||||
lineHighlight: 'rgba(36, 46, 77, .26)',
|
||||
},
|
||||
styles: [
|
||||
{ tag: [t.definitionOperator, t.bool, t.logicOperator, t.bitwiseOperator, t.controlOperator,], color: "#ec4899" },
|
||||
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string), t.function(t.propertyName,), t.function(t.variableName),], color: "#2dd4bf" },
|
||||
{ tag: [t.keyword, t.definitionKeyword, t.special(t.keyword), t.attributeValue, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: "#a855f7" },
|
||||
{ tag: [t.variableName, t.deleted, t.character, t.name, t.special(t.variableName)], color: "#cbd5efEF" },
|
||||
{ tag: [t.docString, t.docComment, t.className, t.punctuation,], color: "#49B6FE" },
|
||||
{ tag: [t.propertyName,], color: "#34d399" },
|
||||
{ tag: [t.string], color: "#4ade80" },
|
||||
{ tag: [t.number,], color: "#D8454A" },
|
||||
{ tag: [t.comment, t.lineComment, t.blockComment, t.punctuation], color: "#5a6fbc" },
|
||||
],
|
||||
styles: [{ tag: t.comment, color: "#6272a4" }],
|
||||
})
|
||||
|
||||
export function CodeEditor({ code, setCode }: JSONObject) {
|
||||
return (
|
||||
<CodeMirror
|
||||
theme={auraTheme}
|
||||
theme={tokyoNightTheme}
|
||||
value={code}
|
||||
onChange={(value) => setCode(value)}
|
||||
extensions={[python()]}
|
||||
className="text-sm font-semibold"
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -76,7 +65,7 @@ export default function EntityEditor({ activeEntity, refetchEntity }: EntityEdit
|
||||
isResizable={true}
|
||||
>
|
||||
<div
|
||||
className="overflow-hidden rounded-md border-mirage-100/0 shadow-lg border z-10 backdrop-blur-sm from-mirage-300/20 to-mirage-100/10 bg-gradient-to-bl from-50% flex flex-col h-full"
|
||||
className="overflow-hidden rounded-md border-mirage-100/0 shadow-lg border z-10 from-mirage-100/10 to-mirage-200/10 bg-gradient-to-tl from-50% flex flex-col h-full"
|
||||
key="b"
|
||||
data-grid={{
|
||||
x: 0,
|
||||
@@ -89,7 +78,7 @@ export default function EntityEditor({ activeEntity, refetchEntity }: EntityEdit
|
||||
minW: 4.5,
|
||||
}}
|
||||
>
|
||||
<ol className="text-sm flex select-none from-mirage-200/20 bg-gradient-to-tr from-40% to-mirage-300/20 relative px-2 py-2 border-b border-mirage-300/80">
|
||||
<ol className="text-sm flex select-none from-mirage-400/30 bg-gradient-to-tr from-40% to-mirage-300/10 relative px-2 py-2 border-b border-mirage-300/80">
|
||||
<li className="flex items-start">
|
||||
<div className="flex items-center">
|
||||
<span className="text-slate-500 font-display truncate">
|
||||
@@ -114,7 +103,7 @@ export default function EntityEditor({ activeEntity, refetchEntity }: EntityEdit
|
||||
<div className="flex justify-between items-center w-full text-slate-400 ">
|
||||
<button
|
||||
onClick={() => {
|
||||
updateEntityById({ hid: activeEntity?.id ?? "", entityUpdate: { source: code as string } }).then(() => toast.info(
|
||||
updateEntityById({ hid: activeEntity?.id ?? "", entityBase: { source: code } }).then(() => toast.info(
|
||||
`The ${activeEntity?.label} entity has been saved.`
|
||||
))
|
||||
refetchEntity()
|
||||
@@ -138,8 +127,10 @@ export default function EntityEditor({ activeEntity, refetchEntity }: EntityEdit
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
<div className="from-mirage-400/20 backdrop-blur-sm bg-gradient-to-tr from-40% to-mirage-400/30 border-mirage-400 border overflow-y-scroll h-full ">
|
||||
<CodeEditor code={code} setCode={setCode} />
|
||||
<div className="container bg-t overflow-y-scroll h-full ">
|
||||
<div className="editor ">
|
||||
<CodeEditor code={code} setCode={setCode} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ResponsiveGridLayout >
|
||||
|
||||
@@ -27,7 +27,7 @@ export function GridPanel({
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="min-h-[3rem] overflow-hidden rounded flex flex-col w-full h-full border border-mirage-400/60 from-mirage-300/30 bg-gradient-to-tr from-40% to-mirage-100/20 shadow "
|
||||
className="min-h-[3rem] from-mirage-400/50 to-mirage-400/30 shadow bg-gradient-to-tl from-10% overflow-hidden rounded flex flex-col w-full h-full border border-mirage-400/60 "
|
||||
>
|
||||
<ol className="text-sm flex select-none justify-between relative px-2 py-2">
|
||||
<li className="flex items-start">
|
||||
|
||||
@@ -15,7 +15,7 @@ export const InquiryHeader = ({ title, header, className, btnTxt, btnAction }: P
|
||||
<div className='min-w-0 max-w-2xl flex-auto pt-3 lg:max-w-none lg:pr-0 px-2'>
|
||||
<section>
|
||||
<header className='space-y-1'>
|
||||
{title && <h4 className='font-display text-sm font-medium text-primary-200'>{title}</h4>}
|
||||
{title && <h4 className='font-display text-sm font-medium text-primary-300'>{title}</h4>}
|
||||
{header && (
|
||||
<h1 className='font-display leading-4 text-xl tracking-tight text-slate-400 dark:text-slate-300'>{header}</h1>
|
||||
)}
|
||||
|
||||
@@ -2,10 +2,12 @@ import classNames from 'classnames';
|
||||
import Sprite from '@assets/images/tabler-sprite.svg';
|
||||
|
||||
export const Icon = ({ icon, className }: { icon: string, className?: string }) => {
|
||||
// TODO: Optimize this?
|
||||
// TODO: Remove sprite and figure this approach out somehow
|
||||
// const svgIcon = import(`@tabler/icons/${icon}.svg`).then(x => x).catch(() => <>why no work?!</>)
|
||||
|
||||
return (
|
||||
<>
|
||||
<svg className={className ? className : 'h-5 w-5'} fill="none" stroke="currentColor">
|
||||
<svg className={className ? className : 'h-5 w-5'} fill='#fff' color='#fff'>
|
||||
<use href={`${Sprite}#tabler-${icon}`} />
|
||||
</svg>
|
||||
</>
|
||||
|
||||
@@ -23,18 +23,16 @@ export default function RoundLoader({ className }: RoundLoaderProps): React.Reac
|
||||
export function UnderConstruction({ header, description, className = 'flex px-6' }: any) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className='bg-mirage-800/30 w-full block shadow rounded-b-lg '>
|
||||
<div className='bg-mirage-700/50 w-full block shadow sm:rounded-lg '>
|
||||
<div className='border-b border-mirage-300 mx-4 py-5 sm:px-6'>
|
||||
<div className='-ml-6 -mt-2 flex flex-wrap items-center justify-between sm:flex-nowrap'>
|
||||
<div className='ml-4 mt-2 w-full'>
|
||||
<h1 className='text-2xl w-full flex items-center justify-between'>
|
||||
<span className="half-grayscale mr-2.5 opacity-30 right-0">🚧</span><p className="font-display font-semibold text-slate-300/90 mr-auto"> Under Construction</p>
|
||||
<span className="half-grayscale opacity-30 right-0 ml-auto">🚧</span>
|
||||
<div className='ml-4 mt-2'>
|
||||
<h1 className='font-display text-2xl tracking-tight text-slate-300'>
|
||||
Under Construction
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section className='flex flex-col px-3 mb-6'>
|
||||
<InquiryHeader className='z-10' title='New Feature' header={header} />
|
||||
<p className='my-3 ml-6 text-slate-400 max-w-xl'>
|
||||
|
||||
@@ -5,8 +5,8 @@ import { Bars3Icon, BellIcon, XMarkIcon } from '@heroicons/react/24/outline';
|
||||
import classNames from 'classnames';
|
||||
import OSINTBuddyLogo from '@assets/images/logo.svg';
|
||||
import { Listbox } from '@headlessui/react'
|
||||
import { Icon } from './Icons';
|
||||
import Logo from '@images/logo-ext.svg';
|
||||
import { GithubIcon } from './Icons';
|
||||
import Logo from '@images/logo.svg';
|
||||
|
||||
const themes = [
|
||||
{ name: 'Light', value: 'light', icon: LightIcon },
|
||||
@@ -60,15 +60,15 @@ export function ThemeSelector(props: any) {
|
||||
} else {
|
||||
setSelectedTheme(
|
||||
// @ts-ignore
|
||||
themes.find((theme) => theme.value === document.documentElement.getAttribute('data-theme')))
|
||||
themes.find((theme) =>theme.value === document.documentElement.getAttribute('data-theme')))
|
||||
}
|
||||
}, [selectedTheme])
|
||||
|
||||
useEffect(() => {
|
||||
let handler = () =>
|
||||
|
||||
|
||||
setSelectedTheme(
|
||||
// @ts-ignore
|
||||
// @ts-ignore
|
||||
themes.find((theme) => theme.value === (window.localStorage.theme ?? 'system')))
|
||||
|
||||
window.addEventListener('storage', handler)
|
||||
@@ -147,36 +147,28 @@ export default function PublicNavbar(): React.ReactElement {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<header
|
||||
<header
|
||||
className={classNames(
|
||||
'sticky w-full top-0 z-50 flex sm:flex-wrap items-center justify-between flex-nowrap px-4 py-2.5 shadow-md shadow-slate-900/5 transition duration-500 dark:shadow-none sm:px-6 lg:px-8',
|
||||
'fixed w-full top-0 z-50 flex flex-wrap items-center justify-between px-4 py-5 shadow-md shadow-slate-900/5 transition duration-500 dark:shadow-none sm:px-6 lg:px-8',
|
||||
isScrolled
|
||||
? 'dark:bg-slate-800/95 dark:backdrop-blur dark:[@supports(backdrop-filter:blur(0))]:bg-slate-900/75'
|
||||
? 'dark:bg-slate-900/95 dark:backdrop-blur dark:[@supports(backdrop-filter:blur(0))]:bg-slate-900/75'
|
||||
: 'dark:bg-transparent'
|
||||
)}
|
||||
>
|
||||
<div className="flex lg:hidden">
|
||||
<Link to="/">
|
||||
<img className=" h-7 w-auto fill-slate-700 dark:fill-sky-100 block" src={Logo} />
|
||||
</Link>
|
||||
<div className="mr-6 flex lg:hidden">
|
||||
{/* @todo add mobile navigation */}
|
||||
</div>
|
||||
<div className="relative flex flex-grow basis-0 items-center">
|
||||
<Link to="/">
|
||||
<img className="hidden h-9 fill-slate-700 dark:fill-sky-100 lg:block" src={Logo} />
|
||||
<img className="hidden h-9 w-auto fill-slate-700 dark:fill-sky-100 lg:block" src={Logo} />
|
||||
</Link>
|
||||
</div>
|
||||
<div className="">
|
||||
<div className="-my-5 mr-6 sm:mr-8 md:mr-0">
|
||||
{/* <Search /> */}
|
||||
</div>
|
||||
<div className="ml-auto relative flex basis-0 justify-end mr-2 gap-6 sm:gap-8 md:flex-grow">
|
||||
<a href="https://discord.gg/gsbbYHA3K3" className="group" aria-label="Discord">
|
||||
<Icon icon="brand-discord" className="h-6 w-6 transition-colors duration-150 ease-in-out text-slate-500 hover:text-slate-300 focus:text-slate-300" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="relative flex justify-end">
|
||||
<div className="relative flex basis-0 justify-end gap-6 sm:gap-8 md:flex-grow">
|
||||
<a href="https://github.com/jerlendds/osintbuddy" className="group" aria-label="GitHub">
|
||||
<Icon icon="brand-github" className="h-6 w-6 transition-colors duration-150 ease-in-out text-slate-500 hover:text-slate-300 focus:text-slate-300" />
|
||||
<GithubIcon className="h-6 w-6 fill-slate-400 group-hover:fill-slate-500 dark:group-hover:fill-slate-300" />
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -24,8 +24,6 @@ export interface Graph extends EditState {
|
||||
project: ActiveProject;
|
||||
viewMode: ProjectViewModes;
|
||||
positionMode: PositionModes
|
||||
showEdges: false,
|
||||
shownEdgesByNodeId: JSONObject[]
|
||||
}
|
||||
|
||||
const initialState: Graph = {
|
||||
@@ -41,9 +39,7 @@ const initialState: Graph = {
|
||||
name: '',
|
||||
},
|
||||
viewMode: 'edit',
|
||||
positionMode: 'manual',
|
||||
showEdges: false,
|
||||
shownEdgesByNodeId: []
|
||||
positionMode: 'manual'
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ export default function AppLayout() {
|
||||
showSidebar ? "translate-x-64" : "translate-x-12"
|
||||
)}
|
||||
>
|
||||
<main id="main-view" className="flex-1 overflow-hidden h-screen ">
|
||||
<main id="main-view" className="flex-1 overflow-hidden h-screen">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ReactElement, Suspense, lazy } from "react";
|
||||
import { Navigate, Route, RouterProvider, Routes, createBrowserRouter } from "react-router-dom";
|
||||
import { Route, RouterProvider, Routes, createBrowserRouter } from "react-router-dom";
|
||||
import NotFound from "./NotFound";
|
||||
import AppLayout from "./AppLayout";
|
||||
import PublicLayout from "./PublicLayout";
|
||||
@@ -24,7 +24,7 @@ const EntitiesOverview = lazy(() => import("@routes/dashboard/_components/entity
|
||||
const EntityDetails = lazy(() => import("@routes/dashboard/_components/entity/EntityDetails"))
|
||||
|
||||
const Settings = lazy(() => import("@routes/settings"));
|
||||
const Workspaces = lazy(() => import("@src/routes/workspaces"));
|
||||
const Incidents = lazy(() => import("@routes/incidents"));
|
||||
|
||||
const ScansCreate = lazy(() => import("@routes/scans-create"))
|
||||
const ScansOverview = lazy(() => import("@routes/scans"));
|
||||
@@ -58,10 +58,6 @@ const router = createBrowserRouter([
|
||||
path: 'dashboard',
|
||||
element: <Suspense><DashboardPage /></Suspense>,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
element: <Navigate to="graph" replace />
|
||||
},
|
||||
{
|
||||
path: 'market',
|
||||
element: <Suspense><Market /></Suspense>
|
||||
@@ -93,8 +89,8 @@ const router = createBrowserRouter([
|
||||
element: <Suspense><Settings /></Suspense>
|
||||
},
|
||||
{
|
||||
path: 'workspaces',
|
||||
element: <Suspense><Workspaces /></Suspense>
|
||||
path: 'incidents',
|
||||
element: <Suspense><Incidents /></Suspense>
|
||||
},
|
||||
{
|
||||
path: 'scans',
|
||||
|
||||
@@ -6,9 +6,7 @@ export default function PublicLayout(): React.ReactElement {
|
||||
return (
|
||||
<>
|
||||
<PublicNavbar />
|
||||
<main id="main-view">
|
||||
<Outlet />
|
||||
</main>
|
||||
<Outlet />
|
||||
<ToastContainer
|
||||
position='bottom-right'
|
||||
autoClose={3000}
|
||||
|
||||
@@ -7,18 +7,18 @@ import { FetchBaseQueryError } from "@reduxjs/toolkit/query"
|
||||
import { SerializedError } from "@reduxjs/toolkit"
|
||||
|
||||
|
||||
const MAX_DESCRIPTION_LENGTH = 79
|
||||
const MAX_LABEL_LENGTH = 32
|
||||
const MAX_DESCRIPTION_LENGTH = 63
|
||||
const MAX_LABEL_LENGTH = 31
|
||||
|
||||
export function GraphLoaderCard() {
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2">
|
||||
<div className="w-full py-6 space-y-1 rounded-md rounded-r-none border border-mirage-400/60 from-mirage-400/50 to-mirage-400/30 shadow bg-gradient-to-tl from-10% before:absolute px-4 via-mirage-400/10 relative before:inset-0 before:bg-gradient-to-r before:from-transparent before:via-mirage-300/5 before:to-transparent isolate overflow-hidden shadow-black/5 border-l border-y border-mirage-500">
|
||||
<div className="w-full py-6 space-y-5 rounded-md rounded-r-none bg-mirage-400/30 before:absolute px-4 bg-gradient-to-r from-transparent via-mirage-400/10 to-transparent relative before:inset-0 before:bg-gradient-to-r before:from-transparent before:via-mirage-300/5 before:to-transparent isolate overflow-hidden shadow-xl shadow-black/5 border-l border-y border-mirage-500">
|
||||
<div className="space-y-3">
|
||||
<div className="h-2 w-3/5 rounded-lg bg-slate-600/20 animate-pulse"></div>
|
||||
<div className="h-2 w-4/5 rounded-lg bg-slate-600/20 animate-pulse"></div>
|
||||
<div className="h-2 w-2/5 rounded-lg bg-slate-600/20 animate-pulse"></div>
|
||||
<div className="h-2 w-3/5 rounded-lg bg-mirage-500/60 animate-pulse"></div>
|
||||
<div className="h-2 w-4/5 rounded-lg bg-mirage-500/60 animate-pulse"></div>
|
||||
<div className="h-2 w-2/5 rounded-lg bg-mirage-500/60 animate-pulse"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -54,16 +54,16 @@ export default function Subpanel({
|
||||
const { hid } = useParams();
|
||||
|
||||
return (
|
||||
<section className={styles["subpanel"]}>
|
||||
<header className={styles["subpanel-header"]} onClick={setShowEntities}>
|
||||
<h2 className={`${styles["cyberpunk"]} ${styles["glitched"]}`}>{label ?? ""}</h2>
|
||||
<ChevronDownIcon className={styles[`show-header-icon-${showEntities}`] + " transition-transform duration-100"} />
|
||||
</header>
|
||||
<div className={styles["subpanel"]}>
|
||||
<div className={styles["subpanel-header"]} onClick={setShowEntities}>
|
||||
<p>{label ?? ""}</p>
|
||||
<ChevronDownIcon className={styles[`show-header-icon-${showEntities}`]} />
|
||||
</div>
|
||||
{showError && showEntities && !isLoading && (
|
||||
<>
|
||||
<p>
|
||||
{errorMessage?.length ? (<p className="text-slate-400/60 text-sm px-2">{errorMessage}</p>) : (
|
||||
<>We ran into an error fetching your data. Please try refreshing the page, if this error continues to occur please <a href="#" className="text-info-300">file an issue</a> on github</>
|
||||
{errorMessage?.length ? (<>{errorMessage}</>) : (
|
||||
<>We ran into an error retrieving your entities. Please try refreshing the page, if this error continues to occur please <a href="#" className="text-info-300">file an issue</a> on github</>
|
||||
)}
|
||||
</p>
|
||||
</>
|
||||
@@ -74,33 +74,32 @@ export default function Subpanel({
|
||||
<GraphLoaderCard />
|
||||
</>
|
||||
)}
|
||||
|
||||
<section className={` transition-transform duration-150 ease-out ${showEntities ? 'translate-y-0' : '-translate-y-[45%] -scale-y-0 !h-0'}`}>
|
||||
{items && items.map((item) => {
|
||||
const isActive = hid === item.id
|
||||
const descriptionClassName = styles["subpanel-desc"] + " " + styles[`subpanel-desc-${isActive}`]
|
||||
return (
|
||||
<Link
|
||||
key={item.id}
|
||||
to={`${to}/${item.id}`}
|
||||
className={`${styles["subpanel-link"]} ${styles[`subpanel-link-${isActive}`]} ${isSuccess && showEntities ? 'translate-y-0 scale-y-100' : '-translate-y-full scale-y-0 '}`}>
|
||||
<div>
|
||||
<p className={styles["subpanel-label"] + " " + styles[`subpanel-label-${isActive}`]}>{item.label}</p>
|
||||
<p className={descriptionClassName}>{item.description}</p>
|
||||
<p className={descriptionClassName}><span className="font-sans">Last seen </span> {formatPGDate(item?.last_edited ? item.last_edited : item.last_seen)}</p>
|
||||
</div>
|
||||
<StarIcon
|
||||
onClick={async () => await onClick(item.id)}
|
||||
className={
|
||||
styles["link-icon"] + " " +
|
||||
styles[`link-icon-${item.is_favorite}`] + " " +
|
||||
styles[`link-active-${hid !== item.id}`]
|
||||
}
|
||||
/>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</section>
|
||||
</section>
|
||||
{isSuccess && showEntities && (
|
||||
<section className={styles["subpanel-section"]}>
|
||||
{items && items.map((item) => {
|
||||
return (
|
||||
<Link
|
||||
key={item.id}
|
||||
to={`${to}/${item.id}`}
|
||||
className={styles["subpanel-link"] + " " + styles[`subpanel-link-${hid === item.id}`]}>
|
||||
<div>
|
||||
<p className={styles["subpanel-label"] + " " + styles[`subpanel-label-${hid === item.id}`]}>{item?.label.length > MAX_LABEL_LENGTH ? `${item.label.slice(0, MAX_LABEL_LENGTH)}...` : item.label}</p>
|
||||
<p className={styles["subpanel-desc"] + " " + styles[`subpanel-desc-${hid === item.id}`]}>{item?.description?.length ?? 0 >= MAX_DESCRIPTION_LENGTH ? `${item?.description?.slice(0, MAX_DESCRIPTION_LENGTH)}...` : item.description}</p>
|
||||
<p className={styles["subpanel-desc"] + " " + styles[`subpanel-desc-${hid === item.id}`]}>Last seen {formatPGDate(item?.last_edited ? item.last_edited : item.last_seen)}</p>
|
||||
</div>
|
||||
<StarIcon
|
||||
onClick={async () => await onClick(item.id)}
|
||||
className={
|
||||
styles["link-icon"] + " " +
|
||||
styles[`link-icon-${item.is_favorite}`] + " " +
|
||||
styles[`link-active-${hid !== item.id}`]
|
||||
}
|
||||
/>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -92,9 +92,9 @@ export default function GraphDetails() {
|
||||
return (
|
||||
<>
|
||||
<section className="flex flex-col h-screen w-full">
|
||||
<header className="flex w-full">
|
||||
<div className="flex w-full">
|
||||
<GraphHeader refetchGraphs={async () => await refetchGraphs()} stats={graphStats} graph={activeGraph as Graph} />
|
||||
</header>
|
||||
</div>
|
||||
<ResponsiveGridLayout
|
||||
className="relative flex-grow h-full z-10 w-full"
|
||||
rowHeight={1}
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function GraphHeader({ graph, stats, refetchGraphs }: GraphHeader
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full">
|
||||
<div className="flex w-full border-b-2 bg-mirage-800/25 border-mirage-400/20 relative">
|
||||
<div className="flex w-full border-b-2 border-mirage-400/20 -top-[2px] relative">
|
||||
<div className=" w-full mx-auto">
|
||||
<section className="flex flex-col items-start justify-between px-4 lg:items-center lg:px-6 md:px-4 lg:flex-row md:flex-row md:items-center relative">
|
||||
<div className="flex flex-col w-full py-1">
|
||||
@@ -49,15 +49,15 @@ export default function GraphHeader({ graph, stats, refetchGraphs }: GraphHeader
|
||||
</button>
|
||||
<Link
|
||||
to={`/graph/inquiry/${graph?.id}`}
|
||||
className='btn-primary mb-3.5 stroke-primary-300/90 hover:!stroke-primary-400/80'
|
||||
className='btn-primary mb-3.5'
|
||||
>
|
||||
Open graph
|
||||
<EyeIcon className='ml-2 w-5 h-5 stroke-inherit' />
|
||||
<EyeIcon className='ml-2 w-5 h-5 ' />
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
<hr className=" border-b border-mirage-400/20" />
|
||||
<section className=" from-mirage-600/20 to-mirage-600/20 bg-gradient-to-l from-10% flex flex-col px-4 py-2 lg:px-6 md:px-4 gap-x-5 lg:flex-row md:flex-row gap-y-4">
|
||||
<section className=" bg-mirage-800/25 flex flex-col px-4 py-2 lg:px-6 md:px-4 gap-x-5 lg:flex-row md:flex-row gap-y-4">
|
||||
{graph && stats && (
|
||||
<>
|
||||
<div className="flex items-center ml-[4px] gap-x-2">
|
||||
|
||||
@@ -18,13 +18,13 @@ export default function GraphOverview() {
|
||||
}, [graphsData])
|
||||
|
||||
return <>
|
||||
<div className="w-full items-center justify-center my-auto relative -top-16">
|
||||
<div className="flex flex-col items-center justify-center text-slate-400">
|
||||
<div className="max-w-2xl w-full from-mirage-300/30 bg-gradient-to-tr from-40% to-mirage-100/20 border-mirage-400 border rounded-md grid place-items-start py-16 px-24 ">
|
||||
<div className="w-full items-center justify-center my-14">
|
||||
<div className="grid place-items-center relative z-10 text-slate-400">
|
||||
<div className="from-mirage-400/30 bg-gradient-to-tr from-40% to-mirage-300/10 border-mirage-400 border rounded-md grid place-items-center my-8 py-16 px-4 md:px-16 lg:px-20">
|
||||
{favoriteGraphs.length === 0 && graphs.length === 0 && (
|
||||
<>
|
||||
<h1 className="text-slate-300/80 text-3xl lg:text-4xl font-bold pr-2">Oh no!</h1>
|
||||
<p className="md:pt-4 max-w-xl text-slate-300/80">
|
||||
<h1 className="text-slate-300 text-3xl lg:text-4xl font-bold pt-12 sm:pt-14 lg:pt-8 ">Oh no!</h1>
|
||||
<p className="py-6 md:py-8 max-w-lg">
|
||||
We're usually a treasure chest of knowledge,
|
||||
but we couldn't find any graphs. Get started
|
||||
by creating a new graph
|
||||
@@ -33,22 +33,15 @@ export default function GraphOverview() {
|
||||
)}
|
||||
{(graphs.length > 0 || favoriteGraphs.length > 0) && (
|
||||
<>
|
||||
<h1 className="text-slate-300/80 text-3xl lg:text-4xl font-bold pr-2">
|
||||
<h1 className="text-slate-300 text-3xl lg:text-4xl font-bold pt-12 sm:pt-14 lg:pt-8 ">
|
||||
{graphs.length + favoriteGraphs.length ?? ''} {favoriteGraphs.length + graphs.length > 1 ? 'graphs' : 'graph'} available</h1>
|
||||
<p className="md:pt-4 max-w-xl text-slate-300/80">
|
||||
<p className="py-6 md:py-8 max-w-md">
|
||||
Get started by selecting an existing graph from the sidebar
|
||||
on the left or you can create a new graph with the button located towards the bottom left
|
||||
on the left, or try creating a new graph
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="max-w-2xl w-full from-mirage-300/30 bg-gradient-to-tr from-40% to-mirage-100/20 border-mirage-400 border rounded-md grid place-items-start my-5 py-10 mt-8 px-24">
|
||||
<h1 className="text-slate-300/80 text-3xl lg:text-4xl font-bold border-b-2 pr-2 border-b-primary-300/80">The availability bias </h1>
|
||||
<p className="md:py-4 max-w-xl text-slate-300/80">
|
||||
Remember, data presentation can impact results during exploration and analysis. Availability bias is the tendency to think readily available examples are more representative than they really are. Even color can influence interpretation.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,26 +1,23 @@
|
||||
.subpanel section {
|
||||
@apply py-1 flex flex-col h-min relative z-0 items-start justify-start
|
||||
}
|
||||
/* refactor me */
|
||||
.subpanel-wrapper {
|
||||
@apply flex flex-col justify-between relative flex-shrink overflow-clip;
|
||||
@apply flex flex-col justify-between relative flex-shrink h-full pr-2;
|
||||
}
|
||||
.subpanel > h2 {
|
||||
.subpanel-section {
|
||||
@apply py-1 px-1 flex flex-col;
|
||||
}
|
||||
.subpanel > p {
|
||||
@apply text-sm py-4 font-medium text-slate-500 focus:outline-none cursor-pointer px-2
|
||||
}
|
||||
|
||||
.subpanel-header {
|
||||
@apply flex py-0.5 pb-1 items-center hover:text-slate-500 text-slate-600 hover:border-primary-300/40 border-mirage-400/60 border-b transition-colors duration-300 ease-in-out z-50
|
||||
@apply flex pb-1 items-center hover:border-mirage-400/60 border-b transition-colors duration-100 ease-in-out border-transparent
|
||||
}
|
||||
.subpanel-header h2 {
|
||||
@apply text-sm px-1 font-semibold font-display leading-3 select-none text-inherit focus:outline-none cursor-pointer
|
||||
.subpanel-header p {
|
||||
@apply text-sm px-1 font-black leading-3 text-slate-600 focus:outline-none cursor-pointer
|
||||
}
|
||||
.subpanel-header svg {
|
||||
@apply h-5 w-5 mr-1 hover:rotate-3 origin-center text-slate-600 ml-auto transform focus:outline-none cursor-pointer
|
||||
}
|
||||
.subpanel-header:hover svg {
|
||||
@apply border-primary-300/40
|
||||
@apply h-5 w-5 mr-2 hover:rotate-3 origin-center text-slate-600 ml-auto transform focus:outline-none cursor-pointer
|
||||
}
|
||||
|
||||
.show-header-icon-true {
|
||||
@apply !-rotate-180
|
||||
}
|
||||
@@ -30,46 +27,36 @@
|
||||
}
|
||||
|
||||
.subpanel-link {
|
||||
@apply mb-1 focus:outline-none bg-transparent border rounded-md border-transparent
|
||||
hover:ring-slate-700/20 hover:to-mirage-400/30 shadow hover:bg-gradient-to-tl from-10% hover:translate-x-px z-0 transition-transform hover:from-mirage-400/10 w-full flex items-center -translate-x-px relative duration-100 ease-out overflow-hidden
|
||||
@apply mb-1 focus:outline-none bg-transparent border rounded-md border-transparent hover:to-mirage-400/20 hover:bg-gradient-to-bl hover:border-mirage-700/70 hover:translate-x-px z-50 transition-transform hover:from-mirage-400/10 text-slate-400 hover:text-slate-400/20 focus:bg-mirage-500/60 focus:text-slate-400/40 w-full flex items-center -translate-x-px
|
||||
}
|
||||
|
||||
|
||||
.subpanel-link div:hover .subpanel-label {
|
||||
@apply !text-slate-400/95
|
||||
@apply !text-slate-400/95
|
||||
}
|
||||
|
||||
.subpanel-link-true {
|
||||
@apply hover:-translate-x-px shadow -translate-x-px ring-slate-600/10 ring-1 ring-inset from-mirage-300/20 to-mirage-300/10 bg-gradient-to-tl from-10% hover:from-mirage-300/20 hover:to-mirage-300/10 hover:bg-gradient-to-tl
|
||||
@apply !border-mirage-300/30 from-mirage-400/10 to-mirage-400/30 hover:from-mirage-400/10 hover:to-mirage-400/30 bg-gradient-to-br hover:bg-gradient-to-br hover:translate-x-0 shadow translate-x-0
|
||||
}
|
||||
|
||||
.subpanel-link div {
|
||||
@apply flex w-full p-2.5 pr-0 flex-col items-start mb-0.5 text-slate-400/40 hover:text-slate-400/60
|
||||
@apply flex w-full p-3 flex-col items-start space-y-1.5 text-slate-400/30 hover:text-slate-400/60
|
||||
}
|
||||
|
||||
|
||||
|
||||
.subpanel-link div p:nth-child(2) {
|
||||
@apply max-h-8 line-clamp-2 leading-4 w-full pr-2 h-full
|
||||
}
|
||||
.subpanel-link div p:last-child {
|
||||
@apply mt-1
|
||||
}
|
||||
svg.link-icon {
|
||||
@apply bg-slate-800/20 border-transparent border ml-auto rounded p-1 h-8 w-8 transition-colors duration-150 hover:text-primary-300 hover:border-mirage-300/20
|
||||
@apply bg-mirage-500/80 border-transparent border ml-auto rounded-md p-1 h-8 w-8 transition-colors duration-150 hover:text-primary-300 hover:border-mirage-300/20 mr-2
|
||||
}
|
||||
svg.link-icon-true {
|
||||
@apply !text-primary-300 hover:!text-primary-200 !bg-transparent;
|
||||
@apply !text-primary-300 hover:!text-primary-200;
|
||||
}
|
||||
svg.link-icon-false {
|
||||
@apply text-slate-600
|
||||
}
|
||||
.link-active-true {
|
||||
@apply !bg-transparent hover:!bg-slate-800/40 transition-colors duration-200 ;
|
||||
@apply !bg-transparent hover:!bg-slate-900 ;
|
||||
}
|
||||
|
||||
.subpanel-label {
|
||||
@apply text-sm leading-4 font-display font-medium line-clamp-1 pb-0.5
|
||||
@apply text-sm font-medium leading-none
|
||||
}
|
||||
.subpanel-label-true {
|
||||
@apply text-slate-400/95;
|
||||
@@ -78,22 +65,8 @@ svg.link-icon-false {
|
||||
@apply text-slate-500/60;
|
||||
}
|
||||
.subpanel-desc {
|
||||
@apply text-xs font-display font-light leading-none text-left;
|
||||
@apply text-xs font-medium leading-none text-left;
|
||||
}
|
||||
.subpanel-desc-true {
|
||||
@apply !text-slate-400/80
|
||||
}
|
||||
|
||||
|
||||
/* .cyberpunk {
|
||||
width: calc(100% - 30px);
|
||||
border: 30px solid #000;
|
||||
border-left: 5px solid #000;
|
||||
border-right: 5px solid #000;
|
||||
border-bottom: 15px solid #000;
|
||||
clip-path: polygon(0px 25px, 26px 0px, calc(60% - 25px) 0px, 60% 25px, 100% 25px, 100% calc(100% - 10px), calc(100% - 15px) calc(100% - 10px), calc(80% - 10px) calc(100% - 10px), calc(80% - 15px) calc(100% - 0px), 10px calc(100% - 0px), 0% calc(100% - 10px));
|
||||
margin-bottom: 10px;
|
||||
padding: 12px;
|
||||
font-size: 1.7rem;
|
||||
font-weight: 700;
|
||||
} */
|
||||
@apply !text-slate-400/60
|
||||
}
|
||||
@@ -30,8 +30,8 @@ export default function EntitiesPanel({
|
||||
|
||||
const [updateEntityIsFavorite] = useUpdateEntityFavoriteIdMutation()
|
||||
|
||||
const updateEntityOnFavorite = (hid: string) => {
|
||||
updateEntityIsFavorite({ hid })
|
||||
const updateEntityOnBookmark = async (hid: string) => {
|
||||
await updateEntityIsFavorite({ hid })
|
||||
refetchEntities()
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ export default function EntitiesPanel({
|
||||
isLoading={isLoading}
|
||||
isSuccess={isSuccess}
|
||||
items={favoriteEntities}
|
||||
onClick={(hid) => updateEntityOnFavorite(hid)}
|
||||
onClick={async (hid) => await updateEntityOnBookmark(hid)}
|
||||
to="/dashboard/entity"
|
||||
/>
|
||||
<Subpanel
|
||||
@@ -56,7 +56,7 @@ export default function EntitiesPanel({
|
||||
isLoading={isLoading}
|
||||
isSuccess={isSuccess}
|
||||
items={entities}
|
||||
onClick={(hid) => updateEntityOnFavorite(hid)}
|
||||
onClick={async (hid) => await updateEntityOnBookmark(hid)}
|
||||
to="/dashboard/entity"
|
||||
/>
|
||||
</section >
|
||||
|
||||
@@ -1,57 +1,51 @@
|
||||
/* wraps the sidebar with search and the main view */
|
||||
.sidebar-wrapper {
|
||||
@apply rounded py-px !min-w-[20rem] max-w-[20rem] flex-col h-screen pt-3.5 border-r-[3px] from-mirage-600/40 to-mirage-600/50 bg-gradient-to-br border-mirage-600/40 justify-between shadow-xl flex relative w-full
|
||||
@apply rounded py-px !min-w-[20rem] max-w-[20rem] flex-col h-screen pt-3.5 border-r-[3px] border-mirage-700/70 from-mirage-600/20 to-mirage-500/20 bg-gradient-to-br pl-2 justify-between shadow-xl flex relative w-full overflow-y-scroll
|
||||
}
|
||||
|
||||
.search-container {
|
||||
@apply flex mx-1.5 items-center relative border text-slate-600 hover:text-slate-300/40 focus:text-slate-300/40 transition-all duration-200 ease-out from-mirage-400/60 to-mirage-300/20 bg-gradient-to-tl rounded border-mirage-300/50 hover:border-mirage-300 mb-1.5
|
||||
@apply flex items-center relative border transition-colors duration-200 ease-out from-mirage-400/60 to-mirage-300/20 bg-gradient-to-tl rounded border-mirage-300/30 hover:border-mirage-300 mb-2 mr-2 ml-1
|
||||
}
|
||||
.search-container:active, .search-container:focus-within {
|
||||
@apply border-primary/50 to-mirage-400/10 from-mirage-400/20 bg-gradient-to-l
|
||||
}
|
||||
|
||||
.search-container input {
|
||||
@apply py-1.5 pl-1.5 w-full focus:outline-none relative text-sm font-medium text-slate-400 bg-transparent placeholder-slate-600
|
||||
@apply py-1.5 pl-1.5 w-full focus:outline-none relative text-sm font-medium text-slate-400 bg-transparent placeholder-slate-600
|
||||
}
|
||||
|
||||
.search-container svg {
|
||||
@apply w-5 h-5 text-inherit ml-2
|
||||
@apply w-5 h-5 text-slate-700 ml-2
|
||||
}
|
||||
|
||||
.dashboard-tabs {
|
||||
@apply overflow-y-hidden flex-grow flex flex-col items-stretch
|
||||
@apply overflow-y-hidden flex-grow h-full pb-12
|
||||
}
|
||||
|
||||
.tabs-list {
|
||||
@apply flex justify-between rounded pb-2 pt-0.5 items-center;
|
||||
@apply flex justify-between w-full rounded p-1 ;
|
||||
}
|
||||
.tab {
|
||||
@apply focus:outline-none transition-colors duration-200 ease-in-out flex items-center justify-center pt-2.5 px-0 mx-0 rounded min-w-[6.5rem] text-sm leading-none z-[1] font-display flex-grow hover:pointer-events-auto
|
||||
}
|
||||
.tab span,
|
||||
.tab-false span,
|
||||
.tab-true span {
|
||||
@apply min-w-min
|
||||
@apply focus:outline-none transition-colors duration-200 ease-in-out flex items-center justify-center py-2.5 px-0 font-display rounded min-w-[6rem] text-sm leading-none z-20
|
||||
}
|
||||
.tab-true {
|
||||
@apply text-slate-300/90 ;
|
||||
@apply text-slate-300 ;
|
||||
}
|
||||
.tab-false {
|
||||
@apply text-slate-600 hover:text-slate-500 ;
|
||||
@apply text-slate-600 hover:text-slate-500;
|
||||
}
|
||||
.tab-slider {
|
||||
@apply min-h-[34px] mr-auto min-w-[86px] left-0 relative z-[0] top-1 hover:scale-x-[100%] transition-all duration-200 ease-in-out rounded scale-x-[104%] from-primary-300/40 to-primary-300/60 border border-mirage-400/60 bg-gradient-to-br;
|
||||
/* clip-path: polygon(0px 0px, 0px 26px, 9px 36px, 90px 36px, 90px 0px, 0px 0px, 0px 0px ); */
|
||||
@apply from-primary-300/40 to-primary-300/60 border border-mirage-400/60 bg-gradient-to-br h-[34px] translate-x-0 w-[90px] absolute transition-transform duration-200 ease-in-out rounded
|
||||
}
|
||||
.graph-tab[aria-selected="true"] ~ div {
|
||||
@apply -translate-x-[300px] scale-x-[104%] cursor-pointer
|
||||
@apply translate-x-[0px] w-[98px]
|
||||
}
|
||||
.entities-tab[aria-selected="true"] ~ div {
|
||||
@apply -translate-x-[200px] scale-x-[110%] cursor-pointer
|
||||
@apply translate-x-[101px] w-[100px]
|
||||
}
|
||||
.market-tab[aria-selected="true"] ~ div {
|
||||
@apply -translate-x-[95px] scale-x-105 cursor-pointer
|
||||
@apply translate-x-[205px] w-[93px]
|
||||
}
|
||||
.tab-panel {
|
||||
@apply w-full relative px-2 pr-2.5;
|
||||
}
|
||||
@apply w-full relative;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Fragment, useEffect, useRef, useState } from "react";
|
||||
import { Link, Outlet, useLocation, useParams } from "react-router-dom";
|
||||
import { MagnifyingGlassIcon, PlusIcon, CloudIcon } from "@heroicons/react/24/outline";
|
||||
import { MagnifyingGlassIcon, PlusIcon } from "@heroicons/react/24/outline";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import GraphPanel from "./_components/tabs/GraphPanel";
|
||||
import EntitiesPanel from "./_components/tabs/EntitiesPanel";
|
||||
@@ -37,7 +37,7 @@ export default function DashboardPage() {
|
||||
const dispatch = useAppDispatch()
|
||||
const location = useLocation()
|
||||
const initialTab = location.pathname.includes("entity") ?
|
||||
0 : location.pathname.includes("market")
|
||||
1 : location.pathname.includes("market")
|
||||
? 2 : 0
|
||||
|
||||
const [tabIndex, setTabIndex] = useState<number>(initialTab)
|
||||
@@ -104,13 +104,12 @@ export default function DashboardPage() {
|
||||
onChange={setTabIndex}
|
||||
className={styles["dashboard-tabs"]}
|
||||
>
|
||||
<Tab.List className={`${styles["tabs-list"]}`}>
|
||||
<Tab.List className={styles["tabs-list"]}>
|
||||
<Tab as={Fragment}>
|
||||
{({ selected }) => (
|
||||
<Link
|
||||
to='graph'
|
||||
className={`${styles["tab"]} ${styles["graph-tab"]} ${styles[`tab-${selected}`]}`}
|
||||
aria-selected={selected}
|
||||
>
|
||||
Graphs
|
||||
</Link>
|
||||
@@ -121,11 +120,8 @@ export default function DashboardPage() {
|
||||
<Link
|
||||
to='entity'
|
||||
className={`${styles["tab"]} ${styles["entities-tab"]} ${styles[`tab-${selected}`]}`}
|
||||
aria-selected={selected}
|
||||
>
|
||||
<span>
|
||||
Entities
|
||||
</span>
|
||||
Entities
|
||||
</Link>
|
||||
)}
|
||||
</Tab>
|
||||
@@ -134,15 +130,14 @@ export default function DashboardPage() {
|
||||
<Link
|
||||
to='market'
|
||||
className={`${styles["tab"]} ${styles["market-tab"]} ${styles[`tab-${selected}`]}`}
|
||||
aria-selected={selected}
|
||||
>
|
||||
Market
|
||||
</Link>
|
||||
)}
|
||||
</Tab>
|
||||
<div className={styles["tab-slider"]} />
|
||||
<div className={styles["tab-slider"]}></div>
|
||||
</Tab.List>
|
||||
<div className="h-full overflow-y-scroll ">
|
||||
<section className="h-full overflow-y-scroll mb-20">
|
||||
<Tab.Panel className={styles["tab-panel"]}>
|
||||
<GraphPanel
|
||||
refetchGraphs={async () => await refetchGraphs()}
|
||||
@@ -165,7 +160,7 @@ export default function DashboardPage() {
|
||||
<Tab.Panel className={styles["tab-panel"]}>
|
||||
<MarketPanel />
|
||||
</Tab.Panel>
|
||||
</div>
|
||||
</section>
|
||||
</Tab.Group>
|
||||
{tabIndex !== 2 ? (
|
||||
<button
|
||||
@@ -176,14 +171,13 @@ export default function DashboardPage() {
|
||||
className='btn-primary mt-auto mb-4 mx-4 mr-6'
|
||||
>
|
||||
Create {tabIndex === 0 ? 'graph' : 'entity'}
|
||||
<PlusIcon className="!ml-7" />
|
||||
<PlusIcon />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className='btn-primary mx-4 mr-6 mt-auto mb-4'
|
||||
>
|
||||
Connect server plugins
|
||||
<CloudIcon />
|
||||
className='btn-primary mx-4 mr-6'
|
||||
>Add transforms
|
||||
|
||||
</button>
|
||||
)}
|
||||
</aside>
|
||||
|
||||
@@ -125,7 +125,7 @@ export default function EditEntityNode({ ctx, sendJsonMessage, closeRef }: JSONO
|
||||
/>
|
||||
);
|
||||
case 'empty':
|
||||
return <input className='h-0 bg-transparent pointer-events-none' />;
|
||||
return <div className='hidden' />;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,10 +4,10 @@ import {
|
||||
TableCellsIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { useState, useEffect, useCallback, useMemo, useLayoutEffect } from 'react';
|
||||
import { Link, useLocation, useParams } from 'react-router-dom';
|
||||
import { Link } from 'react-router-dom';
|
||||
import 'react-grid-layout/css/styles.css';
|
||||
import { Responsive, WidthProvider, Layout } from 'react-grid-layout';
|
||||
import { useGetEntitiesQuery, useRefreshEntityPluginsQuery } from '@src/app/api';
|
||||
import { useGetEntitiesQuery } from '@src/app/api';
|
||||
import { selectPositionMode, selectViewMode, setAllEdges, setAllNodes, setEditState, setNodeType, setPositionMode, setViewMode } from '@src/features/graph/graphSlice';
|
||||
import classNames from 'classnames';
|
||||
import { useAppDispatch, } from '@src/app/hooks';
|
||||
@@ -64,11 +64,11 @@ export const useResize = ({ minWidth }: UseResizeProps): UseResizeReturn => {
|
||||
export function EntityOption({ entity, onDragStart }: JSONObject) {
|
||||
return (
|
||||
<>
|
||||
<li key={entity.id} className='flex items-center w-full justify-between pb-2.5'>
|
||||
<li key={entity.id} className='flex items-center w-full justify-between py-3'>
|
||||
<div
|
||||
draggable
|
||||
onDragStart={(event) => onDragStart(event, entity.label)}
|
||||
className='flex min-w-[12rem] p-2 justify-between overflow-x-hidden from-mirage-400/50 to-mirage-400/40 hover:from-mirage-500/40 hover:to-mirage-400/60 bg-gradient-to-br hover:from-40% border-mirage-300/20 border max-h-[160px] border-l-primary-300/50 hover:border-primary-400 transition-colors duration-100 ease-out border-l-[6px] hover:border-l-[6px] rounded-md w-full backdrop-blur-md'
|
||||
className='flex min-w-[12rem] p-2 justify-between overflow-x-hidden from-mirage-300/10 to-mirage-300/20 bg-gradient-to-br hover:from-mirage-500/20 hover:from-40% hover:to-mirage-300/30 border-mirage-300/20 border max-h-[160px] border-l-primary-300/50 hover:border-primary-400 transition-colors duration-100 ease-out border-l-[6px] hover:border-l-[6px] rounded-md w-full'
|
||||
>
|
||||
<div className='flex flex-col w-full select-none'>
|
||||
<div className='flex items-start justify-between gap-x-3 w-full relative'>
|
||||
@@ -83,15 +83,16 @@ export function EntityOption({ entity, onDragStart }: JSONObject) {
|
||||
{entity.status}Installed
|
||||
</p> */}
|
||||
</div>
|
||||
<div className='flex flex-wrap items-center gap-x-2 text-xs leading-5 text-slate-500'>
|
||||
<p className='truncate whitespace-normal leading-5 line-clamp-2 text-slate-500'>
|
||||
{entity.description}
|
||||
<div className='mt-1 flex flex-wrap items-center gap-x-2 text-xs leading-5 text-slate-500'>
|
||||
<p className='truncate whitespace-normal leading-5 text-slate-500'>
|
||||
{' '}
|
||||
{entity.description && entity.description.length > 76 ?
|
||||
`${entity.description.slice(0, 76)}...` : entity.description}
|
||||
</p>
|
||||
<br />
|
||||
<p className='truncate flex items-center leading-5 text-slate-500 text-xs'>
|
||||
<svg viewBox='0 0 2 2' className='h-0.5 w-0.5 fill-current mr-1.5 ml-0'>
|
||||
<circle cx={1} cy={1} r={1} />
|
||||
</svg>
|
||||
<svg viewBox='0 0 2 2' className='h-0.5 w-0.5 fill-current'>
|
||||
<circle cx={1} cy={1} r={1} />
|
||||
</svg>
|
||||
<p className='truncate leading-5 text-slate-500 text-xs'>
|
||||
Created by {entity.author ? entity.author : 'the OSINTBuddy team'}
|
||||
</p>
|
||||
</div>
|
||||
@@ -107,20 +108,19 @@ const ResponsiveGridLayout = WidthProvider(Responsive);
|
||||
const MAX_GRAPH_LABEL_LENGTH = 22;
|
||||
|
||||
export default function EntityOptions({ positionMode, activeGraph, setElkLayout, toggleForceLayout, fitView }: JSONObject) {
|
||||
const { hid = "" } = useParams()
|
||||
const {
|
||||
data: entitiesData = { plugins: [], },
|
||||
data: entitiesData = { entities: [], count: 0, favorite_entities: [], favorite_count: 0 },
|
||||
isLoading,
|
||||
isSuccess,
|
||||
isError
|
||||
} = useRefreshEntityPluginsQuery({ hid })
|
||||
|
||||
} = useGetEntitiesQuery({ skip: 0, limit: 50 })
|
||||
const [showEntities, setShowEntities] = useState(true);
|
||||
const [searchFilter, setSearchFilter] = useState('');
|
||||
|
||||
const entities = useMemo(() => searchFilter
|
||||
? [...entitiesData?.plugins.filter((entity: JSONObject) => entity.label.toLowerCase().includes(searchFilter.toLowerCase()))]
|
||||
: [...entitiesData?.plugins], [searchFilter, entitiesData])
|
||||
? [...entitiesData?.entities.filter((entity: JSONObject) => entity.label.toLowerCase().includes(searchFilter.toLowerCase())),
|
||||
...entitiesData?.favorite_entities.filter((entity: JSONObject) => entity.label.toLowerCase().includes(searchFilter.toLowerCase()))]
|
||||
: [...entitiesData?.entities, ...entitiesData?.favorite_entities], [searchFilter, entitiesData])
|
||||
|
||||
const onDragStart = (event: DragEvent, nodeType: string) => {
|
||||
if (event?.dataTransfer) {
|
||||
@@ -195,7 +195,7 @@ export default function EntityOptions({ positionMode, activeGraph, setElkLayout,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className=' overflow-hidden rounded-md z-10 border border-mirage-400/60 from-mirage-500/30 to-mirage-500/60 bg-gradient-to-br flex flex-col h-min'
|
||||
className=' overflow-hidden rounded-md z-10 border border-mirage-400/70 from-mirage-700/95 to-mirage-500/95 bg-gradient-to-br flex flex-col h-min'
|
||||
key='entities'
|
||||
id='node-options-tour'
|
||||
>
|
||||
@@ -228,7 +228,7 @@ export default function EntityOptions({ positionMode, activeGraph, setElkLayout,
|
||||
</ol>
|
||||
{showEntities && (
|
||||
<>
|
||||
<div className='mt-2.5 hover:border-mirage-200/40 transition-colors duration-200 ease-in-out block justify-between items-center to-mirage-400/70 from-mirage-300/60 bg-gradient-to-br rounded border mb-2 mx-4 focus-within:!border-primary/40 px-3.5 py-1 text-slate-100 shadow-sm border-mirage-400/20 ring-light-900/10 focus-within:from-mirage-400/20 focus-within:to-mirage-400/30 focus-within:bg-gradient-to-l'>
|
||||
<div className='mt-2.5 hover:border-mirage-200/40 transition-colors duration-200 ease-in-out block justify-between items-center to-mirage-400/50 from-mirage-300/20 bg-gradient-to-br rounded border mb-2 mx-4 focus-within:!border-primary/40 px-3.5 py-1 text-slate-100 shadow-sm border-mirage-400/20 ring-light-900/10 focus-within:from-mirage-400/20 focus-within:to-mirage-400/30 focus-within:bg-gradient-to-l'>
|
||||
<input
|
||||
onChange={(e) => setSearchFilter(e.target.value)}
|
||||
className='block w-full placeholder:text-slate-700 bg-transparent outline-none sm:text-sm'
|
||||
@@ -245,7 +245,7 @@ export default function EntityOptions({ positionMode, activeGraph, setElkLayout,
|
||||
</div>
|
||||
<div
|
||||
key='positions'
|
||||
className=' overflow-hidden rounded-md z-10 border border-mirage-400/60 from-mirage-500/30 to-mirage-500/60 bg-gradient-to-br flex flex-col h-min'>
|
||||
className=' overflow-hidden rounded-md z-10 border border-mirage-400/70 from-mirage-700/95 to-mirage-500/95 bg-gradient-to-br flex flex-col h-min'>
|
||||
<ol className='text-sm flex select-none relative px-4 pt-1'>
|
||||
<li className='flex mr-auto'>
|
||||
<h5
|
||||
@@ -284,7 +284,7 @@ export default function EntityOptions({ positionMode, activeGraph, setElkLayout,
|
||||
type='button'
|
||||
className={classNames(
|
||||
'justify-center flex-grow rounded-l-md from-mirage-300/10 to-mirage-300/20 bg-gradient-to-br hover:from-mirage-500/20 hover:from-40% hover:to-mirage-300/30 border-mirage-300/20 relative py-3 inline-flex items-center border transition-colors duration-100 ease-in-out hover:border-primary-400/50 outline-none px-3 text-slate-500 hover:text-primary-300/80 focus:bg-mirage-800 hover:bg-mirage-600 focus:z-10',
|
||||
positionMode === 'manual' && 'bg-mirage-800/80 hover:bg-mirage-800 border-primary-400/50 hover:border-primary-400/50 '
|
||||
positionMode === 'manual' && 'bg-mirage-800 hover:bg-mirage-800 border-primary-400/50 hover:border-primary-400/50 '
|
||||
)}
|
||||
>
|
||||
<HandRaisedIcon
|
||||
@@ -301,7 +301,7 @@ export default function EntityOptions({ positionMode, activeGraph, setElkLayout,
|
||||
type='button'
|
||||
className={classNames(
|
||||
'justify-center flex-grow from-mirage-300/10 to-mirage-300/20 bg-gradient-to-br hover:from-mirage-500/20 hover:from-40% hover:to-mirage-300/30 border-mirage-300/20 relative py-3 inline-flex items-center border transition-colors duration-100 ease-in-out hover:border-primary-400/50 outline-none px-3 text-slate-500 hover:text-primary-300/80 focus:bg-mirage-800 hover:bg-mirage-600 focus:z-10',
|
||||
positionMode === 'force' && 'bg-mirage-800/80 hover:bg-mirage-800 border-primary-400/50 hover:border-primary-400/50 '
|
||||
positionMode === 'force' && 'bg-mirage-800 hover:bg-mirage-800 border-primary-400/50 hover:border-primary-400/50 '
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
@@ -326,7 +326,7 @@ export default function EntityOptions({ positionMode, activeGraph, setElkLayout,
|
||||
type='button'
|
||||
className={classNames(
|
||||
'justify-center flex-grow from-mirage-300/10 to-mirage-300/20 bg-gradient-to-br hover:from-mirage-500/20 hover:from-40% hover:to-mirage-300/30 border-mirage-300/20 relative py-3 inline-flex items-center border transition-colors duration-100 ease-in-out hover:border-primary-400/50 outline-none px-3 text-slate-500 hover:text-primary-300/80 focus:bg-mirage-800 hover:bg-mirage-600 focus:z-10',
|
||||
positionMode === 'right tree' && 'bg-mirage-800/80 hover:bg-mirage-800 border-primary-400/50 hover:border-primary-400/50 '
|
||||
positionMode === 'right tree' && 'bg-mirage-800 hover:bg-mirage-800 border-primary-400/50 hover:border-primary-400/50 '
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
@@ -348,7 +348,7 @@ export default function EntityOptions({ positionMode, activeGraph, setElkLayout,
|
||||
type='button'
|
||||
className={classNames(
|
||||
'justify-center rounded-r-md flex-grow from-mirage-300/10 to-mirage-300/20 bg-gradient-to-br hover:from-mirage-500/20 hover:from-40% hover:to-mirage-300/30 border-mirage-300/20 relative py-3 inline-flex items-center border transition-colors duration-100 ease-in-out hover:border-primary-400/50 outline-none px-3 text-slate-500 hover:text-primary-300/80 focus:bg-mirage-800 hover:bg-mirage-600 focus:z-10',
|
||||
positionMode === 'tree' && 'bg-mirage-800/80 hover:bg-mirage-800 border-primary-400/50 hover:border-primary-400/50 '
|
||||
positionMode === 'tree' && 'bg-mirage-800 hover:bg-mirage-800 border-primary-400/50 hover:border-primary-400/50 '
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useStore, getBezierPath, BaseEdge, EdgeLabelRenderer } from 'reactflow';
|
||||
|
||||
import { getEdgeParams } from './utils';
|
||||
import classNames from 'classnames';
|
||||
import { ChevronDoubleDownIcon, ChevronDownIcon } from '@heroicons/react/20/solid';
|
||||
|
||||
function EdgeLabel({ transform, label }: { transform: string; label: string }) {
|
||||
return (
|
||||
<>
|
||||
{label && (
|
||||
<div
|
||||
style={{ transform }}
|
||||
className="absolute border-2 bg-slate-800/5 pointer-events-auto cursor-grab font-semibold border-slate-700 p-1 px-3 rounded-md text-[0.6rem] leading-none capitalize text-slate-500 hover:text-slate-400/80 backdrop-blur-md flex items-center justify-between font-display"
|
||||
>
|
||||
<p className="flex items-center justify-between cursor-pointer">
|
||||
{label} <ChevronDownIcon className='h-4 w-4 ml-2 origin-center hover:rotate-3' />
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SimpleFloatingEdge({ id, source, target, markerEnd, style, label }: JSONObject) {
|
||||
const sourceNode = useStore(useCallback((store) => store.nodeInternals.get(source), [source]));
|
||||
const targetNode = useStore(useCallback((store) => store.nodeInternals.get(target), [target]));
|
||||
|
||||
if (!sourceNode || !targetNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(sourceNode, targetNode);
|
||||
|
||||
const [edgePath, labelX, labelY] = getBezierPath({
|
||||
sourceX: sx,
|
||||
sourceY: sy,
|
||||
sourcePosition: sourcePos,
|
||||
targetPosition: targetPos,
|
||||
targetX: tx,
|
||||
targetY: ty,
|
||||
});
|
||||
console.log(style)
|
||||
return (
|
||||
<>
|
||||
<BaseEdge
|
||||
id={id}
|
||||
path={edgePath}
|
||||
// strokeWidth={4}
|
||||
markerEnd={markerEnd}
|
||||
style={{ ...style, cursor: 'grab' }} />
|
||||
|
||||
<EdgeLabelRenderer>
|
||||
<EdgeLabel label={label?.replace("_", " ")} transform={`translate(-50%, -50%) translate(${labelX}px,${labelY}px)`} />
|
||||
{/* <div
|
||||
style={{
|
||||
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
||||
borderRadius: 5,
|
||||
fontSize: 12,
|
||||
fontWeight: 700,
|
||||
}}
|
||||
className="nodrag nopan absolute text-slate-400/95 p-1"
|
||||
>
|
||||
{label}
|
||||
</div> */}
|
||||
</EdgeLabelRenderer>
|
||||
{/* <path */}
|
||||
{/* /> */}
|
||||
</>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export default SimpleFloatingEdge;
|
||||
@@ -3,7 +3,7 @@ import { ChevronUpDownIcon, PaperClipIcon, XMarkIcon } from '@heroicons/react/24
|
||||
import { Combobox } from '@headlessui/react';
|
||||
import classNames from 'classnames';
|
||||
import { ChangeEvent, Dispatch, Fragment, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Handle, HandleType, Position } from 'reactflow';
|
||||
import { Handle, Position } from 'reactflow';
|
||||
import { GripIcon, Icon } from '@src/components/Icons';
|
||||
import { Dialog } from '@headlessui/react';
|
||||
import { useAppDispatch, useAppSelector, useComponentVisible } from '@src/app/hooks';
|
||||
@@ -19,6 +19,20 @@ import {
|
||||
} from '@src/features/graph/graphSlice';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
var dropdownKey = 0;
|
||||
|
||||
const getKey = () => {
|
||||
dropdownKey += 1;
|
||||
return `k_${dropdownKey}`;
|
||||
};
|
||||
|
||||
var nodeKey = 0;
|
||||
|
||||
const getNodeKey = () => {
|
||||
nodeKey += 1;
|
||||
return `k_${nodeKey}`;
|
||||
};
|
||||
|
||||
const handleStyle = { borderColor: '#39477899', background: '#12172720', width: 12, margin: -1, height: 12 };
|
||||
|
||||
type NodeElement = NodeInput & {
|
||||
@@ -29,10 +43,13 @@ type NodeElement = NodeInput & {
|
||||
};
|
||||
|
||||
|
||||
export default function ViewEntityNode({ ctx }: JSONObject) {
|
||||
export default function ViewEntityNode({
|
||||
ctx,
|
||||
dispatch
|
||||
}: JSONObject) {
|
||||
const node = ctx.data;
|
||||
const displayValue = useMemo(() => Array.isArray(node.elements[0]) ? node.elements[0][0]?.value : node.elements[0]?.value, [node.elements])
|
||||
|
||||
const displayValue = useMemo(() => Array.isArray(node.elements[0]) ? node.elements[0][0]?.value : node.elements[0]?.value, [node.elements])
|
||||
return (
|
||||
<>
|
||||
<Handle position={Position.Right} id='r1' key='r1' type='source' style={handleStyle} />
|
||||
@@ -50,7 +67,7 @@ export default function ViewEntityNode({ ctx }: JSONObject) {
|
||||
style={{ backgroundColor: node?.color?.length === 7 ? `${node.color}99` : node?.color }}
|
||||
className='header !rounded-full !p-3'
|
||||
>
|
||||
<Icon icon={node.icon} className='!h-14 !w-14 text-slate-300/95 cursor-grab focus:cursor-grabbing' />
|
||||
<Icon icon={node.icon} className='!h-20 !w-20 cursor-grab focus:cursor-grabbing' />
|
||||
</div>
|
||||
<h2 className={`absolute -left-28 -right-28 max-w-xl pointer-events-none text-center text-slate-500 text-lg top-full -bottom-10 h-auto`}>
|
||||
{displayValue?.length >= 90 ? `${displayValue.slice(0, 90)}...` : displayValue}
|
||||
@@ -7,19 +7,13 @@ import ReactFlow, {
|
||||
NodeDragHandler,
|
||||
Connection,
|
||||
Node,
|
||||
getViewportForBounds,
|
||||
MiniMap,
|
||||
ReactFlowInstance,
|
||||
getNodesBounds,
|
||||
getBoundsOfRects,
|
||||
getTransformForBounds,
|
||||
} from 'reactflow';
|
||||
import EditEntityNode from './EntityEditNode';
|
||||
import { addNodeUpdate, createEdge, disableEntityEdit, enableEntityEdit, graph, onEdgesChange, selectEditState, setEditLabel, setEditState, updateEdgeEvent, updateNodeFlow } from '@src/features/graph/graphSlice';
|
||||
import { useAppDispatch, useAppSelector, useEffectOnce } from '@src/app/hooks';
|
||||
import EditEntityNode from '../_components/EditEntityNode';
|
||||
import { addNodeUpdate, createEdge, disableEntityEdit, enableEntityEdit, onEdgesChange, selectEditState, setEditLabel, setEditState, updateEdgeEvent, updateNodeFlow } from '@src/features/graph/graphSlice';
|
||||
import { useAppDispatch, useAppSelector } from '@src/app/hooks';
|
||||
import { toast } from 'react-toastify';
|
||||
import ViewEntityNode from './EntityViewNode';
|
||||
import { CreateEntityOnDropApiResponse, useCreateEntityOnDropMutation, useRefreshEntityPluginsQuery } from '@src/app/api';
|
||||
import ViewEntityNode from '../_components/ViewEntityNode';
|
||||
import { CreateEntityOnDropApiResponse, useCreateEntityOnDropMutation, useRefreshPluginsQuery } from '@src/app/api';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import NewConnectionLine from './ConnectionLine';
|
||||
import SimpleFloatingEdge from './SimpleFloatingEdge';
|
||||
@@ -31,7 +25,6 @@ const viewOptions: FitViewOptions = {
|
||||
|
||||
// im lazy so im extending the generic JSONObject for now, feel free to fix...
|
||||
interface ProjectGraphProps extends JSONObject {
|
||||
graphInstance?: ReactFlowInstance
|
||||
}
|
||||
|
||||
export default function Graph({
|
||||
@@ -55,7 +48,7 @@ export default function Graph({
|
||||
[]
|
||||
);
|
||||
const { hid } = useParams()
|
||||
useRefreshEntityPluginsQuery({ hid: hid as string })
|
||||
useRefreshPluginsQuery({ hid: hid as string })
|
||||
|
||||
const onDragOver: DragEventHandler<HTMLDivElement> = useCallback((event) => {
|
||||
event.preventDefault();
|
||||
@@ -75,7 +68,7 @@ export default function Graph({
|
||||
if (typeof label === 'undefined' || !label) return;
|
||||
|
||||
const graphBounds = graphRef.current.getBoundingClientRect();
|
||||
const position = graphInstance?.project({
|
||||
const position = graphInstance.project({
|
||||
x: event.clientX - graphBounds.left,
|
||||
y: event.clientY - graphBounds.top,
|
||||
});
|
||||
@@ -121,7 +114,7 @@ export default function Graph({
|
||||
[]
|
||||
);
|
||||
|
||||
const doubleClickThreshold = 320;
|
||||
const doubleClickThreshold = 325;
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
const [isDoubleClick, setIsDoubleClick] = useState(false)
|
||||
const [entityPosition, setEntityPosition] = useState<any>({ x: null, y: null })
|
||||
@@ -137,31 +130,9 @@ export default function Graph({
|
||||
}
|
||||
};
|
||||
|
||||
const handleGraphRead = (readType: string = 'read') => {
|
||||
const viewport: any = graphInstance?.getViewport()
|
||||
if (viewport) {
|
||||
sendJsonMessage({
|
||||
action: `${readType}:graph`,
|
||||
viewport
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// TODO: implement loading/unloading nodes after drag
|
||||
// !isDragging && handleGraphRead()
|
||||
}, [isDragging])
|
||||
|
||||
useEffect(() => {
|
||||
graphInstance?.setViewport({ x: 0, y: 0, zoom: 0.22 })
|
||||
handleGraphRead('initial_read');
|
||||
}, [graphInstance?.getViewport])
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
onlyRenderVisibleElements={true}
|
||||
nodeDragThreshold={20}
|
||||
minZoom={0.22}
|
||||
minZoom={0.2}
|
||||
maxZoom={2.0}
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
@@ -183,12 +154,8 @@ export default function Graph({
|
||||
setClickDelta(newDelta)
|
||||
setIsDoubleClick(isDouble)
|
||||
}}
|
||||
onMoveStart={() => !isDragging && setIsDragging(true)}
|
||||
onMoveEnd={() => setIsDragging(false)}
|
||||
fitViewOptions={viewOptions}
|
||||
nodeTypes={nodeTypes}
|
||||
onDragStart={() => setIsDragging(true)}
|
||||
onDragEnd={() => setIsDragging(false)}
|
||||
panActivationKeyCode='Space'
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
onPaneClick={onPaneClick}
|
||||
@@ -197,8 +164,9 @@ export default function Graph({
|
||||
onSelectionContextMenu={onMultiSelectionCtxMenu}
|
||||
connectionLineComponent={NewConnectionLine}
|
||||
elevateNodesOnSelect={true}
|
||||
fitView
|
||||
>
|
||||
<Background size={2.5} variant={BackgroundVariant.Dots} className='bg-transparent' color='#334155e6' />
|
||||
<Background size={2} variant={BackgroundVariant.Dots} className='bg-transparent' color='#35426FAA' />
|
||||
</ReactFlow>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useStore, getBezierPath } from 'reactflow';
|
||||
|
||||
import { getEdgeParams } from './utils';
|
||||
|
||||
function SimpleFloatingEdge({ id, source, target, markerEnd, style }: JSONObject) {
|
||||
const sourceNode = useStore(useCallback((store) => store.nodeInternals.get(source), [source]));
|
||||
const targetNode = useStore(useCallback((store) => store.nodeInternals.get(target), [target]));
|
||||
|
||||
if (!sourceNode || !targetNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(sourceNode, targetNode);
|
||||
|
||||
const [edgePath] = getBezierPath({
|
||||
sourceX: sx,
|
||||
sourceY: sy,
|
||||
sourcePosition: sourcePos,
|
||||
targetPosition: targetPos,
|
||||
targetX: tx,
|
||||
targetY: ty,
|
||||
});
|
||||
|
||||
return (
|
||||
<path
|
||||
id={id}
|
||||
className="react-flow__edge-path"
|
||||
d={edgePath}
|
||||
strokeWidth={5}
|
||||
markerEnd={markerEnd}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default SimpleFloatingEdge;
|
||||