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;