import React, { useState, useEffect, useContext, memo } from "react";
import { makeStyles } from "@mui/styles";
import { getTheme } from "../theme.jsx";
import { toast } from 'react-toastify';
import ReactJson from "react-json-view-ssr";
import { validateJson, handleReactJsonClipboard, GetIconInfo, } from "../views/Workflows2.jsx";
import { green, red } from "../views/AngularWorkflow.jsx";
import CollectIngestModal from "../components/CollectIngestModal.jsx";
import CorrelationGraph from "../components/CorrelationGraph.jsx";
import { useNavigate, Link, useParams } from "react-router-dom";
import {
Typography,
Tooltip,
Divider,
TextField,
Button,
Tabs,
Tab,
List,
ListItem,
ListItemText,
IconButton,
Dialog,
DialogTitle,
DialogActions,
Skeleton,
Chip,
Checkbox,
MenuItem,
DialogContent,
FormControl,
Select,
Autocomplete,
ButtonGroup,
InputLabel,
Pagination,
PaginationItem,
Avatar,
ListSubheader,
} from "@mui/material";
import {
DataGrid,
} from '@mui/x-data-grid';
import {
Link as LinkIcon,
AutoFixHigh as AutoFixHighIcon,
AutoFixNormal as AutoFixNormalIcon,
Edit as EditIcon,
FileCopy as FileCopyIcon,
SelectAll as SelectAllIcon,
DeleteOutline as DeleteOutlineIcon,
OpenInNew as OpenInNewIcon,
CloudDownload as CloudDownloadIcon,
Description as DescriptionIcon,
CheckCircle as CheckCircleIcon,
Close as CloseIcon,
Apps as AppsIcon,
Image as ImageIcon,
Cached as CachedIcon,
AccessibilityNew as AccessibilityNewIcon,
Lock as LockIcon,
Schedule as ScheduleIcon,
Cloud as CloudIcon,
Business as BusinessIcon,
Visibility as VisibilityIcon,
VisibilityOff as VisibilityOffIcon,
Clear as ClearIcon,
Add as AddIcon,
Remove as RemoveIcon,
Rocket as RocketIcon,
Webhook as WebhookIcon,
Air as AirIcon,
RocketLaunch as RocketLaunchIcon,
Send as SendIcon,
SmartToy as SmartToyIcon,
Settings as SettingsIcon,
FilterAlt as FilterAltIcon,
CompareArrows as CompareArrowsIcon,
Hub as HubIcon,
Key as KeyIcon,
FlashOn as FlashOnIcon,
} from "@mui/icons-material";
import { Context } from "../context/ContextApi.jsx";
const scrollStyle1 = {
height: 100,
width: 225,
overflow: "hidden",
position: "relative",
}
const scrollStyle2 = {
position: "absolute",
top: 0,
left: 0,
bottom: "-20px",
right: "-20px",
overflow: "scroll",
}
const useStyles = makeStyles({
notchedOutline: {
borderColor: "#f85a3e !important",
},
});
//
//const CacheView = (props) => {
const CacheView = memo((props) => {
const { globalUrl, userdata, serverside, orgId, isSelectedDataStore, selectedOrganization } = props;
const [orgCache, setOrgCache] = React.useState("");
const [listCache, setListCache] = React.useState([]);
const [addCache, setAddCache] = React.useState("");
const [editedCache, setEditedCache] = React.useState("");
const [modalOpen, setModalOpen] = React.useState(false);
const [key, setKey] = React.useState("");
const [value, setValue] = React.useState("");
const [cacheInput, setCacheInput] = React.useState("");
const [dataValue, setDataValue] = React.useState({});
const [editCache, setEditCache] = React.useState(false);
const [cachedLoaded, setCachedLoaded] = React.useState(false);
const [show, setShow] = useState({});
const [showDistributionPopup, setShowDistributionPopup] = useState(false);
const [selectedSubOrg, setSelectedSubOrg] = useState([]);
const [selectedCacheKey, setSelectedCacheKey] = useState("");
const [totalAmount, setTotalAmount] = useState(0);
const [page, setPage] = useState(0);
const [pageSize, setPageSize] = useState(50)
const [cursors, setCursors] = useState({
0: "",
})
const [_, setUpdate] = useState(Math.random())
const [selectedRows, setSelectedRows] = useState([]);
// Direct category migration from ../components/Files.jsx
const [selectAllChecked, setSelectAllChecked] = React.useState(false)
const [renderTextBox, setRenderTextBox] = React.useState(false);
const [datastoreCategories, setDatastoreCategories] = React.useState(["default", "protected"]);
const [datastoreCategoryGroups, setDatastoreCategoryGroups] = React.useState([]);
const [selectedCategory, setSelectedCategory] = React.useState("default");
const [selectedFileId, setSelectedFileId] = React.useState("");
const [updateToThisCategory, setUpdateToThisCategory] = useState("")
const [workflows, setWorkflows] = useState([]);
const [apps, setApps] = useState([]);
const [selectedFiles, setSelectedFiles] = useState([]);
const [showAutomationMenu, setShowAutomationMenu] = useState(false);
const [showSettingsMenu, setShowSettingsMenu] = useState(false);
const [showCollectIngestMenu, setShowCollectIngestMenu] = useState(false);
const [showCorrelations, setShowCorrelations] = useState(undefined);
let navigate = useNavigate();
useEffect(() => {
if (selectedCategory === "" || selectedCategory === null || selectedCategory === undefined || selectedCategory === "default") {
return
}
if (datastoreCategories === undefined || datastoreCategories === null || datastoreCategories.length === 0) {
return
}
if (!datastoreCategories.includes(selectedCategory)) {
setDatastoreCategories([...datastoreCategories, selectedCategory])
}
}, [datastoreCategories, selectedCategory])
var to_be_copied = "";
const defaultAutomation = [
{
"name": "Security Rules",
"description": "Describes security rules that are validated BEFORE an update occurs. This is in order for bad writes to be avoided. Control: allow, deny, merge, overwrite. Logic: if, or, and. Functions: same_shape, is_superset, has_deleted_field",
"options": [{
"key": "rule",
"value": "",
}],
"icon": ,
"beta": true,
"disabled": false,
"enabled": false,
},
{
"name": "Run workflow",
"description": "Runs one or more workflows with the updated value as runtime argument",
"options": [{
"key": "workflow_id",
"value": "",
}],
"icon": ,
"enabled": false,
},
/*
{
"name": "Correlate Categories",
"description": "",
"type": "singul",
"options": [{
"key": "datastore_categories",
"value": "",
}],
"icon": ,
"enabled": false,
"disabled": false,
},
*/
{
"name": "Send webhook",
"description": "Sends the updated value to a specified webhook URL as a POST request",
"options": [{
"key": "webhook_url",
"value": "",
}],
"icon": ,
"enabled": false,
},
{
"name": "Run AI Agent",
"description": "Runs an AI Agent to process the updated value. Controllable based on your 'action' input, where the full updated value is added on the end. All actions run in order, meaning it needs to finish the previous one before the next is ran. Uses built-in ShuffleAI configs. Learn more: https://shuffler.io/docs/AI",
"type": "singul",
"options": [{
"key": "action-1",
"description": "Describe the action to perform on this item",
"value": "",
}],
"beta": true,
"icon": ,
"disabled": false,
},
{
"name": "Enrich",
"description": "Enriches the data. Only runs on valid JSON data AND if the 'enrichment' field does not exist.",
"type": "singul",
"options": [{
"key": "",
"value": "",
}],
"beta": true,
"icon": "/images/logos/singul.svg",
"disabled": false,
},
]
const [categoryAutomations, setCategoryAutomations] = useState(defaultAutomation)
const [categoryConfig, setCategoryConfig] = useState(undefined)
const { themeMode, brandColor } = useContext(Context);
const theme = getTheme(themeMode, brandColor);
const classes = useStyles();
const getApps = () => {
const url = `${globalUrl}/api/v1/apps`
fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for workflows :O!");
return;
}
return response.json();
})
.then((responseJson) => {
if (responseJson?.success === false) {
toast.warn("Failed to load apps. Please try again or contact support@shuffler if this persists.")
} else {
setApps(responseJson)
}
})
.catch((error) => {
toast(error.toString());
});
}
const getWorkflows = () => {
const url = `${globalUrl}/api/v1/workflows`
fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for workflows :O!");
return;
}
return response.json();
})
.then((responseJson) => {
if (responseJson?.success !== true) {
setWorkflows(responseJson)
} else {
toast.warn("Failed to load workflows. Please try again or contact support@shuffler if this persists.")
}
})
.catch((error) => {
toast(error.toString());
});
}
useEffect(() => {
setCursors({
0: "",
})
setPage(0)
setSelectedRows([])
}, [selectedCategory])
useEffect(() => {
getWorkflows()
getApps()
var chosenCategory = selectedCategory
const urlParams = new URLSearchParams(window.location.search)
var categoryParam = urlParams.get("category")
if (categoryParam && categoryParam !== undefined && categoryParam !== "default" && categoryParam !== "") {
// In order to make linking weird urls from workflow page work.
if (urlParams.get("src") == "workflow") {
if (categoryParam?.toLowerCase().startsWith("list")) {
const newParam = categoryParam.substring(5).replaceAll("%20", "_")
// Replace the url param. There may be more params.
urlParams.set("category", newParam)
window.history.replaceState({}, '', `${window.location.pathname}?${urlParams.toString()}`)
categoryParam = newParam
}
}
chosenCategory = categoryParam
setSelectedCategory(categoryParam)
}
listOrgCache(orgId, "", 0, pageSize, page)
setTimeout(() => {
listOrgCache(orgId, chosenCategory, 0, pageSize, page)
}, 100)
}, [])
const handleKeyDown = (event) => {
if (event.key === 'Enter') {
datastoreCategories.push(event.target.value);
setSelectedCategory(event.target.value);
setRenderTextBox(false);
}
if (event.key === 'Escape'){ // not working for some reasons
console.log('escape pressed')
setRenderTextBox(false);
}
}
const listOrgCache = (orgId, category, index, amount, page, keyValue) => {
setCachedLoaded(false)
if (index === undefined || index === null) {
index = 0
}
var url = `${globalUrl}/api/v1/orgs/${orgId}/list_cache`
if (category !== undefined && category !== null && category !== "default" && category !== "") {
url += "?category=" + category.replaceAll(" ", "_")
} else {
url += "?category=default"
category = "default"
}
if (amount !== undefined && amount !== null && amount > 0) {
url += "&top=" + amount
}
if (page !== undefined && page !== null && page >= 0) {
if (cursors[page-1] !== undefined && cursors[page-1] !== null && cursors[page-1] !== "") {
url += "&cursor=" + cursors[page-1]
}
}
if (keyValue !== undefined && keyValue !== null && keyValue !== "") {
url += "&key=" + keyValue
setCursors({
0: "",
})
setPage(0)
setSelectedRows([])
}
fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for list cache :O!");
return;
}
return response.json();
})
.then((responseJson) => {
setCachedLoaded(true);
if (responseJson?.success === true) {
setListCache(responseJson.keys);
if (responseJson.total_amount !== undefined && responseJson.total_amount !== null && responseJson.total_amount > 0) {
setTotalAmount(responseJson.total_amount)
} else {
setTotalAmount(responseJson.keys.length)
}
if (responseJson?.cursor !== undefined && responseJson?.cursor !== null && responseJson?.cursor !== "") {
cursors[page] = responseJson.cursor
}
// Especially important during first load
if (index < 2 && (category === "default" || category === "" || category === undefined)) {
// If it exists and isn't blank/default, load it
const urlParams = new URLSearchParams(window.location.search);
const categoryParam = urlParams.get("category");
if (categoryParam && categoryParam !== undefined && categoryParam !== "default" && categoryParam !== "") {
setSelectedCategory(categoryParam);
if (index === undefined || index === null) {
index = 0
}
listOrgCache(orgId, categoryParam, index+1, amount, page)
} else {
setSelectedCategory("default");
}
}
if ((category === undefined || category === "default" || category === "") && datastoreCategories.length === 2 && datastoreCategories[0] === "default") {
var newcategories = ["default", "protected"]
for (var key in responseJson.keys) {
var foundcategory = responseJson.keys[key].category
if (foundcategory !== undefined && foundcategory !== null && foundcategory !== ""){
foundcategory = category.replaceAll(" ", "_")
if (!newcategories.includes(foundcategory)) {
newcategories.push(foundcategory)
}
}
}
if (responseJson?.categories !== undefined && responseJson?.categories !== null && responseJson?.categories.length > 0) {
for (var i = 0; i < responseJson.categories.length; i++) {
const foundcategory = responseJson.categories[i].replaceAll(" ", "_")
if (foundcategory !== undefined && foundcategory !== null && foundcategory !== "" && foundcategory !== "default" && !newcategories.includes(foundcategory)) {
newcategories.push(responseJson.categories[i]);
}
}
}
var foundstartwords = {}
var categorygroups = []
for (var key in newcategories) {
const category = newcategories[key]
if (!category.includes("_")) {
continue
}
const startword = category.split("_")[0]
if (startword.length <= 2) {
continue
}
if (!foundstartwords.hasOwnProperty(startword)) {
foundstartwords[startword] = 1
} else {
foundstartwords[startword] += 1
if (foundstartwords[startword] === 2) {
categorygroups.push(startword)
}
}
}
setDatastoreCategories(newcategories)
setDatastoreCategoryGroups(categorygroups)
}
if (responseJson?.category_config !== undefined && responseJson?.category_config !== null) {
if (responseJson?.category_config?.id !== undefined && responseJson?.category_config?.id !== null && responseJson?.category_config?.id !== "") {
setCategoryConfig(responseJson.category_config)
}
// Handle other configs here.
if (responseJson?.category_config?.automations !== undefined && responseJson?.category_config?.automations !== null && responseJson?.category_config?.automations.length > 0) {
// Find icons if they exist
for (var key in responseJson.category_config.automations) {
//if (responseJson.category_config.automations[key].icon === undefined || responseJson.category_config.automations[key].icon === null || responseJson.category_config.automations[key].icon === "") {
const foundItem = defaultAutomation.find((automation) => automation.name === responseJson.category_config.automations[key].name)
if (foundItem) {
responseJson.category_config.automations[key].description = foundItem.description
responseJson.category_config.automations[key].beta = foundItem.beta
responseJson.category_config.automations[key].disabled = foundItem.disabled
responseJson.category_config.automations[key].icon = foundItem.icon
responseJson.category_config.automations[key].type = foundItem?.type
} else {
responseJson.category_config.automations[key].icon =
}
}
for (var key in defaultAutomation) {
if (!responseJson.category_config.automations.some((automation) => automation.name === defaultAutomation[key].name)) {
// If the automation doesn't exist in the response, add it with default values
responseJson.category_config.automations.push(defaultAutomation[key])
}
}
setCategoryAutomations(responseJson.category_config.automations)
} else {
setCategoryAutomations(defaultAutomation)
}
}
} else {
//toast.warn("Failed to load keys. Please try again or contact support@shuffler if this persists.")
if (category !== undefined && category !== null && category !== "" && category !== "default") {
toast.info(`No keys to load in category ${category}`)
setSelectedCategory(category)
}
}
})
.catch((error) => {
toast(error.toString());
});
};
const deleteEntry = (orgId, key, itemCategory, refreshList) => {
const method = "POST"
const url = `${globalUrl}/api/v1/orgs/${orgId}/delete_cache`
var parsed = {
"org_id": orgId,
"key": key,
"category": selectedCategory === "" || selectedCategory === "default" ? "" : selectedCategory,
}
if (itemCategory !== undefined) {
parsed["category"] = itemCategory.replaceAll(" ", "_");
}
fetch(url, {
method: method,
headers: {
Accept: "application/json",
},
body: JSON.stringify(parsed),
credentials: "include",
})
.then((response) => {
if (response.status === 200) {
if (refreshList === undefined || refreshList === null || refreshList === true) {
toast.success("Deleted datastore entry");
setTimeout(() => {
listOrgCache(orgId, selectedCategory, 0, pageSize, page)
}, 1000);
}
} else {
toast.error(`Failed deleting entry ${key} in category ${itemCategory || selectedCategory}. If this persists, please contact support@shuffler.io.`)
}
})
.catch((error) => {
toast(error.toString());
});
};
const editOrgCache = (orgId) => {
var entry = {
key: dataValue.key,
value: value,
category: selectedCategory,
tags: dataValue?.tags || [],
}
if (dataValue?.category !== undefined && dataValue?.category !== "" && dataValue?.category !== "default") {
entry.category = dataValue?.category?.replaceAll(" ", "_");
}
if (listCache.length > 0) {
const selectedCache = listCache.find((data) => data.key === dataValue.key);
if (selectedCache?.suborg_distribution?.length > 0) {
entry.suborg_distribution = selectedCache.suborg_distribution;
}
}
setCacheInput([entry]);
//entry = [entry]
const parsedUrl = `${globalUrl}/api/v2/datastore`
fetch(parsedUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
body: JSON.stringify(entry),
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for Cache :O!");
return;
}
return response.json();
})
.then((responseJson) => {
setAddCache(responseJson);
toast.success("Edit saved");
setTimeout(() => {
listOrgCache(orgId, selectedCategory, 0, pageSize, page);
}, 7500);
setModalOpen(false);
})
.catch((error) => {
toast(error.toString());
});
};
const addOrgCache = (orgId) => {
const cache = {
key: key,
value: value,
category: selectedCategory,
}
setCacheInput([cache]);
fetch(globalUrl + `/api/v1/orgs/${orgId}/set_cache`, {
method: "POST",
body: JSON.stringify(cache),
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) => {
setAddCache(responseJson);
toast.success("New key added!");
setTimeout(() => {
listOrgCache(orgId, selectedCategory, 0, pageSize, page);
}, 5000);
setModalOpen(false);
})
.catch((error) => {
toast(error.toString());
});
};
const isValidJson = validateJson(value)
const autoFixJson = (inputvalue) => {
console.log("inputvalue: ", inputvalue)
try {
var parsedjson = JSON.parse(inputvalue)
// setValue() with the parsed json as string
setValue(JSON.stringify(parsedjson, null, 2))
} catch (e) {
console.log("Error parsing JSON: ", e)
toast.info("Invalid JSON.", {
autoClose: 1500,
})
}
}
const timestamp = (timestamp) => {
if (timestamp === undefined || timestamp === null || timestamp === "") {
return null
}
const date = new Date(timestamp * 1000);
if (date.toString() === "Invalid Date" || date.toString() === "Invalid Date NaN") {
return null
}
return date.toISOString()?.slice(0, 19)?.replace("T", " ")
}
const modalView = (
// console.log("key:", dataValue.key),
//console.log("value:",dataValue.value),
);
const handleSelectSubOrg = (id, action) => {
if (action === "all") {
const childOrgs = userdata.orgs.filter(
(data) => data.creator_org === userdata.active_org.id
);
setSelectedSubOrg((prev) => {
if (prev.length === childOrgs.length) {
// If all child orgs are already selected, clear the selection
return [];
} else {
// Otherwise, select all child org IDs
return childOrgs.map((data) => data.id);
}
});
} else if (action === "none") {
setSelectedSubOrg([]);
} else {
setSelectedSubOrg((prev) => {
if (prev.includes(id)) {
return prev.filter((data) => data !== id);
} else {
return [...prev, id];
}
});
}
};
const changeDistribution = (id, selectedSubOrg) => {
editFileConfig(id, [...new Set(selectedSubOrg)], selectedCategory)
}
const editFileConfig = (id, selectedSubOrg, category) => {
const data = {
Key: id,
action: "suborg_distribute",
selected_suborgs: selectedSubOrg,
category: category === undefined || category === "" || category === "default" ? "" : category,
}
console.log("data: ", data);
const url = `${globalUrl}/api/v1/orgs/${orgId}/cache/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.error("Failed overwriting datastore");
} else {
toast.success("Successfully updated datastore!");
setTimeout(() => {
listOrgCache(orgId, selectedCategory, 0, pageSize, page);
setShowDistributionPopup(false);
}, 1000);
}
})
)
.catch((error) => {
toast("Err: " + error.toString());
});
};
const cacheDistributionModal = showDistributionPopup ? (
) : null;
const saveAutomation = (allAutomation, settings) => {
// Check if icon is a string. Otherwise make it empty.
var removedIcons = {}
allAutomation.forEach((automation) => {
const originalIcon = automation.icon
if (typeof automation.icon !== "string") {
automation.icon = "";
}
})
const url = `${globalUrl}/api/v2/datastore/automate`
const data = {
"category": selectedCategory === "" || selectedCategory === "default" ? "" : selectedCategory,
"automations": allAutomation,
}
if (settings !== undefined && settings !== null && Object.keys(settings).length > 0) {
data["settings"] = settings
}
fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
body: JSON.stringify(data),
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for automations :O!");
return;
}
return response.json();
})
.then((responseJson) => {
if (responseJson?.success === true) {
toast.success("Saved successfully!")
} else {
toast.warn("Failed to save automations. Please try again or contact support@shuffler if this persists.")
}
})
.catch((error) => {
toast.error(error.toString());
})
}
const AutomationOptions = ({ automation, index }) => {
const [showOptions, setShowOptions] = useState(false);
const [hovered, setHovered] = useState(false);
const [updated, setUpdated] = useState(false);
const [updatedAutomation, setUpdatedAutomation] = useState(automation);
const [_, setUpdate] = useState(Math.random()) // Force re-render
if (automation.icon === undefined || automation.icon === null || automation.icon === "") {
for (var i = 0; i < defaultAutomation.length; i++) {
if (defaultAutomation[i].name === automation.name) {
automation.icon = defaultAutomation[i].icon
break;
}
}
}
const runSave = () => {
setUpdated(false)
const newAutomations = [...categoryAutomations]
newAutomations[index] = updatedAutomation
setCategoryAutomations(newAutomations)
setUpdate(Math.random()) // Force re-render
saveAutomation(newAutomations)
}
return (
{automation?.description}
}
placement="right"
style={theme.palette.tooltip}
>
setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
option.value === ""))}
onChange={(e) => {
e.stopPropagation()
e.preventDefault()
updatedAutomation.enabled = !updatedAutomation.enabled
setUpdatedAutomation(updatedAutomation)
setUpdated(true)
setUpdate(Math.random())
}}
/>
{
if (automation?.disabled === true) {
return
}
if (automation?.name === "Enrich") {
automation.enabled = !automation.enabled
runSave()
return
}
setShowOptions(!showOptions)
// Auto saves when the options are closed/saved
if (updated && showOptions) {
runSave()
}
}}
>
{typeof automation?.icon === "string" && automation?.icon?.length > 0 ?

:
automation.icon
}
{automation.name}
{automation?.beta !== true ? null :
}
{automation?.disabled !== true ?
: null}
{showOptions &&
{updatedAutomation.options.map((option, optionIndex) => {
if (option?.key === "datastore_categories") {
if (datastoreCategories === undefined || datastoreCategories === null || datastoreCategories.length <= 1) {
return (
No categories available. Please add categories in the settings.
)
}
return (
option?.value.includes(c)) || []}
classes={{ inputRoot: classes.inputRoot }}
ListboxProps={{
style: {
backgroundColor: theme.palette.surfaceColor,
color: theme.palette.text.primary,
borderRadius: theme.palette.borderRadius,
},
}}
onChange={(event, newValue) => {
console.log("New Value: ", newValue)
option.value = ""
for (var i = 0; i < newValue.length; i++) {
option.value += newValue[i] + ","
}
if (newValue.length > 0) {
updatedAutomation.enabled = true
} else {
updatedAutomation.enabled = false
}
updatedAutomation.options[optionIndex] = option
setUpdatedAutomation(updatedAutomation)
setUpdated(true)
setUpdate(Math.random()) // Force re-render
}}
getOptionLabel={(option) => {
if (option === undefined || option === null) {
return "No Categories Selected";
}
return option
}}
options={datastoreCategories}
fullWidth
style={{
backgroundColor: theme.palette.textFieldStyle.backgroundColor,
borderRadius: theme.palette.textFieldStyle.borderRadius,
color: theme.palette.textFieldStyle.color,
height: 35,
marginBottom: 40,
}}
renderOption={(props, data, state) => {
const fixedname = data?.charAt(0)?.toUpperCase() + data?.slice(1)?.replaceAll("_", " ")
const iconDetails = GetIconInfo({
"app_name": fixedname,
"name": fixedname,
})
const keyfound = option?.value.includes(data)
return (
{data.image !== undefined && data.image !== null && data.image.length > 0 ?
: null}
Choose {data}
} >
)
}}
renderInput={(params) => {
return (
)
}}
/>
)
} else if (option?.key === "workflow_id") {
return (
option?.value.includes(w.id)) || []}
classes={{ inputRoot: classes.inputRoot }}
ListboxProps={{
style: {
backgroundColor: theme.palette.surfaceColor,
color: theme.palette.text.primary,
borderRadius: theme.palette.borderRadius,
},
}}
onChange={(event, newValue) => {
option.value = ""
for (var i = 0; i < newValue.length; i++) {
option.value += newValue[i].id + ","
}
if (newValue.length > 0) {
updatedAutomation.enabled = true
} else {
updatedAutomation.enabled = false
}
updatedAutomation.options[optionIndex] = option
setUpdatedAutomation(updatedAutomation)
setUpdated(true)
setUpdate(Math.random()) // Force re-render
}}
getOptionLabel={(option) => {
if (
option === undefined ||
option === null ||
option?.name === undefined ||
option?.name === null
) {
return "No Workflows Selected";
}
const newname = (
option.name.charAt(0).toUpperCase() + option.name.substring(1)
).replaceAll("_", " ")
return newname
}}
options={workflows}
fullWidth
style={{
backgroundColor: theme.palette.textFieldStyle.backgroundColor,
borderRadius: theme.palette.textFieldStyle.borderRadius,
color: theme.palette.textFieldStyle.color,
height: 35,
marginBottom: 40,
}}
renderOption={(props, data, state) => {
/*
if (data.id === option?.value) {
data = workflow;
}
*/
return (
{data.image !== undefined && data.image !== null && data.image.length > 0 ?
: null}
Choose {data.name}
} >
)
}}
renderInput={(params) => {
return (
)
}}
/>
)
} else if (updatedAutomation?.name === "Run AI Agent") {
return (
{
if (e.target.value === "") {
updatedAutomation.enabled = false
} else {
updatedAutomation.enabled = true
}
updatedAutomation.options[optionIndex].value = e.target.value;
setUpdatedAutomation(updatedAutomation)
setUpdated(true)
}}
/>
{optionIndex === updatedAutomation.options.length - 1 ?
:
}
)
}
return (
{
if (e.target.value === "") {
updatedAutomation.enabled = false
} else {
updatedAutomation.enabled = true
}
updatedAutomation.options[optionIndex].value = e.target.value;
setUpdatedAutomation(updatedAutomation)
setUpdated(true)
}}
/>
)
})}
}
)
}
const setCategorySettingsField = (field, value) => {
// Check if categoryConfig.settings is set or not. Otherwise set it.
var categoryConfig2 = categoryConfig
if (categoryConfig === undefined || categoryConfig === null) {
categoryConfig2 = {}
}
if (categoryConfig?.settings === undefined || categoryConfig?.settings === null) {
categoryConfig2.settings = {}
}
categoryConfig2.settings[field] = value
setCategoryConfig(categoryConfig2)
saveAutomation(
categoryAutomations,
categoryConfig2.settings,
)
}
const urlParams = new URLSearchParams(window?.location?.search)
const highlightedKey = urlParams.get("key") || ""
const columns = [
{
field: 'key',
headerName: 'Key',
width: 200,
filterable: true,
sortable: true,
renderCell: (props) => {
return (
{props.row.key}
)
}
},
{
width: 540,
field: 'value',
filterable: true,
headerName: 'Value',
renderCell: (props) => {
const data = props.row
if (data?.category?.toLowerCase() === "protected") {
return (
***************
)
}
const validate = validateJson(data.value)
return (
{
e.preventDefault()
e.stopPropagation()
}}
>
{validate.valid ?
{
handleReactJsonClipboard(copy)
}}
collapseStringsAfterLength={theme.palette.jsonCollapseStringsAfterLength}
iconStyle={theme.palette.jsonIconStyle}
displayDataTypes={false}
name={null}
/>
:
{data.value}
}
)
}
},
{
field: 'category',
headerName: 'Category',
description: 'Category for this key.',
width: 75,
filterable: false,
sortable: true,
renderCell: (props) => {
// Return avatar with hover for the category
const data = props.row
const clickCategory = (e) => {
setCategoryConfig(undefined)
setCategoryAutomations(defaultAutomation)
if (selectAllChecked || selectedFiles.length > 0) {
setUpdateToThisCategory(data.category)
return
}
setSelectedCategory(data.category)
if (data.category === "all" || data.category === "default") {
listOrgCache(orgId, "", 0, pageSize, page)
} else {
listOrgCache(orgId, data.category, 0, pageSize, page)
}
// Add it to the url as a query
if (window.location.search.includes("category=")) {
const newurl = window.location.href.replace(/category=[^&]+/, `category=${data.category}`)
window.history.pushState({ path: newurl }, "", newurl)
} else {
window.history.pushState({ path: window.location.href }, "", `${window.location.href}&category=${data.category}`)
}
}
const iconDetails = GetIconInfo({
"app_name": data.category,
"name": data.category,
})
const avatarLetter = (data.category === "" || data.category === "default" ? " " : data.category.charAt(0).toUpperCase())[0]
return (
{
clickCategory(e)
}}
style={{
color: "white",
backgroundColor: iconDetails?.iconBackgroundColor || theme.palette.primary.secondary,
marginLeft: 15,
height: 30,
width: 30,
cursor: data.category !== "" && data.category !== "default" ? "pointer" : "default",
}}
variant="rounded"
>
{iconDetails?.originalIcon ?
iconDetails?.originalIcon
:
avatarLetter
}
)
}
},
{
field: 'actions',
headerName: 'Actions',
description: 'Actions for this key.',
width: 175,
filterable: false,
sortable: false,
renderCell: (props) => {
const data = props.row
return (
{data?.workflow_id === "" || data?.workflow_id === null || data?.workflow_id === undefined || data?.workflow_id?.length !== 36 ?
: (
)}
{
e.preventDefault()
e.stopPropagation()
// Try to make the value JSON indented
const valid = validateJson(data.value)
var newvalue = data.value
if (valid.valid) {
// JSON stringify with indentation
newvalue = JSON.stringify(valid.result, null, 2)
}
setEditCache(true)
setDataValue({
"key": data.key,
"value": newvalue,
"edited": data.edited,
"created": data.created,
"workflow_id": data.workflow_id,
"category": data.category,
"tags": data.tags,
})
setValue(newvalue)
setModalOpen(true)
}}
>
{
if (userdata?.support !== true) {
toast.warn("Correlation graphs are a beta feature.")
}
toast.info("Opening correlations window")
// add the key we are correlating
const currentParams = new URLSearchParams(window.location.search);
currentParams.set("category", selectedCategory);
currentParams.set("correlations", "true");
currentParams.set("key", data.key);
navigate(`${window.location.pathname}?${currentParams.toString()}`);
setShowCorrelations(data)
}}
>
{
e.preventDefault()
e.stopPropagation()
window.open(`${globalUrl}/api/v1/orgs/${orgId}/cache/${data.key}?type=text&authorization=${data.public_authorization}`, "_blank");
}}
>
{
e.preventDefault()
e.stopPropagation()
deleteEntry(orgId, data.key, data.category)
}}
>
)
}
},
{
field: 'distribution',
headerName: 'Distribution',
description: 'Controls whether this key is distributed to sub-organizations.',
width: 100,
filterable: false,
sortable: false,
renderCell: (props) => {
const data = props.row
const isDistributed = data?.suborg_distribution?.length > 0 ? true : false;
return (
{selectedOrganization.id !== undefined && data?.org_id !== selectedOrganization.id ?
}
style={{display: "table-cell", textAlign: 'center', verticalAlign: 'middle', }}
/>
:
{
setShowDistributionPopup(true)
if(data?.suborg_distribution?.length > 0){
setSelectedSubOrg(data.suborg_distribution)
}else{
setSelectedSubOrg([])
}
setSelectedCacheKey(data.key)
}}
/>
}
style={{display: "table-cell", textAlign: 'center', verticalAlign: 'middle', }}
/>
}
)
}
},
];
var previousgroup = ""
const isAutomating = categoryAutomations?.find((automation) => automation.enabled) !== undefined
return (
{modalView}
{showCorrelations !== undefined ?
: null}
{cacheDistributionModal}
Shuffle Datastore
{userdata?.support === true ?
: null}
Datastore is a permanent key-value database for storing data which can be used for automation.
Learn more
{selectedCategory === "protected" ?
Protected keys are encrypted, only available to admins, and will be masked when used in workflows. If you want unreadable secrets, use
App Auth.
: null}
{datastoreCategories !== undefined &&
datastoreCategories !== null &&
datastoreCategories.length > 1 ? (
{
const fixedname = data?.charAt(0)?.toUpperCase() + data?.slice(1)?.replaceAll("_", " ")
return fixedname
}}
groupBy={(data) => {
if (!data.includes("_")) {
return undefined
}
const firstword = data.split("_")[0]
if (datastoreCategoryGroups.includes(firstword)) {
return firstword.charAt(0).toUpperCase() + firstword.slice(1)
}
return undefined
}}
renderInput={(params) => {
return (
)
}}
renderOption={(props, data, state) => {
const fixedname = data?.charAt(0)?.toUpperCase() + data?.slice(1)?.replaceAll("_", " ")
const iconDetails = GetIconInfo({
"app_name": fixedname,
"name": fixedname,
})
return (
)
}}
/>
{/*
{datastoreCategories.map((data, index) => {
var addition = ""
if (data.includes("_")) {
const firstword = data.split("_")[0]
if (firstword !== previousgroup) {
if (datastoreCategoryGroups.includes(firstword)) {
previousgroup = firstword
addition =
{firstword.charAt(0).toUpperCase() + firstword.slice(1)}
} else {
if (previousgroup !== "") {
addition =
previousgroup = ""
}
}
}
} else {
if (previousgroup !== "") {
addition =
previousgroup = ""
}
}
// Should find the icon for things
// Fix uppercase at start of words
const fixedname = data?.charAt(0)?.toUpperCase() + data?.slice(1)?.replaceAll("_", " ")
const iconDetails = GetIconInfo({
"app_name": fixedname,
"name": fixedname,
})
return (
);
})}
*/}
) : null}
{renderTextBox ?
:
}
{renderTextBox &&
{
handleKeyDown(event);
// Check value of the field
const foundValue = event.target.value.trim();
if(event.key === 'Enter' && foundValue?.length > 0){
setUpdateToThisCategory(event.target.value)
listOrgCache(orgId, event.target.value, 0, pageSize, 0)
setPage(0)
}
}}
style={{
height: 40,
width: 200,
marginTop: 0,
}}
InputProps={{
style: {
color: theme.palette.textFieldStyle.color,
height: 40,
fontSize: 16,
borderRadius: 4,
paddingTop: 0,
},
}}
id=""
color="primary"
placeholder="Category name"
required
margin="dense"
defaultValue={""}
autoFocus
/>}
{showAutomationMenu || showSettingsMenu ?
: null}
{isSelectedDataStore? null :
}
{
setSelectedRows(newSelection);
}}
onRowSelectionModelChange={(newSelection) => {
setSelectedRows(newSelection);
}}
keepNonExistentRowsSelected={false}
getRowId={(row) => `${row?.key}_${row?.category}`}
autoHeight={true}
sx={{
marginTop: 1,
height: listCache.length*52+500,
width: "100%",
'.MuiTablePagination-selectLabel, .MuiTablePagination-select, .MuiTablePagination-selectIcon': {
display: 'none',
},
marginBottom: 20,
}}
loading={cachedLoaded === false}
pagination
paginationMode="server"
page={page}
rowCount={totalAmount}
onPageChange={(newPage, second) => {
listOrgCache(orgId, selectedCategory, 0, pageSize, newPage)
setPage(newPage)
}}
onPageSizeChange={(newSize) => {
setPageSize(newSize);
setPage(0)
setSelectedRows([])
setCursors({
0: "",
})
}}
filterMode="client"
onFilterModelChange={(model) => {
// Specific search for the key itself to find it fast across the index
if (model?.items?.length === 1) {
if (model?.items[0]?.operatorValue === "equals" && model?.items[0]?.columnField === "key") {
// Run backend search for a specific key
listOrgCache(orgId, selectedCategory, 0, pageSize, page, model?.items[0]?.value)
}
}
}}
getRowHeight={() => {
return "auto"
}}
hideFooterSelectedRowCount={true}
hideFooter={true}
/>
{page * pageSize + 1} - {Math.min((page + 1) * pageSize, totalAmount)} of {totalAmount}
{
var disabled = false
if (item?.type === "page") {
if (cursors[item.page-1] === undefined) {
disabled = true
}
}
if (item?.type === "previous") {
disabled = page === 0
}
if (cachedLoaded === false) {
disabled = true
}
return (
)
}}
onChange={(e, value) => {
if (value < 1) {
return
}
const newPage = value-1
listOrgCache(orgId, selectedCategory, 0, pageSize, newPage)
setPage(newPage)
}}
/>
{selectedRows.length > 0 ?
: null}
);
})
//export default CacheView;
export default memo(CacheView);