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 ( +
+ + + + ), + }} + autoComplete='off' + type="hidden" + color="primary" + defaultValue={currentRefinement} + placeholder="Find Apps..." + id="shuffle_search_field" + onChange={(event) => { + refine(event.currentTarget.value) + }} + limit={5} + /> + {/*isSearchStalled ? 'My search is stalled' : ''*/} + + ) + } + + 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, + } + ]) + + }}> + + {data.name} + +
+ {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 ( +
+ {/* +
+ +
+ */} +
+ +
+ +
+ + +
+ {showSuggestion === true ? +
+ + Can't find what you're looking for? + +
+ setFormMail(e.target.value)} + /> + setMessage(e.target.value)} + /> +
+ + {formMessage} +
+ : null + } + + + + Search by + + + Algolia logo + + +
+
+ ) +} + +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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ) + return ( +
+
+ + {catItems} +
+ + + + + Getting Started + +
+
+ +
+
+ + + {Array.from(Array(algoliaResult.length)).map((_, index) => ( + + + + + + +
+ +
+ shuffle +
+
+
+ + {algoliaResult[0]["name"]} + +
+
+ + {algoliaResult[0]["description"].substring(0, 20)} + +
+
+ +
+
+
+
+ ))} +
+
+ + + + Most Popular + +
+
+ +
+ + + {Array.from(Array(3)).map((_, index) => ( + + + +
+
+ shuffle +
+
+
+ + App Name + +
+
+ + Description + +
+
+ +
+
+
+
+ ))} +
+
+ + + + + Brand New + +
+
+ +
+ + + {Array.from(Array(3)).map((_, index) => ( + + + +
+
+ shuffle +
+
+
+ + App Name + +
+
+ + Description + +
+
+ +
+
+
+
+ ))} +
+
+ + + + + {Array.from(Array(3)).map((_, index) => ( + + +
+
+ shuffle +
+
+ +
+ ))} +
+
+ + + + + Hybrid work + +
+
+ +
+ + + + {Array.from(Array(3)).map((_, index) => ( + + + +
+
+ shuffle +
+
+
+ + App Name + +
+
+ + Description + +
+
+ +
+
+
+
+ ))} +
+
+ +
+
+ Don't see it? Build it! + Use our APIs to create an app that makes your working life better.And maybe even share it with the world. + visit developer portal +
+
+
+
+ shuffle +
+
+ shuffle +
+
+ shuffle +
+
+
+
+ +
+ +
+
+ + ); +} +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 = ( + + Profile + My account + + ); + + const mobileMenuId = 'primary-search-account-menu-mobile'; + const renderMobileMenu = ( + + + + + + + +

Messages

+
+ + + + + + +

Notifications

+
+ + + + +

Profile

+
+
+ ); + + return ( + + + + shuffle img + + {/* */} + + + + {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 img + + SHUFFLE + +
+
+ +
+
+
+
+
+
+
+
+ + + + Categories + + + + + + + Workflows + + + + + Apps + + + + + Docs + + + +
+
+ + Workflow + +
+ {!isMobile && usecases !== null && usecases !== undefined && usecases.length > 0 ? +
+ + {usecases.map((usecase, index) => { + //console.log(usecase) + return ( + + { + console.log("clicked...") + }} + > + + + {usecase.name} + + + In use: {usecase.matches.length}/{usecase.list.length} + + + + + ) + })} + +
+ : 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+) + + 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;