Compare commits

51 Commits

Author SHA1 Message Date
jerlendds
2e7f4934ca Add hackers code of conduct 2024-02-07 12:45:05 -07:00
jerlendds
3f1b9e89c8 Add config support to transform run
graph_inquiry.py, init_db.py, and osintbuddy-plugins
2024-01-21 11:13:04 -07:00
jerlendds
034563d910 Rename node endpoint file
api.py, graph_inquiry.py, node.py, and main.py
2024-01-20 15:50:48 -07:00
jerlendds
27ed0948a1 Add docs, update frontend deps
architecture-guide.md, entities-api.md, entities.md, how-to-contribute.md, getting-started.md, installation.md, roadmap.md, plugin-recipes.md, plugin-system.md, plugins-api.md, project-graphs.md, registry-api.md, settings-api.md, transforms-api.md, transforms.md, understanding-osintbuddy.md, writing-plugins.md, index.md, package.json, icg.svg, AppLayoutSidebar.tsx, Loaders.tsx, index.tsx, LandingPage.tsx, and 2 more files
2024-01-20 15:50:32 -07:00
jerlendds
f1d1c63c90 Update compose override
alpha-compose.override.yml
2024-01-19 22:29:17 -07:00
jerlendds
732780b68d Rework naming
entity.py, graph.py, node.py, deps.py, utils.py, init_db.py, entities.py, graphs.py, osintbuddy-plugins, api.ts, global.css, EntityEditor.tsx, PublicNavbar.tsx, and EntitiesPanel.tsx
2024-01-19 22:29:00 -07:00
jerlendds
92132ad08b Rework logo and polish compose cfg
node.py, logo-ext.svg, AppLayoutSidebar.tsx, and alpha-compose.override.yml
2024-01-19 17:13:15 -07:00
jerlendds
6904c242af Rework init_db load and entities for plugins update
entity.py, node.py, crud_entities.py, init_db.py, entities.py, backend.Dockerfile, and .gitignore
2024-01-19 10:50:32 -07:00
jerlendds
3363303afb Add gitignore for plugins mount
.gitignore
2024-01-19 10:46:39 -07:00
jerlendds
96e06db272 Update submodule
osintbuddy-plugins
2024-01-17 19:55:12 -07:00
jerlendds
65df5495eb Fix empty width bug
EntityEditNode.tsx
2024-01-17 19:54:56 -07:00
jerlendds
fa274711e8 Rework entity conventions
node.py, init_db.py, initial_data.py, osintbuddy-plugins, api.ts, global.css, nodes.css, Subpanel.tsx, GraphOverview.tsx, index.tsx, EntityOptions.tsx, Graph.tsx, and SimpleFloatingEdge.tsx
2024-01-14 18:27:52 -07:00
jerlendds
741381d9a7 Polish docker cfg
.env.alpha and alpha-compose.override.yml
2024-01-10 14:18:53 -07:00
jerlendds
ab9a7b21c6 Add new readme demo
README.md
2024-01-09 21:30:48 -07:00
jerlendds
f3392ab1d5 Fix styles and edges
logo-ext.svg, EntityEditor.tsx, index.tsx, and .env.alpha
2024-01-09 21:29:55 -07:00
jerlendds
281ec0a37b Fix entity editor styles
docs.css, global.css, and EntityEditor.tsx
2024-01-09 12:55:25 -07:00
jerlendds
2a173dea7d Rework naming
node.py
2024-01-09 12:21:10 -07:00
jerlendds
3a3e475068 Rework styles
Subpanel.tsx, subpanel.module.css, and index.module.css
2024-01-09 12:20:58 -07:00
jerlendds
6e6324fdd3 Fix subpanel slider
index.module.css
2024-01-05 15:30:53 -07:00
jerlendds
688b4d6653 Rework subpanel and grayscale styles
global.css, AppLayoutSidebar.tsx, index.module.css, index.tsx, and LandingPage.tsx
2024-01-04 22:09:20 -07:00
jerlendds
834b28c48e Fix subpanel alignment
AppLayoutSidebar.tsx
2024-01-04 19:00:04 -07:00
jerlendds
a63679ab04 Fix sidebar active page styles
Subpanel.tsx
2024-01-04 18:47:00 -07:00
jerlendds
afc9b5e25d Fix sidebar and graph overview styles
layouts.css, AppLayoutSidebar.tsx, Headers.tsx, Loaders.tsx, GraphDetails.tsx, GraphHeader.tsx, subpanel.module.css, index.module.css, and index.tsx
2024-01-04 18:23:39 -07:00
jerlendds
7a1256e28a Rework subpanel styles
subpanel.module.css
2024-01-04 15:50:45 -07:00
jerlendds
25d9d950c3 Rework subpanel styles
buttons.css, nodes.css, Subpanel.tsx, subpanel.module.css, EntityOptions.tsx, EntityViewNode.tsx, LandingPage.tsx, and index.tsx
2024-01-04 15:47:57 -07:00
jerlendds
2308efbff6 Fix flash on sidebar navigate
layouts.css and AppLayoutSidebar.tsx
2024-01-03 22:14:15 -07:00
jerlendds
43bb3a5a28 Rework styles all over
icg-gh.svg, icg-text.svg, logo-full.svg, logo-ghorg.png, logo-icg-gh-org.png, logo-icg-graphmark.png, logo-icg-graphmark.svg, logo-mark-primary.png, logo-mark-primary.svg, logo-mark.svg, logo-og-alter.svg, logo-og.svg, logo-primary.svg, logo-sm.svg, logo-watermark.svg, logo.bak.svg, logo.png, buttons.css, layouts.css, nodes.css, AppLayoutSidebar.tsx, PublicNavbar.tsx, GraphHeader.tsx, subpanel.module.css, and 5 more files
2024-01-03 22:11:04 -07:00
jerlendds
aff17088ae Rework panel styles
nodes.css, EntityEditor.tsx, index.tsx, EntityOptions.tsx, and LandingPage.tsx
2024-01-03 13:53:16 -07:00
jerlendds
b3d424bda4 Fix gridpanel and graph overview panel colors
index.tsx, GraphOverview.tsx, and index.module.css
2024-01-03 12:57:46 -07:00
jerlendds
231953956c Fix ws to prevent disconnect bug
node.py and session.py
2024-01-03 12:48:13 -07:00
jerlendds
76339e4b37 Add pool size for sqlalchemy
.env.alpha
2024-01-03 12:47:31 -07:00
jerlendds
0cf82e9cf3 Rework global background
icg-gh.png, icg-gh.svg, icg.svg, image-11ldby0i.png, image-1hmfchzd.png, image-4z9yhycr.png, image-bg.siteorigin.com-doublelined-overlay.png, image-bg.siteorigin.com-inflicted-overlay.png, logo-icg-gh-org.png, logo-icg-graphmark.png, logo-icg-graphmark.svg, logo.svg, global.css, nodes.css, AppLayoutSidebar.tsx, PublicLayout.tsx, Subpanel.tsx, index.module.css, EntityOptions.tsx, index.tsx, and LandingPage.tsx
2024-01-03 12:47:06 -07:00
jerlendds
ed4765f8ec Rework global background gradient to previous
global.css, AppLayoutSidebar.tsx, Subpanel.tsx, index.module.css, EntityOptions.tsx, EntityViewNode.tsx, and index.tsx
2024-01-02 12:01:04 -07:00
jerlendds
da1dbbf8b8 Fix landing page overflow
buttons.css, global.css, layouts.css, index.tsx, and LandingPage.tsx
2024-01-02 09:05:12 -07:00
jerlendds
e73769c049 Fix landing page mobile overflow bug
logo.svg, global.css, layouts.css, PublicNavbar.tsx, subpanel.module.css, index.module.css, EditEntityNode.tsx, EntityEditNode.tsx, EntityOptions.tsx, EntityViewNode.tsx, Graph.tsx, ViewEntityNode.tsx, and LandingPage.tsx
2024-01-02 00:07:31 -07:00
jerlendds
d60f803231 Rework earth fog color
LandingPage.tsx
2024-01-01 16:51:59 -07:00
jerlendds
98f9b38c4d Rework background gradient colors
global.css, subpanel.module.css, index.module.css, index.tsx, EntityOptions.tsx, Graph.tsx, index.tsx, and LandingPage.tsx
2024-01-01 16:50:28 -07:00
jerlendds
d0fce28f0a Rework styles
global.css, layouts.css, AppLayoutSidebar.tsx, subpanel.module.css, index.module.css, index.tsx, and LandingPage.tsx
2024-01-01 12:32:19 -07:00
jerlendds
f17d945c75 Rework theme
package.json, logo-og-alter.svg, logo-og.svg, logo.bak.svg, logo.svg, buttons.css, charts.css, global.css, layouts.css, AppLayoutSidebar.tsx, index.tsx, PublicNavbar.tsx, AppLayout.tsx, Subpanel.tsx, subpanel.module.css, index.module.css, index.tsx, EntityOptions.tsx, index.tsx, LandingPage.tsx, and yarn.lock
2024-01-01 03:23:12 -07:00
jerlendds
5102250f54 Polish frontend 2023-12-31 04:56:44 -07:00
jerlendds
2417ddecb3 Add ws graph read logic
Graph.tsx, ViewEntityNode.tsx, and index.tsx
2023-12-29 07:00:26 -07:00
jerlendds
ace9f06bad Rework init_db error logging
init_db.py and osintbuddy-plugins
2023-12-29 06:58:51 -07:00
jerlendds
461c6bea77 Rework User to UserInDBBase schema
account.py, config.py, entity.py, graph.py, node.py, deps.py, crud_graphs.py, and user.py
2023-12-29 05:42:30 -07:00
jerlendds
578e4b9662 Polish backend
new_account.mjml, reset_password.mjml, and test_email.mjml
2023-12-27 10:24:14 -07:00
jerlendds
be8cd35448 Add restart on fail to janusgraph image
alpha-compose.yml
2023-12-27 09:26:58 -07:00
jerlendds
7072a7c8e8 Rework inquiry-graph components to _components
ConnectionLine.tsx, Graph.tsx, SimpleFloatingEdge.tsx, utils.ts, ConnectionLine.tsx, Graph.tsx, SimpleFloatingEdge.tsx, utils.ts, index.tsx, and utils.ts
2023-12-27 09:26:46 -07:00
jerlendds
ead5bda91e Add wip 2023-12-25 08:37:15 -07:00
jerlendds
ecca61b662 Add wip 2023-12-25 08:36:42 -07:00
jerlendds
e8cf687002 Update reconnect notification type and message
index.tsx
2023-12-21 19:31:34 -07:00
jerlendds
16073e314a Add docs and update destruct of nodes to structuredClone
index.tsx
2023-12-21 18:02:14 -07:00
jerlendds
ded7af095c Update backend to use new osintbuddy pkg variable names 2023-12-21 18:01:27 -07:00
109 changed files with 2436 additions and 2573 deletions

