Merge branch 'main' into nightly

This commit is contained in:
Yash Singh
2026-02-10 18:32:12 +05:30
committed by GitHub
18 changed files with 194 additions and 193 deletions
+4 -4
View File
@@ -20,19 +20,19 @@ jobs:
include: include:
- app: frontend - app: frontend
path: frontend path: frontend
version: 2.1.2 version: 2.1.3
experimental: true experimental: true
- app: backend - app: backend
path: backend path: backend
version: 2.1.2 version: 2.1.3
experimental: true experimental: true
- app: orborus - app: orborus
path: functions/onprem/orborus path: functions/onprem/orborus
version: 2.1.2 version: 2.1.3
experimental: true experimental: true
- app: worker - app: worker
path: functions/onprem/worker path: functions/onprem/worker
version: 2.1.2 version: 2.1.3
experimental: true experimental: true
steps: steps:
- name: Checkout - name: Checkout
+1 -1
View File
@@ -1129,7 +1129,7 @@ const AppFramework = (props) => {
}, [newSelectedApp]) }, [newSelectedApp])
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (import.meta.env.VITE_IS_SSR === "true"); const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
const imgSize = 50; const imgSize = 50;
var parsedFrameworkData = frameworkData === undefined ? {} : frameworkData var parsedFrameworkData = frameworkData === undefined ? {} : frameworkData
+2 -1
View File
@@ -64,7 +64,8 @@ const AppSelection = props => {
document.title = "Choose your apps" document.title = "Choose your apps"
const ref = useRef() const ref = useRef()
let navigate = useNavigate(); let navigate = useNavigate();
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (import.meta.env.VITE_IS_SSR === "true");
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
useEffect(() => { useEffect(() => {
if (newSelectedApp === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID.length === 0) { if (newSelectedApp === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID.length === 0) {
+104 -149
View File
@@ -43,6 +43,8 @@ const ChatBot = (props) => {
const [appname, setAppname] = useState(""); const [appname, setAppname] = useState("");
const [threadId, setThreadId] = useState(""); const [threadId, setThreadId] = useState("");
const [runId, setRunId] = useState(""); const [runId, setRunId] = useState("");
const [responseId, setResponseId] = useState("");
const [conversationId, setConversationId] = useState("");
// New state for thread management // New state for thread management
const [isLoadingThread, setIsLoadingThread] = useState(false); const [isLoadingThread, setIsLoadingThread] = useState(false);
@@ -54,7 +56,7 @@ const ChatBot = (props) => {
const [showAppSearch, setShowAppSearch] = useState(false); const [showAppSearch, setShowAppSearch] = useState(false);
// Get thread ID from URL params // Get thread ID from URL params
const { threadId: urlThreadId } = useParams(); const { conversationId: urlConversationId } = useParams();
let navigate = useNavigate(); let navigate = useNavigate();
const waitingMsg = "Processing..." const waitingMsg = "Processing..."
@@ -93,24 +95,24 @@ const ChatBot = (props) => {
} }
}, [appname]) }, [appname])
// Load existing thread if threadId is in URL // Load existing conversation if conversationId is in URL
useEffect(() => { useEffect(() => {
if (urlThreadId && urlThreadId !== threadId) { if (urlConversationId && urlConversationId !== conversationId) {
loadExistingThread(urlThreadId); loadExistingThread(urlConversationId);
} }
}, [urlThreadId]); }, [urlConversationId]);
const loadExistingThread = (threadIdToLoad) => { const loadExistingThread = (conversationIdToLoad) => {
setIsLoadingThread(true); setIsLoadingThread(true);
setThreadError(""); setThreadError("");
fetch(`${globalUrl}/api/v1/conversation/thread`, { fetch(`${globalUrl}/api/v1/conversation/history`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
credentials: "include", credentials: "include",
body: JSON.stringify({ thread_id: threadIdToLoad }), body: JSON.stringify({ conversation_id: conversationIdToLoad }),
}) })
.then((response) => { .then((response) => {
if (response.status === 403) { if (response.status === 403) {
@@ -138,22 +140,22 @@ const ChatBot = (props) => {
return; return;
} }
// Set thread data - this is crucial for continuing the conversation // Set conversation data - this is crucial for continuing the conversation
setThreadId(data.thread_id); setConversationId(data.conversation_id);
console.log("Loaded existing thread:", data.thread_id);
// Transform API message format to UI format // Transform API message format to UI format
const transformedMessages = (data.messages || []).map((msg, index) => ({ const transformedMessages = (data.messages || []).map((msg, index) => ({
id: `${data.thread_id}_${index}`, id: `${data.conversation_id}_${index}`,
status: msg.role === "user" ? "sent" : "received", status: msg.role === "user" ? "sent" : "received",
message: msg.content, message: msg.content,
timestamp: msg.timestamp timestamp: msg.timestamp
})); }));
setMessages(transformedMessages); setMessages(transformedMessages);
setThreadOrgId(data.thread_org_id); setThreadOrgId(data.org_id);
setIsActiveOrg(data.is_active_org); setIsActiveOrg(data.is_active_org);
// Disable chat if not in active org
if (!data.is_active_org) { if (!data.is_active_org) {
setChatDisabled(true); setChatDisabled(true);
} else { } else {
@@ -260,12 +262,9 @@ const ChatBot = (props) => {
const sentId = uuidv4(); const sentId = uuidv4();
var parsedData = { var parsedData = {
"query": inputmsg, "query": inputmsg,
"thread_id": threadId, "conversation_id": conversationId,
"run_id": runId,
} }
console.log("Sending message with thread_id:", threadId);
if (appname !== undefined && appname !== null && appname !== "") { if (appname !== undefined && appname !== null && appname !== "") {
parsedData["app_name"] = appname parsedData["app_name"] = appname
} }
@@ -315,169 +314,125 @@ const ChatBot = (props) => {
setMessages(newmessages); setMessages(newmessages);
//fetch(`http://localhost:8080/api/v1/conversation`, { // Use streaming endpoint
fetch(`${globalUrl}/api/v1/conversation`, { fetch(`${globalUrl}/api/v1/conversation/stream`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"Accept": "text/event-stream",
}, },
credentials: "include", credentials: "include",
body: JSON.stringify(parsedData), body: JSON.stringify(parsedData),
}) })
.then((res) => res.text()) .then(async (response) => {
.then((resText) => { if (!response.ok) {
setLoading(false) const errorText = await response.text();
var data = {} throw new Error(`Server returned status ${response.status}: ${errorText}`);
// JSON parse
try {
data = JSON.parse(resText);
} catch (e) {
console.log("Error parsing response as JSON: ", e);
newmessages = newmessages.filter((msg) => msg.message !== waitingMsg);
newmessages.push({
"status": "received",
"message": resText,
"id": uuidv4(),
});
setMessages(newmessages);
return;
} }
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let streamedText = "";
let newConversationId = "";
if (data.run_id !== undefined && data.run_id !== null && data.run_id !== "") { // Keep waiting message until first chunk arrives
setRunId(data.run_id) const streamMessageId = uuidv4();
} let firstChunkReceived = false;
if (data.thread_id !== undefined && data.thread_id !== null && data.thread_id !== "") { while (true) {
setThreadId(data.thread_id) const { done, value } = await reader.read();
// Update URL if this is a new thread (not already in URL) if (done) {
if (!urlThreadId && data.thread_id !== threadId) { setLoading(false);
console.log("Navigating to new thread URL:", data.thread_id); break;
navigate(`/chat/${data.thread_id}`, { replace: true });
} }
}
if (data.success === undefined) { buffer += decoder.decode(value, { stream: true });
newmessages = newmessages.filter((msg) => msg.message !== waitingMsg); const lines = buffer.split('\n');
newmessages.push({ buffer = lines.pop() || ""; // Keep incomplete line in buffer
"status": "received",
"message": resText,
"id": uuidv4(),
});
setMessages(newmessages); for (const line of lines) {
if (line.startsWith('data: ')) {
const jsonStr = line.replace('data: ', '');
try {
const data = JSON.parse(jsonStr);
const eventType = data.type;
return; if (eventType === "chunk") {
} // On first chunk, remove waiting message and add streaming message
if (!firstChunkReceived) {
newmessages = newmessages.filter((msg) => msg.message !== waitingMsg);
newmessages.push({
"id": streamMessageId,
"status": "received",
"message": "",
});
firstChunkReceived = true;
}
// authentication for app // Append chunk to streamed text
// app validation (choose one) streamedText += data.chunk || "";
const defaultMessage = `Default output. The feature you're interacting with may not have been implemented yet. Contact ${supportEmail} with a screenshot of this and your input please.`
// Update the message with accumulated text
var outputmessage = defaultMessage; newmessages = newmessages.map(msg =>
var status = "received"; msg.id === streamMessageId
var action = "" ? { ...msg, message: streamedText }
if (data.success === false) { : msg
if (data.reason !== undefined) { );
outputmessage = data.reason setMessages([...newmessages]);
}
status = "error" } else if (eventType === "done") {
} else { // Capture conversation ID for next turn
if (data.reason !== undefined) { newConversationId = data.data;
outputmessage = data.reason if (newConversationId) {
} setConversationId(newConversationId);
}
// Update URL if this is a new conversation
if (!urlConversationId && newConversationId !== conversationId) {
navigate(`/chat/${newConversationId}`, { replace: true });
}
}
if (data.action !== undefined) { } else if (eventType === "error") {
//console.log("Action is defined: ", data.action); // Handle error event
action = data.action const errorMsg = data.data || "An error occurred";
console.error("[Stream Error]:", errorMsg);
if (data.action === "app_authentication") {
// If success & app auth -> say auth success and show available labels newmessages = newmessages.map(msg =>
// If !success & app auth -> do authentication msg.id === streamMessageId
if (data.success === true) { ? { ...msg, status: "error", message: errorMsg }
newmessages = newmessages.filter((msg) => msg.message !== waitingMsg); : msg
// No action for this. );
// "action": action, setMessages([...newmessages]);
var appname = "" setLoading(false);
if (data.apps !== undefined && data.apps !== null && data.apps.length > 0) { return;
appname = data.apps[0].name.replaceAll("_", " ")
}
var outputmessage = `**Please specify which ${appname} action you want to use**: \n`
if (data.available_labels !== undefined && data.available_labels !== null && data.available_labels.length > 0) {
for (var i = 0; i < data.available_labels.length; i++) {
outputmessage += "* " + data.available_labels[i] + "\n"
} }
outputmessage += "* Reauthenticate ([see auth](/admin?tab=app_auth))"
}
//Some opavailable actions: " + data.apps.map((app) => app.name).join(", ")
const parsedmessage = {
"status": status,
"message": outputmessage,
"id": uuidv4(),
"category": data.category,
"thread_id": data.thread_id, } catch (e) {
"run_id": data.run_id, console.error("[ERROR] Failed to parse SSE data:", e, jsonStr);
}
newmessages.push(parsedmessage);
setMessages(newmessages);
return
} else {
if (data.apps !== undefined) {
setInputAuth(data.apps)
setMessage(inputmsg);
setForceReauthentication(true)
} }
} }
} else if (data.action === "select_category" || data.action === "select_app") {
console.log("[DEBUG] APP SELECTION! Should help them choose an app to use")
// Show a search field
setShowAppSearch(true)
} }
} }
newmessages = newmessages.filter((msg) => msg.message !== waitingMsg); setLoading(false);
const parsedmessage = {
"status": status,
"message": outputmessage,
"id": uuidv4(),
"action": action,
"category": data.category,
"thread_id": data.thread_id,
"run_id": data.run_id,
}
newmessages.push(parsedmessage);
setMessages(newmessages);
console.log("New message: ", parsedmessage)
}) })
.catch((err) => { .catch((err) => {
setLoading(false) setLoading(false)
console.log("Problem: ", err); console.log("Problem: ", err);
setMessage(message); setMessage(inputmsg);
newmessages = newmessages.filter((msg) => msg.message !== waitingMsg); newmessages = newmessages.filter((msg) => msg.message !== waitingMsg);
// Find the message with the sentId and change the status to error // Add error message
newmessages.push({ newmessages.push({
"status": "error", "status": "error",
"message": message, "message": inputmsg,
"error_message": "Failed to send: "+err, "error_message": "Failed to send: "+err.message,
"id": sentId, "id": sentId,
}); });
setMessages(newmessages); setMessages([...newmessages]);
}); });
}; };
@@ -654,21 +609,21 @@ const ChatBot = (props) => {
<Card style={{backgroundColor: theme.palette.surfaceColor, borderRadius: theme.palette?.borderRadius,}}> <Card style={{backgroundColor: theme.palette.surfaceColor, borderRadius: theme.palette?.borderRadius,}}>
<CardContent> <CardContent>
<Typography variant="h6"> <Typography variant="h6">
How many incidents did we get last week? What is the difference between a trigger and an action?
</Typography> </Typography>
</CardContent> </CardContent>
</Card> </Card>
<Card style={{backgroundColor: theme.palette.surfaceColor, borderRadius: theme.palette?.borderRadius, marginTop: 10, }}> <Card style={{backgroundColor: theme.palette.surfaceColor, borderRadius: theme.palette?.borderRadius, marginTop: 10, }}>
<CardContent> <CardContent>
<Typography variant="h6"> <Typography variant="h6">
Answer the last email from Jim about the new project, and say we're on it How do I create a new workflow?
</Typography> </Typography>
</CardContent> </CardContent>
</Card> </Card>
<Card style={{backgroundColor: theme.palette.surfaceColor, borderRadius: theme.palette?.borderRadius, marginTop: 10, }}> <Card style={{backgroundColor: theme.palette.surfaceColor, borderRadius: theme.palette?.borderRadius, marginTop: 10, }}>
<CardContent> <CardContent>
<Typography variant="h6"> <Typography variant="h6">
Is the IP 1.2.3.4 blocked? If not, block it. Is there a way to mass abort executions of a specific workflow?
</Typography> </Typography>
</CardContent> </CardContent>
</Card> </Card>
@@ -1006,9 +961,9 @@ const ChatBot = (props) => {
padding: "0 20px" padding: "0 20px"
}}> }}>
{[ {[
"How many incidents did we get last week?", "What is the difference between a trigger and an action?",
"Answer the last email from Jim about the new project", "How do I create a new workflow?",
"Is the IP 1.2.3.4 blocked? If not, block it." "Is there a way to mass abort executions of a specific workflow?"
].map((sample, index) => ( ].map((sample, index) => (
<Card key={index} style={{ <Card key={index} style={{
backgroundColor: theme.palette.surfaceColor, backgroundColor: theme.palette.surfaceColor,
+1 -1
View File
@@ -30,7 +30,7 @@ const EditOrgTab = (props) => {
const [organizationFeatures, setOrganizationFeatures] = React.useState({}); const [organizationFeatures, setOrganizationFeatures] = React.useState({});
const [users, setUsers] = React.useState([]); const [users, setUsers] = React.useState([]);
const [orgRequest, setOrgRequest] = React.useState(true); const [orgRequest, setOrgRequest] = React.useState(true);
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (import.meta.env.VITE_IS_SSR === "true"); const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
useEffect(() => { useEffect(() => {
if(users.length === 0) { if(users.length === 0) {
getUsers(); getUsers();
+1 -1
View File
@@ -633,7 +633,7 @@ const EditWorkflow = (props) => {
<AutoAwesomeIcon style={{ marginRight: 8 }} /> <AutoAwesomeIcon style={{ marginRight: 8 }} />
)} )}
AI Generate AI Generate (beta)
</Button> </Button>
</span> </span>
</Tooltip> </Tooltip>
+1 -1
View File
@@ -120,7 +120,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
setCurrentSelectedTheme(userdata?.theme); setCurrentSelectedTheme(userdata?.theme);
} }
}, [userdata]); }, [userdata]);
const CustomPopper = (props) => { const CustomPopper = (props) => {
+14 -4
View File
@@ -1910,7 +1910,7 @@ const ParsedAction = (props) => {
<Tooltip <Tooltip
title={ title={
<Typography variant="body2" style={{margin: 3, }}> <Typography variant="body2" style={{margin: 3, }}>
Rerun this action with results from previous executions. Built for testing individual actions in the middle of workflows. Rerun this action with results from your previously selected workflow run. Allows testing individual action changes without rerunning the full workflow.
</Typography> </Typography>
} }
placement="top" placement="top"
@@ -1922,8 +1922,8 @@ const ParsedAction = (props) => {
marginTop: "auto", marginTop: "auto",
marginBottom: "auto", marginBottom: "auto",
height: 30, height: 30,
marginLeft: 98,
textTransform: "none", textTransform: "none",
marginLeft: 115,
}} }}
disabled={autoCompleting} disabled={autoCompleting}
onClick={() => { onClick={() => {
@@ -1934,7 +1934,7 @@ const ParsedAction = (props) => {
} }
}} }}
> >
<PlayArrowIcon style={{marginRight: 5, }}/> <CachedIcon style={{marginRight: 5, }}/>
Rerun Rerun
</Button> </Button>
</Tooltip> </Tooltip>
@@ -2813,6 +2813,8 @@ const ParsedAction = (props) => {
style: { style: {
backgroundColor: theme.palette.platformColor, backgroundColor: theme.palette.platformColor,
color: theme.palette.textColor, color: theme.palette.textColor,
border: "1px solid rgba(255,255,255,0.3)",
paddingRight: 20,
}, },
}} }}
filterOptions={(options, { inputValue }) => { filterOptions={(options, { inputValue }) => {
@@ -3091,6 +3093,14 @@ const ParsedAction = (props) => {
</div> </div>
{apps.map((app, appIndex) => { {apps.map((app, appIndex) => {
// Forces it into every category (for now)
// This is to make it possible to "use" shuffle for Singul natively
if (app.name === "Shuffle Tools") {
if (actionname == "Intel" || actionname == "Intel") {
app.categories = [actionname]
}
}
if (app.categories === undefined || app.categories === null || app.categories.length === 0) { if (app.categories === undefined || app.categories === null || app.categories.length === 0) {
return null return null
} }
@@ -3176,7 +3186,7 @@ const ParsedAction = (props) => {
marginRight: 5, marginRight: 5,
borderRadius: 5, borderRadius: 5,
cursor: "pointer", cursor: "pointer",
border: isAppSelected ? "3px solid #86c142" : "2px solid rgba(255,255,255,0.6)", border: isAppSelected ? "5px solid #86c142" : "2px solid rgba(255,255,255,0.6)",
}} /> }} />
</Tooltip> </Tooltip>
</div> </div>
+1 -1
View File
@@ -25,7 +25,7 @@ const Priority = (props) => {
const { globalUrl, clickedFromOrgTab,userdata, serverside, priority, checkLogin, setAdminTab, setCurTab, appFramework, } = props; const { globalUrl, clickedFromOrgTab,userdata, serverside, priority, checkLogin, setAdminTab, setCurTab, appFramework, } = props;
const { themeMode, supportEmail } = useContext(Context); const { themeMode, supportEmail } = useContext(Context);
const theme = getTheme(themeMode); const theme = getTheme(themeMode);
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (import.meta.env.VITE_IS_SSR === "true"); const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
let navigate = useNavigate(); let navigate = useNavigate();
if (window.location.pathname === "/workflows") { if (window.location.pathname === "/workflows") {
+1 -1
View File
@@ -77,7 +77,7 @@ const SearchData = props => {
// return null // return null
//} //}
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (import.meta.env.VITE_IS_SSR === "true"); const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
// if (window.location.pathname === "/docs" || window.location.pathname === "/apps" || window.location.pathname === "/usecases" ) { // if (window.location.pathname === "/docs" || window.location.pathname === "/apps" || window.location.pathname === "/usecases" ) {
// setModalOpen(false) // setModalOpen(false)
// } // }
+1 -1
View File
@@ -349,7 +349,7 @@ const UsecaseSearch = (props) => {
const [selectedAction, setSelectedAction] = React.useState({}); const [selectedAction, setSelectedAction] = React.useState({});
const [firstRequest, setFirstRequest] = React.useState(true); const [firstRequest, setFirstRequest] = React.useState(true);
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (import.meta.env.VITE_IS_SSR === "true"); const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
//const alert = useAlert() //const alert = useAlert()
useEffect(() => { useEffect(() => {
+1 -1
View File
@@ -161,7 +161,7 @@ const WelcomeForm = (props) => {
const [clickdiff, setclickdiff] = useState(0); const [clickdiff, setclickdiff] = useState(0);
const [mouseHoverIndex, setMouseHoverIndex] = useState(-1) const [mouseHoverIndex, setMouseHoverIndex] = useState(-1)
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (import.meta.env.VITE_IS_SSR === "true"); const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
//const alert = useAlert(); //const alert = useAlert();
let navigate = useNavigate(); let navigate = useNavigate();
+1 -1
View File
@@ -29,7 +29,7 @@ const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b
const AppGrid = props => { const AppGrid = props => {
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, alternativeView, onlyResults, inputsearch } = props const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, alternativeView, onlyResults, inputsearch } = props
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (import.meta.env.VITE_IS_SSR === "true"); const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows
const xs = parsedXs === undefined || parsedXs === null ? isMobile ? 6 : 4 : parsedXs const xs = parsedXs === undefined || parsedXs === null ? isMobile ? 6 : 4 : parsedXs
//const [apps, setApps] = React.useState([]); //const [apps, setApps] = React.useState([]);
@@ -47,7 +47,7 @@ const WorkflowTemplatePopup = (props) => {
const [requestSent, setRequestSent] = React.useState(false) const [requestSent, setRequestSent] = React.useState(false)
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (import.meta.env.VITE_IS_SSR === "true"); const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
let navigate = useNavigate(); let navigate = useNavigate();
useEffect(() => { useEffect(() => {
if (modalOpen !== true) { if (modalOpen !== true) {
+50 -17
View File
@@ -62,7 +62,6 @@ const dividerColor = "rgb(225, 228, 232)";
const hrefStyle = { const hrefStyle = {
color: "rgba(255, 255, 255, 0.8)", color: "rgba(255, 255, 255, 0.8)",
textDecoration: "none", textDecoration: "none",
marginRight: window.location.pathname.includes("/articles/") ? "0.8em" : undefined,
}; };
@@ -387,6 +386,7 @@ export const CodeHandler = (props) => {
minWidth: "50%", minWidth: "50%",
maxWidth: "100%", maxWidth: "100%",
backgroundColor: theme.palette.inputColor, backgroundColor: theme.palette.inputColor,
whiteSpace: "pre-wrap",
overflowY: "auto", overflowY: "auto",
// Have it inline // Have it inline
borderRadius: theme.palette?.borderRadius, borderRadius: theme.palette?.borderRadius,
@@ -1020,7 +1020,7 @@ const Docs = (defaultprops) => {
} }
const fetchDocList = (resetCache = false) => { const fetchDocList = (resetCache = false) => {
var url = `${globalUrl}/api/v1/docs` var url = `${globalUrl}/api/v1/docs?resetCache=${resetCache}`
if (location.pathname.includes("/legal")) { if (location.pathname.includes("/legal")) {
url = `${globalUrl}/api/v1/docs?folder=legal&resetCache=${resetCache}` url = `${globalUrl}/api/v1/docs?folder=legal&resetCache=${resetCache}`
} else if (location.pathname.includes("/articles")) { } else if (location.pathname.includes("/articles")) {
@@ -1047,16 +1047,16 @@ const Docs = (defaultprops) => {
.catch((error) => { }); .catch((error) => { });
}; };
const fetchDoc = (docId) => { const fetchDoc = (docId, resetCache = false) => {
if (docId === undefined) { if (docId === undefined) {
return return
} }
var url = `${globalUrl}/api/v1/docs/${docId}` var url = `${globalUrl}/api/v1/docs/${docId}?resetCache=${resetCache}`
if (location.pathname.includes("/legal")) { if (location.pathname.includes("/legal")) {
url = `${globalUrl}/api/v1/docs/${docId}?folder=legal` url = `${globalUrl}/api/v1/docs/${docId}?folder=legal&resetCache=${resetCache}`
} else if (location.pathname.includes("/articles")) { } else if (location.pathname.includes("/articles")) {
url = `${globalUrl}/api/v1/docs/${docId}?folder=articles` url = `${globalUrl}/api/v1/docs/${docId}?folder=articles&resetCache=${resetCache}`
} }
fetch(url, { fetch(url, {
@@ -1082,7 +1082,7 @@ const Docs = (defaultprops) => {
autoClose: 2000, autoClose: 2000,
}); });
setTimeout(() => { setTimeout(() => {
navigate(`/articles/2.0_release`) navigate(`/articles`)
}, 2000) }, 2000)
return return
} }
@@ -1130,7 +1130,8 @@ const Docs = (defaultprops) => {
const handleResetCache = () => { const handleResetCache = () => {
fetchDocList(true); fetchDocList(true);
toast("Cache has been reset"); fetchDoc(props.match.params.key, true);
toast("Cache and list will be reset in a few seconds");
} }
if (firstrequest) { if (firstrequest) {
@@ -1343,6 +1344,21 @@ const Docs = (defaultprops) => {
tr: TableRowRenderer, tr: TableRowRenderer,
th: TableHeaderCellRenderer, th: TableHeaderCellRenderer,
td: TableCellRenderer, td: TableCellRenderer,
ul: ({ children }) => (
<Box component="ul" sx={{ my: 0.5, fontFamily: '"Segoe UI", Inter, sans-serif', fontSize: "17.6px" }}>
{children}
</Box>
),
ol: ({ children }) => (
<Box component="ol" sx={{ my: 0.5, fontFamily: '"Segoe UI", Inter, sans-serif', fontSize: "17.6px" }}>
{children}
</Box>
),
li: ({ children }) => (
<Typography component="li" sx={{ my: 0.5, color: "inherit", fontFamily: '"Segoe UI", Inter, sans-serif', fontSize: "17.6px" }}>
{children}
</Typography>
),
} }
@@ -1354,7 +1370,6 @@ const Docs = (defaultprops) => {
const activeListItemStyle = { const activeListItemStyle = {
backgroundColor: "rgba(248, 106, 62, 0.08)", // Slight orange tint backgroundColor: "rgba(248, 106, 62, 0.08)", // Slight orange tint
marginRight: window.location.pathname.includes("/articles/") ? "0.8em" : undefined,
borderLeft: "3px solid #f86a3e", borderLeft: "3px solid #f86a3e",
paddingLeft: "13px", // Compensate for the border paddingLeft: "13px", // Compensate for the border
}; };
@@ -1414,6 +1429,7 @@ const Docs = (defaultprops) => {
<List style={{ <List style={{
listStyle: "none", listStyle: "none",
paddingLeft: "0", paddingLeft: "0",
paddingRight: isArticlePage ? 15 : undefined,
paddingTop: !isArticlePage ? "60px" : undefined, paddingTop: !isArticlePage ? "60px" : undefined,
paddingBottom: !isArticlePage ? "30px" : undefined, paddingBottom: !isArticlePage ? "30px" : undefined,
display: isArticlePage ? sidebarOpen ? "block" : "none" : undefined, display: isArticlePage ? sidebarOpen ? "block" : "none" : undefined,
@@ -1523,15 +1539,32 @@ const Docs = (defaultprops) => {
} }
</div> </div>
<div style={IndexBar}> <div style={IndexBar}>
{userdata?.support && isArticlePage && ( {userdata?.support && (
<Button <Tooltip
variant="contained" title="Wait about 5 minutes after updating the file on GitHub, then click to reset the cache. (Support only) "
color="secondary" placement="top"
onClick={handleResetCache} componentsProps={{
style={{marginBottom: "15px"}} tooltip: {
sx: {
backgroundColor: "rgba(33, 33, 33, 1)",
color: "rgba(241, 241, 241, 1)",
fontSize: 14,
border: "1px solid rgba(73, 73, 73, 1)",
fontFamily: theme?.typography?.fontFamily,
}
},
}}
arrow
> >
Reset Cache <Button
</Button> variant="contained"
color="secondary"
onClick={handleResetCache}
style={{marginBottom: "15px"}}
>
Reset Cache
</Button>
</Tooltip>
)} )}
{tocLines.length > 0 ? {tocLines.length > 0 ?
( (
+8 -6
View File
@@ -109,8 +109,8 @@ const NewDashboard = (props) => {
const STATIC_TIME_PERCENT = 'TBD' const STATIC_TIME_PERCENT = 'TBD'
const STATIC_MONEY_PERCENT = 'TBD' const STATIC_MONEY_PERCENT = 'TBD'
const kpis = [ const kpis = [
{ value: timeFmt.display, title: timeFmt.title, label: 'Time saved', icon: <TrendingUpIcon sx={{ color: '#5cc879', fontSize: 34 }} />, percentage: STATIC_TIME_PERCENT, color: '#5cc879', disabled: true}, //{ value: timeFmt.display, title: timeFmt.title, label: 'Time saved', icon: <TrendingUpIcon sx={{ color: '#5cc879', fontSize: 34 }} />, percentage: STATIC_TIME_PERCENT, color: '#5cc879', disabled: true},
{ value: formatCurrencyCompact(totals.moneySavedDollars), label: 'Money saved', icon: <TrendingUpIcon sx={{ color: '#5cc879', fontSize: 34 }} />, percentage: STATIC_MONEY_PERCENT, color: '#5cc879', disabled: true, }, //{ value: formatCurrencyCompact(totals.moneySavedDollars), label: 'Money saved', icon: <TrendingUpIcon sx={{ color: '#5cc879', fontSize: 34 }} />, percentage: STATIC_MONEY_PERCENT, color: '#5cc879', disabled: true, },
{ value: String(unreadCount), label: 'Total errors', icon: <ErrorOutlineIcon sx={{ color: '#f87171', fontSize: 34, opacity: 0.9 }} />, percentage: "", color: '#f87171' }, { value: String(unreadCount), label: 'Total errors', icon: <ErrorOutlineIcon sx={{ color: '#f87171', fontSize: 34, opacity: 0.9 }} />, percentage: "", color: '#f87171' },
{ value: String(readCount), label: 'Errors resolved', icon: <TaskAltIcon sx={{ color: '#5cc879', fontSize: 34, opacity: 0.9 }} />, percentage: "", color: '#5cc879' }, { value: String(readCount), label: 'Errors resolved', icon: <TaskAltIcon sx={{ color: '#5cc879', fontSize: 34, opacity: 0.9 }} />, percentage: "", color: '#5cc879' },
]; ];
@@ -131,12 +131,12 @@ const NewDashboard = (props) => {
useEffect(() => { useEffect(() => {
const anyLoading = const anyLoading =
loadingSfw || //loadingSfw ||
loadingRot || //loadingRot ||
loadingNoti || loadingNoti ||
loadingSelectedOrgStats || loadingSelectedOrgStats //||
!selectedOrganization || !selectedOrganization ||
!selectedOrgForStats; //!selectedOrgForStats;
setShowOverlay(anyLoading); setShowOverlay(anyLoading);
}, [ }, [
loadingSfw, loadingSfw,
@@ -397,6 +397,7 @@ const NewDashboard = (props) => {
headerSubtitle="Complete these steps to start seeing insights." headerSubtitle="Complete these steps to start seeing insights."
/> />
)} )}
{showOverlay && ( {showOverlay && (
<div style={{ position: 'absolute', inset: 0, background: 'rgba(17,17,17,0.6)', backdropFilter: 'blur(2px)', zIndex: 10, display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: 12 }}> <div style={{ position: 'absolute', inset: 0, background: 'rgba(17,17,17,0.6)', backdropFilter: 'blur(2px)', zIndex: 10, display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: 12 }}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 12 }}> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 12 }}>
@@ -405,6 +406,7 @@ const NewDashboard = (props) => {
</div> </div>
</div> </div>
)} )}
{/* Header / Greeting */} {/* Header / Greeting */}
<Box style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', margin: '8px 0 16px 0' }}> <Box style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', margin: '8px 0 16px 0' }}>
<Typography variant="h5">{`${getGreeting()}, ${displayName ?? 'User'}!`}</Typography> <Typography variant="h5">{`${getGreeting()}, ${displayName ?? 'User'}!`}</Typography>
+1 -1
View File
@@ -25,7 +25,7 @@ const SetAuthentication = (props) => {
const [loadFail, setLoadFail] = useState(""); const [loadFail, setLoadFail] = useState("");
const [appAuthentication, setAppAuthentication] = React.useState([]); const [appAuthentication, setAppAuthentication] = React.useState([]);
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (import.meta.env.VITE_IS_SSR === "true"); const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
//const alert = useAlert(); //const alert = useAlert();
const parseIncomingOpenapiData = (data) => { const parseIncomingOpenapiData = (data) => {
+1 -1
View File
@@ -47,7 +47,7 @@ const Welcome = (props) => {
} }
}, [activeStep]) }, [activeStep])
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (import.meta.env.VITE_IS_SSR === "true"); const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
const [steps, setSteps] = useState([ const [steps, setSteps] = useState([
"Help us get to know you", "Help us get to know you",
"Find your Apps", "Find your Apps",