diff --git a/.github/install-guide.md b/.github/install-guide.md
index 45ddde3a..73550fca 100644
--- a/.github/install-guide.md
+++ b/.github/install-guide.md
@@ -11,7 +11,7 @@ The Docker setup is done with docker-compose
1. Make sure you have [Docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/) installed.
2. Download Shuffle
```bash
-git clone https://github.com/frikky/Shuffle
+git clone https://github.com/Shuffle/Shuffle
cd Shuffle
```
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index cf051999..00006b66 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -39,7 +39,7 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml
new file mode 100644
index 00000000..716b42db
--- /dev/null
+++ b/.github/workflows/docker-build.yaml
@@ -0,0 +1,66 @@
+name: docker-build
+
+on:
+ push:
+ branches: launch
+jobs:
+ main:
+ runs-on: ubuntu-latest
+ continue-on-error: ${{ matrix.experimental }}
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - app: frontend
+ path: frontend
+ version: 1.0.0
+ experimental: true
+ - app: backend
+ path: backend
+ version: 1.0.0
+ experimental: false
+ - app: orborus
+ path: functions/onprem/orborus
+ version: 1.0.0
+ experimental: false
+ - app: database
+ path: backend/database
+ version: 1.0.0
+ experimental: false
+ steps:
+ -
+ name: Checkout
+ uses: actions/checkout@v2
+ -
+ name: Set up QEMU
+ uses: docker/setup-qemu-action@v1
+ -
+ name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v1
+ -
+ name: Login to DockerHub
+ uses: docker/login-action@v1
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+# Use below configuration for ghcr.io
+# with:
+# registry: ghcr.io
+# username: ${{ github.repository_owner }}
+# password: ${{ secrets.CR_PAT }}
+ -
+ name: Build and push
+ id: docker_build
+ uses: docker/build-push-action@v2
+ env:
+ BUILDX_NO_DEFAULT_LOAD: true
+ with:
+ context: ${{ matrix.path }}/
+ file: ${{ matrix.path }}/Dockerfile
+ platforms: linux/amd64,linux/arm64
+ #,linux/386 - no node image I guess?
+ push: true
+ tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.app }}:${{ matrix.version }}
+ -
+ name: Image digest
+ run: echo ${{ steps.docker_build.outputs.digest }}
diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml
new file mode 100644
index 00000000..b3d1927e
--- /dev/null
+++ b/.github/workflows/release-please.yml
@@ -0,0 +1,13 @@
+on:
+ push:
+ branches:
+ - launch
+name: release-please
+jobs:
+ release-please:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: google-github-actions/release-please-action@v3
+ with:
+ release-type: node
+ package-name: release-please-action
diff --git a/.github/workflows/upload_sdk.yml b/.github/workflows/upload_sdk.yml
index f76e9adb..9faa33e4 100644
--- a/.github/workflows/upload_sdk.yml
+++ b/.github/workflows/upload_sdk.yml
@@ -1,12 +1,12 @@
# This is a basic workflow to help you get started with Actions
-name: CI
+name: App SDK upload
# Controls when the workflow will run
on:
# Triggers the workflow on push or pull request events but only for the main branch
push:
- branches: [ master ]
+ branches: [ master, launch ]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
diff --git a/SECURITY.md b/SECURITY.md
index 3c372911..51662a7d 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -2,7 +2,7 @@
## Supported Versions
-Shuffle is currently still in beta, but we aim to support older version with critical severity issues, but do advise you to stay up to date with Major versions.
+Shuffle is now live in version 1.0.0, but we aim to support older version with critical severity security issues, but do advise you to stay up to date with Major versions.
| Version | Supported |
| ------- | ------------------ |
@@ -11,9 +11,9 @@ Shuffle is currently still in beta, but we aim to support older version with cri
## Reporting a Vulnerability
-Reporting a vulnerability can either be done to (frikky@shuffler.io)[mailto:frikky@shuffler.io] or [through the contact page on our website](https://shuffler.io/contact)
+Reporting a vulnerability can either be done to [support@shuffler.io](mailto:support@shuffler.io) or [through the contact page on our website](https://shuffler.io/contact)
-Security.txt: https://shuffler.io/.well_known/security.txt
+Security.txt: https://shuffler.io/.well-known/security.txt
-When a >medium severity vulnerability is discovered, expect it to be fixed ASAP - please nag us until it is. Security is a top priority, and we expect you to keep us accountable.
+When a >medium severity vulnerability is discovered, expect it to be fixed ASAP - please nag us until it is otherwise. Security is a top priority, and we expect and hope you hold us accountable.
In the case it makes sense, we'll further create a security advisory, and publish a new CVE for your new glorious finding.
diff --git a/docker-compose.yml b/docker-compose.yml
index c4eb10ef..4451238d 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,8 +1,7 @@
version: '3'
services:
frontend:
- #build: ./frontend
- image: ghcr.io/shuffle/shuffle-frontend:nightly
+ image: ghcr.io/shuffle/shuffle-frontend:latest
container_name: shuffle-frontend
hostname: shuffle-frontend
ports:
@@ -16,8 +15,7 @@ services:
depends_on:
- backend
backend:
- build: ./backend
- image: ghcr.io/shuffle/shuffle-backend:nightly
+ image: ghcr.io/shuffle/shuffle-backend:latest
container_name: shuffle-backend
hostname: ${BACKEND_HOSTNAME}
# Here for debugging:
@@ -29,7 +27,6 @@ services:
- /var/run/docker.sock:/var/run/docker.sock
- ${SHUFFLE_APP_HOTLOAD_LOCATION}:/shuffle-apps:z
- ${SHUFFLE_FILE_LOCATION}:/shuffle-files:z
- #- ${SHUFFLE_OPENSEARCH_CERTIFICATE_FILE}:/shuffle-files/es_certificate
env_file: .env
environment:
#- DOCKER_HOST=tcp://docker-socket-proxy:2375
@@ -37,7 +34,7 @@ services:
- SHUFFLE_FILE_LOCATION=/shuffle-files
restart: unless-stopped
orborus:
- image: ghcr.io/shuffle/shuffle-orborus:nightly
+ image: ghcr.io/shuffle/shuffle-orborus:latest
container_name: shuffle-orborus
hostname: shuffle-orborus
networks:
diff --git a/frontend/public/images/demo1.png b/frontend/public/images/demo1.png
new file mode 100644
index 00000000..4cd8faf1
Binary files /dev/null and b/frontend/public/images/demo1.png differ
diff --git a/frontend/public/images/demo2.png b/frontend/public/images/demo2.png
new file mode 100644
index 00000000..2d98b193
Binary files /dev/null and b/frontend/public/images/demo2.png differ
diff --git a/frontend/public/images/demo3.png b/frontend/public/images/demo3.png
new file mode 100644
index 00000000..cbe2ed96
Binary files /dev/null and b/frontend/public/images/demo3.png differ
diff --git a/frontend/public/images/testing.png b/frontend/public/images/testing.png
new file mode 100644
index 00000000..032c94a8
Binary files /dev/null and b/frontend/public/images/testing.png differ
diff --git a/frontend/src/components/AppGrid1.jsx b/frontend/src/components/AppGrid1.jsx
new file mode 100644
index 00000000..8706ef17
--- /dev/null
+++ b/frontend/src/components/AppGrid1.jsx
@@ -0,0 +1,377 @@
+import React, {useEffect, useState} from 'react';
+
+import ReactGA from 'react-ga';
+import { useTheme } from '@material-ui/core/styles';
+import {Link} from 'react-router-dom';
+
+import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon } from '@material-ui/icons';
+
+import algoliasearch from 'algoliasearch/lite';
+import { InstantSearch, Configure, connectSearchBox, connectHits, connectHitInsights } from 'react-instantsearch-dom';
+
+import aa from 'search-insights'
+
+import {
+ Zoom,
+ Grid,
+ Paper,
+ TextField,
+ ButtonBase,
+ InputAdornment,
+ Typography,
+ Button,
+ Tooltip
+} from '@material-ui/core';
+
+const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240")
+//const searchClient = algoliasearch("L55H18ZINA", "a19be455e7e75ee8f20a93d26b9fc6d6")
+const AppGrid1 = props => {
+ const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, userdata, searchValue } = props
+
+ const isCloud =
+ window.location.host === "localhost:3000" ||
+ window.location.host === "shuffler.io";
+
+ const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows
+ const xs = parsedXs === undefined || parsedXs === null ? isMobile ? 6 : 2 : parsedXs
+ const theme = useTheme();
+ //const [apps, setApps] = React.useState([]);
+ //const [filteredApps, setFilteredApps] = React.useState([]);
+ const [formMail, setFormMail] = React.useState("");
+ const [message, setMessage] = React.useState("");
+ const [formMessage, setFormMessage] = React.useState("");
+
+ const buttonStyle = {borderRadius: 30, height: 50, width: 220, margin: isMobile ? "15px auto 15px auto" : 20, fontSize: 18,}
+
+ const innerColor = "rgba(255,255,255,0.65)"
+ const borderRadius = 3
+ window.title = "Shuffle | Apps | Find and integrate any app"
+
+ const submitContact = (email, message) => {
+ const data = {
+ "firstname": "",
+ "lastname": "",
+ "title": "",
+ "companyname": "",
+ "email": email,
+ "phone": "",
+ "message": message,
+ }
+
+ const errorMessage = "Something went wrong. Please contact frikky@shuffler.io directly."
+
+ fetch(globalUrl+"/api/v1/contact", {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(data),
+ })
+ .then(response => response.json())
+ .then(response => {
+ if (response.success === true) {
+ setFormMessage(response.reason)
+ //alert.info("Thanks for submitting!")
+ } else {
+ setFormMessage(errorMessage)
+ }
+
+ setFormMail("")
+ setMessage("")
+ })
+ .catch(error => {
+ setFormMessage(errorMessage)
+ console.log(error)
+ });
+ }
+
+ const SearchBox = ({currentRefinement, refine, isSearchStalled} ) => {
+
+ useEffect(() => {
+ if (window !== undefined && window.location !== undefined && window.location.search !== undefined && window.location.search !== null) {
+ const urlSearchParams = new URLSearchParams(window.location.search)
+ const params = Object.fromEntries(urlSearchParams.entries())
+ const foundQuery = {searchValue}
+ if (foundQuery !== null && foundQuery !== undefined) {
+ console.log("Got query: ", foundQuery)
+ refine(foundQuery)
+ }
+ }
+ }, [])
+
+ return (
+
+ )
+ }
+
+ var workflowDelay = -50
+ const Hits = ({ hits, insights }) => {
+ const [mouseHoverIndex, setMouseHoverIndex] = useState(-1)
+ var counted = 0
+
+ //console.log(hits)
+ //var curhits = hits
+ //if (hits.length > 0 && defaultApps.length === 0) {
+ // setDefaultApps(hits)
+ //}
+
+ //const [defaultApps, setDefaultApps] = React.useState([])
+ //console.log(hits)
+ //if (hits.length > 0 && hits.length !== innerHits.length) {
+ // setInnerHits(hits)
+ //}
+
+ return (
+
+ {hits.map((data, index) => {
+
+ workflowDelay += 50
+
+ const paperStyle = {
+ backgroundColor: index === mouseHoverIndex ? "rgba(255,255,255,0.8)" : theme.palette.inputColor,
+ color: index === mouseHoverIndex ? theme.palette.inputColor : "rgba(255,255,255,0.8)",
+ border: `1px solid ${innerColor}`,
+ padding: 15,
+ cursor: "pointer",
+ position: "relative",
+ minHeight: 116,
+ }
+
+ if (counted === 12/xs*rowHandler) {
+ return null
+ }
+
+ counted += 1
+ var parsedname = ""
+ for (var key = 0; key < data.name.length; key++) {
+ var character = data.name.charAt(key)
+ if (character === character.toUpperCase()) {
+ //console.log(data.name[key], data.name[key+1])
+ if (data.name.charAt(key+1) !== undefined && data.name.charAt(key+1) === data.name.charAt(key+1).toUpperCase()) {
+ } else {
+ parsedname += " "
+ }
+ }
+
+ parsedname += character
+ }
+
+ parsedname = (parsedname.charAt(0).toUpperCase()+parsedname.substring(1)).replaceAll("_", " ")
+
+ return (
+
+
+
+ {
+ setMouseHoverIndex(index)
+ /*
+ ReactGA.event({
+ category: "app_grid_view",
+ action: `search_bar_click`,
+ label: "",
+ })
+ */
+ }} onMouseOut={() => {
+ setMouseHoverIndex(-1)
+ }} onClick={() => {
+ if (isCloud) {
+ ReactGA.event({
+ category: "app_grid_view",
+ action: `app_${parsedname}_${data.id}_click`,
+ label: "",
+ })
+ }
+
+ //const searchClient = algoliasearch("L55H18ZINA", "a19be455e7e75ee8f20a93d26b9fc6d6")
+ console.log(searchClient)
+ aa('init', {
+ appId: searchClient.appId,
+ apiKey: searchClient.transporter.queryParameters["x-algolia-api-key"]
+ })
+
+ const timestamp = new Date().getTime()
+ aa('sendEvents', [
+ {
+ eventType: 'click',
+ eventName: 'Product Clicked',
+ index: 'appsearch',
+ objectIDs: [data.objectID],
+ timestamp: timestamp,
+ queryID: data.__queryID,
+ positions: [data.__position],
+ userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id,
+ }
+ ])
+
+ }}>
+
+
+
+
+ {index === mouseHoverIndex || showName === true ?
+ parsedname
+ :
+ null
+ }
+ {data.generated ?
+
+ {data.invalid ?
+
+ :
+
+ }
+
+ :
+
+
+
+ }
+
+
+
+
+ )
+ })}
+
+ )
+ }
+
+ const CustomSearchBox = connectSearchBox(SearchBox)
+ const CustomHits = connectHits(Hits)
+ //const CustomHits = connectHitInsights(aa)(Hits)
+ const selectButtonStyle = {
+ minWidth: 150,
+ maxWidth: 150,
+ minHeight: 50,
+ }
+
+ return (
+
+ {/*
+
+ {
+ const searchField = document.createElement("shuffle_search_field")
+ console.log("Field: ", searchField)
+ if (searchField !== null & searchField !== undefined) {
+ console.log("Set field.")
+ searchField.value = "WHAT WABALABA"
+ searchField.setAttribute("value", "WHAT WABALABA")
+ }
+ }}
+ >
+ Cases
+
+
+ */}
+
+
+
+
+
+
+
+
+ {showSuggestion === true ?
+
+
+ Can't find what you're looking for?
+
+
+ setFormMail(e.target.value)}
+ />
+ setMessage(e.target.value)}
+ />
+
+
{
+ submitContact(formMail, message)
+ }}
+ >
+ Submit
+
+
{formMessage}
+
+ : null
+ }
+
+
+
+ Search by
+
+
+
+
+
+
+
+ )
+}
+
+export default AppGrid1;
diff --git a/frontend/src/views/AppExplorer.jsx b/frontend/src/views/AppExplorer.jsx
new file mode 100644
index 00000000..01fc4546
--- /dev/null
+++ b/frontend/src/views/AppExplorer.jsx
@@ -0,0 +1,468 @@
+
+import { Grid, Divider, List, ListItem, ListItemText } from "@mui/material";
+import { experimentalStyled as styled } from '@mui/material/styles';
+import Typography from "@material-ui/core/Typography";
+import Paper from "@material-ui/core/Paper";
+import Button from '@mui/material/Button';
+import Box from '@material-ui/core/Box';
+import React, { useState, useEffect } from "react";
+
+
+import algoliasearch from "algoliasearch";
+//import algoliarecommend from "algoliarecommend";
+
+const searchClient = algoliasearch(
+ "JNSS5CFDZZ",
+ "db08e40265e2941b9a7d8f644b6e5240"
+);
+
+// https://www.algolia.com/doc/api-client/getting-started/install/
+/*const algoliarecommend = require('@algolia/recommend');
+
+const client = algoliarecommend(
+ "NSS5CFDZZ",
+ "db08e40265e2941b9a7d8f644b6e5240"
+);*/
+
+
+const Item = styled(Paper)(({ theme }) => ({
+ padding: theme.spacing(2),
+ border: "0.0625rem solid #b2b2b2",
+ borderRadius: "1.5625rem",
+ boxSizing: "content-box",
+ backgroundColor: "transparent",
+ width: "200px",
+ textAlign: 'center',
+ color: theme.palette.text.secondary,
+ marginTop: "20px",
+ marginBottom: "20px",
+ color: "textPrimary"
+}));
+const AppExplorer = (props) => {
+ const [algoliaResult, setAlgoliaResult] = useState("");
+
+ const runAlgoliaAppSearch = (query) => {
+ const index = searchClient.initIndex("appsearch");
+
+ index
+ .search(`${query}`)
+ .then(({ hits }) => {
+ setAlgoliaResult(hits);
+ })
+ .catch((err) => {
+ console.log(err);
+ });
+ };
+
+ useEffect(() => {
+
+ runAlgoliaAppSearch("wazuh")
+
+ }, [])
+
+
+ const brandApp = () => {
+ const index = searchClient.initIndex("appsearch");
+
+ const replicaIndex = searchClient.initIndex('appsearch');
+ replicaIndex.setSettings({
+ customRanking: [
+ "asc(time_edited)"
+ ]
+ })
+
+ .then(({ hits }) => {
+ console.log(hits);
+ })
+ .catch((err) => {
+ console.log(err);
+ });
+ };
+
+ useEffect(() => {
+
+ brandApp()
+
+ }, [])
+
+ /*const trandingApp = () => {
+
+ const index = client.getTrendingGlobalItems([
+ {
+ indexName: "appsearch",
+ threshold: 60
+ },
+ ])
+ .then(({ results }) => {
+ console.log(results);
+ })
+ .catch(err => {
+ console.log(err);
+ });
+ };
+
+ useEffect(() => {
+
+ trandingApp();
+
+ }, [])
+ */
+
+ const SideBar = {
+ minWidth: 250,
+
+ borderRight: "1px solid rgba(255,255,255,0.3)",
+ left: 0,
+ position: "sticky",
+ minHeight: "90vh",
+ maxHeight: "90vh",
+ overflowX: "hidden",
+ overflowY: "auto",
+ zIndex: 1000,
+ color: "white"
+ };
+ const contentbar = {
+ padding: "40px",
+
+ };
+ const boxdata = {
+ paddingLeft: "30px"
+ }
+ const link = {
+ textDecoration: "none"
+ }
+ const catItems = (
+
+
+
+
+ Categories
+
+
+
+
+
+
+
+ ASSETS
+
+
+
+
+
+
+ CASES
+
+
+
+
+
+
+ COMMS
+
+
+
+
+
+
+ EDR & AV
+
+
+
+
+
+
+ IAM
+
+
+
+
+
+
+ INTEL
+
+
+
+
+
+
+ NETWORK
+
+
+
+
+
+
+ SIEM
+
+
+
+
+
+ )
+ return (
+
+
+ );
+}
+export default AppExplorer;
diff --git a/frontend/src/views/AppHub.jsx b/frontend/src/views/AppHub.jsx
new file mode 100644
index 00000000..5a4db83a
--- /dev/null
+++ b/frontend/src/views/AppHub.jsx
@@ -0,0 +1,789 @@
+import React from "react";
+import { Grid, Container, Divider, CardMedia, List, ListItem, ListItemText } from "@mui/material";
+
+import { makeStyles } from "@material-ui/core/styles";
+import Card from "@material-ui/core/Card";
+import CardContent from "@material-ui/core/CardContent";
+
+import Table from "@material-ui/core/Table";
+import TableBody from "@material-ui/core/TableBody";
+import TableCell from "@material-ui/core/TableCell";
+import TableContainer from "@material-ui/core/TableContainer";
+import TableHead from "@material-ui/core/TableHead";
+import TableRow from "@material-ui/core/TableRow";
+import Paper from "@material-ui/core/Paper";
+
+import { LineChart, LineSeries, BarChart } from "reaviz";
+import { Gridline, GridStripe } from "reaviz";
+import { GridlineSeries } from "reaviz";
+
+import InputLabel from '@material-ui/core/InputLabel';
+import FormControl from '@material-ui/core/FormControl';
+import Select from '@material-ui/core/Select';
+
+import { styled, alpha } from '@mui/material/styles';
+import AppBar from '@mui/material/AppBar';
+import Box from '@mui/material/Box';
+import Toolbar from '@mui/material/Toolbar';
+import IconButton from '@mui/material/IconButton';
+import Typography from '@mui/material/Typography';
+import InputBase from '@mui/material/InputBase';
+import Badge from '@mui/material/Badge';
+import MenuItem from '@mui/material/MenuItem';
+import Menu from '@mui/material/Menu';
+import MenuIcon from '@mui/icons-material/Menu';
+import SearchIcon from '@mui/icons-material/Search';
+import AccountCircle from '@mui/icons-material/AccountCircle';
+import MailIcon from '@mui/icons-material/Mail';
+import NotificationsIcon from '@mui/icons-material/Notifications';
+import MoreIcon from '@mui/icons-material/MoreVert';
+import SearchField from "../components/Searchfield";
+import { SpaRounded } from "@material-ui/icons";
+import { isMobile } from "react-device-detect"
+
+const data = [
+ {
+ key: new Date("11/29/2019"),
+ data: 10,
+ },
+ {
+ key: new Date("11/30/2019"),
+ data: 14,
+ },
+ {
+ key: new Date("12/01/2019"),
+ data: 5,
+ },
+ {
+ key: new Date("12/02/2019"),
+ data: 18,
+ },
+];
+
+const useStyles1 = makeStyles((theme) => ({
+ formControl: {
+ margin: theme.spacing(1),
+ minWidth: 120,
+ },
+ selectEmpty: {
+ marginTop: theme.spacing(2),
+ },
+}));
+
+
+const useStyles = makeStyles({
+ table: {
+ minWidth: 650,
+ },
+ root: {
+ minWidth: 275,
+ },
+ bullet: {
+ display: "inline-block",
+ margin: "0 2px",
+ transform: "scale(0.8)",
+ },
+ title: {
+ fontSize: 14,
+ },
+ pos: {
+ marginBottom: 12,
+ },
+});
+
+function createData(name, calories, fat, carbs, protein) {
+ return { name, calories, fat, carbs, protein };
+}
+
+const rows = [
+ createData("Frozen yoghurt", 159, 6.0, 24, 4.0),
+ createData("Ice cream sandwich", 237, 9.0, 37, 4.3),
+ createData("Eclair", 262, 16.0, 24, 6.0),
+ createData("Cupcake", 305, 3.7, 67, 4.3),
+ createData("Gingerbread", 356, 16.0, 49, 3.9),
+];
+
+const Search = styled('div')(({ theme }) => ({
+ position: 'relative',
+ borderRadius: theme.shape.borderRadius,
+ backgroundColor: alpha(theme.palette.common.white, 0.15),
+ '&:hover': {
+ backgroundColor: alpha(theme.palette.common.white, 0.25),
+ },
+ marginRight: theme.spacing(2),
+ marginLeft: 0,
+ width: '100%',
+ [theme.breakpoints.up('sm')]: {
+ marginLeft: theme.spacing(3),
+ width: 'auto',
+ },
+}));
+
+const SearchIconWrapper = styled('div')(({ theme }) => ({
+ padding: theme.spacing(0, 2),
+ height: '100%',
+ position: 'absolute',
+ pointerEvents: 'none',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+}));
+
+const StyledInputBase = styled(InputBase)(({ theme }) => ({
+ color: 'inherit',
+ '& .MuiInputBase-input': {
+ padding: theme.spacing(1, 1, 1, 0),
+ // vertical padding + font size from searchIcon
+ paddingLeft: `calc(1em + ${theme.spacing(4)})`,
+ transition: theme.transitions.create('width'),
+ width: '100%',
+ [theme.breakpoints.up('md')]: {
+ width: '20ch',
+ },
+ },
+}));
+
+function PrimarySearchAppBar() {
+ const [anchorEl, setAnchorEl] = React.useState(null);
+ const [mobileMoreAnchorEl, setMobileMoreAnchorEl] = React.useState(null);
+
+ const isMenuOpen = Boolean(anchorEl);
+ const isMobileMenuOpen = Boolean(mobileMoreAnchorEl);
+
+ const handleProfileMenuOpen = (event) => {
+ setAnchorEl(event.currentTarget);
+ };
+
+ const handleMobileMenuClose = () => {
+ setMobileMoreAnchorEl(null);
+ };
+
+ const handleMenuClose = () => {
+ setAnchorEl(null);
+ handleMobileMenuClose();
+ };
+
+ const handleMobileMenuOpen = (event) => {
+ setMobileMoreAnchorEl(event.currentTarget);
+ };
+
+ const menuId = 'primary-search-account-menu';
+ const renderMenu = (
+
+ );
+
+ const mobileMenuId = 'primary-search-account-menu-mobile';
+ const renderMobileMenu = (
+
+ );
+
+ return (
+
+
+
+
+
+ {/* */}
+
+
+
+ {renderMobileMenu}
+ {renderMenu}
+
+ );
+}
+
+const AppHub = () => {
+ const classes = useStyles();
+ const classes1 = useStyles1();
+
+ const [usecases, setUsecases] = React.useState([
+ {
+ "name": "1. Collect",
+ "color": "#c51152",
+ "list": [
+ {
+ "name": "Email management",
+ "priority": 100,
+ "type": "communication",
+ "items": {
+ "name": "Release a quarantined message",
+ "items": {}
+ },
+ "matches": []
+ },
+ {
+ "name": "EDR to ticket",
+ "priority": 100,
+ "type": "edr",
+ "items": {
+ "name": "Get host information",
+ "items": {}
+ },
+ "matches": []
+ },
+ {
+ "name": "SIEM to ticket",
+ "priority": 100,
+ "type": "siem",
+ "description": "Ensure tickets are forwarded to the correct destination. Alternatively add enrichment on it's way there.",
+ "video": "https://www.youtube.com/watch?v=FBISHA7V15c&t=197s&ab_channel=OpenSecure",
+ "blogpost": "https://medium.com/shuffle-automation/introducing-shuffle-an-open-source-soar-platform-part-1-58a529de7d12",
+ "reference_image": "/images/detectionframework.png",
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "2-way Ticket synchronization",
+ "priority": 90,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "ChatOps",
+ "priority": 70,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Threat Intel received",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Assign tickets",
+ "priority": 30,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Firewall alerts",
+ "priority": 90,
+ "items": {
+ "name": "URL filtering",
+ "items": {}
+ },
+ "matches": []
+ },
+ {
+ "name": "IDS/IPS alerts",
+ "priority": 90,
+ "items": {
+ "name": "Manage policies",
+ "items": {}
+ },
+ "matches": []
+ },
+ {
+ "name": "Deduplicate information",
+ "priority": 70,
+ "items": {},
+ "matches": []
+ }
+ ],
+ "matches": []
+ },
+ {
+ "name": "2. Enrich",
+ "color": "#f4c20d",
+ "list": [
+ {
+ "name": "Internal Enrichment",
+ "priority": 100,
+ "items": {
+ "name": "...",
+ "items": {}
+ },
+ "matches": []
+ },
+ {
+ "name": "External historical Enrichment",
+ "priority": 90,
+ "items": {
+ "name": "...",
+ "items": {}
+ },
+ "matches": []
+ },
+ {
+ "name": "Realtime",
+ "priority": 50,
+ "items": {
+ "name": "Analyze screenshots",
+ "items": {}
+ },
+ "matches": []
+ }
+ ],
+ "matches": []
+ },
+ {
+ "name": "3. Detect",
+ "color": "#3cba54",
+ "list": [
+ {
+ "name": "Search SIEM (Sigma)",
+ "priority": 90,
+ "items": {
+ "name": "Endpoint",
+ "items": {}
+ },
+ "matches": []
+ },
+ {
+ "name": "Search EDR (OSQuery)",
+ "priority": 90,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Search emails (Sublime)",
+ "priority": 90,
+ "items": {
+ "name": "Check headers and IOCs",
+ "items": {}
+ },
+ "matches": []
+ },
+ {
+ "name": "Search IOCs (ioc-finder)",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Search files (Yara)",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Memory Analysis (Volatility)",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "IDS & IPS (Snort/Surricata)",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Validate old tickets",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Honeypot access",
+ "priority": 50,
+ "items": {
+ "name": "...",
+ "items": {}
+ },
+ "matches": []
+ }
+ ],
+ "matches": []
+ },
+ {
+ "name": "4. Respond",
+ "color": "#4885ed",
+ "list": [
+ {
+ "name": "Eradicate malware",
+ "priority": 90,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Quarantine host(s)",
+ "priority": 90,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Block IPs, URLs, Domains and Hashes",
+ "priority": 90,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Trigger scans",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Update indicators (FW, EDR, SIEM...)",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Autoblock activity when threat intel is received",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Lock/Delete/Reset account",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Lock vault",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Increase authentication",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Get policies from assets",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Run ansible scripts",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ }
+ ],
+ "matches": []
+ },
+ {
+ "name": "5. Verify",
+ "color": "#7f00ff",
+ "list": [
+ {
+ "name": "Discover vulnerabilities",
+ "priority": 80,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Discover assets",
+ "priority": 80,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Ensure policies are followed",
+ "priority": 80,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Find Inactive users",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Botnet tracker",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Ensure access rights match HR systems",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Ensure onboarding is followed",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Third party apps in SaaS",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Devices used for your cloud account",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Too much access in GCP/Azure/AWS/ other clouds",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Certificate validation",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Domain investigation with LetsEncrypt",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Monitor new DNS entries for domain with passive DNS",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Monitor and track password dumps",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Monitor for mentions of domain on darknet sites",
+ "priority": 50,
+ "items": {},
+ "matches": []
+ },
+ {
+ "name": "Reporting",
+ "priority": 50,
+ "items": {
+ "name": "Monthly reports",
+ "items": {
+ "name": "...",
+ "items": {}
+ }
+ },
+ "matches": []
+ }
+ ],
+ "matches": []
+ }
+ ]);
+
+ const SideBar = {
+ minWidth: 250,
+ maxWidth: 300,
+ borderRight: "1px solid rgba(255,255,255,0.3)",
+ left: 0,
+ position: "sticky",
+ minHeight: "90vh",
+ maxHeight: "90vh",
+ overflowX: "hidden",
+ overflowY: "auto",
+ zIndex: 1000,
+ color: "black"
+ };
+
+ const [age, setAge] = React.useState(0);
+
+ const handleChange = (event) => {
+ setAge(event.target.value);
+
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ SHUFFLE
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Categories
+
+
+
+
+
+
+ Workflows
+
+
+
+
+ Apps
+
+
+
+
+ Docs
+
+
+
+
+
+
+ Workflow
+
+
+ {!isMobile && usecases !== null && usecases !== undefined && usecases.length > 0 ?
+
+ : null}
+
+
+
+
+
+
+
+ footer
+
+
+
+
+ );
+};
+
+export default AppHub;
\ No newline at end of file
diff --git a/frontend/src/views/Appdemo.jsx b/frontend/src/views/Appdemo.jsx
new file mode 100644
index 00000000..3fe5aae1
--- /dev/null
+++ b/frontend/src/views/Appdemo.jsx
@@ -0,0 +1,268 @@
+import React, { useState, useEffect } from "react";
+import { Grid, Container, Divider, CardMedia, List, ListItem, ListItemText } from "@mui/material";
+import Typography from "@material-ui/core/Typography";
+
+import theme from '../theme';
+import {isMobile} from "react-device-detect";
+import AppGrid1 from "../components/AppGrid1.jsx"
+import WorkflowGrid from "../components/WorkflowGrid.jsx"
+import CreatorGrid from "../components/CreatorGrid.jsx"
+import DocsGrid from "../components/DocsGrid.jsx"
+import Button from '@mui/material/Button';
+import { useNavigate, Link } from "react-router-dom";
+
+import {
+ Tabs,
+ Paper,
+ Tab,
+} from "@material-ui/core";
+
+import {
+ Business as BusinessIcon,
+ Apps as AppsIcon,
+ Polymer as PolymerIcon,
+ EmojiObjects as EmojiObjectsIcon,
+ Description as DescriptionIcon,
+} from "@material-ui/icons";
+
+
+const bodyDivStyle = {
+ margin: "auto",
+ maxWidth: 1024,
+ scrollX: "hidden",
+ overflowX: "hidden",
+}
+
+// Should be different if logged in :|
+const Appdemo = (props) => {
+ const { globalUrl, isLoaded, serverside, userdata, hidemargins, } = props;
+ const [appCategory,setAppCategory] = useState();
+ let navigate = useNavigate();
+
+ const [curTab, setCurTab] = useState(0);
+ const iconStyle = { marginRight: 10 };
+
+ useEffect(() => {
+ if (serverside !== true && window.location.search !== undefined && window.location.search !== null) {
+ const urlSearchParams = new URLSearchParams(window.location.search)
+ const params = Object.fromEntries(urlSearchParams.entries())
+ const foundTab = params["tab"]
+ if (foundTab !== null && foundTab !== undefined) {
+ for (var key in Object.keys(views)) {
+ const value = views[key]
+ console.log(key, value)
+ if (value === foundTab) {
+ setConfig("", key)
+ break
+ }
+ }
+ }
+ }
+ }, [])
+
+ if (serverside === true) {
+ return null
+ }
+
+ const boxStyle = {
+ color: "white",
+ flex: "1",
+ marginLeft: 10,
+ marginRight: 10,
+ paddingLeft: 30,
+ paddingRight: 30,
+ paddingBottom: 30,
+ paddingTop: hidemargins === true ? 0 : 30,
+ display: "flex",
+ flexDirection: "column",
+ overflowX: "hidden",
+ minHeight: 400,
+ }
+ const NoArguments_NoReturn = () => {
+
+ alert('Function Called...');
+
+ }
+ const SideBar = {
+ minWidth: 250,
+ maxWidth: 300,
+ borderRight: "1px solid rgba(255,255,255,0.3)",
+ left: 0,
+ position: "sticky",
+ minHeight: "90vh",
+ maxHeight: "90vh",
+ overflowX: "hidden",
+ overflowY: "auto",
+ zIndex: 1000,
+ color: "white"
+ };
+ const catItems = (
+
+
+
+
+ Categories
+
+
+
+
+
+
+ {setAppCategory("assets")}}>
+ ASSETS
+
+
+
+
+
+ {setAppCategory("cases")}}>
+ CASES
+
+
+
+
+
+ {setAppCategory("comms")}}>
+ COMMS
+
+
+
+
+
+ {setAppCategory("edr av")}}>
+ EDR & AV
+
+
+
+
+
+ {setAppCategory("iam")}}>
+ IAM
+
+
+
+
+
+ {setAppCategory("intel")}}>
+ INTEL
+
+
+
+
+
+ {setAppCategory("network")}}>
+ NETWORK
+
+
+
+
+
+ {setAppCategory("siem")}}>
+ SIEM
+
+
+
+
+
+)
+
+ const views = {
+ 0: "apps",
+ 1: "workflows",
+ 2: "docs",
+ 3: "creators",
+ }
+
+ const setConfig = (event, inputValue) => {
+ const newValue = parseInt(inputValue)
+
+ setCurTab(newValue)
+ if (newValue === 0) {
+ document.title = "Shuffle - search - apps";
+ } else if (newValue === 1) {
+ document.title = "Shuffle - search - workflows";
+ } else if (newValue === 2) {
+ document.title = "Shuffle - search - documentation";
+ } else if (newValue === 3) {
+ document.title = "Shuffle - search - creators";
+ } else {
+ document.title = "Shuffle - search";
+ }
+
+
+ const urlSearchParams = new URLSearchParams(window.location.search)
+ const params = Object.fromEntries(urlSearchParams.entries())
+ const foundQuery = params["q"]
+ var extraQ = ""
+ if (foundQuery !== null && foundQuery !== undefined) {
+ extraQ = "&q="+foundQuery
+ }
+
+
+ if ((serverside === false || serverside === undefined) && window.location.pathname.includes("/search")) {
+ navigate(`/search?tab=${views[newValue]}`+extraQ)
+ }
+ }
+
+ if (isLoaded === false) {
+ return null
+ }
+
+
+ // Random names for type & autoComplete. Didn't research :^)
+ const landingpageDataBrowser =
+
+
+
+
+ Apps
+
+ />
+
+
+ {curTab === 0 ?
+
+ :
+ curTab === 1 ?
+ window.location.pathname === "/search" ?
+
+ :
+
+ :
+ curTab === 2 ?
+
+ :
+ curTab === 3 ?
+
+ :
+ null}
+
+
+ //{/*alternativeView={true} />*/}
+
+ const loadedCheck = isLoaded ?
+
+
{landingpageDataBrowser}
+
+ :
+
+
+
+ // #1f2023?
+ return(
+
+ {catItems}
+ {loadedCheck}
+
+ )
+}
+
+export default Appdemo;