Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add React Frontend #2094

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/publish-dockerhub.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Publish on Docker Hub

on: workflow_dispatch

jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2

- name: Login to Docker Hub
run: docker login -u ${{ secrets.DOCKERHUB_USERNAME }} -p ${{ secrets.DOCKERHUB_KEY }}

- name: Build base Sherlock image
run: docker build -t sherlock .

- name: Build and push Sherlock API image
run: cd sherlock-web/api &&
docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/sherlock-api:latest . &&
docker push ${{ secrets.DOCKERHUB_USERNAME }}/sherlock-api:latest

- name: Build and push sherlock-frontend image
run: |
docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/sherlock-web:latest -f ./sherlock-web/frontend/Dockerfile . &&
docker push ${{ secrets.DOCKERHUB_USERNAME }}/sherlock-web:latest
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ tests/.excluded_sites

# Vim swap files
*.swp

# JS Files
node_modules
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
   |   
<a href="#docker-notes">Docker Notes</a>
&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;
<a href="#web-ui">Web UI</a>
&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;
<a href="#contributing">Contributing</a>
</p>

Expand Down Expand Up @@ -133,6 +135,25 @@ You can use the `docker-compose.yml` file from the repository and use this comma
docker-compose run sherlock -o /opt/sherlock/results/text.txt user123
```

### Web UI
Before starting the Web UI you need to have in your machine the base image of sherlock and you can do this by running this code in the root of this project:

(In newer versions of docker you need to use "docker compose" instead.)

```
docker-compose build
```

You can then build and start the web UI by entering "sherlock-web" folder and running the command:

```
docker-compose up
```

The command will create an API that is only accessible by the frontend's server and a web UI that can be acessed by your preffered web browser in port 3000.

Keep in mind this web version has no user credentials or any kind of security check, thus it is not nearly production ready.

## Contributing
We would love to have you help us with the development of Sherlock. Each and every contribution is greatly valued!

Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ version: '2'
services:
sherlock:
build: .
image: sherlock
volumes:
- "./results:/opt/sherlock/results"
9 changes: 9 additions & 0 deletions sherlock-web/api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM sherlock

RUN pip3 install fastapi[all]

COPY main.py /app/

WORKDIR /app/

ENTRYPOINT ["uvicorn", "main:app", "--host=0.0.0.0", "--port=8000", "--reload"]
58 changes: 58 additions & 0 deletions sherlock-web/api/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import subprocess
import sys
from typing import Annotated
from pydantic import BaseModel

from fastapi import FastAPI, Query
from fastapi.responses import StreamingResponse

app = FastAPI()

class Body(BaseModel):
usernames: list[str]
sites: list[str]
f: list[str]


@app.post("/")
async def root(body: Body):
command = ["python3", "/opt/sherlock/sherlock/sherlock.py"]

usernames = body.usernames
sites = body.sites
f = body.f

if usernames:
for name in usernames:
command.append(name)

if sites:
for site in sites:
command.append("--site")
command.append(site)

if f:
for flag in f:
command.append("--"+flag)

process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=sys.stderr)

response = []

for line in iter(process.stdout.readline, b''):
line = line.decode("utf-8").rstrip("\n").rstrip("\r")[4:]

if line == "":
continue

if line.startswith("Checking username"):
continue

if line.startswith("Search complete"):
continue

data = line.split(": ")

response.append(dict(name= data[0], url= data[1]))

return response
18 changes: 18 additions & 0 deletions sherlock-web/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
services:
api:
build: ./api
volumes:
- ./api/main.py:/app/main.py

frontend:
build:
context: ../
dockerfile: ./sherlock-web/frontend/Dockerfile
command: npm run dev
volumes:
- ./frontend/app:/app/app/
- ./frontend/public:/app/public
ports:
- 3000:3000
depends_on:
- api
18 changes: 18 additions & 0 deletions sherlock-web/frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM node:20
WORKDIR /app/

COPY ./sherlock-web/frontend/package.json ./sherlock-web/frontend/package-lock.json ./
RUN npm i

COPY ./sherlock-web/frontend/tsconfig.json ./tsconfig.json
COPY ./sherlock-web/frontend/next-env.d.ts ./next-env.d.ts
COPY ./sherlock-web/frontend/next.config.mjs ./src/next.config.mjs

COPY ./sherlock-web/frontend/app/ ./app/
COPY ./sherlock-web/frontend/public/ ./public/

COPY ./sherlock/resources/data.json ./data.json

RUN npm run build

CMD npm run start
22 changes: 22 additions & 0 deletions sherlock-web/frontend/app/api/submit/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export async function POST(req: Request) {
const data = await req.json();

const { username, sites, withNSFW } = data;

const f = [];

if (withNSFW)
f.push("nsfw");

return await fetch("http://api:8000", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
usernames: [username],
sites,
f
})
});
}
126 changes: 126 additions & 0 deletions sherlock-web/frontend/app/components/form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
"use client"
import { Grid, Input, Divider, Stack, Checkbox, Button, Link, Typography } from "@mui/joy";

import SiteCheckbox from "./siteCheckbox";

import * as data from "../../data.json";
import { ChangeEvent, FormEvent, useState } from "react";
import { pageList } from "../page";

let sites: pageList = [];
let sitesWithNSFW: pageList = [];

const checkedSitesDefault: Record<string, boolean> = {};

const dataEntries = Object.entries(data).sort(([a], [b]) => a.toUpperCase() > b.toUpperCase() ? 1 : -1);

for (const [name, values] of dataEntries) {
if (name === "default")
continue;

const {url, isNSFW} = values as any;

const parsedData = {name, url};

sitesWithNSFW.push(parsedData);

checkedSitesDefault[name] = false;

if (!isNSFW)
sites.push(parsedData)
}

type FormProps = {
setFoundData: (data: pageList) => void
};

export default function Form({setFoundData}: FormProps) {
const [withNSFW, setWithNSFW] = useState(false);
const [loading, setLoading] = useState(false);

const [checkedSites, setCheckedSites] = useState(checkedSitesDefault);

const clickNSFW = (e: ChangeEvent<HTMLInputElement>) => setWithNSFW(e.target.checked);

const clickCheckAll = (e: ChangeEvent<HTMLInputElement>) => {
const {checked} = e.target;

let newCheckedSites = {...checkedSites};
for (let key in checkedSites)
newCheckedSites[key] = checked;

setCheckedSites(newCheckedSites);
};

const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();

setLoading(true);

const response = await fetch("/api/submit", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
username: e.currentTarget.username.value,
sites: Object.entries(checkedSites).filter(([_, value]) => value).map(([key, _]) => key),
withNSFW
})
});

let parsedData = await response.json();

parsedData = parsedData.sort((a: any, b: any) => a.name.toUpperCase() > b.name.toUpperCase() ? 1 : -1);

parsedData = parsedData.filter((item : any) => (item.url !== "Desired sites not found"));

setFoundData(parsedData);

setLoading(false);
};

const currentSite = withNSFW ? sitesWithNSFW : sites;

const required = !Object.values(checkedSites).some(value => value);

return (
<form style={{ height: "95%"}} onSubmit={onSubmit}>
<Stack direction="column" spacing={2} sx={{height: "100%"}}>
<Input name="username" required />

<Divider>
<Stack direction="row" spacing={3}>
<Checkbox label="With NSFW" onChange={clickNSFW} />
<Checkbox label="Check All" onChange={clickCheckAll} />
</Stack>
</Divider>

<Grid container spacing={2} columns={{ xs: 4, sm: 6, md: 8, lg: 10, xl: 12 }} sx={{maxHeight: "100%", overflow: "auto"}} flexGrow={1}>
{currentSite.map(
({name, url}) => {
const change = (value: boolean) => setCheckedSites({
...checkedSites,
[name]: value
});

return (
<SiteCheckbox
name={name}
url={url}
key={"site-"+name}
checked={checkedSites[name]}
onChange={change}
required={required}
/>
);
}
)}
</Grid>

<Button type="submit" loading={loading}>Send</Button>
</Stack>
</form>
);
}

29 changes: 29 additions & 0 deletions sherlock-web/frontend/app/components/siteCheckbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"use client"
import { Checkbox, Grid, Tooltip } from "@mui/joy"
import { ChangeEvent } from "react";

type props = {
name: string,
url: string,
checked: boolean | undefined,
onChange: (value: boolean) => void,
required: boolean,
}

export default function SiteCheckBox({name, url, checked, onChange, required}: props) {
const change = (e: ChangeEvent<HTMLInputElement>) => onChange(e.target.checked);

return (
<Grid xs={2} sm={2} md={2} lg={2} >
<Tooltip title={url}>
<Checkbox
name={"site["+name+"]"}
label={name}
checked={checked}
onChange={change}
required={required}
/>
</Tooltip>
</Grid>
);
}
Binary file added sherlock-web/frontend/app/favicon.ico
Binary file not shown.
16 changes: 16 additions & 0 deletions sherlock-web/frontend/app/global.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
html,
body,
#__next {
background: #000000;
}

#__next {
width: 100%;
height: 100%;
}

body {
width: calc(100vw - 5vh);
height: 95vh;
margin: 2.5vh;
}
4 changes: 4 additions & 0 deletions sherlock-web/frontend/app/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module '*.json' {
const value: any;
export default value;
}
Loading