Minor fixes for frontend
This commit is contained in:
@@ -2,7 +2,7 @@ module main
|
|||||||
|
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
//replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared
|
replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared
|
||||||
|
|
||||||
//replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi
|
//replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi
|
||||||
//replace github.com/frikky/go-elasticsearch => ../../../../git/go-elasticsearch
|
//replace github.com/frikky/go-elasticsearch => ../../../../git/go-elasticsearch
|
||||||
|
|||||||
@@ -1048,6 +1048,10 @@ func cloudExecuteAction(execution shuffle.WorkflowExecution) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1. Check CORS
|
||||||
|
// 2. Check authentication
|
||||||
|
// 3. Check authorization
|
||||||
|
// 4. Run the actual function
|
||||||
func executeWorkflow(resp http.ResponseWriter, request *http.Request) {
|
func executeWorkflow(resp http.ResponseWriter, request *http.Request) {
|
||||||
cors := shuffle.HandleCors(resp, request)
|
cors := shuffle.HandleCors(resp, request)
|
||||||
if cors {
|
if cors {
|
||||||
@@ -1104,8 +1108,8 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
executionAuthValid, newOrgId = shuffle.RunExecuteAccessValidation(request, workflow)
|
executionAuthValid, newOrgId = shuffle.RunExecuteAccessValidation(request, workflow)
|
||||||
if !executionAuthValid {
|
if !executionAuthValid {
|
||||||
log.Printf("[INFO] Api authentication failed in execute workflow: %s", userErr)
|
log.Printf("[INFO] Api authorization failed in execute workflow: %s", userErr)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(403)
|
||||||
resp.Write([]byte(`{"success": false}`))
|
resp.Write([]byte(`{"success": false}`))
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
@@ -1122,7 +1126,7 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
log.Printf("[AUDIT] Letting user %s execute %s because they're admin of the same org", user.Username, workflow.ID)
|
log.Printf("[AUDIT] Letting user %s execute %s because they're admin of the same org", user.Username, workflow.ID)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("[AUDIT] Wrong user (%s) for workflow %s (execute)", user.Username, workflow.ID)
|
log.Printf("[AUDIT] Wrong user (%s) for workflow %s (execute)", user.Username, workflow.ID)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(403)
|
||||||
resp.Write([]byte(`{"success": false}`))
|
resp.Write([]byte(`{"success": false}`))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -2606,7 +2610,7 @@ func executeSingleAction(resp http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[INFO] Execution: %s should execute onprem with execution environment \"%s\". Workflow: %s", workflowExecution.ExecutionId, environment, workflowExecution.Workflow.ID)
|
log.Printf("[INFO] Execution (single action): %s should execute onprem with execution environment \"%s\". Workflow: %s", workflowExecution.ExecutionId, environment, workflowExecution.Workflow.ID)
|
||||||
|
|
||||||
executionRequest := shuffle.ExecutionRequest{
|
executionRequest := shuffle.ExecutionRequest{
|
||||||
ExecutionId: workflowExecution.ExecutionId,
|
ExecutionId: workflowExecution.ExecutionId,
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
version: '3'
|
version: '3'
|
||||||
services:
|
services:
|
||||||
frontend:
|
frontend:
|
||||||
build: ./frontend
|
#build: ./frontend
|
||||||
image: ghcr.io/frikky/shuffle-frontend:nightly
|
image: ghcr.io/frikky/shuffle-frontend:nightly
|
||||||
container_name: shuffle-frontend
|
container_name: shuffle-frontend
|
||||||
hostname: shuffle-frontend
|
hostname: shuffle-frontend
|
||||||
|
|||||||
@@ -319,7 +319,6 @@ const App = (message, props) => {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
}
|
}
|
||||||
<Header
|
<Header
|
||||||
notifications={notifications}
|
notifications={notifications}
|
||||||
|
|||||||
@@ -167,6 +167,8 @@ const ParsedAction = (props) => {
|
|||||||
isCloud,
|
isCloud,
|
||||||
lastSaved,
|
lastSaved,
|
||||||
setLastSaved,
|
setLastSaved,
|
||||||
|
//expansionModalOpen,
|
||||||
|
//setExpansionModalOpen,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
//const theme = useTheme();
|
//const theme = useTheme();
|
||||||
@@ -1395,7 +1397,7 @@ const ParsedAction = (props) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="Autocomplete the text" placement="top">
|
<Tooltip title="Autocomplete text" placement="top">
|
||||||
<AddCircleOutlineIcon
|
<AddCircleOutlineIcon
|
||||||
style={{ cursor: "pointer", margin: multiline ? 5 : 0, }}
|
style={{ cursor: "pointer", margin: multiline ? 5 : 0, }}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
Typography,
|
Typography,
|
||||||
Paper
|
Paper,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
Button,
|
||||||
} from '@material-ui/core';
|
} from '@material-ui/core';
|
||||||
|
|
||||||
import Checkbox from '@mui/material/Checkbox';
|
import Checkbox from '@mui/material/Checkbox';
|
||||||
@@ -34,6 +37,11 @@ import 'codemirror/theme/gruvbox-dark.css';
|
|||||||
import 'codemirror/theme/duotone-light.css';
|
import 'codemirror/theme/duotone-light.css';
|
||||||
import { padding, textAlign } from '@mui/system';
|
import { padding, textAlign } from '@mui/system';
|
||||||
|
|
||||||
|
const liquidFilters = [
|
||||||
|
{"name": "Size", "value": "size", "example": ""},
|
||||||
|
{"name": "Date", "value": "date", "example": `{{ "now" | date: "%s" }}`},
|
||||||
|
]
|
||||||
|
|
||||||
const CodeEditor = (props) => {
|
const CodeEditor = (props) => {
|
||||||
const { fieldCount, setFieldCount, actionlist, changeActionParameterCodeMirror, expansionModalOpen, setExpansionModalOpen, codedata, setcodedata } = props
|
const { fieldCount, setFieldCount, actionlist, changeActionParameterCodeMirror, expansionModalOpen, setExpansionModalOpen, codedata, setcodedata } = props
|
||||||
const [localcodedata, setlocalcodedata] = React.useState(codedata === undefined || codedata === null || codedata.length === 0 ? "" : codedata);
|
const [localcodedata, setlocalcodedata] = React.useState(codedata === undefined || codedata === null || codedata.length === 0 ? "" : codedata);
|
||||||
@@ -51,6 +59,9 @@ const CodeEditor = (props) => {
|
|||||||
const [variableOccurences, setVariableOccurences] = React.useState([]);
|
const [variableOccurences, setVariableOccurences] = React.useState([]);
|
||||||
const [currentLocation, setCurrentLocation] = React.useState([]);
|
const [currentLocation, setCurrentLocation] = React.useState([]);
|
||||||
const [currentVariable, setCurrentVariable] = React.useState("");
|
const [currentVariable, setCurrentVariable] = React.useState("");
|
||||||
|
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||||
|
const liquidOpen = Boolean(anchorEl);
|
||||||
|
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
// console.log(currentLocation)
|
// console.log(currentLocation)
|
||||||
// }, [currentLocation])
|
// }, [currentLocation])
|
||||||
@@ -340,11 +351,23 @@ const CodeEditor = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const handleClick = (item) => {
|
||||||
|
if (item === undefined || item.value === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setlocalcodedata(localcodedata+" "+item.value)
|
||||||
|
setAnchorEl(null)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
disableEnforceFocus={true}
|
aria-labelledby="draggable-code-modal"
|
||||||
hideBackdrop={true}
|
|
||||||
disableBackdropClick={true}
|
disableBackdropClick={true}
|
||||||
|
disableEnforceFocus={true}
|
||||||
|
//style={{ pointerEvents: "none" }}
|
||||||
|
hideBackdrop={true}
|
||||||
open={expansionModalOpen}
|
open={expansionModalOpen}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
console.log("In closer")
|
console.log("In closer")
|
||||||
@@ -352,7 +375,6 @@ const CodeEditor = (props) => {
|
|||||||
//setExpansionModalOpen(false)
|
//setExpansionModalOpen(false)
|
||||||
}}
|
}}
|
||||||
PaperComponent={PaperComponent}
|
PaperComponent={PaperComponent}
|
||||||
aria-labelledby="draggable-dialog-title"
|
|
||||||
PaperProps={{
|
PaperProps={{
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: theme.palette.surfaceColor,
|
backgroundColor: theme.palette.surfaceColor,
|
||||||
@@ -401,9 +423,45 @@ const CodeEditor = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
id="basic-button"
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-controls={liquidOpen ? 'basic-menu' : undefined}
|
||||||
|
aria-expanded={liquidOpen ? 'true' : undefined}
|
||||||
|
style={{
|
||||||
|
textTransform: "none",
|
||||||
|
width: 200,
|
||||||
|
marginLeft: 200,
|
||||||
|
}}
|
||||||
|
onClick={(event) => {
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Liquid
|
||||||
|
</Button>
|
||||||
|
<Menu
|
||||||
|
id="basic-menu"
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
open={liquidOpen}
|
||||||
|
onClose={() => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
}}
|
||||||
|
MenuListProps={{
|
||||||
|
'aria-labelledby': 'basic-button',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{liquidFilters.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<MenuItem onClick={() => {
|
||||||
|
handleClick(item)
|
||||||
|
}}>{item.name}</MenuItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Menu>
|
||||||
<span style={{
|
<span style={{
|
||||||
border: `2px solid ${theme.palette.inputColor}`,
|
border: `2px solid ${theme.palette.inputColor}`,
|
||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette.borderRadius,
|
||||||
|
position: "relative",
|
||||||
}}>
|
}}>
|
||||||
<CodeMirror
|
<CodeMirror
|
||||||
value = {localcodedata}
|
value = {localcodedata}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React, { useState, useEffect } from "react";
|
|||||||
import { makeStyles } from "@material-ui/styles";
|
import { makeStyles } from "@material-ui/styles";
|
||||||
import { useTheme } from "@material-ui/core/styles";
|
import { useTheme } from "@material-ui/core/styles";
|
||||||
import { useNavigate, Link } from "react-router-dom";
|
import { useNavigate, Link } from "react-router-dom";
|
||||||
|
import countries from "../components/Countries.jsx"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -36,8 +37,13 @@ import {
|
|||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
|
Box,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Autocomplete,
|
||||||
|
} from "@mui/material"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Edit as EditIcon,
|
Edit as EditIcon,
|
||||||
FileCopy as FileCopyIcon,
|
FileCopy as FileCopyIcon,
|
||||||
@@ -105,6 +111,7 @@ const Admin = (props) => {
|
|||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = React.useState(false);
|
||||||
|
|
||||||
const [selectedOrganization, setSelectedOrganization] = React.useState({});
|
const [selectedOrganization, setSelectedOrganization] = React.useState({});
|
||||||
|
const [selectedDealModalOpen, setSelectedDealModalOpen] = React.useState(false);
|
||||||
//console.log("Selected: ", selectedOrganization)
|
//console.log("Selected: ", selectedOrganization)
|
||||||
const [organizationFeatures, setOrganizationFeatures] = React.useState({});
|
const [organizationFeatures, setOrganizationFeatures] = React.useState({});
|
||||||
const [loginInfo, setLoginInfo] = React.useState("");
|
const [loginInfo, setLoginInfo] = React.useState("");
|
||||||
@@ -140,6 +147,17 @@ const Admin = (props) => {
|
|||||||
const [secret2FA, setSecret2FA] = React.useState("");
|
const [secret2FA, setSecret2FA] = React.useState("");
|
||||||
const [show2faSetup, setShow2faSetup] = useState(false);
|
const [show2faSetup, setShow2faSetup] = useState(false);
|
||||||
|
|
||||||
|
const [dealName, setDealName] = React.useState("");
|
||||||
|
const [dealAddress, setDealAddress] = React.useState("");
|
||||||
|
const [dealType, setDealType] = React.useState("MSSP");
|
||||||
|
const [dealCountry, setDealCountry] = React.useState("United States");
|
||||||
|
const [dealCurrency, setDealCurrency] = React.useState("USD");
|
||||||
|
const [dealStatus, setDealStatus] = React.useState("initiated");
|
||||||
|
const [dealValue, setDealValue] = React.useState("");
|
||||||
|
const [dealDiscount, setDealDiscount] = React.useState("");
|
||||||
|
const [dealerror, setDealerror] = React.useState("");
|
||||||
|
const [dealList, setDealList] = React.useState([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isDropzone) {
|
if (isDropzone) {
|
||||||
//redirectOpenApi();
|
//redirectOpenApi();
|
||||||
@@ -648,6 +666,45 @@ const Admin = (props) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleGetDeals = (orgId) => {
|
||||||
|
console.log("Get deals!")
|
||||||
|
|
||||||
|
if (orgId.length === 0) {
|
||||||
|
alert.error(
|
||||||
|
"Organization ID not defined. Please contact us on https://shuffler.io if this persists logout."
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${globalUrl}/api/v1/orgs/${orgId}/deals`
|
||||||
|
fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log("Bad status code in get deals: ", response.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
console.log("Got deals: ", responseJson)
|
||||||
|
if (responseJson.success === false) {
|
||||||
|
alert.error("Failed loading deals. Contact support if this persists")
|
||||||
|
} else {
|
||||||
|
setDealList(responseJson)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("Error getting org deals: ", error);
|
||||||
|
alert.error("Failed getting deals for your org. Contact support if this persists.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const handleGetOrg = (orgId) => {
|
const handleGetOrg = (orgId) => {
|
||||||
if (orgId.length === 0) {
|
if (orgId.length === 0) {
|
||||||
alert.error(
|
alert.error(
|
||||||
@@ -674,7 +731,7 @@ const Admin = (props) => {
|
|||||||
})
|
})
|
||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
if (responseJson["success"] === false) {
|
if (responseJson["success"] === false) {
|
||||||
alert.error("Failed getting org: ", responseJson.readon);
|
alert.error("Failed getting your org: ", responseJson.readon);
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (
|
||||||
responseJson.sync_features === undefined ||
|
responseJson.sync_features === undefined ||
|
||||||
@@ -682,6 +739,12 @@ const Admin = (props) => {
|
|||||||
) {
|
) {
|
||||||
responseJson.sync_features = {};
|
responseJson.sync_features = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isCloud && responseJson.partner_info !== undefined && responseJson.partner_info.reseller === true) {
|
||||||
|
handleGetDeals(orgId)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
setSelectedOrganization(responseJson);
|
setSelectedOrganization(responseJson);
|
||||||
var lists = {
|
var lists = {
|
||||||
active: {
|
active: {
|
||||||
@@ -1682,6 +1745,235 @@ const Admin = (props) => {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
: null
|
: null
|
||||||
|
|
||||||
|
|
||||||
|
const products = [
|
||||||
|
{ code: '', label: 'MSSP', phone: '' },
|
||||||
|
{ code: '', label: 'Enterprise', phone: '' },
|
||||||
|
{ code: '', label: 'Consultancy', phone: '' },
|
||||||
|
{ code: '', label: 'Support', phone: '' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const addDealModal = (
|
||||||
|
<Dialog
|
||||||
|
open={selectedDealModalOpen}
|
||||||
|
onClose={() => {
|
||||||
|
setSelectedDealModalOpen(false);
|
||||||
|
|
||||||
|
}}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: theme.palette.surfaceColor,
|
||||||
|
color: "white",
|
||||||
|
minWidth: "800px",
|
||||||
|
minHeight: "320px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogTitle style={{ maxWidth: 450, margin: "auto" }}>
|
||||||
|
<span style={{ color: "white" }}>
|
||||||
|
Register new deal
|
||||||
|
</span>
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<div style={{ display: "flex" }}>
|
||||||
|
<TextField
|
||||||
|
style={{
|
||||||
|
marginTop: 0,
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
flex: 3,
|
||||||
|
marginRight: 10,
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
style: {
|
||||||
|
height: 50,
|
||||||
|
color: "white",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
color="primary"
|
||||||
|
required
|
||||||
|
fullWidth={true}
|
||||||
|
placeholder="Name"
|
||||||
|
type="text"
|
||||||
|
id="standard-required"
|
||||||
|
autoComplete="username"
|
||||||
|
margin="normal"
|
||||||
|
label="Name"
|
||||||
|
variant="outlined"
|
||||||
|
defaultValue={dealName}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDealName(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
style={{
|
||||||
|
marginTop: 0,
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
flex: 3,
|
||||||
|
marginRight: 10,
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
style: {
|
||||||
|
height: 50,
|
||||||
|
color: "white",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
color="primary"
|
||||||
|
required
|
||||||
|
fullWidth={true}
|
||||||
|
placeholder="Address"
|
||||||
|
label="Address"
|
||||||
|
type="text"
|
||||||
|
id="standard-required"
|
||||||
|
autoComplete="username"
|
||||||
|
margin="normal"
|
||||||
|
variant="outlined"
|
||||||
|
defaultValue={dealAddress}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDealAddress(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", marginTop: 10, }}>
|
||||||
|
<TextField
|
||||||
|
style={{
|
||||||
|
marginTop: 0,
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
flex: 1,
|
||||||
|
marginRight: 10,
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
style: {
|
||||||
|
height: 50,
|
||||||
|
color: "white",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
color="primary"
|
||||||
|
required
|
||||||
|
fullWidth={true}
|
||||||
|
placeholder="1000"
|
||||||
|
label="Value (USD)"
|
||||||
|
type="text"
|
||||||
|
id="standard-required"
|
||||||
|
margin="normal"
|
||||||
|
variant="outlined"
|
||||||
|
defaultValue={dealValue}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDealValue(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Autocomplete
|
||||||
|
id="country-select"
|
||||||
|
sx={{width: 250}}
|
||||||
|
options={countries}
|
||||||
|
variant="outlined"
|
||||||
|
autoHighlight
|
||||||
|
getOptionLabel={(option) => option.label}
|
||||||
|
onChange={(event, newValue) => {
|
||||||
|
setDealCountry(newValue.label)
|
||||||
|
}}
|
||||||
|
renderOption={(props, option) => (
|
||||||
|
<Box component="li" sx={{ '& > img': { mr: 2, flexShrink: 0 } }} {...props}>
|
||||||
|
<img
|
||||||
|
loading="lazy"
|
||||||
|
width="20"
|
||||||
|
src={`https://flagcdn.com/w20/${option.code.toLowerCase()}.png`}
|
||||||
|
srcSet={`https://flagcdn.com/w40/${option.code.toLowerCase()}.png 2x`}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
{option.label} ({option.code}) +{option.phone}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<TextField
|
||||||
|
{...params}
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
flex: 1,
|
||||||
|
marginTop: 0,
|
||||||
|
marginRight: 10,
|
||||||
|
}}
|
||||||
|
variant="outlined"
|
||||||
|
label="Choose a country"
|
||||||
|
defaultValue={dealCountry}
|
||||||
|
inputProps={{
|
||||||
|
...params.inputProps,
|
||||||
|
autoComplete: 'new-password', // disable autocomplete and autofill
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Autocomplete
|
||||||
|
id="product-select"
|
||||||
|
sx={{width: 250}}
|
||||||
|
options={products}
|
||||||
|
variant="outlined"
|
||||||
|
autoHighlight
|
||||||
|
onChange={(event, newValue) => {
|
||||||
|
setDealType(newValue)
|
||||||
|
}}
|
||||||
|
getOptionLabel={(option) => option.label}
|
||||||
|
renderOption={(props, option) => (
|
||||||
|
<Box component="li" sx={{ '& > img': { mr: 2, flexShrink: 0 } }} {...props}>
|
||||||
|
{option.label}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<TextField
|
||||||
|
{...params}
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
flex: 1,
|
||||||
|
marginTop: 0,
|
||||||
|
}}
|
||||||
|
variant="outlined"
|
||||||
|
label="Choose a product"
|
||||||
|
defaultValue={dealType}
|
||||||
|
inputProps={{
|
||||||
|
...params.inputProps,
|
||||||
|
autoComplete: 'new-password', // disable autocomplete and autofill
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{dealerror.length > 0 ?
|
||||||
|
<Typography variant="body1" color="textSecondary" style={{margin: 10, }}>
|
||||||
|
error registering: {dealerror}
|
||||||
|
</Typography>
|
||||||
|
: null}
|
||||||
|
<div style={{display: "flex", width: 300, margin: "auto"}}>
|
||||||
|
<Button
|
||||||
|
style={{ maxHeight: 50, flex: 1, margin: 5, }}
|
||||||
|
variant="outlined"
|
||||||
|
color="secondary"
|
||||||
|
disabled={false}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedDealModalOpen(false);
|
||||||
|
|
||||||
|
//setDealName("")
|
||||||
|
//setDealAddress("")
|
||||||
|
//setDealCountry("")
|
||||||
|
//setDealValue("")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
style={{ maxHeight: 50, flex: 1, margin: 5, }}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
disabled={dealName.length <= 3 || dealAddress.length <= 3 || dealCountry.length === 0 || dealValue.length === 0 || dealType.length === 0}
|
||||||
|
onClick={() => {
|
||||||
|
submitDeal(dealName, dealAddress, dealCountry, dealValue)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
|
||||||
const editUserModal = (
|
const editUserModal = (
|
||||||
<Dialog
|
<Dialog
|
||||||
open={selectedUserModalOpen}
|
open={selectedUserModalOpen}
|
||||||
@@ -2115,6 +2407,60 @@ const Admin = (props) => {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const submitDeal = (dealName, dealAddress, dealCountry, dealValue) => {
|
||||||
|
if (dealerror.length > 0) {
|
||||||
|
setDealerror("")
|
||||||
|
}
|
||||||
|
|
||||||
|
const orgId = selectedOrganization.id;
|
||||||
|
const data = {
|
||||||
|
reseller_org: orgId,
|
||||||
|
name: dealName,
|
||||||
|
address: dealAddress,
|
||||||
|
country: dealCountry,
|
||||||
|
value: dealValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
const url = `${globalUrl}/api/v1/orgs/${orgId}/deals`
|
||||||
|
fetch(url, {
|
||||||
|
mode: "cors",
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
credentials: "include",
|
||||||
|
crossDomain: true,
|
||||||
|
withCredentials: true,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(function (response) {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log("Error in response");
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(function (responseJson) {
|
||||||
|
if (responseJson.success === true) {
|
||||||
|
setSelectedDealModalOpen(false);
|
||||||
|
alert.success("Added new deal! We will be in touch shortly with an update.");
|
||||||
|
|
||||||
|
setDealName("")
|
||||||
|
setDealAddress("")
|
||||||
|
setDealValue("")
|
||||||
|
setDealCountry("United States")
|
||||||
|
setDealType("MSSP")
|
||||||
|
} else {
|
||||||
|
setDealerror(responseJson.reason)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
//console.log("Error: ", error);
|
||||||
|
setDealerror(error.toString())
|
||||||
|
alert.error("Failed adding deal reg: ", error)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const cancelSubscriptions = (subscription_id) => {
|
const cancelSubscriptions = (subscription_id) => {
|
||||||
console.log(selectedOrganization);
|
console.log(selectedOrganization);
|
||||||
const orgId = selectedOrganization.id;
|
const orgId = selectedOrganization.id;
|
||||||
@@ -2450,6 +2796,169 @@ const Admin = (props) => {
|
|||||||
backgroundColor: theme.palette.inputColor,
|
backgroundColor: theme.palette.inputColor,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{isCloud && selectedOrganization.partner_info !== undefined && selectedOrganization.partner_info.reseller === true ?
|
||||||
|
<div style={{ marginTop: 30, marginBottom: 200, }}>
|
||||||
|
<Typography
|
||||||
|
style={{ marginTop: 40, marginLeft: 10, marginBottom: 5 }}
|
||||||
|
variant="h6"
|
||||||
|
>
|
||||||
|
Reseller dashboard
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
style={{ margin: 15, }}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedDealModalOpen(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add deal
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
style={{ marginLeft: 5, marginRight: 15 }}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => {
|
||||||
|
handleGetDeals(userdata.active_org.id)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CachedIcon />
|
||||||
|
</Button>
|
||||||
|
<List>
|
||||||
|
<ListItem>
|
||||||
|
<ListItemText
|
||||||
|
primary="Name"
|
||||||
|
style={{
|
||||||
|
minWidth: 200,
|
||||||
|
maxWidth: 200,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary="Status"
|
||||||
|
style={{ minWidth: 150, maxWidth: 150, marginLeft: 5 }}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary="Value"
|
||||||
|
style={{ minWidth: 125, maxWidth: 125 }}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary="Discount"
|
||||||
|
style={{ minWidth: 100, maxWidth: 100 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ListItemText
|
||||||
|
primary="Address"
|
||||||
|
style={{
|
||||||
|
marginleft: 10,
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 100,
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary="Country"
|
||||||
|
style={{
|
||||||
|
marginleft: 10,
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 100,
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ListItemText
|
||||||
|
primary="Created"
|
||||||
|
style={{ minWidth: 200, maxWidth: 100 }}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary="Last edited"
|
||||||
|
style={{ minWidth: 200, maxWidth: 100 }}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
<Divider />
|
||||||
|
{dealList.length === 0 ?
|
||||||
|
<Typography variant="h6" style={{textAlign: "center", margin: "auto", width: 600, marginTop: 50, marginBottom: 50, }}>
|
||||||
|
No deals registered yet. Click "Add deal" to register one
|
||||||
|
</Typography>
|
||||||
|
:
|
||||||
|
dealList.map((deal, index) => {
|
||||||
|
var bgColor = "#27292d";
|
||||||
|
if (index % 2 === 0) {
|
||||||
|
bgColor = "#1f2023";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListItem key={index} style={{ backgroundColor: bgColor }}>
|
||||||
|
<ListItemText
|
||||||
|
primary={deal.name}
|
||||||
|
style={{
|
||||||
|
minWidth: 200,
|
||||||
|
maxWidth: 200,
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary={deal.status}
|
||||||
|
style={{
|
||||||
|
minWidth: 150,
|
||||||
|
maxWidth: 150,
|
||||||
|
marginLeft: 5,
|
||||||
|
color: deal.status.toLowerCase() === "requested" ? "yellow" : deal.status.toLowerCase() === "denied" || deal.status.toLowerCase() === "cancelled" ? "red" : "green",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary={`\$${deal.value}`}
|
||||||
|
style={{ minWidth: 125, maxWidth: 125 }}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary={deal.discount.length === 0 ? "TBD" : deal.discount}
|
||||||
|
style={{ minWidth: 100, maxWidth: 100 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ListItemText
|
||||||
|
primary={deal.address}
|
||||||
|
style={{
|
||||||
|
marginleft: 10,
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 100,
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary={deal.country}
|
||||||
|
style={{
|
||||||
|
marginleft: 10,
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 100,
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ListItemText
|
||||||
|
primary={new Date(deal.created * 1000).toISOString()}
|
||||||
|
style={{ minWidth: 200, maxWidth: 200 }}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary={new Date(deal.edited * 1000).toISOString()}
|
||||||
|
style={{ minWidth: 200, maxWidth: 200 }}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</List>
|
||||||
|
|
||||||
|
<Divider
|
||||||
|
style={{
|
||||||
|
marginTop: 20,
|
||||||
|
marginBottom: 20,
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
: null}
|
||||||
|
|
||||||
|
|
||||||
{isCloud &&
|
{isCloud &&
|
||||||
selectedOrganization.subscriptions !== undefined &&
|
selectedOrganization.subscriptions !== undefined &&
|
||||||
selectedOrganization.subscriptions !== null &&
|
selectedOrganization.subscriptions !== null &&
|
||||||
@@ -4291,6 +4800,7 @@ const Admin = (props) => {
|
|||||||
{modalView}
|
{modalView}
|
||||||
{cloudSyncModal}
|
{cloudSyncModal}
|
||||||
{editUserModal}
|
{editUserModal}
|
||||||
|
{addDealModal}
|
||||||
{editAuthenticationModal}
|
{editAuthenticationModal}
|
||||||
{data}
|
{data}
|
||||||
<TextField
|
<TextField
|
||||||
|
|||||||
@@ -11601,7 +11601,7 @@ const parsedExecutionArgument = () => {
|
|||||||
variant="temporary"
|
variant="temporary"
|
||||||
BackdropProps={{
|
BackdropProps={{
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: "transparent",
|
//backgroundColor: "transparent",
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
PaperProps={{
|
PaperProps={{
|
||||||
|
|||||||
@@ -1511,7 +1511,7 @@ const Apps = (props) => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
color="primary"
|
color="primary"
|
||||||
id="app_search_field"
|
id="app_search_field"
|
||||||
placeholder={"Search apps"}
|
placeholder={"Search your apps"}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
handleSearchChange(event.target.value);
|
handleSearchChange(event.target.value);
|
||||||
setCursearch(event.target.value);
|
setCursearch(event.target.value);
|
||||||
|
|||||||
@@ -598,7 +598,7 @@ const Docs = (defaultprops) => {
|
|||||||
Documentation
|
Documentation
|
||||||
</Typography>
|
</Typography>
|
||||||
<div style={{display: "flex", marginTop: 25, }}>
|
<div style={{display: "flex", marginTop: 25, }}>
|
||||||
<CustomButton title="Open a Ticket" icon=<img src="/images/Shuffle_logo_new.png" style={{height: 35, width: 35, border: "", borderRadius: theme.palette.borderRadius, }} /> link="https://support.shuffler.io" />
|
<CustomButton title="Open a Ticket" icon=<img src="/images/Shuffle_logo_new.png" style={{height: 35, width: 35, border: "", borderRadius: theme.palette.borderRadius, }} /> link="mailto:support@shuffler.io" />
|
||||||
<CustomButton title="Ask the community" icon=<img src="/images/social/discord.png" style={{height: 35, width: 35, border: "", borderRadius: theme.palette.borderRadius, }} /> link="https://discord.gg/B2CBzUm" />
|
<CustomButton title="Ask the community" icon=<img src="/images/social/discord.png" style={{height: 35, width: 35, border: "", borderRadius: theme.palette.borderRadius, }} /> link="https://discord.gg/B2CBzUm" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user