2657 lines
99 KiB
React
2657 lines
99 KiB
React
import React, { memo, useContext, useEffect, useState } from 'react';
|
|
import {
|
|
Edit as EditIcon,
|
|
SelectAll as SelectAllIcon,
|
|
Delete as DeleteIcon,
|
|
CheckCircle as CheckCircleIcon,
|
|
Cancel as CancelIcon,
|
|
Search as SearchIcon,
|
|
Clear as ClearIcon,
|
|
DragIndicator as DragIndicatorIcon,
|
|
Close as CloseIcon,
|
|
LockOpen as LockOpenIcon,
|
|
} from "@mui/icons-material";
|
|
import { useNavigate } from "react-router-dom";
|
|
import { toast } from "react-toastify";
|
|
import theme from "../theme.jsx";
|
|
import Markdown from "react-markdown";
|
|
import AuthenticationOauth2 from "../components/Oauth2Auth.jsx";
|
|
import { isMobile } from "react-device-detect"
|
|
import PaperComponent from "../components/PaperComponent.jsx";
|
|
import { CodeHandler, Img, OuterLink, } from '../views/Docs.jsx'
|
|
import { v4 as uuidv4} from "uuid";
|
|
|
|
import {
|
|
Divider,
|
|
List,
|
|
ListItem,
|
|
ListItemText,
|
|
IconButton,
|
|
Tooltip,
|
|
Chip,
|
|
Checkbox,
|
|
Typography,
|
|
TextField,
|
|
Button,
|
|
Dialog,
|
|
DialogTitle,
|
|
DialogActions,
|
|
DialogContent,
|
|
Select,
|
|
FormControl,
|
|
InputLabel,
|
|
MenuItem,
|
|
FormControlLabel,
|
|
InputAdornment,
|
|
Grid,
|
|
Zoom,
|
|
Paper,
|
|
Skeleton,
|
|
Link,
|
|
} from "@mui/material";
|
|
|
|
import algoliasearch from "algoliasearch/lite";
|
|
import {
|
|
InstantSearch,
|
|
Configure,
|
|
connectSearchBox,
|
|
connectHits,
|
|
connectHitInsights,
|
|
RefinementList,
|
|
ClearRefinements,
|
|
connectStateResults
|
|
} from "react-instantsearch-dom";
|
|
import aa from "search-insights";
|
|
import { Context } from '../context/ContextApi.jsx';
|
|
|
|
const searchClient = algoliasearch(
|
|
"JNSS5CFDZZ",
|
|
"db08e40265e2941b9a7d8f644b6e5240"
|
|
)
|
|
|
|
const AppAuthTab = memo((props) => {
|
|
const { globalUrl, userdata, isCloud, selectedOrganization } = props;
|
|
const [selectedAuthentication, setSelectedAuthentication] = React.useState({});
|
|
const [authentication, setAuthentication] = React.useState([]);
|
|
const [selectedUserModalOpen, setSelectedUserModalOpen] = React.useState(false);
|
|
const [authenticationFields, setAuthenticationFields] = React.useState([]);
|
|
const [selectedAuthenticationModalOpen, setSelectedAuthenticationModalOpen] = React.useState(false);
|
|
const [appAuthenticationGroupEnvironment, setAppAuthenticationGroupEnvironment] = React.useState("");
|
|
const [environments, setEnvironments] = React.useState([]);
|
|
const [listItemExpanded, setListItemExpanded] = React.useState(-1);
|
|
const [appAuthenticationGroupModalOpen, setAppAuthenticationGroupModalOpen] = React.useState(false);
|
|
const [appAuthenticationGroupId, setAppAuthenticationGroupId] = React.useState("");
|
|
const [appAuthenticationGroups, setAppAuthenticationGroups] = React.useState([]);
|
|
const [appAuthenticationGroupName, setAppAuthenticationGroupName] = React.useState("");
|
|
const [appAuthenticationGroupDescription, setAppAuthenticationGroupDescription] = React.useState("");
|
|
const [appsForAppAuthGroup, setAppsForAppAuthGroup] = React.useState([]);
|
|
const [searchQuery, setSearchQuery] = React.useState("");
|
|
const [showAppModal, setShowAppModal] = useState(false)
|
|
const [showAuthenticationLoader, setShowAuthenticationLoader] = useState(true)
|
|
const [showAppAuthGroupLoader, setShowAppAuthGroupLoader] = useState(true)
|
|
const changeDistribution = (data) => {
|
|
//changeDistributed(data, !isDistributed)
|
|
editAuthenticationConfig(data.id, "suborg_distribute")
|
|
}
|
|
|
|
useEffect(() => {
|
|
getAppAuthentication();
|
|
getAppAuthenticationGroups();
|
|
getEnvironments();
|
|
}, [])
|
|
|
|
const getAppAuthentication = () => {
|
|
fetch(globalUrl + "/api/v1/apps/authentication", {
|
|
method: "GET",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Accept: "application/json",
|
|
},
|
|
credentials: "include",
|
|
})
|
|
.then((response) => {
|
|
if (response.status !== 200) {
|
|
console.log("Status not 200 for apps :O!");
|
|
return;
|
|
}
|
|
|
|
return response.json();
|
|
})
|
|
.then((responseJson) => {
|
|
if (responseJson.success === true) {
|
|
setAuthentication(responseJson.data);
|
|
setShowAuthenticationLoader(false)
|
|
} else {
|
|
toast("Failed getting authentications");
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
toast(error.toString());
|
|
});
|
|
};
|
|
|
|
const updateAppAuthentication = (field) => {
|
|
setSelectedAuthenticationModalOpen(true);
|
|
setSelectedAuthentication(field);
|
|
//{selectedAuthentication.fields.map((data, index) => {
|
|
var newfields = [];
|
|
for (var key in field.fields) {
|
|
newfields.push({
|
|
key: field.fields[key].key,
|
|
value: "",
|
|
});
|
|
}
|
|
setAuthenticationFields(newfields);
|
|
};
|
|
const saveAuthentication = (authentication) => {
|
|
const data = authentication;
|
|
const url = globalUrl + "/api/v1/apps/authentication";
|
|
|
|
fetch(url, {
|
|
mode: "cors",
|
|
method: "PUT",
|
|
body: JSON.stringify(data),
|
|
credentials: "include",
|
|
crossDomain: true,
|
|
withCredentials: true,
|
|
headers: {
|
|
"Content-Type": "application/json; charset=utf-8",
|
|
},
|
|
})
|
|
.then((response) =>
|
|
response.json().then((responseJson) => {
|
|
if (responseJson["success"] === false) {
|
|
// Check if .reason exists
|
|
if (responseJson.reason !== undefined) {
|
|
toast("Failed changing authentication: " + responseJson.reason);
|
|
} else {
|
|
toast("Failed changing authentication");
|
|
}
|
|
} else {
|
|
getAppAuthentication();
|
|
|
|
|
|
setSelectedAuthentication({});
|
|
setSelectedAuthenticationModalOpen(false);
|
|
}
|
|
})
|
|
)
|
|
.catch((error) => {
|
|
toast("Err: " + error.toString());
|
|
});
|
|
};
|
|
|
|
const deleteAuthentication = (data) => {
|
|
toast("Deleting auth " + data.label);
|
|
|
|
// Just use this one?
|
|
const url = globalUrl + "/api/v1/apps/authentication/" + data.id;
|
|
console.log("URL: ", url);
|
|
fetch(url, {
|
|
method: "DELETE",
|
|
credentials: "include",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
})
|
|
.then((response) =>
|
|
response.json().then((responseJson) => {
|
|
if (responseJson.success === false) {
|
|
toast("Failed deleting auth");
|
|
} else {
|
|
// Need to wait because query in ES is too fast
|
|
setTimeout(() => {
|
|
getAppAuthentication();
|
|
}, 1000);
|
|
//toast("Successfully deleted authentication!")
|
|
}
|
|
})
|
|
)
|
|
.catch((error) => {
|
|
console.log("Error in userdata: ", error);
|
|
});
|
|
};
|
|
|
|
const editAuthenticationConfig = (id, parentAction) => {
|
|
const data = {
|
|
id: id,
|
|
action: parentAction !== undefined && parentAction !== null ? parentAction : "assign_everywhere",
|
|
}
|
|
|
|
const url = globalUrl + "/api/v1/apps/authentication/" + id + "/config";
|
|
|
|
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((response) =>
|
|
response.json().then((responseJson) => {
|
|
if (responseJson["success"] === false) {
|
|
toast("Failed overwriting appauth");
|
|
} else {
|
|
toast("Successfully updated auth!");
|
|
setSelectedUserModalOpen(false);
|
|
setTimeout(() => {
|
|
getAppAuthentication();
|
|
}, 1000);
|
|
}
|
|
})
|
|
)
|
|
.catch((error) => {
|
|
toast("Err: " + error.toString());
|
|
});
|
|
};
|
|
|
|
const editAuthenticationModal = selectedAuthenticationModalOpen ? (
|
|
<Dialog
|
|
open={selectedAuthenticationModalOpen}
|
|
onClose={() => {
|
|
setSelectedAuthenticationModalOpen(false);
|
|
}}
|
|
PaperProps={{
|
|
sx: {
|
|
borderRadius: theme?.palette?.DialogStyle?.borderRadius,
|
|
border: theme?.palette?.DialogStyle?.border,
|
|
minWidth: "800px",
|
|
minHeight: "320px",
|
|
fontFamily: theme?.typography?.fontFamily,
|
|
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
|
zIndex: 1000,
|
|
'& .MuiDialogContent-root': {
|
|
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
|
},
|
|
'& .MuiDialogTitle-root': {
|
|
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
|
},
|
|
'& .MuiDialogActions-root': {
|
|
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
|
},
|
|
},
|
|
}}
|
|
>
|
|
<DialogTitle>
|
|
<span style={{ color: "white" }}>
|
|
Edit authentication for {selectedAuthentication.app.name.replaceAll("_", " ")} (
|
|
{selectedAuthentication.label})
|
|
</span>
|
|
<Typography variant="body1" color="textSecondary" style={{ marginTop: 10 }}>
|
|
You can <b>not</b> see the previous values for an authentication while editing. This is to keep your data secure. You can overwrite one- or multiple fields at a time.
|
|
</Typography>
|
|
</DialogTitle>
|
|
<DialogContent>
|
|
<Typography style={{ marginBottom: 0, marginTop: 10 }}>
|
|
Authentication Label
|
|
</Typography>
|
|
<TextField
|
|
style={{
|
|
backgroundColor: theme.palette.innerTextfieldStyle.backgroundColor,
|
|
marginTop: 0,
|
|
}}
|
|
InputProps={{
|
|
style: {
|
|
height: 50,
|
|
color: "white",
|
|
},
|
|
}}
|
|
color="primary"
|
|
required
|
|
fullWidth={true}
|
|
placeholder={selectedAuthentication.label}
|
|
defaultValue={selectedAuthentication.label}
|
|
type="text"
|
|
margin="normal"
|
|
variant="outlined"
|
|
onChange={(e) => {
|
|
selectedAuthentication.label = e.target.value
|
|
}}
|
|
/>
|
|
|
|
<Divider />
|
|
{selectedAuthentication.type === "oauth" || selectedAuthentication.type === "oauth2" || selectedAuthentication.type === "oauth2-app" ?
|
|
<div>
|
|
<Typography variant="body1" color="textSecondary" style={{ marginBottom: 0, marginTop: 10 }}>
|
|
Only the name and url can be modified for Oauth2/OpenID connect. Please remake the authentication if you want to change the other fields like Client ID, Secret, Scopes etc.
|
|
</Typography>
|
|
</div>
|
|
: null}
|
|
|
|
{selectedAuthentication.fields.map((data, index) => {
|
|
var fieldname = data.key.replaceAll("_", " ")
|
|
if (fieldname.endsWith(" basic")) {
|
|
fieldname = fieldname.substring(0, fieldname.length - 6)
|
|
}
|
|
|
|
if (selectedAuthentication.type === "oauth" || selectedAuthentication.type === "oauth2" || selectedAuthentication.type === "oauth2-app") {
|
|
if (selectedAuthentication.fields[index].key !== "url") {
|
|
return null
|
|
}
|
|
}
|
|
|
|
|
|
//console.log("DATA: ", data, selectedAuthentication)
|
|
return (
|
|
<div key={index}>
|
|
<Typography style={{ marginBottom: 0, marginTop: 10 }}>
|
|
{fieldname}
|
|
</Typography>
|
|
<TextField
|
|
style={{
|
|
backgroundColor: theme.palette.innerTextfieldStyle.backgroundColor,
|
|
marginTop: 0,
|
|
}}
|
|
InputProps={{
|
|
style: {
|
|
height: 50,
|
|
color: "white",
|
|
},
|
|
}}
|
|
color="primary"
|
|
required
|
|
fullWidth={true}
|
|
placeholder={fieldname}
|
|
type="text"
|
|
id={`authentication-${index}`}
|
|
margin="normal"
|
|
variant="outlined"
|
|
onChange={(e) => {
|
|
authenticationFields[index].value = e.target.value;
|
|
setAuthenticationFields(authenticationFields);
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
})}
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button
|
|
style={{ borderRadius: "2px", textTransform: "none", fontSize: 16 }}
|
|
onClick={() => setSelectedAuthenticationModalOpen(false)}
|
|
color="primary"
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
variant="contained"
|
|
style={{ borderRadius: "2px", textTransform: "none", fontSize: 16, color: "#1a1a1a", backgroundColor: "#FF8444" }}
|
|
onClick={() => {
|
|
var error = false;
|
|
var fails = 0
|
|
for (var key in authenticationFields) {
|
|
const item = authenticationFields[key];
|
|
if (item.value.length === 0) {
|
|
fails += 1
|
|
console.log("ITEM: ", item);
|
|
// var currentnode = cy.getElementById(data.id)
|
|
var textfield = document.getElementById(
|
|
`authentication-${key}`
|
|
);
|
|
if (textfield !== null && textfield !== undefined) {
|
|
console.log("HANDLE ERROR FOR KEY ", key);
|
|
}
|
|
error = true;
|
|
}
|
|
}
|
|
|
|
if (selectedAuthentication.type === "oauth" || selectedAuthentication.type === "oauth2" || selectedAuthentication.type === "oauth2-app") {
|
|
selectedAuthentication.fields = []
|
|
}
|
|
|
|
if (error && fails === authenticationFields.length) {
|
|
toast("Updating auth with new name only")
|
|
saveAuthentication(selectedAuthentication);
|
|
} else {
|
|
toast("Saving new version of this authentication");
|
|
selectedAuthentication.fields = authenticationFields;
|
|
saveAuthentication(selectedAuthentication);
|
|
}
|
|
}}
|
|
color="primary"
|
|
|
|
>
|
|
Submit
|
|
</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
) : null;
|
|
|
|
const getEnvironments = () => {
|
|
fetch(globalUrl + "/api/v1/getenvironments", {
|
|
method: "GET",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Accept: "application/json",
|
|
},
|
|
credentials: "include",
|
|
})
|
|
.then((response) => {
|
|
if (response.status !== 200) {
|
|
console.log("Status not 200 for apps :O!");
|
|
return;
|
|
}
|
|
|
|
return response.json();
|
|
})
|
|
.then((responseJson) => {
|
|
setEnvironments(responseJson);
|
|
|
|
// Helper info for users in case they have a large queue and don't know about queue flushing
|
|
if (responseJson !== undefined && responseJson !== null && responseJson.length > 0) {
|
|
if (responseJson.length === 1 && responseJson[0].Type !== "cloud") {
|
|
setListItemExpanded(0)
|
|
}
|
|
|
|
for (var i = 0; i < responseJson.length; i++) {
|
|
const env = responseJson[i];
|
|
|
|
// Check if queuesize is too large
|
|
if (
|
|
env.queue !== undefined &&
|
|
env.queue !== null &&
|
|
env.queue > 100
|
|
) {
|
|
toast(
|
|
"Queue size for " +
|
|
env.name +
|
|
" is very large. We recommend you to reduce it by flushing the queue before continuing.",
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
toast(error.toString());
|
|
});
|
|
};
|
|
|
|
const getAppAuthenticationGroups = () => {
|
|
//console.log("DEBUG: Skipping app auth group loading")
|
|
//return
|
|
|
|
fetch(globalUrl + "/api/v1/authentication/group", {
|
|
method: "GET",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Accept: "application/json",
|
|
},
|
|
credentials: "include",
|
|
})
|
|
.then((response) => {
|
|
if (response.status !== 200) {
|
|
console.log("Status not 200 for apps :O!");
|
|
return;
|
|
}
|
|
|
|
return response.json();
|
|
})
|
|
.then((responseJson) => {
|
|
if (responseJson.success === true) {
|
|
setAppAuthenticationGroups(responseJson.data);
|
|
setShowAppAuthGroupLoader(false)
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
toast(error.toString());
|
|
});
|
|
};
|
|
|
|
const deleteAppAuthenticationGroup = (appAuthGroupId) => {
|
|
const url = `${globalUrl}/api/v1/authentication/group/${appAuthGroupId}`
|
|
fetch(url, {
|
|
method: "DELETE",
|
|
credentials: "include",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
})
|
|
.then((response) => {
|
|
if (response.status !== 200) {
|
|
console.log("Status not 200 for deleting app auth group");
|
|
}
|
|
|
|
return response.json();
|
|
})
|
|
.then((responseJson) => {
|
|
if (responseJson.success === false) {
|
|
toast("Failed to delete app authentication group");
|
|
} else {
|
|
toast("App authentication group deleted")
|
|
getAppAuthenticationGroups()
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
toast(error.toString())
|
|
})
|
|
}
|
|
|
|
const createAppAuthenticationGroup = (name, environment, description, appAuthIds) => {
|
|
// Makes list of ids into a full-on list of auth, but just with the ID
|
|
// The backend fills in the rest
|
|
console.log("INput auth: ", appAuthIds)
|
|
let app_auths = appAuthIds.map((appAuthId) => {
|
|
return { id: appAuthId };
|
|
})
|
|
|
|
var parsedAppGroup = {
|
|
label: name,
|
|
environment: environment,
|
|
description: description,
|
|
app_auths: app_auths
|
|
}
|
|
|
|
if (appAuthenticationGroupId !== undefined && appAuthenticationGroupId !== null && appAuthenticationGroupId !== "") {
|
|
parsedAppGroup.id = appAuthenticationGroupId
|
|
}
|
|
|
|
fetch(globalUrl + "/api/v1/authentication/group", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Accept: "application/json",
|
|
},
|
|
credentials: "include",
|
|
body: JSON.stringify(parsedAppGroup),
|
|
})
|
|
.then((response) => {
|
|
if (response.status !== 200) {
|
|
throw new Error("Failed to create app authentication group");
|
|
}
|
|
|
|
return response.json();
|
|
})
|
|
.then((responseJson) => {
|
|
if (responseJson.success === false) {
|
|
toast("Failed to create. Please try again, or contact support@shuffler.io")
|
|
} else {
|
|
// Close the modal
|
|
setAppAuthenticationGroupModalOpen(false)
|
|
|
|
toast("App authentication group created")
|
|
getAppAuthenticationGroups()
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
toast(error.toString());
|
|
});
|
|
};
|
|
|
|
const handleAppAuthGroupCheckbox = (data) => {
|
|
//let groupApp = data.app.id
|
|
var newappauth = appsForAppAuthGroup
|
|
if (appsForAppAuthGroup.includes(data.app.id)) {
|
|
newappauth = newappauth.filter((item) => item !== data.app.id)
|
|
}
|
|
|
|
if (appsForAppAuthGroup.includes(data.id)) {
|
|
// Remove app from app auth group
|
|
newappauth = newappauth.filter((item) => item !== data.id)
|
|
setAppsForAppAuthGroup(newappauth)
|
|
return
|
|
}
|
|
|
|
for (var i = 0; i < authentication.length; i++) {
|
|
if (authentication[i].id === data.id) {
|
|
continue
|
|
}
|
|
|
|
if (!appsForAppAuthGroup.includes(authentication[i].id)) {
|
|
continue
|
|
}
|
|
|
|
if (authentication[i].app.id === data.app.id) {
|
|
// Remove app from app auth group
|
|
newappauth = newappauth.filter((item) => item !== authentication[i].id)
|
|
toast(`App ${data.app.name} is already in this group`)
|
|
}
|
|
}
|
|
|
|
setAppsForAppAuthGroup(newappauth.concat(data.id))
|
|
}
|
|
|
|
const authenticationView = appAuthenticationGroupModalOpen ?
|
|
(
|
|
<div>
|
|
{/* (appAuthenticationGroupModalOpen : { */}
|
|
{appAuthenticationGroupModalOpen && (
|
|
<Dialog
|
|
open={appAuthenticationGroupModalOpen}
|
|
onClose={() => {
|
|
setAppAuthenticationGroupModalOpen(false);
|
|
|
|
setAppAuthenticationGroupId("")
|
|
setAppAuthenticationGroupName("")
|
|
setAppAuthenticationGroupEnvironment("")
|
|
setAppAuthenticationGroupDescription("")
|
|
setAppsForAppAuthGroup([])
|
|
}}
|
|
PaperProps={{
|
|
sx: {
|
|
borderRadius: theme?.palette?.DialogStyle?.borderRadius,
|
|
border: theme?.palette?.DialogStyle?.border,
|
|
minWidth: "1000px",
|
|
padding: "25px",
|
|
paddingLeft: "50px",
|
|
minHeight: "320px",
|
|
fontFamily: theme?.typography?.fontFamily,
|
|
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
|
zIndex: 1000,
|
|
'& .MuiDialogContent-root': {
|
|
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
|
},
|
|
'& .MuiDialogTitle-root': {
|
|
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
|
},
|
|
'& .MuiDialogActions-root': {
|
|
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
|
},
|
|
},
|
|
}}
|
|
sx={{
|
|
"& .MuiDialog-paper": {
|
|
backgroundColor: "rgb(26, 26, 26)",
|
|
},
|
|
}}
|
|
>
|
|
<DialogTitle>
|
|
<span style={{ color: "white" }}>App Authentication Groups</span>
|
|
</DialogTitle>
|
|
|
|
<DialogContent style={{marginLeft: 0, paddingLeft: 0, }}>
|
|
<div style={{display: "flex", position: "sticky", top: 0, zIndex: 1, backgroundColor: theme.palette.surfaceColor, borderRadius: theme.palette?.borderRadius, padding: 20, marginBottom: 10, }}>
|
|
<div style={{marginRight: 50, minWidth: 250, }}>
|
|
<Typography style={{marginTop: 10, }}>
|
|
Name
|
|
</Typography>
|
|
<TextField
|
|
color="primary"
|
|
label="Name"
|
|
style={{ backgroundColor: theme.palette.inputColor }}
|
|
autoFocus
|
|
InputProps={{
|
|
style: {
|
|
height: "50px",
|
|
color: "white",
|
|
fontSize: "1em",
|
|
},
|
|
}}
|
|
required
|
|
fullWidth={true}
|
|
placeholder="Name"
|
|
id="namefield"
|
|
margin="normal"
|
|
variant="outlined"
|
|
defaultValue={appAuthenticationGroupName}
|
|
onChange={(event) => {
|
|
setAppAuthenticationGroupName(event.target.value);
|
|
}}
|
|
/>
|
|
</div>
|
|
<div style={{marginRight: 50, }}>
|
|
<Typography style={{marginTop: 10, marginBottom: 10}}>
|
|
Evironment
|
|
</Typography>
|
|
{environments !== undefined && environments !== null && environments.length > 0 ?
|
|
<Select
|
|
defaultValue={appAuthenticationGroupEnvironment === "" ? environments[0].Name : appAuthenticationGroupEnvironment}
|
|
onChange={(e) => {
|
|
setAppAuthenticationGroupEnvironment(e.target.value);
|
|
}}
|
|
>
|
|
{environments.map((env, index) => {
|
|
return (
|
|
<MenuItem key={index} value={env.Name}>
|
|
{env.Name}
|
|
</MenuItem>
|
|
)
|
|
})}
|
|
</Select>
|
|
:
|
|
<Typography>
|
|
Locations failed to load. Please try again
|
|
</Typography>
|
|
}
|
|
</div>
|
|
<div style={{marginTop: 65, }}>
|
|
<Button
|
|
style={{}}
|
|
disabled={appAuthenticationGroupName === "" || appAuthenticationGroupEnvironment === "" || appsForAppAuthGroup.length === 0}
|
|
variant="contained"
|
|
color="primary"
|
|
onClick={() => {
|
|
createAppAuthenticationGroup(
|
|
appAuthenticationGroupName,
|
|
appAuthenticationGroupEnvironment,
|
|
appAuthenticationGroupDescription,
|
|
appsForAppAuthGroup,
|
|
);
|
|
}}
|
|
>
|
|
Set Group
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<Divider style={{marginTop: 10, marginBottom: 10, }}/>
|
|
|
|
<div style={{marginLeft: 25, }}>
|
|
{/* Show a check box list of all app authentications to add to the auth group */}
|
|
<div>
|
|
{authentication.map((data, index) => {
|
|
var checked = data.checked
|
|
if (data.label !== undefined && data.label !== null && data.label.toLowerCase() === "kms shuffle storage") {
|
|
return null
|
|
}
|
|
|
|
if (checked === undefined || checked === null) {
|
|
checked = false
|
|
}
|
|
|
|
if (appsForAppAuthGroup.includes(data.id)) {
|
|
checked = true
|
|
}
|
|
|
|
return (
|
|
<div key={index}>
|
|
<FormControlLabel
|
|
control={
|
|
<Tooltip
|
|
title={data.app.name}
|
|
placement="left"
|
|
>
|
|
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '10px', marginLeft: '5px', }}>
|
|
<img
|
|
src={data.app.large_image ? data.app.large_image : '/images/no_image.png'}
|
|
alt=""
|
|
style={{ borderRadius: theme.palette?.borderRadius, width: 50, height: 50, marginRight: 10 }}
|
|
/>
|
|
<Checkbox
|
|
checked={checked}
|
|
onChange={(event) => {
|
|
handleAppAuthGroupCheckbox(data)
|
|
}}
|
|
name={data.label}
|
|
disabled={data.app.id in appsForAppAuthGroup}
|
|
/>
|
|
</div>
|
|
</Tooltip>
|
|
}
|
|
label={data.label}
|
|
/>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
|
|
|
|
</DialogContent>
|
|
</Dialog>
|
|
)}
|
|
|
|
|
|
</div>
|
|
): null
|
|
|
|
|
|
|
|
const appModal = showAppModal ? (
|
|
<Dialog
|
|
open={showAppModal}
|
|
onClose={() => {
|
|
setShowAppModal(false);
|
|
}}
|
|
PaperProps={{
|
|
sx: {
|
|
borderRadius: theme?.palette?.DialogStyle?.borderRadius,
|
|
border: theme?.palette?.DialogStyle?.border,
|
|
width: '700px',
|
|
maxWidth: '700px',
|
|
overflowY: 'hidden',
|
|
height: "600px",
|
|
maxHeight: "600px",
|
|
fontFamily: theme?.typography?.fontFamily,
|
|
zIndex: 1000,
|
|
'& .MuiDialogContent-root': {
|
|
padding: '30px',
|
|
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
|
},
|
|
'& .MuiDialogTitle-root': {
|
|
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
|
},
|
|
'& .MuiDialogActions-root': {
|
|
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
|
|
},
|
|
},
|
|
}}
|
|
>
|
|
<div style={{ overflowY: 'hidden', }}>
|
|
<DialogContent>
|
|
<Typography style={{ marginBottom: 0, fontSize: 24, textAlign: 'center', }}>
|
|
Add App Authentication
|
|
</Typography>
|
|
<InstantSearch searchClient={searchClient} indexName="appsearch">
|
|
<CustomSearchBox searchQuery={searchQuery} setSearchQuery={setSearchQuery} />
|
|
<CustomHits
|
|
hitsPerPage={5}
|
|
searchQuery={searchQuery}
|
|
globalUrl={globalUrl}
|
|
isCloud={isCloud !== true}
|
|
userdata={userdata}
|
|
getAppAuthentication={getAppAuthentication}
|
|
/>
|
|
</InstantSearch>
|
|
</DialogContent>
|
|
</div>
|
|
</Dialog>
|
|
) : null;
|
|
|
|
return (
|
|
<div style={{width: "100%", minHeight: 1100, maxHeight: 1700, overflowY: "auto", scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin',boxSizing: 'border-box', padding: "27px 10px 19px 27px", height:"100%", backgroundColor: '#212121',borderTopRightRadius: '8px', borderBottomRightRadius: 8, borderLeft: "1px solid #494949", }}>
|
|
{appModal}
|
|
<div style={{ height: "100%", width: "calc(100% - 20px)", scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin'}}>
|
|
<div style={{ width: 'auto', display:'flex',}}>
|
|
<div style={{display: 'flex', flexDirection: 'column'}}>
|
|
<h2 style={{ marginBottom: 8, marginTop: 0, color: "#FFFFFF" }}>App Authentication</h2>
|
|
<div>
|
|
<span style={{}}>
|
|
Control the authentication options for individual apps.
|
|
</span>
|
|
|
|
<a
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
href="/docs/organizations#app_authentication"
|
|
style={{ color: "#FF8444" }}
|
|
>
|
|
Learn more about App Authentication
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<Button
|
|
style={{ color: '#1a1a1a', textTransform: 'none', backgroundColor: "#FF8444", marginLeft:"auto", borderRadius: 4,fontSize: 16, minWidth: 162, height: 40, boxShadow:'none', }}
|
|
variant="contained"
|
|
color="primary"
|
|
disabled={!isCloud}
|
|
onClick={() => setShowAppModal(true)}
|
|
>
|
|
Add App Auth
|
|
</Button>
|
|
</div>
|
|
{/* <Divider
|
|
style={{
|
|
marginTop: 20,
|
|
marginBottom: 20,
|
|
backgroundColor: theme.palette.inputColor,
|
|
}}
|
|
/> */}
|
|
|
|
<div
|
|
style={{
|
|
borderRadius: 4,
|
|
marginTop: 24,
|
|
border: "1px solid #494949",
|
|
width: "100%",
|
|
overflowX: "auto",
|
|
paddingBottom: 0,
|
|
}}
|
|
>
|
|
<List
|
|
style={{
|
|
borderRadius: 4,
|
|
width: '100%',
|
|
tableLayout: "auto",
|
|
display: "table",
|
|
minWidth: 800,
|
|
overflowX: "auto",
|
|
paddingBottom: 0
|
|
}}>
|
|
|
|
|
|
<ListItem style={{ width: "100%", paddingTop: 10, paddingBottom: 10, paddingRight: 10, borderBottom: "1px solid #494949", display: 'table-row'}}>
|
|
|
|
{["Valid", "Label", "App Name", "Workflows", "Fields", "Edited", "Actions", "Distribution"].map((header, index) => (
|
|
<ListItemText
|
|
key={index}
|
|
primary={header}
|
|
style={{
|
|
display: "table-cell",
|
|
padding: index === 0 ? "0px 8px 8px 15px": "0px 8px 8px 8px",
|
|
whiteSpace: "nowrap",
|
|
textOverflow: "ellipsis",
|
|
borderBottom: "1px solid #494949",
|
|
position: "sticky",
|
|
}}
|
|
/>
|
|
))}
|
|
</ListItem>
|
|
|
|
{showAuthenticationLoader
|
|
?
|
|
[...Array(6)].map((_, rowIndex) => (
|
|
<ListItem
|
|
key={rowIndex}
|
|
style={{
|
|
display: "table-row",
|
|
backgroundColor: "#212121",
|
|
}}
|
|
>
|
|
{Array(8)
|
|
.fill()
|
|
.map((_, colIndex) => (
|
|
<ListItemText
|
|
key={colIndex}
|
|
style={{
|
|
display: "table-cell",
|
|
padding: "8px",
|
|
}}
|
|
>
|
|
<Skeleton
|
|
variant="text"
|
|
animation="wave"
|
|
sx={{
|
|
backgroundColor: "#1a1a1a",
|
|
height: "20px",
|
|
borderRadius: "4px",
|
|
}}
|
|
/>
|
|
</ListItemText>
|
|
))}
|
|
</ListItem>
|
|
))
|
|
: authentication?.length === 0 ? (
|
|
<div style={{ textAlign: 'center'}}>
|
|
<Typography style={{ color: "#FFFFFF", textAlign: 'center', padding: 20}}>
|
|
No authentication found.
|
|
</Typography>
|
|
</div>
|
|
):authentication.map((data, index) => {
|
|
var bgColor = "#212121";
|
|
if (index % 2 === 0) {
|
|
bgColor = "#1A1A1A";
|
|
}
|
|
|
|
//console.log("Auth data: ", data)
|
|
if (data.type === "oauth2") {
|
|
data.fields = [
|
|
{
|
|
key: "url",
|
|
value: "Secret. Replaced during app execution!",
|
|
},
|
|
{
|
|
key: "client_id",
|
|
value: "Secret. Replaced during app execution!",
|
|
},
|
|
{
|
|
key: "client_secret",
|
|
value: "Secret. Replaced during app execution!",
|
|
},
|
|
{
|
|
key: "scope",
|
|
value: "Secret. Replaced during app execution!",
|
|
},
|
|
];
|
|
}
|
|
|
|
const isDistributed = data.suborg_distributed === true ? true : false;
|
|
var validIcon = <CheckCircleIcon style={{ color: "green" }} />
|
|
if (data.validation !== null && data.validation !== undefined && data.validation.valid === false) {
|
|
|
|
if (data.validation.changed_at === 0) {
|
|
validIcon = ""
|
|
} else {
|
|
validIcon = <CancelIcon style={{ color: "red" }} />
|
|
}
|
|
}
|
|
return (
|
|
<ListItem key={index} style={{ backgroundColor: bgColor, borderBottomLeftRadius: authentication?.length - 1 === index ? 8 : 0, borderBottomRightRadius: authentication?.length - 1 === index ? 8 : 0, display: 'table-row' }}>
|
|
<ListItemText
|
|
primary= {(
|
|
<Tooltip title={data.validation !== null && data.validation !== undefined && data.validation.valid === true ? "Valid. Click to explore." : "Configuration failed. Click to learn why"} placement="top">
|
|
<IconButton>
|
|
{validIcon}
|
|
</IconButton>
|
|
</Tooltip>
|
|
)}
|
|
style={{ display: "table-cell", verticalAlign: 'middle', minWidth: 60 }}
|
|
primaryTypographyProps={{
|
|
style: {
|
|
padding: "8px 8px 8px 15px",
|
|
}
|
|
}}
|
|
onClick={() => {
|
|
if (data.validation === null || data.validation === undefined) {
|
|
return
|
|
}
|
|
|
|
if (data.validation.workflow_id === undefined || data.validation.workflow_id === null || data.validation.workflow_id.length === 0) {
|
|
toast.warn("No workflow runs found for this auth yet. Check back later.")
|
|
return
|
|
}
|
|
|
|
const url = `/workflows/${data.validation.workflow_id}?execution_id=${data.validation.execution_id}&node=${data.validation.node_id}`
|
|
window.open(url, "_blank")
|
|
}}
|
|
/>
|
|
<ListItemText
|
|
primary={data.label}
|
|
style={{
|
|
overflowX: "auto",
|
|
display: "table-cell",
|
|
verticalAlign: 'middle',
|
|
maxWidth: 250,
|
|
}}
|
|
primaryTypographyProps={{
|
|
style: {
|
|
padding: 8
|
|
}
|
|
}}
|
|
/>
|
|
<ListItemText
|
|
primary={
|
|
<Tooltip title={"Try the app in our API explorer"} placement="top">
|
|
<a href={`/apis/${data.app.id}`} style={{ color: "#FF8444", textDecoration: "none", cursor: "pointer", }} target="_blank" rel="noopener noreferrer">
|
|
{data?.app?.name?.replaceAll("_", " ")}
|
|
</a>
|
|
</Tooltip>
|
|
}
|
|
primaryTypographyProps={{
|
|
style: {
|
|
padding: 8
|
|
}
|
|
}}
|
|
style={{ marginLeft: 10, display: "table-cell", textAlign: 'center', verticalAlign: 'middle', padding: 8 }}
|
|
/>
|
|
<ListItemText
|
|
primary={
|
|
data.workflow_count === null ? 0 : data.workflow_count
|
|
}
|
|
primaryTypographyProps={{
|
|
style: {
|
|
padding: 8
|
|
}
|
|
}}
|
|
style={{
|
|
display: "table-cell",
|
|
textAlign: "center",
|
|
overflow: "hidden",
|
|
verticalAlign: 'middle',
|
|
}}
|
|
/>
|
|
<ListItemText
|
|
primary={
|
|
data.fields === null || data.fields === undefined
|
|
? ""
|
|
: data.fields
|
|
.map((data) => {
|
|
return data.key;
|
|
})
|
|
.join(", ")
|
|
}
|
|
primaryTypographyProps={{
|
|
style: {
|
|
padding: 8
|
|
}
|
|
}}
|
|
style={{
|
|
overflow: "auto",
|
|
display: "table-cell",
|
|
verticalAlign: 'middle'
|
|
}}
|
|
/>
|
|
<ListItemText
|
|
style={{
|
|
overflow: "hidden",
|
|
display: "table-cell",
|
|
verticalAlign: 'middle'
|
|
}}
|
|
primaryTypographyProps={{
|
|
style: {
|
|
padding: 8
|
|
}
|
|
}}
|
|
primary={new Date(data.edited * 1000).toISOString()}
|
|
/>
|
|
<ListItemText
|
|
style={{
|
|
display: "table-cell",
|
|
verticalAlign: 'middle'
|
|
}}
|
|
primaryTypographyProps={{ style: { display: "flex", flexDirection: 'row', padding: 8 } }}
|
|
>
|
|
<IconButton
|
|
onClick={() => {
|
|
updateAppAuthentication(data);
|
|
}}
|
|
disabled={data.org_id !== selectedOrganization.id}
|
|
>
|
|
<img src="/icons/editIcon.svg" alt="Edit icon" color="secondary" />
|
|
</IconButton>
|
|
{data.defined ? (
|
|
<Tooltip
|
|
color="primary"
|
|
title="Set for EVERY instance of this App being used in this organization"
|
|
placement="top"
|
|
>
|
|
<IconButton
|
|
style={{ }}
|
|
disabled={
|
|
data.defined === false || data.org_id !== selectedOrganization.id
|
|
}
|
|
onClick={() => {
|
|
editAuthenticationConfig(data.id);
|
|
}}
|
|
>
|
|
<SelectAllIcon color="secondary" />
|
|
</IconButton>
|
|
</Tooltip>
|
|
) : (
|
|
<Tooltip
|
|
color="primary"
|
|
title="Must edit before you can set in all workflows"
|
|
placement="top"
|
|
>
|
|
<IconButton
|
|
onClick={() => {}}
|
|
disabled={data.org_id !== selectedOrganization.id}
|
|
>
|
|
<SelectAllIcon color="secondary" />
|
|
</IconButton>
|
|
</Tooltip>
|
|
)}
|
|
<IconButton
|
|
style={{ }}
|
|
disabled={data.org_id !== selectedOrganization.id}
|
|
onClick={() => {
|
|
deleteAuthentication(data);
|
|
}}
|
|
>
|
|
<img src='/icons/deleteIcon.svg' alt='delete icon' color="secondary" />
|
|
</IconButton>
|
|
</ListItemText>
|
|
<ListItemText primaryTypographyProps={{
|
|
style: {
|
|
padding: 8
|
|
}
|
|
}} style={{ display: "table-cell", textAlign: 'center', verticalAlign: 'middle'}} >
|
|
{selectedOrganization.id !== undefined && data.org_id !== selectedOrganization.id ?
|
|
<Tooltip
|
|
title="Parent organization controlled auth. You can use, but not modify this auth. Contact an admin of your parent organization if you need changes to this."
|
|
placement="top"
|
|
>
|
|
<Chip
|
|
label={"Parent"}
|
|
variant="contained"
|
|
color="secondary"
|
|
style={{display: "table-cell",}}
|
|
/>
|
|
</Tooltip>
|
|
:
|
|
<Tooltip
|
|
title="Distributed to sub-organizations. This means the sub organizations can use this authentication, but not modify it."
|
|
placement="top"
|
|
>
|
|
<Checkbox
|
|
disabled={selectedOrganization.creator_org !== undefined && selectedOrganization.creator_org !== null && selectedOrganization.creator_org !== "" ? true : false}
|
|
checked={isDistributed}
|
|
style={{ }}
|
|
color="secondary"
|
|
onClick={() => {
|
|
changeDistribution(data, !isDistributed)
|
|
}}
|
|
/>
|
|
</Tooltip>
|
|
}
|
|
</ListItemText>
|
|
</ListItem>
|
|
);
|
|
})}
|
|
</List>
|
|
</div>
|
|
{editAuthenticationModal}
|
|
{authenticationView}
|
|
<div style={{marginTop: 50, }}>
|
|
<div style={{ marginTop: 150, marginBottom: 20 }}>
|
|
<h2 style={{ color: "#FFFFFF" }}>App Authentication Groups</h2>
|
|
<span style={{ marginLeft: 0 }}>
|
|
<b>Disabled until further notice</b>. Makes a workflow run replicate across all relevant authentications in an app auth group. Useful when the EXACT same workflow is supposed to run many times from one single input. {" "}
|
|
<a
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
href="/docs/organizations#app_authentication_groups"
|
|
style={{ textDecoration: "none", color: "#FF8444" }}
|
|
>
|
|
Learn more about App Authentication Groups
|
|
</a>
|
|
</span>
|
|
|
|
<br />
|
|
<Button
|
|
style={{ marginTop: 20, backgroundColor: "grey", color: "#1a1a1a", borderRadius: 4, fontSize: 16, boxShadow: "none", textTransform: "none" }}
|
|
variant="contained"
|
|
color="primary"
|
|
onClick={() => {
|
|
if (environments !== undefined && environments !== null && environments.length > 0) {
|
|
setAppAuthenticationGroupEnvironment(environments[0].Name)
|
|
}
|
|
|
|
setAppAuthenticationGroupModalOpen(true)
|
|
}}
|
|
disabled={true}
|
|
>
|
|
Add Group
|
|
</Button>
|
|
<div
|
|
style={{
|
|
borderRadius: 4,
|
|
marginTop: 24,
|
|
border: "1px solid #494949",
|
|
width: "100%",
|
|
overflowX: "auto",
|
|
paddingBottom: 0,
|
|
}}
|
|
>
|
|
<List
|
|
style={{
|
|
borderRadius: 4,
|
|
width: '100%',
|
|
tableLayout: "auto",
|
|
display: "table",
|
|
minWidth: 800,
|
|
overflowX: "auto",
|
|
paddingBottom: 0
|
|
}}
|
|
>
|
|
<ListItem style={{ width: "100%", borderBottom: "1px solid #494949", display: "table-row" }}>
|
|
{["Label", "Environment", "App Auth", "Created At", "Actions"].map((header, index) => (
|
|
<ListItemText
|
|
key={index}
|
|
primary={header}
|
|
style={{
|
|
display: "table-cell",
|
|
padding: index === 0 ? "0px 8px 8px 15px": "0px 8px 8px 8px",
|
|
whiteSpace: "nowrap",
|
|
textOverflow: "ellipsis",
|
|
borderBottom: "1px solid #494949",
|
|
position: "sticky",}}
|
|
/>
|
|
))}
|
|
</ListItem>
|
|
{showAppAuthGroupLoader ?
|
|
[...Array(6)].map((_, rowIndex) => (
|
|
<ListItem
|
|
key={rowIndex}
|
|
style={{
|
|
display: "table-row",
|
|
backgroundColor: "#212121",
|
|
}}
|
|
>
|
|
{Array(5)
|
|
.fill()
|
|
.map((_, colIndex) => (
|
|
<ListItemText
|
|
key={colIndex}
|
|
style={{
|
|
display: "table-cell",
|
|
padding: "8px",
|
|
}}
|
|
>
|
|
<Skeleton
|
|
variant="text"
|
|
animation="wave"
|
|
sx={{
|
|
backgroundColor: "#1a1a1a",
|
|
height: "20px",
|
|
borderRadius: "4px",
|
|
}}
|
|
/>
|
|
</ListItemText>
|
|
))}
|
|
</ListItem>
|
|
))
|
|
: appAuthenticationGroups.length === 0 ? (
|
|
<div style={{ textAlign: 'center'}}>
|
|
<Typography style={{ color: "#FFFFFF", textAlign: 'center', padding: 20}}>
|
|
No authentication groups found.
|
|
</Typography>
|
|
</div>
|
|
): appAuthenticationGroups.map((data, index) => {
|
|
var bgColor = "#212121";
|
|
if (index % 2 === 0) {
|
|
bgColor = "#1A1A1A";
|
|
}
|
|
|
|
if (data.app_auths === undefined || data.app_auths === null) {
|
|
data.app_auths = []
|
|
}
|
|
|
|
return (
|
|
<ListItem key={index} style={{display: 'table-row', padding: 8, backgroundColor: bgColor, borderBottomLeftRadius: appAuthenticationGroups?.length - 1 === index ? 8 : 0, borderBottomRightRadius: appAuthenticationGroups?.length - 1 === index ? 8 : 0 }}>
|
|
<ListItemText
|
|
primary={data.label}
|
|
style={{ display:'table-cell', verticalAlign: 'middle' }}
|
|
primaryTypographyProps={{
|
|
style: {
|
|
padding: "8px 8px 8px 15px",
|
|
}
|
|
}}
|
|
/>
|
|
<ListItemText
|
|
primary={data.environment}
|
|
style={{ display:'table-cell', verticalAlign: 'middle' }}
|
|
/>
|
|
<ListItemText
|
|
primary={
|
|
<div style={{ display: 'flex' }}>
|
|
{data.app_auths.map((appAuth, index) => {
|
|
if (appAuth.app.large_image === undefined || appAuth.app.large_image === null || appAuth.app.large_image === "") {
|
|
const foundImage = authentication.find((auth) => auth.app.id === appAuth.app.id)
|
|
if (foundImage !== undefined) {
|
|
appAuth.app.large_image = foundImage.app.large_image
|
|
|
|
appAuth.app.name = foundImage.app.name
|
|
}
|
|
}
|
|
|
|
const tooltip = `${appAuth.app.name.replaceAll("_", " ")} (authname: ${appAuth.label})`
|
|
|
|
return (
|
|
<Tooltip
|
|
title={tooltip}
|
|
>
|
|
<img
|
|
key={index}
|
|
src={appAuth.app.large_image}
|
|
alt={appAuth.app.name}
|
|
style={{ width: 30, height: 30, marginRight: 5 }}
|
|
/>
|
|
</Tooltip>
|
|
)
|
|
})}
|
|
</div>
|
|
}
|
|
style={{ display:'table-cell', verticalAlign: 'middle' }}
|
|
/>
|
|
<ListItemText
|
|
primary={new Date(data.created * 1000).toLocaleDateString('en-GB')}
|
|
style={{ display:'table-cell', verticalAlign: 'middle' }}
|
|
/>
|
|
<ListItemText
|
|
primary={
|
|
<div style={{ display: 'flex' }}>
|
|
<IconButton
|
|
onClick={() => {
|
|
setAppAuthenticationGroupId(data.id)
|
|
|
|
setAppAuthenticationGroupName(data.label)
|
|
setAppAuthenticationGroupDescription(data.description)
|
|
|
|
setAppsForAppAuthGroup(data.app_auths.map((appAuth) => appAuth.id))
|
|
setAppAuthenticationGroupEnvironment(data.environment)
|
|
setAppAuthenticationGroupModalOpen(true)
|
|
}}
|
|
>
|
|
<img src='/icons/editIcon.svg' alt='edit icon' style={{width: 24, height: 24}} />
|
|
</IconButton>
|
|
<IconButton
|
|
onClick={() => {
|
|
deleteAppAuthenticationGroup(data.id)
|
|
}}
|
|
>
|
|
<img src='/icons/deleteIcon.svg' alt='delete icon' style={{width: 24, height: 24}}/>
|
|
</IconButton>
|
|
</div>
|
|
}
|
|
style={{ display:'table-cell', verticalAlign: 'middle' }}
|
|
/>
|
|
|
|
</ListItem>
|
|
);
|
|
}
|
|
)}
|
|
</List>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
});
|
|
|
|
export default AppAuthTab;
|
|
|
|
|
|
const SearchBox = ({ currentRefinement, refine, isSearchStalled, searchQuery, setSearchQuery }) => {
|
|
const handleSearch = (e) => {
|
|
refine(searchQuery.trim());
|
|
};
|
|
|
|
return (
|
|
<form noValidate action="" role="search">
|
|
<TextField
|
|
value={searchQuery}
|
|
fullWidth
|
|
style={{
|
|
backgroundColor: theme.palette.textFieldStyle.backgroundColor,
|
|
marginTop: 20,
|
|
borderRadius: 4,
|
|
fontSize: 16,
|
|
}}
|
|
InputProps={{
|
|
style: {
|
|
color: "white",
|
|
fontSize: "1em",
|
|
height: 50,
|
|
borderRadius: 4,
|
|
backgroundColor: theme.palette.textFieldStyle.backgroundColor
|
|
},
|
|
startAdornment: (
|
|
<InputAdornment position="start">
|
|
<SearchIcon style={{ marginLeft: 5 }} />
|
|
</InputAdornment>
|
|
),
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
{searchQuery?.length > 0 && (
|
|
<ClearIcon
|
|
style={{
|
|
color: "white",
|
|
cursor: "pointer",
|
|
marginRight: 10
|
|
}}
|
|
onClick={() => {
|
|
setSearchQuery('')
|
|
// removeQuery("q");
|
|
refine('')
|
|
}}
|
|
/>
|
|
)}
|
|
<button
|
|
type="button"
|
|
onClick={handleSearch}
|
|
style={{
|
|
backgroundImage:
|
|
"linear-gradient(to right, rgb(248, 106, 62), rgb(243, 64, 121))",
|
|
color: "white",
|
|
border: "none",
|
|
padding: "10px 20px",
|
|
width: 100,
|
|
height: 35,
|
|
borderRadius: 17.5,
|
|
cursor: "pointer",
|
|
}}
|
|
>
|
|
Search
|
|
</button>
|
|
</InputAdornment>
|
|
),
|
|
|
|
}}
|
|
autoComplete="off"
|
|
color="primary"
|
|
placeholder="Search more than 2500 Apps"
|
|
id="shuffle_search_field"
|
|
onChange={(event) => {
|
|
setSearchQuery(event.currentTarget.value);
|
|
// removeQuery("q");
|
|
refine(event.currentTarget.value);
|
|
}}
|
|
onKeyDown={(event) => {
|
|
if(event.key === "Enter") {
|
|
event.preventDefault();
|
|
}
|
|
}}
|
|
limit={5}
|
|
/>
|
|
{/*isSearchStalled ? 'My search is stalled' : ''*/}
|
|
</form>
|
|
);
|
|
};
|
|
|
|
const Hits = ({
|
|
hits,
|
|
insights,
|
|
setIsAnyAppActivated,
|
|
searchQuery,
|
|
isCloud,
|
|
globalUrl,
|
|
userdata,
|
|
getAppAuthentication
|
|
}) => {
|
|
const [mouseHoverIndex, setMouseHoverIndex] = useState(-1);
|
|
const [selectedAppData, setSelectedAppData] = useState({})
|
|
const [appid, setAppId] = useState("")
|
|
const [selectedAuthentication, setSelectedAuthentication] = useState({})
|
|
const [authenticationModalOpen, setAuthenticationModalOpen] = useState(false)
|
|
const [authenticationType, setAuthenticationType] = React.useState("");
|
|
const [appAuthentication, setAppAuthentication] = useState([]);
|
|
const [selectedMeta, setSelectedMeta] = useState(undefined);
|
|
const [selectedAction, setSelectedAction] = useState(
|
|
{
|
|
"app_name": selectedAppData.name,
|
|
"app_id": selectedAppData.id,
|
|
"app_version": selectedAppData.version,
|
|
"large_image": selectedAppData.large_image,
|
|
}
|
|
)
|
|
const navigate = useNavigate();
|
|
|
|
const normalizedString = (name) => {
|
|
if (typeof name === 'string') {
|
|
return name.replace(/_/g, ' ');
|
|
} else {
|
|
return name;
|
|
}
|
|
};
|
|
|
|
|
|
let workflowDelay = 0;
|
|
const isHeader = true;
|
|
const paperStyle = {
|
|
color: "rgba(241, 241, 241, 1)",
|
|
padding: isHeader ? null : 15,
|
|
cursor: "pointer",
|
|
width: '100%',
|
|
maxHeight: 96,
|
|
borderRadius: 4,
|
|
transition: 'background-color 0.3s ease',
|
|
};
|
|
|
|
const base64_decode = (str) => {
|
|
return decodeURIComponent(
|
|
atob(str)
|
|
.split("")
|
|
.map(function (c) {
|
|
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
|
|
})
|
|
.join("")
|
|
);
|
|
};
|
|
|
|
const handleAppAuthenticationType = (selectedAppData)=> {
|
|
|
|
if (selectedAppData.authentication === undefined || selectedAppData.authentication === null) {
|
|
setAuthenticationType({
|
|
type: "",
|
|
})
|
|
|
|
selectedAppData.authentication = {
|
|
type: "",
|
|
required: false,
|
|
}
|
|
} else {
|
|
setAuthenticationType(
|
|
selectedAppData.authentication.type === "oauth2-app" || (selectedAppData.authentication.type === "oauth2" && selectedAppData.authentication.redirect_uri !== undefined && selectedAppData.authentication.redirect_uri !== null) ? {
|
|
type: selectedAppData.authentication.type,
|
|
redirect_uri: selectedAppData.authentication.redirect_uri,
|
|
refresh_uri: selectedAppData.authentication.refresh_uri,
|
|
token_uri: selectedAppData.authentication.token_uri,
|
|
scope: selectedAppData.authentication.scope,
|
|
client_id: selectedAppData.authentication.client_id,
|
|
client_secret: selectedAppData.authentication.client_secret,
|
|
grant_type: selectedAppData.authentication.grant_type,
|
|
} : {
|
|
type: "",
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
function Heading(props) {
|
|
const element = React.createElement(
|
|
`h${props.level}`,
|
|
{ style: { marginTop: 40 } },
|
|
props.children
|
|
);
|
|
return (
|
|
<Typography>
|
|
{props.level !== 1 ? (
|
|
<Divider
|
|
style={{
|
|
width: "90%",
|
|
marginTop: 40,
|
|
backgroundColor: theme.palette.inputColor,
|
|
}}
|
|
/>
|
|
) : null}
|
|
{element}
|
|
</Typography>
|
|
);
|
|
}
|
|
|
|
const getAppDocs = (appname, location, version) => {
|
|
fetch(`${globalUrl}/api/v1/docs/${appname}?location=${location}&version=${version}`, {
|
|
headers: {
|
|
Accept: "application/json",
|
|
},
|
|
credentials: "include",
|
|
})
|
|
.then((response) => {
|
|
if (response.status === 200) {
|
|
//toast("Successfully GOT app "+appId)
|
|
} else {
|
|
//toast("Failed getting app");
|
|
}
|
|
|
|
return response.json();
|
|
})
|
|
.then((responseJson) => {
|
|
if (responseJson.success === true) {
|
|
if (responseJson.meta !== undefined && responseJson.meta !== null && Object.getOwnPropertyNames(responseJson.meta).length > 0) {
|
|
setSelectedMeta(responseJson.meta)
|
|
}
|
|
|
|
if (responseJson.reason !== undefined && responseJson.reason !== undefined && responseJson.reason.length > 0) {
|
|
if (!responseJson.reason.includes("404: Not Found") && responseJson.reason.length > 25) {
|
|
// Translate <img> into markdown ![]()
|
|
const imgRegex = /<img.*?src="(.*?)"/g;
|
|
const newdata = responseJson.reason.replace(imgRegex, '');
|
|
setSelectedAppData(prevState => ({
|
|
...prevState,
|
|
documentation: newdata,
|
|
}));
|
|
}
|
|
}
|
|
}
|
|
|
|
})
|
|
.catch((error) => {
|
|
toast(error.toString());
|
|
});
|
|
};
|
|
|
|
const handleDecodeOfOpenApiData = (data) => {
|
|
var appexists = false;
|
|
var parsedapp = {};
|
|
|
|
if (data.app !== undefined && data.app !== null) {
|
|
var parsedBaseapp = "";
|
|
try {
|
|
parsedBaseapp = base64_decode(data.app);
|
|
} catch (e) {
|
|
parsedBaseapp = data;
|
|
}
|
|
|
|
parsedapp = JSON.parse(parsedBaseapp);
|
|
parsedapp.name = parsedapp.name.replaceAll("_", " ");
|
|
|
|
appexists =
|
|
parsedapp.name !== undefined &&
|
|
parsedapp.name !== null &&
|
|
parsedapp.name.length !== 0;
|
|
if(parsedapp?.id.length > 0){
|
|
setSelectedAppData(parsedapp)
|
|
handleAppAuthenticationType(parsedapp)
|
|
const apptype = selectedAppData?.generated === false ? "python" : "openapi"
|
|
getAppDocs(parsedapp.name, apptype, parsedapp.version);
|
|
setAuthenticationModalOpen(true);
|
|
}
|
|
}
|
|
|
|
if (data.openapi === undefined || data.openapi === null) {
|
|
return;
|
|
}
|
|
|
|
var parsedDecoded = "";
|
|
try {
|
|
parsedDecoded = base64_decode(data.openapi);
|
|
} catch (e) {
|
|
parsedDecoded = data;
|
|
}
|
|
|
|
parsedapp = JSON.parse(parsedDecoded);
|
|
data =
|
|
parsedapp.body === undefined ? parsedapp : JSON.parse(parsedapp.body);
|
|
};
|
|
|
|
const getAppData = (appid) => {
|
|
if (appid === undefined || appid === null || appid.length === 0) {
|
|
return;
|
|
}
|
|
const url = `${globalUrl}/api/v1/apps/${appid}/config`;
|
|
|
|
fetch(url, {
|
|
credentials: "include",
|
|
method: "GET",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
})
|
|
.then((response) => {
|
|
if (response.status !== 200) {
|
|
toast.error("Failed to get app data or App doesn't. Please contact support@shuffler.io");
|
|
return;
|
|
}
|
|
return response.json();
|
|
})
|
|
.then((responseJson) => {
|
|
if (responseJson.success === true) {
|
|
handleDecodeOfOpenApiData(responseJson);
|
|
} else {
|
|
toast.error("Failed to get app data or App doesn't exist");
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error("error for app is :", error);
|
|
});
|
|
};
|
|
|
|
const UpdateAppAuthentication = (data) => {
|
|
if (data === undefined || data === null) {
|
|
return;
|
|
}
|
|
const filteredData = data.filter((appAuth) => appAuth?.app?.id === appid);
|
|
if (filteredData.length === 0) {
|
|
setAppAuthentication([]);
|
|
setSelectedAuthentication({});
|
|
} else {
|
|
setAppAuthentication(filteredData);
|
|
setSelectedAuthentication(filteredData[0]);
|
|
}
|
|
};
|
|
|
|
const HandleAppAuthentication = ()=>{
|
|
|
|
const url = `${globalUrl}/api/v1/apps/authentication`;
|
|
|
|
fetch(url, {
|
|
method: "GET",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
credentials: "include",
|
|
}).then((response) => {
|
|
if (response.status !== 200) {
|
|
return;
|
|
}
|
|
return response.json();
|
|
}).then((responseJson) => {
|
|
if (responseJson.success === true) {
|
|
UpdateAppAuthentication(responseJson.data);
|
|
} else {
|
|
toast.error("Failed to get app authentication data");
|
|
}
|
|
}).catch((error) => {
|
|
console.error("error for app is :", error);
|
|
});
|
|
}
|
|
|
|
const handleAppAuthenticationNew = (data) => () => {
|
|
|
|
const appid = data.objectID;
|
|
if (appid > 0) {
|
|
setAppId(appid);
|
|
}
|
|
if (appid.length > 0) {
|
|
toast.info(`Getting authentication for ${data.name}. Please wait...`);
|
|
getAppData(appid);
|
|
HandleAppAuthentication();
|
|
}
|
|
}
|
|
|
|
const AuthenticationData = (props) => {
|
|
const selectedApp = props.app;
|
|
|
|
const [authenticationOption, setAuthenticationOptions] = React.useState({
|
|
app: JSON.parse(JSON.stringify(selectedApp)),
|
|
fields: {},
|
|
label: "",
|
|
usage: [
|
|
{
|
|
// workflow_id: workflow.id,
|
|
},
|
|
],
|
|
id: uuidv4(),
|
|
active: true,
|
|
});
|
|
|
|
if (
|
|
selectedApp.authentication === undefined ||
|
|
selectedApp.authentication.parameters === null ||
|
|
selectedApp.authentication.parameters === undefined ||
|
|
selectedApp.authentication.parameters.length === 0
|
|
) {
|
|
return (
|
|
<DialogContent style={{ textAlign: "center", marginTop: 50 }}>
|
|
<Typography variant="h4" id="draggable-dialog-title" style={{ cursor: "move", }}>
|
|
{selectedApp.name} does not require authentication
|
|
</Typography>
|
|
</DialogContent>
|
|
);
|
|
}
|
|
|
|
authenticationOption.app.actions = [];
|
|
|
|
for (let paramkey in selectedApp.authentication.parameters) {
|
|
if (
|
|
authenticationOption.fields[
|
|
selectedApp.authentication.parameters[paramkey].name
|
|
] === undefined
|
|
) {
|
|
authenticationOption.fields[
|
|
selectedApp.authentication.parameters[paramkey].name
|
|
] = "";
|
|
}
|
|
}
|
|
|
|
const setNewAppAuth = (appAuthData, refresh) => {
|
|
setSelectedAuthentication(appAuthData);
|
|
var headers = {
|
|
"Content-Type": "application/json",
|
|
"Accept": "application/json",
|
|
}
|
|
|
|
headers["Org-Id"] = userdata?.active_org?.id
|
|
|
|
fetch(globalUrl + "/api/v1/apps/authentication", {
|
|
method: "PUT",
|
|
headers: headers,
|
|
body: JSON.stringify(appAuthData),
|
|
credentials: "include",
|
|
})
|
|
.then((response) => {
|
|
if (response.status !== 200) {
|
|
console.log("Status not 200 for setting app auth :O!");
|
|
|
|
if (response.status === 400) {
|
|
toast.error("Failed setting new auth. Please try again", {
|
|
"autoClose": true,
|
|
})
|
|
}
|
|
}
|
|
|
|
return response.json();
|
|
})
|
|
.then((responseJson) => {
|
|
if (!responseJson.success) {
|
|
toast.error("Error: " + responseJson.reason, {
|
|
"autoClose": false,
|
|
})
|
|
|
|
} else {
|
|
HandleAppAuthentication()
|
|
setAuthenticationModalOpen(false)
|
|
getAppAuthentication()
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.log("New auth error: ", error.toString());
|
|
});
|
|
};
|
|
|
|
const handleSubmitCheck = () => {
|
|
if (authenticationOption.label.length === 0) {
|
|
authenticationOption.label = `Auth for ${selectedApp.name}`;
|
|
}
|
|
for (let paramkey in selectedApp.authentication.parameters) {
|
|
if (
|
|
authenticationOption.fields[
|
|
selectedApp.authentication.parameters[paramkey].name
|
|
].length === 0
|
|
) {
|
|
if (
|
|
selectedApp.authentication.parameters[paramkey].value !== undefined &&
|
|
selectedApp.authentication.parameters[paramkey].value !== null &&
|
|
selectedApp.authentication.parameters[paramkey].value.length > 0
|
|
) {
|
|
authenticationOption.fields[
|
|
selectedApp.authentication.parameters[paramkey].name
|
|
] = selectedApp.authentication.parameters[paramkey].value;
|
|
} else {
|
|
if (
|
|
selectedApp.authentication.parameters[paramkey].schema.type === "bool"
|
|
) {
|
|
authenticationOption.fields[
|
|
selectedApp.authentication.parameters[paramkey].name
|
|
] = "false";
|
|
} else {
|
|
toast(
|
|
"Field " +
|
|
selectedApp.authentication.parameters[paramkey].name +
|
|
" can't be empty"
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var newAuthOption = JSON.parse(JSON.stringify(authenticationOption));
|
|
var newFields = [];
|
|
for (let authkey in newAuthOption.fields) {
|
|
const value = newAuthOption.fields[authkey];
|
|
newFields.push({
|
|
"key": authkey,
|
|
"value": value,
|
|
});
|
|
}
|
|
|
|
newAuthOption.fields = newFields
|
|
setNewAppAuth(newAuthOption)
|
|
}
|
|
|
|
if (authenticationOption.label === null || authenticationOption.label === undefined) {
|
|
authenticationOption.label = selectedApp.name + " authentication";
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<DialogTitle id="draggable-dialog-title" style={{ cursor: "move", }}>
|
|
<div style={{ color: "white" }}>
|
|
Authentication for {selectedApp.name.replaceAll("_", " ", -1)}
|
|
</div>
|
|
</DialogTitle>
|
|
<DialogContent>
|
|
<a
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
href="https://shuffler.io/docs/apps#authentication"
|
|
style={{ textDecoration: "none", color: "#f85a3e" }}
|
|
>
|
|
What is app authentication?
|
|
</a>
|
|
<div />
|
|
These are required fields for authenticating with {selectedApp.name}
|
|
<div style={{ marginTop: 15 }} />
|
|
<b>Label for you to remember</b>
|
|
<TextField
|
|
style={{
|
|
backgroundColor: theme.palette.inputColor,
|
|
borderRadius: theme.palette?.borderRadius,
|
|
}}
|
|
InputProps={{
|
|
style: {
|
|
},
|
|
}}
|
|
fullWidth
|
|
color="primary"
|
|
placeholder={"Auth july 2020"}
|
|
defaultValue={`Auth for ${selectedApp.name}`}
|
|
onChange={(event) => {
|
|
authenticationOption.label = event.target.value;
|
|
}}
|
|
/>
|
|
<Divider
|
|
style={{
|
|
marginTop: 15,
|
|
marginBottom: 15,
|
|
backgroundColor: "rgb(91, 96, 100)",
|
|
}}
|
|
/>
|
|
<div />
|
|
{selectedApp.authentication.parameters.map((data, index) => {
|
|
if (data.value === "" || data.value === null || data.value === undefined || data.name === "url") {
|
|
}
|
|
|
|
|
|
return (
|
|
<div key={index} style={{ marginTop: 10 }}>
|
|
<LockOpenIcon style={{ marginRight: 10 }} />
|
|
<b>{data.name}</b>
|
|
|
|
{data.schema !== undefined &&
|
|
data.schema !== null &&
|
|
data.schema.type === "bool" ? (
|
|
<Select
|
|
MenuProps={{
|
|
disableScrollLock: true,
|
|
}}
|
|
SelectDisplayProps={{
|
|
style: {
|
|
},
|
|
}}
|
|
defaultValue={"false"}
|
|
fullWidth
|
|
onChange={(e) => {
|
|
authenticationOption.fields[data.name] = e.target.value;
|
|
}}
|
|
style={{
|
|
backgroundColor: theme.palette.surfaceColor,
|
|
color: "white",
|
|
height: 50,
|
|
}}
|
|
>
|
|
<MenuItem
|
|
key={"false"}
|
|
style={{
|
|
backgroundColor: theme.palette.inputColor,
|
|
color: "white",
|
|
}}
|
|
value={"false"}
|
|
>
|
|
false
|
|
</MenuItem>
|
|
<MenuItem
|
|
key={"true"}
|
|
style={{
|
|
backgroundColor: theme.palette.inputColor,
|
|
color: "white",
|
|
}}
|
|
value={"true"}
|
|
>
|
|
true
|
|
</MenuItem>
|
|
</Select>
|
|
) : (
|
|
<TextField
|
|
style={{
|
|
backgroundColor: theme.palette.inputColor,
|
|
borderRadius: theme.palette?.borderRadius,
|
|
}}
|
|
InputProps={{
|
|
style: {
|
|
},
|
|
}}
|
|
fullWidth
|
|
type={
|
|
data.example !== undefined && data.example.includes("***")
|
|
? "password"
|
|
: "text"
|
|
}
|
|
color="primary"
|
|
defaultValue={
|
|
data.value !== undefined && data.value !== null
|
|
? data.value
|
|
: ""
|
|
}
|
|
placeholder={data.example}
|
|
onChange={(event) => {
|
|
authenticationOption.fields[data.name] =
|
|
event.target.value;
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button
|
|
style={{}}
|
|
onClick={() => {
|
|
setAuthenticationModalOpen(false);
|
|
}}
|
|
color="secondary"
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
style={{}}
|
|
variant="outlined"
|
|
onClick={() => {
|
|
setAuthenticationOptions(authenticationOption);
|
|
handleSubmitCheck()
|
|
}}
|
|
color="primary"
|
|
>
|
|
Submit
|
|
</Button>
|
|
</DialogActions>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const authenticationModal = authenticationModalOpen ? (
|
|
<Dialog
|
|
PaperComponent={PaperComponent}
|
|
aria-labelledby="draggable-dialog-title"
|
|
hideBackdrop={true}
|
|
disableEnforceFocus={true}
|
|
disableBackdropClick={true}
|
|
style={{ pointerEvents: "none" }}
|
|
open={authenticationModalOpen}
|
|
onClose={() => {setSelectedMeta(undefined)}}
|
|
PaperProps={{
|
|
style: {
|
|
pointerEvents: "auto",
|
|
color: "white",
|
|
minWidth: 1100,
|
|
minHeight: 700,
|
|
maxHeight: 700,
|
|
padding: 15,
|
|
overflow: "hidden",
|
|
zIndex: 10012,
|
|
border: theme.palette.defaultBorder,
|
|
},
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
zIndex: 5000,
|
|
position: "absolute",
|
|
top: 20,
|
|
right: 75,
|
|
height: 50,
|
|
width: 50,
|
|
}}
|
|
>
|
|
{ selectedAppData.reference_info === undefined ||
|
|
selectedAppData.reference_info === null ||
|
|
selectedAppData.reference_info.github_url === undefined ||
|
|
selectedAppData.reference_info.github_url === null ||
|
|
selectedAppData.reference_info.github_url.length === 0 ? (
|
|
|
|
<a
|
|
rel="noopener noreferrer"
|
|
target="_blank"
|
|
href={"https://github.com/shuffle/python-apps"}
|
|
style={{ textDecoration: "none", color: "#f86a3e" }}
|
|
>
|
|
<img
|
|
alt={`Documentation image for ${selectedAppData.name}`}
|
|
src={selectedAppData.large_image}
|
|
style={{
|
|
width: 30,
|
|
height: 30,
|
|
border: "2px solid rgba(255,255,255,0.6)",
|
|
borderRadius: theme.palette?.borderRadius,
|
|
maxHeight: 30,
|
|
maxWidth: 30,
|
|
overflow: "hidden",
|
|
fontSize: 8,
|
|
}}
|
|
/>
|
|
</a>
|
|
) : (
|
|
<a
|
|
rel="noopener noreferrer"
|
|
target="_blank"
|
|
href={selectedAppData.reference_info.github_url}
|
|
style={{ textDecoration: "none", color: "#f86a3e" }}
|
|
>
|
|
<img
|
|
alt={`Documentation image for ${selectedAppData.name}`}
|
|
src={selectedAppData.large_image}
|
|
style={{
|
|
width: 30,
|
|
height: 30,
|
|
border: "2px solid rgba(255,255,255,0.6)",
|
|
borderRadius: theme.palette?.borderRadius,
|
|
maxWidth: 30,
|
|
maxHeight: 30,
|
|
overflow: "hidden",
|
|
fontSize: 8,
|
|
}}
|
|
/>
|
|
</a>
|
|
)}
|
|
</div>
|
|
<Tooltip
|
|
color="primary"
|
|
title={`Move window`}
|
|
placement="left"
|
|
>
|
|
<IconButton
|
|
id="draggable-dialog-title"
|
|
style={{
|
|
zIndex: 5000,
|
|
position: "absolute",
|
|
top: 14,
|
|
right: 50,
|
|
color: "grey",
|
|
|
|
cursor: "move",
|
|
}}
|
|
>
|
|
<DragIndicatorIcon />
|
|
</IconButton>
|
|
</Tooltip>
|
|
<IconButton
|
|
style={{
|
|
zIndex: 5000,
|
|
position: "absolute",
|
|
top: 14,
|
|
right: 18,
|
|
color: "grey",
|
|
}}
|
|
onClick={() => {
|
|
setAuthenticationModalOpen(false);
|
|
}}
|
|
>
|
|
<CloseIcon />
|
|
</IconButton>
|
|
<div style={{ display: "flex", flexDirection: "row" }}>
|
|
<div
|
|
style={{
|
|
flex: 2,
|
|
padding: 0,
|
|
minHeight: isMobile ? "90%" : 700,
|
|
maxHeight: isMobile ? "90%" : 700,
|
|
overflowY: "auto",
|
|
overflowX: isMobile ? "auto" : "hidden",
|
|
}}
|
|
>
|
|
{authenticationType.type === "oauth2" || authenticationType.type === "oauth2-app" ?
|
|
<AuthenticationOauth2
|
|
selectedApp={selectedAppData}
|
|
selectedAction={{
|
|
"app_name": selectedAppData.name,
|
|
"app_id": selectedAppData.id,
|
|
"app_version": selectedAppData.version,
|
|
"large_image": selectedAppData.large_image,
|
|
}}
|
|
authenticationType={authenticationType}
|
|
getAppAuthentication={getAppAuthentication}
|
|
appAuthentication={appAuthentication}
|
|
setAuthenticationModalOpen={setAuthenticationModalOpen}
|
|
isCloud={isCloud}
|
|
org_id={userdata?.active_org?.id}
|
|
setSelectedAction={setSelectedAction}
|
|
/>
|
|
:
|
|
<AuthenticationData app={selectedAppData} />
|
|
}
|
|
</div>
|
|
<div
|
|
style={{
|
|
flex: 3,
|
|
borderLeft: `1px solid ${theme.palette.inputColor}`,
|
|
padding: "70px 30px 30px 30px",
|
|
maxHeight: 630,
|
|
minHeight: 630,
|
|
overflowY: "auto",
|
|
overflowX: "hidden",
|
|
}}
|
|
>
|
|
{selectedAppData.documentation === undefined ||
|
|
selectedAppData.documentation === null ||
|
|
selectedAppData.documentation.length === 0 ? (
|
|
<span
|
|
style={{ textAlign: "center" }}
|
|
>
|
|
<div style={{textAlign: "left", }}>
|
|
<Markdown
|
|
components={{
|
|
img: Img,
|
|
code: CodeHandler,
|
|
h1: Heading,
|
|
h2: Heading,
|
|
h3: Heading,
|
|
h4: Heading,
|
|
h5: Heading,
|
|
h6: Heading,
|
|
a: OuterLink,
|
|
}}
|
|
id="markdown_wrapper"
|
|
escapeHtml={false}
|
|
style={{
|
|
maxWidth: "100%",
|
|
minWidth: "100%",
|
|
textAlign: "left",
|
|
}}
|
|
>
|
|
{selectedAppData?.description}
|
|
</Markdown>
|
|
</div>
|
|
<Divider
|
|
style={{
|
|
marginTop: 25,
|
|
marginBottom: 25,
|
|
backgroundColor: "rgba(255,255,255,0.6)",
|
|
}}
|
|
/>
|
|
|
|
<div
|
|
style={{
|
|
backgroundColor: theme.palette.inputColor,
|
|
padding: 15,
|
|
borderRadius: theme.palette?.borderRadius,
|
|
marginBottom: 30,
|
|
}}
|
|
>
|
|
<Typography variant="h6" style={{marginBottom: 25, }}>
|
|
There is no Shuffle-specific documentation for this app yet outside of the general description above. Documentation is written for each api, and is a community effort. We hope to see your contribution!
|
|
</Typography>
|
|
<Button
|
|
variant="contained"
|
|
color="primary"
|
|
onClick={() => {
|
|
toast.success("Opening remote Github documentation link. Thanks for contributing!")
|
|
|
|
setTimeout(() => {
|
|
window.open(`https://github.com/Shuffle/openapi-apps/new/master/docs?filename=${selectedAppData.name.toLowerCase()}.md`, "_blank")
|
|
}, 2500)
|
|
}}
|
|
>
|
|
<EditIcon /> Create Docs
|
|
</Button>
|
|
</div>
|
|
|
|
<Typography variant="body1" style={{ marginTop: 25 }}>
|
|
Want to help the making of, or improve this app?{" "}
|
|
<br />
|
|
<a
|
|
rel="noopener noreferrer"
|
|
target="_blank"
|
|
href="https://discord.gg/B2CBzUm"
|
|
style={{ textDecoration: "none", color: "#f86a3e" }}
|
|
>
|
|
Join the community on Discord!
|
|
</a>
|
|
</Typography>
|
|
|
|
<Typography variant="h6" style={{ marginTop: 50 }}>
|
|
Want to help change this app directly?
|
|
</Typography>
|
|
{selectedAppData.reference_info === undefined ||
|
|
selectedAppData.reference_info === null ||
|
|
selectedAppData.reference_info.github_url === undefined ||
|
|
selectedAppData.reference_info.github_url === null ||
|
|
selectedAppData.reference_info.github_url.length === 0 ? (
|
|
<span>
|
|
<Typography variant="body1" style={{ marginTop: 25 }}>
|
|
<a
|
|
rel="noopener noreferrer"
|
|
target="_blank"
|
|
href={"https://github.com/shuffle/python-apps"}
|
|
style={{ textDecoration: "none", color: "#f86a3e" }}
|
|
>
|
|
Check it out on Github!
|
|
</a>
|
|
</Typography>
|
|
</span>
|
|
) : (
|
|
<span>
|
|
<Typography variant="body1" style={{ marginTop: 25 }}>
|
|
<a
|
|
rel="noopener noreferrer"
|
|
target="_blank"
|
|
href={selectedAppData.reference_info.github_url}
|
|
style={{ textDecoration: "none", color: "#f86a3e" }}
|
|
>
|
|
Check it out on Github!
|
|
</a>
|
|
</Typography>
|
|
</span>
|
|
)}
|
|
</span>
|
|
) : (
|
|
<div>
|
|
{selectedMeta !== undefined && selectedMeta !== null && Object.getOwnPropertyNames(selectedMeta).length > 0 && selectedMeta.name !== undefined && selectedMeta.name !== null ?
|
|
<div
|
|
style={{
|
|
backgroundColor: theme.palette.inputColor,
|
|
padding: 15,
|
|
borderRadius: theme.palette?.borderRadius,
|
|
marginBottom: 30,
|
|
display: "flex",
|
|
}}
|
|
>
|
|
<div style={{ flex: 3, display: "flex", vAlign: "center", position: "sticky", top: 50, }}>
|
|
{isMobile ? null : (
|
|
<Typography style={{ display: "inline", marginTop: 6 }}>
|
|
<a
|
|
rel="noopener noreferrer"
|
|
target="_blank"
|
|
href={selectedMeta.link}
|
|
style={{ textDecoration: "none", color: "#f85a3e" }}
|
|
>
|
|
<Button style={{ color: "white", }} variant="outlined" color="secondary">
|
|
<EditIcon /> Edit
|
|
</Button>
|
|
</a>
|
|
</Typography>
|
|
)}
|
|
{isMobile ? null : (
|
|
<div
|
|
style={{
|
|
height: "100%",
|
|
width: 1,
|
|
backgroundColor: "white",
|
|
marginLeft: 50,
|
|
marginRight: 50,
|
|
}}
|
|
/>
|
|
)}
|
|
<Typography style={{ display: "inline", marginTop: 11 }}>
|
|
{selectedMeta.read_time} minute
|
|
{selectedMeta.read_time === 1 ? "" : "s"} to read
|
|
</Typography>
|
|
</div>
|
|
<div style={{ flex: 2 }}>
|
|
{isMobile ||
|
|
selectedMeta.contributors === undefined ||
|
|
selectedMeta.contributors === null ? (
|
|
""
|
|
) : (
|
|
<div style={{ margin: 10, height: "100%", display: "inline" }}>
|
|
{selectedMeta.contributors.slice(0, 7).map((data, index) => {
|
|
return (
|
|
<a
|
|
key={index}
|
|
rel="noopener noreferrer"
|
|
target="_blank"
|
|
href={data.url}
|
|
style={{ textDecoration: "none", color: "#f85a3e" }}
|
|
>
|
|
<Tooltip title={data.url} placement="bottom">
|
|
<img
|
|
alt={data.url}
|
|
src={data.image}
|
|
style={{
|
|
marginTop: 5,
|
|
marginRight: 10,
|
|
height: 40,
|
|
borderRadius: 40,
|
|
}}
|
|
/>
|
|
</Tooltip>
|
|
</a>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
: null}
|
|
|
|
<Markdown
|
|
components={{
|
|
img: Img,
|
|
code: CodeHandler,
|
|
h1: Heading,
|
|
h2: Heading,
|
|
h3: Heading,
|
|
h4: Heading,
|
|
h5: Heading,
|
|
h6: Heading,
|
|
a: OuterLink,
|
|
}}
|
|
id="markdown_wrapper"
|
|
escapeHtml={false}
|
|
style={{
|
|
maxWidth: "100%", minWidth: "100%",
|
|
}}
|
|
>
|
|
{selectedAppData.documentation}
|
|
</Markdown>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</Dialog>
|
|
) : null;
|
|
|
|
return (
|
|
<div>
|
|
{authenticationModal}
|
|
{hits.length === 0 && searchQuery.length >= 0 ? (
|
|
<div style={{minWidth: 500, minHeight: 500, textAlign: 'center'}}>
|
|
<Typography variant="body1" style={{ marginTop: '30%' }}>
|
|
No Apps Found
|
|
</Typography>
|
|
</div>
|
|
) : (
|
|
<Grid container spacing={2} style={{ marginTop: 16, }}>
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
gap: 5,
|
|
marginLeft: 24,
|
|
height: 480,
|
|
overflowY: "auto",
|
|
scrollbarWidth: "thin",
|
|
scrollbarColor: "#494949 #2f2f2f",
|
|
width: "100%",
|
|
}}
|
|
>
|
|
{hits.map((data, index) => {
|
|
const appUrl = isCloud
|
|
? `/apps/${data.objectID}?queryID=${data.__queryID}`
|
|
: `https://shuffler.io/apps/${data.objectID}?queryID=${data.__queryID}`;
|
|
|
|
return (
|
|
<Zoom
|
|
key={index}
|
|
in={true}
|
|
style={{
|
|
transitionDelay: `${workflowDelay}ms`,
|
|
}}
|
|
>
|
|
<Grid item>
|
|
<a
|
|
href={appUrl}
|
|
rel="noopener noreferrer"
|
|
target="_blank"
|
|
style={{
|
|
textDecoration: "none",
|
|
color: "#f85a3e",
|
|
}}
|
|
>
|
|
<Paper
|
|
elevation={0}
|
|
style={{
|
|
...paperStyle,
|
|
backgroundColor: mouseHoverIndex === index ? "#2F2F2F" : "rgba(26, 26, 26, 1)",
|
|
width: "100%",
|
|
}}
|
|
onMouseEnter={() => setMouseHoverIndex(index)}
|
|
onMouseLeave={() => setMouseHoverIndex(-1)}
|
|
>
|
|
<button
|
|
style={{
|
|
borderRadius: 4,
|
|
fontSize: 16,
|
|
display: "flex",
|
|
alignItems: "flex-start",
|
|
padding: 0,
|
|
border: "none",
|
|
width: "100%",
|
|
height: 96,
|
|
cursor: "pointer",
|
|
color: "rgba(26, 26, 26, 1)",
|
|
backgroundColor: "transparent",
|
|
}}
|
|
>
|
|
<img
|
|
id={`image_${index}`}
|
|
alt={data.name}
|
|
src={data.image_url || "/images/no_image.png"}
|
|
onError={(e) => {
|
|
const foundImage = document.getElementById(`image_${index}`);
|
|
if (foundImage) {
|
|
foundImage.src = "/images/defaultImage.png";
|
|
}
|
|
}}
|
|
style={{
|
|
width: 80,
|
|
height: 80,
|
|
borderRadius: 4,
|
|
margin: 8,
|
|
}}
|
|
/>
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
alignItems: "flex-start",
|
|
gap: 4,
|
|
width: '100%',
|
|
overflow: "hidden",
|
|
margin: "12px 0",
|
|
marginLeft: 8,
|
|
fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)",
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
flexDirection: "row",
|
|
overflow: "hidden",
|
|
gap: 8,
|
|
textOverflow: "ellipsis",
|
|
whiteSpace: "nowrap",
|
|
color: "#F1F1F1",
|
|
}}
|
|
>
|
|
{normalizedString(data.name)}
|
|
</div>
|
|
<div
|
|
style={{
|
|
overflow: "hidden",
|
|
textOverflow: "ellipsis",
|
|
whiteSpace: "nowrap",
|
|
color: "rgba(158, 158, 158, 1)",
|
|
marginTop: 5,
|
|
}}
|
|
>
|
|
{data.categories ? normalizedString(data.categories).join(", ") : "NA"}
|
|
</div>
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
justifyContent: "space-between",
|
|
width: "100%",
|
|
textAlign: "start",
|
|
color: "rgba(158, 158, 158, 1)",
|
|
}}
|
|
>
|
|
<div style={{ marginBottom: 15 }}>
|
|
<div
|
|
style={{
|
|
width: "100%",
|
|
textOverflow: "ellipsis",
|
|
overflow: "hidden",
|
|
whiteSpace: "nowrap",
|
|
maxWidth: "300px"
|
|
}}
|
|
>
|
|
{data.tags?.map((tag, tagIndex) => (
|
|
<span key={tagIndex}>
|
|
{normalizedString(tag)}
|
|
{tagIndex < data.tags.length - 1 ? ", " : ""}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<Button style={{position: 'relative', borderRadius: 6, bottom: 10, marginRight: 10, fontSize: 16, backgroundColor: '#ff8544', color: "#1a1a1a", textTransform:'none', marginLeft: 'auto'}} onClick={(e)=> {e.preventDefault();e.stopPropagation();handleAppAuthenticationNew(data)()}}>
|
|
Authenticate app
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</button>
|
|
</Paper>
|
|
</a>
|
|
</Grid>
|
|
</Zoom>
|
|
);
|
|
})}
|
|
</div>
|
|
</Grid>
|
|
)}
|
|
</div>
|
|
);
|
|
|
|
};
|
|
|
|
const CustomSearchBox = connectSearchBox(SearchBox);
|
|
const CustomHits = connectHits(Hits);
|