View File

@@ -0,0 +1,46 @@
---
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 Executable file
View File

@@ -0,0 +1,7 @@
---
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 Executable file
View File

@@ -0,0 +1,7 @@
---
title: Entities
pageTitle: Entities
description:
---
Coming soon. ETA 12-24 weeks from June 10th 2023.

View File

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

View File

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

View File

@@ -0,0 +1,55 @@
---
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 Executable file
View File

@@ -0,0 +1,16 @@
---
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 %}

View File

@@ -0,0 +1,7 @@
---
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 Executable file
View File

@@ -0,0 +1,88 @@
---
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 Executable file
View File

@@ -0,0 +1,7 @@
---
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 Executable file
View File

@@ -0,0 +1,12 @@
---
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.
![ob-demo 2023-07-14 20-51](https://github.com/jerlendds/osintbuddy/assets/29207058/b69e08f3-ac2a-4cef-9a85-713e7df6b12f)

8
.github/osintbuddy.com/docs/registry-api.md vendored Executable file
View File

@@ -0,0 +1,8 @@
---
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 Executable file
View File

@@ -0,0 +1,7 @@
---
title: Settings API reference
pageTitle: Settings API reference
description:
---
Coming soon. ETA 12-24 weeks from June 10th 2023.

View File

@@ -0,0 +1,7 @@
---
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 Executable file
View File

@@ -0,0 +1,6 @@
---
title: Transforms
pageTitle: Transforms
description:
---

View File

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

View File

@@ -0,0 +1,72 @@
---
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 Executable file
View File

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

45
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,45 @@
# 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 dont care if youre liberal or conservative, Black or white, straight or gay, or anything in between!** In fact, we wont 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.

View File

@@ -37,7 +37,7 @@
🚧 <ins>Work in progress</ins> 🚧
<br/>
[2023-12-12_19-07.webm](https://github.com/jerlendds/osintbuddy/assets/29207058/307ddc96-251d-4830-9fdc-c2a3719de369)
[2024-01-09 20-55.webm](https://github.com/jerlendds/osintbuddy/assets/29207058/fb07fa95-30af-45aa-a75b-2fa1473bc37b)
⚠️ **Searching for contributors**, please contact me on the **[forum (work-in-progress)](https://forum.osintbuddy.com)**
@@ -233,8 +233,8 @@ Patched `aiogremlin` library: [jerlendds/gremlinpy](https://github.com/jerlendds
## [↑](#introducing-osintbuddy)Sponsor OSINTBuddy
Help us keep the project free and maintained.
Sponsor the OSINTBuddy project for unique benefits. Learn more on the [OSINTBuddy OpenCollective](https://opencollective.com/openinfolabs/projects/osintbuddy#category-CONTRIBUTE)
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)
[![Yearly OpenCollective Income](https://badgen.net/opencollective/yearly/osintbuddy)](https://opencollective.com/openinfolabs/projects/osintbuddy#category-CONTRIBUTE)
[![OpenCollective Backers](https://badgen.net/opencollective/backers/osintbuddy)](https://opencollective.com/openinfolabs/projects/osintbuddy#category-CONTRIBUTE)

View File

@@ -1,11 +1,11 @@
from fastapi import APIRouter
from app.core.config import settings
from .endpoints import entity, graph, node, login, scan, account, config
from .endpoints import entity, graph, graph_inquiry, login, scan, account, config
api_router = APIRouter(prefix=settings.API_V1_STR)
api_router.include_router(node.router, tags=["Nodes"])
api_router.include_router(graph_inquiry.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"])

View File

@@ -14,12 +14,12 @@ router = APIRouter(prefix="/account")
@router.get(
"/",
response_model=Union[schemas.User, schemas.HTTPError],
response_model=Union[schemas.UserInDBBase, schemas.HTTPError],
response_model_exclude_none=True
)
async def get_account(
request: APIRequest,
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
) -> models.User:
try:
return user

View File

@@ -15,7 +15,7 @@ router = APIRouter(prefix="/config")
@router.get("/casdoor", response_model=Any)
async def get_casdoor_config(
request: APIRequest,
user: Annotated[schemas.User, Depends(deps.get_user_from_session)]
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)]
):
sdk = request.app.state.CASDOOR_SDK
casdoor_apps = sdk.get_applications()

View File

@@ -1,8 +1,10 @@
import os, importlib, inspect
from uuid import UUID
from typing import Annotated
from app.schemas.entities import ENTITY_NAMESPACE
from osintbuddy import Registry
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.templates.default import plugin_source_template
from osintbuddy.utils.generic import to_snake_case
from fastapi import APIRouter, HTTPException, Depends
@@ -10,7 +12,6 @@ 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
@@ -19,15 +20,14 @@ router = APIRouter(prefix="/entity")
async def fetch_node_transforms(plugin_label):
plugin = await Registry.get_plugin(plugin_label=to_snake_case(plugin_label))
plugin = await EntityRegistry.get_plugin(plugin_label=to_snake_case(plugin_label))
if plugin is not None:
labels = plugin().transform_labels
return labels
return plugin().transform_labels
@router.get("/plugins/transform/")
async def get_entity_transforms(
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
user: Annotated[schemas.UserInDBBase, 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("Error inside entity.get_entity_transforms")
log.error(e)
log.error("Error inside entity.get_entity_transforms")
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="There was an error fetching transforms, please try again."
@@ -51,13 +51,12 @@ async def get_entity_transforms(
@router.get("/details/{hid}", response_model=schemas.Entity)
async def get_entity(
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
hid: Annotated[str, Depends(deps.get_entity_id)],
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
hid: Annotated[int, Depends(deps.get_entity_id)],
db: Annotated[Session, Depends(deps.get_db)],
):
try:
entities = crud.entities.get(db=db, id=hid)
return entities
return crud.entities.get(db=db, id=hid)
except Exception as e:
log.error('Error inside entity.get_entity:')
log.error(e)
@@ -72,7 +71,7 @@ async def get_entity(
response_model=schemas.AllEntitiesList
)
async def get_entities(
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
db: Annotated[Session, Depends(deps.get_db)],
skip: int = 0,
limit: int = 100,
@@ -80,6 +79,22 @@ 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,
@@ -94,16 +109,17 @@ async def get_entities(
)
entities = []
favorite_entities = []
for entity in db_entities:
entity = entity._asdict()
entity["id"] = deps.hid(db_id=entity.get("id"), ns=ENTITY_NAMESPACE)
entity["id"] = deps.get_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.hid(db_id=entity.get("id"), ns=ENTITY_NAMESPACE)
entity["id"] = deps.get_hid(db_id=entity.get("id"), ns=ENTITY_NAMESPACE)
favorite_entities.append(entity)
return {
"entities": entities,
"count": entities_count,
@@ -120,12 +136,12 @@ async def get_entities(
@router.post("")
async def create_entity(
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
entity: schemas.PostEntityCreate,
db: Annotated[Session, Depends(deps.get_db)],
):
try:
return crud.entities.create(db, obj_in=schemas.EntityCreate(
obj_in = schemas.EntityCreate(
label=entity.label,
author=entity.author,
description=entity.description,
@@ -134,7 +150,8 @@ 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)
@@ -146,19 +163,17 @@ async def create_entity(
@router.put("/{hid}")
async def update_entity_by_id(
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
hid: Annotated[str, Depends(deps.get_entity_id)],
obj_in: schemas.EntityBase,
obj_in: schemas.EntityUpdate,
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_uuid:')
log.error('Error inside entity.update_entity_by_id:')
log.error(e)
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
@@ -168,7 +183,7 @@ async def update_entity_by_id(
@router.delete("/{hid}")
async def delete_entity(
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
hid: Annotated[str, Depends(deps.get_entity_id)],
db: Annotated[Session, Depends(deps.get_db)],
):
@@ -185,7 +200,7 @@ async def delete_entity(
@router.put("/{hid}/favorite")
async def update_entity_favorite_id(
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
hid: Annotated[str, Depends(deps.get_entity_id)],
db: Annotated[Session, Depends(deps.get_db)],
):

View File

@@ -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 hid
from app.api.utils import get_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.User, Depends(deps.get_user_from_session)],
user: Annotated[schemas.UserInDBBase, 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.User, Depends(deps.get_user_from_session)],
user: Annotated[schemas.UserInDBBase, 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.User, Depends(deps.get_user_from_session)],
user: Annotated[schemas.UserInDBBase, 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.User, Depends(deps.get_user_from_session)],
user: Annotated[schemas.UserInDBBase, 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.User, Depends(deps.get_user_from_session)],
user: Annotated[schemas.UserInDBBase, 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.User, Depends(deps.get_user_from_session)],
user: Annotated[schemas.UserInDBBase, 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.User, Depends(deps.get_user_from_session)],
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
hid: Annotated[str, Depends(deps.get_graph_id)],
db: Annotated[Session, Depends(deps.get_db)],
):

View File

@@ -1,12 +1,14 @@
from uuid import UUID
from typing import List, Callable, Tuple, Any, Annotated
from typing import List, Callable, Tuple, Any, Annotated, Dict
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 Registry, PluginUse, load_plugins
from osintbuddy import EntityRegistry, TransformUse, EntityConfig
from osintbuddy.errors import OBPluginError
from sqlalchemy.orm import Session
from starlette import status
@@ -20,30 +22,19 @@ 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 Registry.get_plugin(plugin_label=plugin_label)
plugin = await EntityRegistry.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')
@@ -56,7 +47,6 @@ 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
@@ -117,7 +107,11 @@ async def save_node_on_drop(
}
}
async def load_initial_graph(uuid: UUID) -> tuple[list, list]:
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 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(
@@ -125,8 +119,20 @@ async def load_initial_graph(uuid: UUID) -> tuple[list, list]:
_g.project('from').by(_g.select('from')).unfold(),
_g.project('to').by(_g.select('to')).unfold()
).fold().toList()
nodes: list = await graph.V().valueMap(True).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?!
return nodes, edges
@@ -149,10 +155,13 @@ 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(action_type, send_json, project_uuid):
async def read_graph(viewport_event, send_json, project_uuid, is_initial_read: bool = False):
nodes = []
edges_data = []
data_nodes, edges = await load_initial_graph(project_uuid)
data_nodes, edges = await load_nodes_from_db(project_uuid, viewport_event)
if is_initial_read:
EntityRegistry.discover_plugins()
tmp_invalid_fix = []
tmp_invalid_id_fix = []
for node in data_nodes:
@@ -162,9 +171,9 @@ async def read_graph(action_type, send_json, project_uuid):
}
entity_id = node.pop(T.id)
entity_type = node.pop(T.label)
plugin = await Registry.get_plugin(to_snake_case(entity_type))
plugin = await EntityRegistry.get_plugin(to_snake_case(entity_type))
if plugin:
ui_entity = plugin.blueprint()
ui_entity = plugin.create()
for element in ui_entity['elements']:
if isinstance(element, list):
[node_to_blueprint_entity(
@@ -187,8 +196,8 @@ async def read_graph(action_type, send_json, project_uuid):
}
nodes.append(ui_entity)
else:
tmp_invalid_id_fix.append(entity_id)
# TODO detect invalid/renamed entities/plugins on any plugin label updates and fix graph
tmp_invalid_id_fix.append(entity_id)
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:
@@ -207,22 +216,24 @@ async def read_graph(action_type, send_json, project_uuid):
'type': 'float'
})
await send_json({
'action': 'isInitialRead',
'action': 'isInitialRead' if is_initial_read else 'read',
'nodes': nodes,
'edges': edges_data
})
async def update_node(node, action_type, send_json, uuid: UUID):
async def update_node(node, 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, action_type, send_json, uuid: UUID):
async def remove_nodes(node, send_json, uuid: UUID):
async with ProjectGraphConnection(uuid) as graph:
if targetNode := node.get('id'):
await graph.V(targetNode).drop().next()
@@ -243,23 +254,20 @@ async def get_transform_notification(transform_output, transform_type):
async def nodes_transform(
node: dict,
entity_context: dict,
send_json: Callable[[dict], None],
uuid: UUID
):
transform_result = {}
entity_type = node.get('data', {}).get('label')
transform_type = None
plugin = await Registry.get_plugin(entity_type)
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)
if plugin := plugin():
transform_type = node["transform"]
transform_result = await plugin.get_transform(
transform_result = await plugin.run_transform(
transform_type=transform_type,
entity=node,
use=PluginUse(
get_driver=deps.get_driver,
get_graph=lambda: None
)
transform_context=entity_context,
use=TransformUse(get_driver=deps.get_driver, config=EntityConfig(enabled=False, test=1, test2="two"))
)
async def create_entity_data(
graph: AsyncGraphTraversal,
@@ -298,13 +306,15 @@ async def nodes_transform(
async with ProjectGraphConnection(uuid) as graph:
if isinstance(transform_result, list):
out_result = [await create_entity_data(graph, result, node) for result in transform_result]
out_result = [await create_entity_data(graph, result, entity_context) for result in transform_result]
else:
out_result = list(await create_entity_data(graph, transform_result, node))
out_result = list(await create_entity_data(graph, transform_result, entity_context))
transform_notification = await get_transform_notification(transform_result, transform_type)
await send_json({
"action": "isLoading",
"detail": False,
"message": await get_transform_notification(transform_result, transform_type)
"message": transform_notification
})
await send_json({
"action": "createEntity",
@@ -314,101 +324,115 @@ async def nodes_transform(
await send_json({'error': 'noPluginFound'})
async def get_command_type(event):
async def run_user_graph_event(event: dict, send_json: Callable, uuid: UUID) -> None:
user_event: list = event["action"].split(":")
action = user_event[0]
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
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
ACTION_TARGET = user_event[1]
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 ACTION_TARGET == 'node':
if IS_UPDATE:
await update_node(event["node"], USER_ACTION, send_json, uuid)
await update_node(event["node"], send_json, uuid)
if IS_DELETE:
await remove_nodes(event["node"], USER_ACTION, send_json, uuid)
await remove_nodes(event["node"], 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.User, Depends(deps.get_user_from_ws)],
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_ws)],
hid: Annotated[int, Depends(deps.get_graph_id)],
db: Session = Depends(deps.get_db)
):
await websocket.accept()
is_project_active = True
EntityRegistry.discover_plugins()
graph_users[user.cid.hex] = websocket
active_inquiry = crud.graphs.get(db, id=hid)
if active_inquiry is None:
is_project_active = False
else:
await websocket.send_json({"action": "isLoading", "detail": True })
while is_project_active:
await websocket.accept()
if active_inquiry is not None:
await graph_users[user.cid.hex].send_json({"action": "isLoading", "detail": True })
else:
websocket.send({"action": "error"})
for user_cid, ws in graph_users.items():
try:
event: dict = await websocket.receive_json()
await run_user_graph_event(
event=event,
send_json=websocket.send_json,
uuid=active_inquiry.uuid
)
async for event in ws.iter_json():
await run_user_graph_event(
event=event,
send_json=ws.send_json,
uuid=active_inquiry.uuid
)
except OBPluginError as e:
await websocket.send_json({"action": "error", "detail": f"{e}"})
await websocket.send_json({"action": "isLoading", "detail": False })
await ws.send_json({"action": "error", "detail": f"{e}"})
await ws.send_json({"action": "isLoading", "detail": False })
log.error(e)
except (WebSocketException, BufferError, ConnectionClosedError) as e:
await websocket.send_json({"action": "isLoading", "detail": False })
except (WebSocketException, ConnectionClosedError) as e:
log.error("Exception inside node.active_project")
log.error(e)
is_project_active = False
await websocket.close()
except WebSocketDisconnect as e:
log.info(f"disconnected!")
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
@router.get("/refresh")
async def refresh_plugins(
async def refresh_entity_plugins(
hid: Annotated[str, Depends(deps.get_graph_id)],
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
user: Annotated[schemas.UserInDBBase, Depends(deps.get_user_from_session)],
db: Session = Depends(deps.get_db)
):
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."
)
# 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."
# )
@router.post("/")
async def create_entity_on_drop(
hid: Annotated[str, Depends(deps.get_graph_id)],
user: Annotated[schemas.User, Depends(deps.get_user_from_session)],
user: Annotated[schemas.UserInDBBase, 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 Registry.get_plugin(plugin_label=to_snake_case(create_node.label))
plugin = await EntityRegistry.get_plugin(plugin_label=to_snake_case(create_node.label))
if plugin:
blueprint = plugin.blueprint()
blueprint = plugin.create()
blueprint["position"] = create_node.position.model_dump()
return await save_node_on_drop(
create_node.label,

View File

@@ -1,9 +1,12 @@
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
@@ -13,13 +16,16 @@ 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, hid
from app.api.utils import APIRequest, HidChecker, get_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()
@@ -31,26 +37,56 @@ def get_db() -> Generator[Any, Session, Any]:
async def get_user_from_session(
request: APIRequest,
db: Session = Depends(get_db)
) -> schemas.User:
if 'dev' in settings.ENVIRONMENT:
return {}
) -> schemas.UserInDBBase:
if user := request.session.get("member"):
ob_user: schemas.User | None = crud.user.get_by_cid(db=db, cid=user.get("id"))
ob_user: schemas.UserInDBBase | None = crud.user.get_by_cid(db=db, cid=user.get("id"))
if ob_user:
return 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(),
)
raise HTTPException(status_code=401, detail="Unauthorized")
async def get_user_from_ws(
websocket: WebSocket,
db: Session = Depends(get_db)
) -> schemas.User:
if 'dev' in settings.ENVIRONMENT:
return {}
) -> schemas.UserInDBBase:
if user := websocket.session.get("member"):
ob_user: schemas.User | None = crud.user.get_by_cid(db=db, cid=user.get("id"))
ob_user: schemas.UserInDBBase | None = crud.user.get_by_cid(db=db, cid=user.get("id"))
if ob_user:
return 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(),
)
raise WebSocketException(code=401)

View File

@@ -34,7 +34,7 @@ if TYPE_CHECKING:
sqids = Sqids(alphabet=settings.SQIDS_ALPHABET, min_length=4)
def hid(db_id: int, ns: int = settings.SQIDS_NAMESPACE) -> str:
def get_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])

View File

@@ -1,5 +1,6 @@
from typing import List
from fastapi.encoders import jsonable_encoder
from sqlalchemy.orm import Session
from sqlalchemy import func, select
@@ -31,6 +32,11 @@ 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)

View File

@@ -45,7 +45,7 @@ class CRUDGraphs(CRUDBase[
def get_many_user_graphs(
self,
db: Session,
user: schemas.User,
user: schemas.UserInDBBase,
skip: int = 0,
limit: int = 50,
is_favorite: bool = True

View File

@@ -1,6 +1,8 @@
import inspect, time, importlib, imp
import requests
from sqlalchemy.orm import Session
from osintbuddy import load_plugin, Registry
from osintbuddy.plugins import EntityRegistry
from app import crud, schemas
from app.core.logger import get_logger
@@ -9,56 +11,61 @@ from .base import * # noqa
log = get_logger(__name__)
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'
}
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'
]
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 plugin_label, plugin_mod in core_plugins.items():
log.info(f'Loading core plugins: {plugin_label}')
for file_endpoint in core_entity_files:
log.info(f"Saving core plugin/entity file: {file_endpoint}")
try:
resp = requests.get(core_ob_url + plugin_mod + '.py')
load_initial_plugin(db=db, plugin_mod=plugin_mod, plugin_code=resp.text)
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()
except requests.exceptions.ConnectionError as e:
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
# 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)
return {
"status": "success",
"service": "[Entities: Create]",
"message": f"{len(Registry.plugins)} initial entities (plugins) loaded",
"message": f"{len(EntityRegistry.entities)} initial entities (plugins) loaded",
}

View File

@@ -1,9 +1,14 @@
import os
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.core.config import settings
engine = create_engine(settings.SQLALCHEMY_DATABASE_URI, pool_pre_ping=True)
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)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

View File

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

View File

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

View File

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

View File

@@ -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__":

View File

@@ -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, node
from app.api.api_v1.api import api_router, graph_inquiry
from app.core.config import settings, use_route_names_as_operation_ids
from app.core.casdoor import Config as CasdoorConfig
# if settings.SENTRY_DSN:

View File

@@ -3,7 +3,7 @@ from uuid import UUID
from typing import Optional, List
from pydantic import BaseModel, validator, ConfigDict
from app.api.utils import hid
from app.api.utils import get_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: hid(v, ENTITY_NAMESPACE))
)(lambda v: get_hid(v, ENTITY_NAMESPACE))
last_edited: datetime.datetime
updated: datetime.datetime
@@ -55,7 +55,8 @@ class EntityInDBBase(EntityBase):
class Entity(EntityInDBBase):
model_config = ConfigDict(from_attributes=True)
# model_config = ConfigDict(from_attributes=True)
pass
class EntityInList(PostEntityCreate):

View File

@@ -3,7 +3,7 @@ from uuid import UUID
from typing import Optional, List
from pydantic import BaseModel, validator
from app.api.utils import hid, sqids
from app.api.utils import get_hid, sqids
GRAPH_NAMESPACE = 5172
@@ -31,7 +31,7 @@ class GraphInDBBase(GraphsBase):
'id',
pre=True,
allow_reuse=True
)(lambda v: hid(v, GRAPH_NAMESPACE))
)(lambda v: get_hid(v, GRAPH_NAMESPACE))
class Config:
from_orm = True

View File

@@ -72,9 +72,6 @@ class User(UserCreate):
class UserInDBBase(UserCreate):
is_admin: bool
created_time: datetime.datetime
updated_time: datetime.datetime
cid: UUID
class Config:

View File

@@ -1,10 +1,16 @@
import osintbuddy
import osintbuddy as ob
from osintbuddy import load_local_plugins
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"),
@@ -12,6 +18,11 @@ print(
###############################################################################
# The following imports and variables have been included in your current shell:
###############################################################################
import osintbuddy
import osintbuddy as ob
from osintbuddy import load_local_plugins
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"))

View File

@@ -1,4 +1,4 @@
FROM python:3.11.4-slim-bullseye
FROM python:3.11.7-slim-bullseye
LABEL maintainer="jerlendds <support@forum.osintbuddy.com>"
WORKDIR /app/

View File

@@ -28,7 +28,8 @@ watchfiles==0.19.0
gremlinpy @ git+https://github.com/jerlendds/gremlinpy.git@eaba7dca12ad0156eb0d6d8ba2eb5751551c6a6d
warcio==1.7.4
sqids==0.4.0
sentry-sdk==1.33.1
sentry-sdk==1.39.1
boto3-stubs==1.28.75

1
backend/plugins/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.py

View File

@@ -28,5 +28,6 @@ watchfiles==0.19.0
gremlinpy @ git+https://github.com/jerlendds/gremlinpy.git@eaba7dca12ad0156eb0d6d8ba2eb5751551c6a6d
warcio==1.7.4
sqids==0.4.0
sentry-sdk==1.33.1
sentry-sdk==1.39.1

View File

@@ -3,9 +3,6 @@
"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",
@@ -13,6 +10,7 @@
"@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",

View File

@@ -1,9 +1,9 @@
import { emptyApi as api } from "./baseApi";
const injectedRtkApi = api.injectEndpoints({
endpoints: (build) => ({
refreshPlugins: build.query<
RefreshPluginsApiResponse,
RefreshPluginsApiArg
refreshEntityPlugins: build.query<
RefreshEntityPluginsApiResponse,
RefreshEntityPluginsApiArg
>({
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.entityBase,
body: queryArg.entityUpdate,
}),
}),
deleteEntity: build.mutation<DeleteEntityApiResponse, DeleteEntityApiArg>({
@@ -176,9 +176,9 @@ const injectedRtkApi = api.injectEndpoints({
overrideExisting: false,
});
export { injectedRtkApi as api };
export type RefreshPluginsApiResponse =
export type RefreshEntityPluginsApiResponse =
/** status 200 Successful Response */ any;
export type RefreshPluginsApiArg = {
export type RefreshEntityPluginsApiArg = {
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 */
| User
| UserInDbBase
| 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;
entityBase: EntityBase;
entityUpdate: EntityUpdate;
};
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 User = {
export type UserInDbBase = {
name: string;
username?: string | null;
email?: string | null;
@@ -325,7 +325,7 @@ export type User = {
is_admin: boolean;
created_time: string;
updated_time: string;
uuid: string;
cid: string;
};
export type Graph = {
label: string;
@@ -381,11 +381,11 @@ export type PostEntityCreate = {
author: string;
description: string;
};
export type EntityBase = {
label?: string;
author?: string;
description?: string;
source?: string;
export type EntityUpdate = {
label?: string | null;
author?: string | null;
description?: string | null;
source: string | null;
is_favorite?: boolean;
};
export type ScanMachineCreate = {
@@ -393,7 +393,7 @@ export type ScanMachineCreate = {
description: string;
};
export const {
useRefreshPluginsQuery,
useRefreshEntityPluginsQuery,
useCreateEntityOnDropMutation,
useGetCasdoorConfigQuery,
usePostSigninMutation,

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 KiB

View File

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

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 33 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

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

After

Width:  |  Height:  |  Size: 4.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 23 KiB

View File

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

Before

Width:  |  Height:  |  Size: 3.2 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

2
frontend/src/assets/images/logo.svg Executable file → Normal file

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -1,17 +1,29 @@
.btn-primary {
@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;
@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 ;
}
.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-5 w-5 h-5
@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
}
.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 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
@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
}
.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
}
@@ -22,11 +34,10 @@
@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
}

View File

@@ -1,6 +1,9 @@
.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
@@ -33,4 +36,7 @@
}
.bar-hz .ct-series-j .ct-bar {
@apply stroke-slate-200
}
.ct-label.ct-vertical.ct-end {
}

View File

@@ -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: 14px;
font-size: 0.8rem;
font-weight: 600;
line-height: 24.5px;
tab-size: 4;

View File

@@ -50,20 +50,28 @@ button {
::selection {
@apply text-slate-200 bg-radiance-500/50
}
/* 2d6cffb0 */
html {
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(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%);
background-color: rgb(7, 21, 35, 1);
background-image: url(../images/image-bg.siteorigin.com-inflicted-overlay.png );
}
/* 1hmfchzd.png */
/* https://gradienta.io/editor */
body {
@apply min-h-screen from-mirage-800/90 to-mirage-700/50 bg-gradient-to-br;
}
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 */
#root {
@apply flex flex-col min-h-screen relative bg-mirage-800/60
@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);
}
svg.icon {
@@ -84,6 +92,7 @@ button,
a,
figcaption {
@apply text-mirage-400 font-display;
/* color: #ff9602; */
}
/*
@@ -91,23 +100,23 @@ figcaption {
Works on Chrome, Edge, and Safari
*/
*::-webkit-scrollbar {
width: 8px;
width: 10px;
border-radius: none;
}
*::-webkit-scrollbar-track {
background: #19233433;
width: 8px;
width: 10px;
border-radius: none;
}
*::-webkit-scrollbar-thumb {
background-color: #1a1f2793;
border-radius: 2px;
border-right: 10px solid #1b243a84;
border-right: 9px solid #1b243a84;
}
* {
scrollbar-width: 8px;
scrollbar-width: 10px;
scrollbar-color: #1d222f62 #202d4333;
}
@@ -200,6 +209,7 @@ input[type='number'] {
}
.react-flow__attribution a {
@apply !text-slate-500;
}
.react-flow__edge-path {
@@ -293,7 +303,5 @@ 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 */

View File

@@ -1,25 +1,28 @@
.sidebar-link {
@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;
@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;
}
.sidebar-link.active {
@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 ;
@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
}
.sidebar-link.active button {
@apply bg-mirage-500 hover:bg-mirage-600 border-mirage-300/40 ;
@apply bg-mirage-500 border-mirage-300/40 ;
}
.sidebar-link.active svg {
@apply text-slate-400
@apply text-primary-300 -ml-0.5
}
.sidebar-link button {
@apply border border-mirage-400/40 transition-colors duration-200 hover:border-mirage-300 bg-mirage-500
@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
}
.sidebar-link button svg {
@apply text-slate-400
@apply origin-center rotate-0
}
.sidebar-link:hover button svg,
.sidebar-link.active button svg {
@apply !rotate-180
}
.sidebar-link button:hover svg {
@apply text-slate-300;
}

View File

@@ -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/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;
@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;
}
.node .side-header {
@@ -23,8 +23,6 @@
@apply leading-4;
}
.node .node-display h1,
.node .node-display h2,
.node .node-display p {
@@ -38,11 +36,11 @@
}
.node .header {
@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;
@apply flex h-full w-full items-center justify-between rounded-t-md text-slate-50/70 py-2 px-1 ;
}
.node .header svg {
@apply h-7 w-7;
@apply h-6 w-6;
}
.text-container {

View File

@@ -1,15 +1,16 @@
import classNames from "classnames";
import { ReactComponent as OSINTBuddyLogo } from "@images/logo.svg";
import { ReactComponent as OSINTBuddyLogomark } from "@images/logo-ext.svg"; // OSINTBuddy-text.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: "Dashboard", to: "/dashboard", icon: HomeIcon },
{ name: "Incidents *", to: "/incidents", icon: FolderOpenIcon },
{ name: "Scans *", to: "/scans", icon: DocumentMagnifyingGlassIcon },
{ 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 },
];
interface AppLayoutSidebarProps {
@@ -22,7 +23,7 @@ export default function AppLayoutSidebar({ showSidebar, toggleSidebar, setShowIn
return (
<div
className={classNames(
"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",
"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",
showSidebar ? "translate-x-0" : "-translate-x-52 border-r-2"
)}
>
@@ -34,7 +35,7 @@ export default function AppLayoutSidebar({ showSidebar, toggleSidebar, setShowIn
)}
>
<Link to="/" replace>
<OSINTBuddyLogo className="h-7 w-auto fill-slate-400" />
<OSINTBuddyLogomark className="h-7 ml-1 w-auto fill-slate-400" />
</Link>
<HamburgerMenu
@@ -44,86 +45,75 @@ export default function AppLayoutSidebar({ showSidebar, toggleSidebar, setShowIn
/>
</div>
<div className="flex flex-1 flex-col overflow-y-auto">
<nav className="flex-1 flex py-4 pt-1 flex-col">
<nav className="flex-1 flex flex-col">
{navigation.map((item) => (
<NavLink
key={item.name}
key={item.to}
to={item.to}
className={({ isActive }) =>
classNames(
isActive && "active",
"sidebar-link",
!showSidebar && "mx-0"
isActive && "active mx-2",
"sidebar-link ",
!showSidebar ? "mx-0 ml-0 mr-px " : 'mr-2.5'
)
}
>
<item.icon
className={classNames(
"transition-all",
location.pathname.includes(item.to)
? "text-slate-500"
: "",
"mr-3 flex-shrink-0 h-6 w-6 duration-100",
"transition-all mr-2 flex-shrink-0 h-6 w-6 duration-100",
location.pathname.includes(item.to) && "text-slate-400/30",
showSidebar
? "translate-x-0"
: "translate-x-[13.15rem]"
: "translate-x-[12.75rem] "
)}
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(
isActive && "active",
"sidebar-link mt-auto",
!showSidebar && "mx-0"
!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"
)
}
>
<CogIcon
className={classNames(
"transition-all",
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]"
"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] "
)}
aria-hidden="true"
/>
Settings
<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] "
)}
/>
Discord
</a>
</nav>
</div>
</div>

View File

@@ -12,25 +12,36 @@ import { UpdateEntityByIdApiArg } from '../../app/api';
import { useParams } from "react-router-dom";
import { toast } from "react-toastify";
export const tokyoNightTheme = auraInit({
export const auraTheme = auraInit({
settings: {
caret: "#c6c6c6",
caret: "#cbd5ef",
background: 'rgba(0 0 0 0)',
fontFamily: 'monospace',
gutterBackground: 'rgba(36, 46, 77, .16)',
selectionMatch: 'rgba(76, 86, 107, .00)',
lineHighlight: 'rgba(36, 46, 77, .26)',
fontFamily: 'Fira Code',
gutterBackground: 'rgba(36, 46, 107, .17)',
selectionMatch: 'rgba(76, 86, 107, .14)',
lineHighlight: 'rgba(36, 46, 77, .20)',
},
styles: [{ tag: t.comment, color: "#6272a4" }],
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" },
],
})
export function CodeEditor({ code, setCode }: JSONObject) {
return (
<CodeMirror
theme={tokyoNightTheme}
theme={auraTheme}
value={code}
onChange={(value) => setCode(value)}
extensions={[python()]}
className="text-sm font-semibold"
/>
)
}
@@ -65,7 +76,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 from-mirage-100/10 to-mirage-200/10 bg-gradient-to-tl from-50% flex flex-col h-full"
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"
key="b"
data-grid={{
x: 0,
@@ -78,7 +89,7 @@ export default function EntityEditor({ activeEntity, refetchEntity }: EntityEdit
minW: 4.5,
}}
>
<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">
<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">
<li className="flex items-start">
<div className="flex items-center">
<span className="text-slate-500 font-display truncate">
@@ -103,7 +114,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 ?? "", entityBase: { source: code } }).then(() => toast.info(
updateEntityById({ hid: activeEntity?.id ?? "", entityUpdate: { source: code as string } }).then(() => toast.info(
`The ${activeEntity?.label} entity has been saved.`
))
refetchEntity()
@@ -127,10 +138,8 @@ export default function EntityEditor({ activeEntity, refetchEntity }: EntityEdit
</div>
</li>
</ol>
<div className="container bg-t overflow-y-scroll h-full ">
<div className="editor ">
<CodeEditor code={code} setCode={setCode} />
</div>
<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>
</div>
</ResponsiveGridLayout >

View File

@@ -27,7 +27,7 @@ export function GridPanel({
return (
<>
<div
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 "
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 "
>
<ol className="text-sm flex select-none justify-between relative px-2 py-2">
<li className="flex items-start">

View File

@@ -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-300'>{title}</h4>}
{title && <h4 className='font-display text-sm font-medium text-primary-200'>{title}</h4>}
{header && (
<h1 className='font-display leading-4 text-xl tracking-tight text-slate-400 dark:text-slate-300'>{header}</h1>
)}

View File

@@ -2,12 +2,10 @@ import classNames from 'classnames';
import Sprite from '@assets/images/tabler-sprite.svg';
export const Icon = ({ icon, className }: { icon: string, className?: string }) => {
// TODO: Remove sprite and figure this approach out somehow
// const svgIcon = import(`@tabler/icons/${icon}.svg`).then(x => x).catch(() => <>why no work?!</>)
// TODO: Optimize this?
return (
<>
<svg className={className ? className : 'h-5 w-5'} fill='#fff' color='#fff'>
<svg className={className ? className : 'h-5 w-5'} fill="none" stroke="currentColor">
<use href={`${Sprite}#tabler-${icon}`} />
</svg>
</>

View File

@@ -23,16 +23,18 @@ 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-700/50 w-full block shadow sm:rounded-lg '>
<div className='bg-mirage-800/30 w-full block shadow rounded-b-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'>
<h1 className='font-display text-2xl tracking-tight text-slate-300'>
Under Construction
<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>
</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'>

View File

@@ -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 { GithubIcon } from './Icons';
import Logo from '@images/logo.svg';
import { Icon } from './Icons';
import Logo from '@images/logo-ext.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,28 +147,36 @@ export default function PublicNavbar(): React.ReactElement {
}, [])
return (
<header
<header
className={classNames(
'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',
'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',
isScrolled
? 'dark:bg-slate-900/95 dark:backdrop-blur dark:[@supports(backdrop-filter:blur(0))]:bg-slate-900/75'
? 'dark:bg-slate-800/95 dark:backdrop-blur dark:[@supports(backdrop-filter:blur(0))]:bg-slate-900/75'
: 'dark:bg-transparent'
)}
>
<div className="mr-6 flex lg:hidden">
<div className="flex lg:hidden">
<Link to="/">
<img className=" h-7 w-auto fill-slate-700 dark:fill-sky-100 block" src={Logo} />
</Link>
{/* @todo add mobile navigation */}
</div>
<div className="relative flex flex-grow basis-0 items-center">
<Link to="/">
<img className="hidden h-9 w-auto fill-slate-700 dark:fill-sky-100 lg:block" src={Logo} />
<img className="hidden h-9 fill-slate-700 dark:fill-sky-100 lg:block" src={Logo} />
</Link>
</div>
<div className="-my-5 mr-6 sm:mr-8 md:mr-0">
<div className="">
{/* <Search /> */}
</div>
<div className="relative flex basis-0 justify-end gap-6 sm:gap-8 md:flex-grow">
<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">
<a href="https://github.com/jerlendds/osintbuddy" className="group" aria-label="GitHub">
<GithubIcon className="h-6 w-6 fill-slate-400 group-hover:fill-slate-500 dark:group-hover:fill-slate-300" />
<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" />
</a>
</div>
</header>

View File

@@ -24,6 +24,8 @@ export interface Graph extends EditState {
project: ActiveProject;
viewMode: ProjectViewModes;
positionMode: PositionModes
showEdges: false,
shownEdgesByNodeId: JSONObject[]
}
const initialState: Graph = {
@@ -39,7 +41,9 @@ const initialState: Graph = {
name: '',
},
viewMode: 'edit',
positionMode: 'manual'
positionMode: 'manual',
showEdges: false,
shownEdgesByNodeId: []
};

View File

@@ -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>

View File

@@ -1,5 +1,5 @@
import { ReactElement, Suspense, lazy } from "react";
import { Route, RouterProvider, Routes, createBrowserRouter } from "react-router-dom";
import { Navigate, 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 Incidents = lazy(() => import("@routes/incidents"));
const Workspaces = lazy(() => import("@src/routes/workspaces"));
const ScansCreate = lazy(() => import("@routes/scans-create"))
const ScansOverview = lazy(() => import("@routes/scans"));
@@ -58,6 +58,10 @@ const router = createBrowserRouter([
path: 'dashboard',
element: <Suspense><DashboardPage /></Suspense>,
children: [
{
path: '',
element: <Navigate to="graph" replace />
},
{
path: 'market',
element: <Suspense><Market /></Suspense>
@@ -89,8 +93,8 @@ const router = createBrowserRouter([
element: <Suspense><Settings /></Suspense>
},
{
path: 'incidents',
element: <Suspense><Incidents /></Suspense>
path: 'workspaces',
element: <Suspense><Workspaces /></Suspense>
},
{
path: 'scans',

View File

@@ -6,7 +6,9 @@ export default function PublicLayout(): React.ReactElement {
return (
<>
<PublicNavbar />
<Outlet />
<main id="main-view">
<Outlet />
</main>
<ToastContainer
position='bottom-right'
autoClose={3000}

View File

@@ -7,18 +7,18 @@ import { FetchBaseQueryError } from "@reduxjs/toolkit/query"
import { SerializedError } from "@reduxjs/toolkit"
const MAX_DESCRIPTION_LENGTH = 63
const MAX_LABEL_LENGTH = 31
const MAX_DESCRIPTION_LENGTH = 79
const MAX_LABEL_LENGTH = 32
export function GraphLoaderCard() {
return (
<>
<div className="mb-2">
<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="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="space-y-3">
<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 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>
</div>
</div>
@@ -54,16 +54,16 @@ export default function Subpanel({
const { hid } = useParams();
return (
<div className={styles["subpanel"]}>
<div className={styles["subpanel-header"]} onClick={setShowEntities}>
<p>{label ?? ""}</p>
<ChevronDownIcon className={styles[`show-header-icon-${showEntities}`]} />
</div>
<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>
{showError && showEntities && !isLoading && (
<>
<p>
{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</>
{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</>
)}
</p>
</>
@@ -74,32 +74,33 @@ export default function Subpanel({
<GraphLoaderCard />
</>
)}
{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>
<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>
)
}

View File

@@ -92,9 +92,9 @@ export default function GraphDetails() {
return (
<>
<section className="flex flex-col h-screen w-full">
<div className="flex w-full">
<header className="flex w-full">
<GraphHeader refetchGraphs={async () => await refetchGraphs()} stats={graphStats} graph={activeGraph as Graph} />
</div>
</header>
<ResponsiveGridLayout
className="relative flex-grow h-full z-10 w-full"
rowHeight={1}

View File

@@ -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 border-mirage-400/20 -top-[2px] relative">
<div className="flex w-full border-b-2 bg-mirage-800/25 border-mirage-400/20 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'
className='btn-primary mb-3.5 stroke-primary-300/90 hover:!stroke-primary-400/80'
>
Open graph
<EyeIcon className='ml-2 w-5 h-5 ' />
<EyeIcon className='ml-2 w-5 h-5 stroke-inherit' />
</Link>
</div>
</section>
<hr className=" border-b border-mirage-400/20" />
<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">
<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">
{graph && stats && (
<>
<div className="flex items-center ml-[4px] gap-x-2">

View File

@@ -18,13 +18,13 @@ export default function GraphOverview() {
}, [graphsData])
return <>
<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">
<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 ">
{favoriteGraphs.length === 0 && graphs.length === 0 && (
<>
<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">
<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">
We're usually a treasure chest of knowledge,
but we couldn't find any graphs. Get started
by creating a new graph
@@ -33,15 +33,22 @@ export default function GraphOverview() {
)}
{(graphs.length > 0 || favoriteGraphs.length > 0) && (
<>
<h1 className="text-slate-300 text-3xl lg:text-4xl font-bold pt-12 sm:pt-14 lg:pt-8 ">
<h1 className="text-slate-300/80 text-3xl lg:text-4xl font-bold pr-2">
{graphs.length + favoriteGraphs.length ?? ''} {favoriteGraphs.length + graphs.length > 1 ? 'graphs' : 'graph'} available</h1>
<p className="py-6 md:py-8 max-w-md">
<p className="md:pt-4 max-w-xl text-slate-300/80">
Get started by selecting an existing graph from the sidebar
on the left, or try creating a new graph
on the left or you can create a new graph with the button located towards the bottom left
</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>
</>

View File

@@ -1,23 +1,26 @@
.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 h-full pr-2;
@apply flex flex-col justify-between relative flex-shrink overflow-clip;
}
.subpanel-section {
@apply py-1 px-1 flex flex-col;
}
.subpanel > p {
.subpanel > h2 {
@apply text-sm py-4 font-medium text-slate-500 focus:outline-none cursor-pointer px-2
}
.subpanel-header {
@apply flex pb-1 items-center hover:border-mirage-400/60 border-b transition-colors duration-100 ease-in-out border-transparent
@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
}
.subpanel-header p {
@apply text-sm px-1 font-black leading-3 text-slate-600 focus:outline-none cursor-pointer
.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 svg {
@apply h-5 w-5 mr-2 hover:rotate-3 origin-center text-slate-600 ml-auto transform focus:outline-none cursor-pointer
@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
}
.show-header-icon-true {
@apply !-rotate-180
}
@@ -27,36 +30,46 @@
}
.subpanel-link {
@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
@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
}
.subpanel-link div:hover .subpanel-label {
@apply !text-slate-400/95
@apply !text-slate-400/95
}
.subpanel-link-true {
@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
@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
}
.subpanel-link div {
@apply flex w-full p-3 flex-col items-start space-y-1.5 text-slate-400/30 hover:text-slate-400/60
@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
}
.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-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
@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
}
svg.link-icon-true {
@apply !text-primary-300 hover:!text-primary-200;
@apply !text-primary-300 hover:!text-primary-200 !bg-transparent;
}
svg.link-icon-false {
@apply text-slate-600
}
.link-active-true {
@apply !bg-transparent hover:!bg-slate-900 ;
@apply !bg-transparent hover:!bg-slate-800/40 transition-colors duration-200 ;
}
.subpanel-label {
@apply text-sm font-medium leading-none
@apply text-sm leading-4 font-display font-medium line-clamp-1 pb-0.5
}
.subpanel-label-true {
@apply text-slate-400/95;
@@ -65,8 +78,22 @@ svg.link-icon-false {
@apply text-slate-500/60;
}
.subpanel-desc {
@apply text-xs font-medium leading-none text-left;
@apply text-xs font-display font-light leading-none text-left;
}
.subpanel-desc-true {
@apply !text-slate-400/60
}
@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;
} */

View File

@@ -30,8 +30,8 @@ export default function EntitiesPanel({
const [updateEntityIsFavorite] = useUpdateEntityFavoriteIdMutation()
const updateEntityOnBookmark = async (hid: string) => {
await updateEntityIsFavorite({ hid })
const updateEntityOnFavorite = (hid: string) => {
updateEntityIsFavorite({ hid })
refetchEntities()
}
@@ -45,7 +45,7 @@ export default function EntitiesPanel({
isLoading={isLoading}
isSuccess={isSuccess}
items={favoriteEntities}
onClick={async (hid) => await updateEntityOnBookmark(hid)}
onClick={(hid) => updateEntityOnFavorite(hid)}
to="/dashboard/entity"
/>
<Subpanel
@@ -56,7 +56,7 @@ export default function EntitiesPanel({
isLoading={isLoading}
isSuccess={isSuccess}
items={entities}
onClick={async (hid) => await updateEntityOnBookmark(hid)}
onClick={(hid) => updateEntityOnFavorite(hid)}
to="/dashboard/entity"
/>
</section >

View File

@@ -1,51 +1,57 @@
/* 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] 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
@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
}
.search-container {
@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
@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
}
.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-slate-700 ml-2
@apply w-5 h-5 text-inherit ml-2
}
.dashboard-tabs {
@apply overflow-y-hidden flex-grow h-full pb-12
@apply overflow-y-hidden flex-grow flex flex-col items-stretch
}
.tabs-list {
@apply flex justify-between w-full rounded p-1 ;
@apply flex justify-between rounded pb-2 pt-0.5 items-center;
}
.tab {
@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
@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
}
.tab-true {
@apply text-slate-300 ;
@apply text-slate-300/90 ;
}
.tab-false {
@apply text-slate-600 hover:text-slate-500;
@apply text-slate-600 hover:text-slate-500 ;
}
.tab-slider {
@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
@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 ); */
}
.graph-tab[aria-selected="true"] ~ div {
@apply translate-x-[0px] w-[98px]
@apply -translate-x-[300px] scale-x-[104%] cursor-pointer
}
.entities-tab[aria-selected="true"] ~ div {
@apply translate-x-[101px] w-[100px]
@apply -translate-x-[200px] scale-x-[110%] cursor-pointer
}
.market-tab[aria-selected="true"] ~ div {
@apply translate-x-[205px] w-[93px]
@apply -translate-x-[95px] scale-x-105 cursor-pointer
}
.tab-panel {
@apply w-full relative;
}
@apply w-full relative px-2 pr-2.5;
}

View File

@@ -1,6 +1,6 @@
import { Fragment, useEffect, useRef, useState } from "react";
import { Link, Outlet, useLocation, useParams } from "react-router-dom";
import { MagnifyingGlassIcon, PlusIcon } from "@heroicons/react/24/outline";
import { MagnifyingGlassIcon, PlusIcon, CloudIcon } 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") ?
1 : location.pathname.includes("market")
0 : location.pathname.includes("market")
? 2 : 0
const [tabIndex, setTabIndex] = useState<number>(initialTab)
@@ -104,12 +104,13 @@ 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>
@@ -120,8 +121,11 @@ export default function DashboardPage() {
<Link
to='entity'
className={`${styles["tab"]} ${styles["entities-tab"]} ${styles[`tab-${selected}`]}`}
aria-selected={selected}
>
Entities
<span>
Entities
</span>
</Link>
)}
</Tab>
@@ -130,14 +134,15 @@ 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>
<div className={styles["tab-slider"]} />
</Tab.List>
<section className="h-full overflow-y-scroll mb-20">
<div className="h-full overflow-y-scroll ">
<Tab.Panel className={styles["tab-panel"]}>
<GraphPanel
refetchGraphs={async () => await refetchGraphs()}
@@ -160,7 +165,7 @@ export default function DashboardPage() {
<Tab.Panel className={styles["tab-panel"]}>
<MarketPanel />
</Tab.Panel>
</section>
</div>
</Tab.Group>
{tabIndex !== 2 ? (
<button
@@ -171,13 +176,14 @@ export default function DashboardPage() {
className='btn-primary mt-auto mb-4 mx-4 mr-6'
>
Create {tabIndex === 0 ? 'graph' : 'entity'}
<PlusIcon />
<PlusIcon className="!ml-7" />
</button>
) : (
<button
className='btn-primary mx-4 mr-6'
>Add transforms
className='btn-primary mx-4 mr-6 mt-auto mb-4'
>
Connect server plugins
<CloudIcon />
</button>
)}
</aside>

View File

@@ -125,7 +125,7 @@ export default function EditEntityNode({ ctx, sendJsonMessage, closeRef }: JSONO
/>
);
case 'empty':
return <div className='hidden' />;
return <input className='h-0 bg-transparent pointer-events-none' />;
}
};

View File

@@ -4,10 +4,10 @@ import {
TableCellsIcon,
} from '@heroicons/react/24/outline';
import { useState, useEffect, useCallback, useMemo, useLayoutEffect } from 'react';
import { Link } from 'react-router-dom';
import { Link, useLocation, useParams } from 'react-router-dom';
import 'react-grid-layout/css/styles.css';
import { Responsive, WidthProvider, Layout } from 'react-grid-layout';
import { useGetEntitiesQuery } from '@src/app/api';
import { useGetEntitiesQuery, useRefreshEntityPluginsQuery } 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 py-3'>
<li key={entity.id} className='flex items-center w-full justify-between pb-2.5'>
<div
draggable
onDragStart={(event) => onDragStart(event, entity.label)}
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'
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'
>
<div className='flex flex-col w-full select-none'>
<div className='flex items-start justify-between gap-x-3 w-full relative'>
@@ -83,16 +83,15 @@ export function EntityOption({ entity, onDragStart }: JSONObject) {
{entity.status}Installed
</p> */}
</div>
<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}
<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}
</p>
<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'>
<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>
Created by {entity.author ? entity.author : 'the OSINTBuddy team'}
</p>
</div>
@@ -108,19 +107,20 @@ 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 = { entities: [], count: 0, favorite_entities: [], favorite_count: 0 },
data: entitiesData = { plugins: [], },
isLoading,
isSuccess,
isError
} = useGetEntitiesQuery({ skip: 0, limit: 50 })
} = useRefreshEntityPluginsQuery({ hid })
const [showEntities, setShowEntities] = useState(true);
const [searchFilter, setSearchFilter] = useState('');
const entities = useMemo(() => searchFilter
? [...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])
? [...entitiesData?.plugins.filter((entity: JSONObject) => entity.label.toLowerCase().includes(searchFilter.toLowerCase()))]
: [...entitiesData?.plugins], [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/70 from-mirage-700/95 to-mirage-500/95 bg-gradient-to-br flex flex-col h-min'
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'
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/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'>
<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'>
<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/70 from-mirage-700/95 to-mirage-500/95 bg-gradient-to-br flex flex-col h-min'>
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'>
<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 hover:bg-mirage-800 border-primary-400/50 hover:border-primary-400/50 '
positionMode === 'manual' && 'bg-mirage-800/80 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 hover:bg-mirage-800 border-primary-400/50 hover:border-primary-400/50 '
positionMode === 'force' && 'bg-mirage-800/80 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 hover:bg-mirage-800 border-primary-400/50 hover:border-primary-400/50 '
positionMode === 'right tree' && 'bg-mirage-800/80 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 hover:bg-mirage-800 border-primary-400/50 hover:border-primary-400/50 '
positionMode === 'tree' && 'bg-mirage-800/80 hover:bg-mirage-800 border-primary-400/50 hover:border-primary-400/50 '
)}
>
<Icon

View File

@@ -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, Position } from 'reactflow';
import { Handle, HandleType, Position } from 'reactflow';
import { GripIcon, Icon } from '@src/components/Icons';
import { Dialog } from '@headlessui/react';
import { useAppDispatch, useAppSelector, useComponentVisible } from '@src/app/hooks';
@@ -19,20 +19,6 @@ 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 & {
@@ -43,13 +29,10 @@ type NodeElement = NodeInput & {
};
export default function ViewEntityNode({
ctx,
dispatch
}: JSONObject) {
export default function ViewEntityNode({ ctx }: JSONObject) {
const node = ctx.data;
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} />
@@ -67,7 +50,7 @@ export default function ViewEntityNode({
style={{ backgroundColor: node?.color?.length === 7 ? `${node.color}99` : node?.color }}
className='header !rounded-full !p-3'
>
<Icon icon={node.icon} className='!h-20 !w-20 cursor-grab focus:cursor-grabbing' />
<Icon icon={node.icon} className='!h-14 !w-14 text-slate-300/95 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}

View File

@@ -7,13 +7,19 @@ import ReactFlow, {
NodeDragHandler,
Connection,
Node,
getViewportForBounds,
MiniMap,
ReactFlowInstance,
getNodesBounds,
getBoundsOfRects,
getTransformForBounds,
} from 'reactflow';
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 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 { toast } from 'react-toastify';
import ViewEntityNode from '../_components/ViewEntityNode';
import { CreateEntityOnDropApiResponse, useCreateEntityOnDropMutation, useRefreshPluginsQuery } from '@src/app/api';
import ViewEntityNode from './EntityViewNode';
import { CreateEntityOnDropApiResponse, useCreateEntityOnDropMutation, useRefreshEntityPluginsQuery } from '@src/app/api';
import { useParams } from 'react-router-dom';
import NewConnectionLine from './ConnectionLine';
import SimpleFloatingEdge from './SimpleFloatingEdge';
@@ -25,6 +31,7 @@ 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({
@@ -48,7 +55,7 @@ export default function Graph({
[]
);
const { hid } = useParams()
useRefreshPluginsQuery({ hid: hid as string })
useRefreshEntityPluginsQuery({ hid: hid as string })
const onDragOver: DragEventHandler<HTMLDivElement> = useCallback((event) => {
event.preventDefault();
@@ -68,7 +75,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,
});
@@ -114,7 +121,7 @@ export default function Graph({
[]
);
const doubleClickThreshold = 325;
const doubleClickThreshold = 320;
const [isDragging, setIsDragging] = useState(false)
const [isDoubleClick, setIsDoubleClick] = useState(false)
const [entityPosition, setEntityPosition] = useState<any>({ x: null, y: null })
@@ -130,9 +137,31 @@ 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
minZoom={0.2}
onlyRenderVisibleElements={true}
nodeDragThreshold={20}
minZoom={0.22}
maxZoom={2.0}
nodes={nodes}
edges={edges}
@@ -154,8 +183,12 @@ 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}
@@ -164,9 +197,8 @@ export default function Graph({
onSelectionContextMenu={onMultiSelectionCtxMenu}
connectionLineComponent={NewConnectionLine}
elevateNodesOnSelect={true}
fitView
>
<Background size={2} variant={BackgroundVariant.Dots} className='bg-transparent' color='#35426FAA' />
<Background size={2.5} variant={BackgroundVariant.Dots} className='bg-transparent' color='#334155e6' />
</ReactFlow>
);
}

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More