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 { GetIconInfo, } from "../views/Workflows2.jsx"; import { validateJson, handleReactJsonClipboard, } from "../views/Workflows.jsx"; import { red } from "../views/AngularWorkflow.jsx"; import CollectIngestModal from "../components/CollectIngestModal.jsx"; 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, } from "@mui/material"; import { DataGrid, GridColDef, } 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, Polymer as PolymerIcon, CheckCircle as CheckCircleIcon, Close as CloseIcon, Apps as AppsIcon, Image as ImageIcon, Cached as CachedIcon, AccessibilityNew as AccessibilityNewIcon, Lock as LockIcon, Eco as EcoIcon, Schedule as ScheduleIcon, Cloud as CloudIcon, Business as BusinessIcon, Visibility as VisibilityIcon, VisibilityOff as VisibilityOffIcon, Clear as ClearIcon, Add as AddIcon, 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, } 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 [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); 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": "Run workflow", "description": "Runs a workflow with the updated value.", "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": "Run AI Agent", "description": "", "options": [{ "key": "", "value": "", }], "icon": , "enabled": false, "disabled": true, }, { "name": "Send webhook", "description": "Sends the updated value to a specified webhook URL.", "options": [{ "key": "webhook_url", "value": "", }], "icon": , "enabled": false, }, { "name": "Send message", "description": "", "type": "singul", "options": [{ "key": "app", "value": "", }], "icon": , "disabled": true, "enabled": false, }, { "name": "Enrich", "description": "", "type": "singul", "options": [{ "key": "", "value": "", }], "icon": "/images/logos/singul.svg", "enabled": false, "disabled": true, }, ] 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) const categoryParam = urlParams.get("category") if (categoryParam && categoryParam !== undefined && categoryParam !== "default" && categoryParam !== "") { chosenCategory = categoryParam setSelectedCategory(categoryParam) } listOrgCache(orgId, chosenCategory, 0, pageSize, page) }, []) 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]); } } } setDatastoreCategories(newcategories) } 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].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) => { console.log(`Deleting entry for orgId: ${orgId}, key: ${key}, category: ${itemCategory}`); 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, } 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]); fetch(globalUrl + `/api/v1/orgs/${orgId}/set_cache`, { 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), { setModalOpen(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, }, }, }} > { editCache ? "Edit Key" : "Add Key"}{selectedCategory === "" || selectedCategory === "default" ? "" : ` in category '${selectedCategory}'`}
Key setKey(e.target.value)} />
Value - ({isValidJson.valid === true ? "Valid" : "Invalid"} JSON) { autoFixJson(value) }} >
setValue(e.target.value)} /> {editCache ?
Created: {timestamp(dataValue?.created)} Edited: {timestamp(dataValue?.edited)} {dataValue?.workflow_id !== "" ? Workflow: {dataValue.workflow_id} : null} {dataValue?.category !== "" && dataValue?.category !== "default" ? Category: {dataValue.category} : null}
: null}
); 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 ? ( setShowDistributionPopup(false)} PaperProps={{ sx: { borderRadius: theme?.palette?.DialogStyle?.borderRadius, border: theme?.palette?.DialogStyle?.border, fontFamily: theme?.typography?.fontFamily, backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, zIndex: 1000, minWidth: "600px", minHeight: "320px", overflow: "auto", '& .MuiDialogContent-root': { backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, }, '& .MuiDialogTitle-root': { backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, }, '& .MuiDialogActions-root': { backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, }, }, }} > Select sub-org to distribute Datastore key {handleSelectSubOrg(null, "none")}}>None {handleSelectSubOrg(null, "all")}}>All {userdata.orgs.map((data, index) => { if (data.creator_org !== userdata.active_org.id) { return null; } const imagesize = 22; const imageStyle = { width: imagesize, height: imagesize, pointerEvents: "none", marginRight: 10, marginLeft: data.id === userdata.active_org.id ? 0 : 20, }; const image = data.image === "" ? ( {data.name} ) : ( {data.name} ); return ( handleSelectSubOrg(data.id)} style={{ display: "flex", alignItems: "center" }} > {image} {data.name} ); })}
) : 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 (
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 } setShowOptions(!showOptions) // Auto saves when the options are closed/saved if (updated && showOptions) { runSave() } }} > {typeof automation?.icon === "string" && automation?.icon?.length > 0 ? {automation.name} : automation.icon } {automation.name}
{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 ? {data.name} : null} Choose {data} } >
{iconDetails?.originalIcon && ( iconDetails?.originalIcon )}
{fixedname}
) }} 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 ? {data.name} : null} Choose {data.name} } > {data.name} ) }} renderInput={(params) => { return ( ) }} /> ) } 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 columns: GridColDef<(typeof rows)[number]>[] = [ { field: 'key', headerName: 'Key', width: 200, filterable: true, sortable: true, }, { 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, }) setValue(newvalue) setModalOpen(true) }} > { 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', }} /> }
) } }, ]; const isAutomating = categoryAutomations?.find((automation) => automation.enabled) !== undefined return (
{modalView} {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 ? ( Category ) : 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: 35, width: 200, marginTop: 0, }} InputProps={{ style: { color: theme.palette.textFieldStyle.color, height: 35, fontSize: 16, borderRadius: 4, paddingTop: 0, }, }} id="" color="primary" placeholder="Category name" required margin="dense" defaultValue={""} autoFocus />}
{showAutomationMenu || showSettingsMenu ? { setShowAutomationMenu(false) setShowSettingsMenu(false) }} PaperProps={{ sx: { borderRadius: theme?.palette?.DialogStyle?.borderRadius, border: theme?.palette?.DialogStyle?.border, minWidth: 500, minHeight: 700, 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, }, }, }} > {showSettingsMenu === true ?
Settings for category '{selectedCategory}'
Timeout You can set a timeout for the category. This will delete all keys in this category after the specified time. Timeout is in seconds and based on last EDITED time.
{ // Check if it's a number or not var timeoutValue = 0 if (isNaN(e.target.value) || e.target.value === "" || e.target.value === null) { toast.info("Timeout must be a number. Setting to 0.") } else { timeoutValue = parseInt(e.target.value, 10) if (timeoutValue < 60) { toast.info("Timeout must be between 60 seconds or more. Setting to 0.") } if (timeoutValue === categoryConfig?.settings?.timeout) { return } } setCategorySettingsField("timeout", timeoutValue) }} />
{categoryConfig?.settings?.public === true ? "" : "NOT"} Public This will make the url for this category public. Metadata will be cleared, except for timestamps. Types: keys,ndjson,csv,values,json,meta
URL (when public):
{globalUrl}/api/v2/datastore/category/{selectedCategory}?top=10000&type=keys&org_id={orgId}
{ setCategorySettingsField("public", e.target.checked) setUpdate(Math.random()) // Force update to re-render the component }} style={{marginTop: 10, }} color="secondary" />
Subscribing Enabling this feature will allow other organizations to subscribe to this category. This is NOT fully available yet.
:
Automation for category '{selectedCategory}' When A key is edited Do {categoryAutomations.map((automation, index) => { return ( ) })}
}
: null}
{isSelectedDataStore? null :} { setSelectedRows(newSelection); }} onRowSelectionModelChange={(newSelection) => { setSelectedRows(newSelection); }} keepNonExistentRowsSelected={false} getRowId={(row) => row?.category ? `${row.key}_${row.category}` : row.key} 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);