From 6bc64f3657c672f9b32436fb7f7187bfdb4aca3f Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Wed, 6 Mar 2024 16:19:51 +0530 Subject: [PATCH 01/16] Merge pull request #1 from tesla999936/main added an endpoint to get user apps --- backend/go-app/main.go | 1 + backend/go-app/walkoff.go | 64 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 43d9eb53..71d30988 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -4785,6 +4785,7 @@ func initHandlers() { r.HandleFunc("/api/v1/users/checkusers", checkAdminLogin).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/users/getinfo", handleInfo).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/users/apps", getUserApps).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/users/generateapikey", shuffle.HandleApiGeneration).Methods("GET", "POST", "OPTIONS") r.HandleFunc("/api/v1/users/logout", shuffle.HandleLogout).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/users/getsettings", shuffle.HandleSettings).Methods("GET", "OPTIONS") diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 40f9ba3a..ed32e90d 100755 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -2404,6 +2404,70 @@ func setExampleresult(ctx context.Context, result shuffle.AppExecutionExample) e return nil } +func getUserApps(resp http.ResponseWriter, request *http.Request) { + cors := shuffle.HandleCors(resp, request) + if cors { + return + } + + ctx := context.Background() + user, userErr := shuffle.HandleApiAuthentication(resp, request) + if userErr != nil { + log.Printf("[WARNING] Api authentication failed in get all apps - this does NOT require auth in the cloud.: %s", userErr) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + workflowapps, err := shuffle.GetAllWorkflowApps(ctx, 1000, 0) + if err != nil { + log.Printf("[WARNING] Failed getting apps (getworkflowapps): %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + filteredApps := workflowapps[:0] + for _, app := range workflowapps { + if app.Owner == user.Id { + filteredApps = append(filteredApps, app) + } else if app.Contributors != nil { + for _, contributor := range app.Contributors { + if contributor == user.Id { + filteredApps = append(filteredApps, app) + } + } + } + } + + if len(user.PrivateApps) > 0 { + for _, item := range user.PrivateApps { + found := false + for _, app := range filteredApps { + if item.ID == app.ID || !(item.Owner == user.Id) { + found = true + break + } + } + + if !found { + filteredApps = append(filteredApps, item) + } + } + } + + newbody, err := json.Marshal(filteredApps) + if err != nil { + log.Printf("[ERROR] Failed unmarshalling all newapps: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking workflow apps"}`))) + return + } + + resp.WriteHeader(200) + resp.Write(newbody) +} + func getWorkflowApps(resp http.ResponseWriter, request *http.Request) { cors := shuffle.HandleCors(resp, request) if cors { From 40d84087cdc0fad5b338538d99f63459cc27e7d6 Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Sat, 9 Mar 2024 18:00:10 +0530 Subject: [PATCH 02/16] Merge pull request #2 from tesla999936/main reverting getUserApps --- backend/go-app/main.go | 2 +- backend/go-app/walkoff.go | 64 --------------------------------------- 2 files changed, 1 insertion(+), 65 deletions(-) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 71d30988..0c9da252 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -4785,7 +4785,7 @@ func initHandlers() { r.HandleFunc("/api/v1/users/checkusers", checkAdminLogin).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/users/getinfo", handleInfo).Methods("GET", "OPTIONS") - r.HandleFunc("/api/v1/users/apps", getUserApps).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/users/apps", shuffle.HandleGetUserApps).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/users/generateapikey", shuffle.HandleApiGeneration).Methods("GET", "POST", "OPTIONS") r.HandleFunc("/api/v1/users/logout", shuffle.HandleLogout).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/users/getsettings", shuffle.HandleSettings).Methods("GET", "OPTIONS") diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index ed32e90d..40f9ba3a 100755 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -2404,70 +2404,6 @@ func setExampleresult(ctx context.Context, result shuffle.AppExecutionExample) e return nil } -func getUserApps(resp http.ResponseWriter, request *http.Request) { - cors := shuffle.HandleCors(resp, request) - if cors { - return - } - - ctx := context.Background() - user, userErr := shuffle.HandleApiAuthentication(resp, request) - if userErr != nil { - log.Printf("[WARNING] Api authentication failed in get all apps - this does NOT require auth in the cloud.: %s", userErr) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - workflowapps, err := shuffle.GetAllWorkflowApps(ctx, 1000, 0) - if err != nil { - log.Printf("[WARNING] Failed getting apps (getworkflowapps): %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - filteredApps := workflowapps[:0] - for _, app := range workflowapps { - if app.Owner == user.Id { - filteredApps = append(filteredApps, app) - } else if app.Contributors != nil { - for _, contributor := range app.Contributors { - if contributor == user.Id { - filteredApps = append(filteredApps, app) - } - } - } - } - - if len(user.PrivateApps) > 0 { - for _, item := range user.PrivateApps { - found := false - for _, app := range filteredApps { - if item.ID == app.ID || !(item.Owner == user.Id) { - found = true - break - } - } - - if !found { - filteredApps = append(filteredApps, item) - } - } - } - - newbody, err := json.Marshal(filteredApps) - if err != nil { - log.Printf("[ERROR] Failed unmarshalling all newapps: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking workflow apps"}`))) - return - } - - resp.WriteHeader(200) - resp.Write(newbody) -} - func getWorkflowApps(resp http.ResponseWriter, request *http.Request) { cors := shuffle.HandleCors(resp, request) if cors { From 2b64d71f053a9cb088f3a3ac8b44aa8c47cc72bf Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Tue, 23 Apr 2024 11:11:22 +0000 Subject: [PATCH 03/16] added webhooks to the trigger view --- backend/go-app/main.go | 2 +- frontend/src/views/Admin.jsx | 540 ++++++++++++++++++++++++++++------- 2 files changed, 433 insertions(+), 109 deletions(-) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 52d8bc78..45bd1b83 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -4907,7 +4907,7 @@ func initHandlers() { r.HandleFunc("/api/v1/triggers/outlook/{key}", shuffle.HandleGetSpecificTrigger).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/triggers/gmail/register", shuffle.HandleNewGmailRegister).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/triggers/gmail/getFolders", shuffle.HandleGetGmailFolders).Methods("GET", "OPTIONS") - + //r.HandleFunc("/api/v1/triggers/all", shuffle.HandleGetTriggers).Methods("GET", "OPTIONS") //r.HandleFunc("/api/v1/triggers/gmail/routing", handleGmailRouting).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/triggers/gmail/{key}", shuffle.HandleGetSpecificTrigger).Methods("GET", "OPTIONS") diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index a68a9831..aa866c0f 100755 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -192,6 +192,8 @@ const Admin = (props) => { const [showApiKey, setShowApiKey] = useState(false); const [billingInfo, setBillingInfo] = React.useState({}); const [selectedStatus, setSelectedStatus] = React.useState([]); + const [webHooks, setWebHooks] = React.useState([]); + const [allSchedules, setAllSchedules] = React.useState([]); const [, forceUpdate] = React.useState(); @@ -231,6 +233,9 @@ const Admin = (props) => { else console.log("error in user data") }, [userdata]); + useEffect(() => { + handleGetAllTriggers() + }, []); const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; @@ -560,6 +565,31 @@ If you're interested, please let me know a time that works for you, or set up a }); }; + const handleGetAllTriggers = () => { + fetch(globalUrl + "/api/v1/triggers/all", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for getting all triggers"); + } + + return response.json(); + }) + .then((responseJson) => { + setWebHooks(responseJson.webhooks || []); // Handling the case where the result is null or undefined + setAllSchedules(responseJson.schedules || []); + }) + .catch((error) => { + toast(error.toString()); + }); + }; + const deleteSchedule = (data) => { // FIXME - add some check here ROFL console.log("INPUT: ", data); @@ -585,18 +615,150 @@ If you're interested, please let me know a time that works for you, or set up a if (responseJson["success"] === false) { toast("Failed stopping schedule"); } else { - setTimeout(() => { - getSchedules(); - }, 1500); - //toast("Successfully stopped schedule!") + toast("Successfully stopped schedule!"); } - }) + setTimeout(handleGetAllTriggers, 1000); + }), ) .catch((error) => { console.log("Error in userdata: ", error); }); }; + const startSchedule = (trigger) => { + if (trigger.name.length <= 0) { + toast("Error: name can't be empty"); + return; + } + + toast("Creating schedule"); + const data = { + name: trigger.name, + frequency: trigger.frequency, + execution_argument: trigger.argument, + environment: trigger.environment, + id: trigger.id, + start: trigger.start_node, + }; + + fetch(`${globalUrl}/api/v1/workflows/${trigger.workflow_id}/schedule`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(data), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for stream results :O!"); + } + + return response.json(); + }) + .then((responseJson) => { + if (!responseJson.success) { + toast("Failed to set schedule: " + responseJson.reason); + } else { + toast("Successfully created schedule"); + } + setTimeout(handleGetAllTriggers, 1000); + }) + .catch((error) => { + //toast(error.toString()); + console.log("Get schedule error: ", error.toString()); + }); + }; + + const deleteWebhook = (trigger) => { + if (trigger === undefined) { + return; + } + + fetch(globalUrl + "/api/v1/hooks/" + trigger.id + "/delete", { + method: "DELETE", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for stream results :O!"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success) { + toast("Successfully stopped webhook"); + } else { + if (responseJson.reason !== undefined) { + toast("Failed stopping webhook: " + responseJson.reason); + } + } + setTimeout(handleGetAllTriggers, 1000); + }) + .catch((error) => { + toast( + "Delete webhook error. Contact support or check logs if this persists.", + ); + }); + }; + + const startWebHook = (trigger) => { + const hookname = trigger.info.name; + if (hookname.length === 0) { + toast("Missing name"); + return; + } + + if (trigger.id.length !== 36) { + toast("Missing id"); + return; + } + + toast("Starting webhook"); + + const data = { + name: hookname, + type: "webhook", + id: trigger.id, + workflow: trigger.workflows[0], + start: trigger.start, + environment: trigger.environment, + auth: trigger.auth, + custom_response: trigger.custom_response, + version: trigger.version, + version_timeout: 15, + }; + + console.log("Trigger data: ", data); + + fetch(globalUrl + "/api/v1/hooks/new", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(data), + credentials: "include", + }) + .then((response) => response.json()) + .then((responseJson) => { + if (responseJson.success) { + // Set the status + toast("Successfully started webhook"); + } else { + toast("Failed starting webhook: " + responseJson.reason); + } + setTimeout(handleGetAllTriggers, 1000); + }) + .catch((error) => { + //console.log(error.toString()); + console.log("New webhook error: ", error.toString()); + }); + }; + if (userdata.support === true && selectedOrganization.id !== "" && selectedOrganization.id !== undefined && selectedOrganization.id !== null && selectedOrganization.id !== userdata.active_org.id) { toast("Refreshing window to fix org support access") @@ -3923,110 +4085,272 @@ If you're interested, please let me know a time that works for you, or set up a /> const schedulesView = - curTab === 5 ? ( -
-
-

Schedules

- - Schedules used in Workflows. Makes locating and control easier.{" "} - - Learn more - - -
- - - - - - - - - - - {schedules === undefined || schedules === null - ? null - : schedules.map((schedule, index) => { - var bgColor = "#27292d"; - if (index % 2 === 0) { - bgColor = "#1f2023"; - } - - return ( - - 0 ? - schedule.frequency - : - {schedule.seconds} seconds - } - /> - - - {schedule.workflow_id} - - } - /> - - - - - - ); - })} - + curTab === 5 ? ( +
+
+

Schedules

+ + Schedules used in Workflows. Makes locating and control easier.{" "} + + Learn more + +
- ) : null; + + + + + + + + + + + {allSchedules === undefined || allSchedules === null + ? null + : allSchedules.map((schedule, index) => { + var bgColor = "#27292d"; + if (index % 2 === 0) { + bgColor = "#1f2023"; + } + + return ( + + 0 ? ( + schedule.frequency + ) : ( + {schedule.seconds} seconds + ) + } + /> + + + {schedule.workflow_id} + + } + /> + + + + + + ); + })} + + +
+

WebHooks

+
+ + + + + + + + + + + + {webHooks === undefined || webHooks === null + ? null + : webHooks.map((webhook, index) => { + var bgColor = "#27292d"; + if (index % 2 === 0) { + bgColor = "#1f2023"; + } + + return ( + + + + + {webhook.workflows[0]} + + } + /> + + + { + const elementName = "copy_element_shuffle"; + var copyText = document.getElementById(elementName); + if (copyText !== null && copyText !== undefined) { + const clipboard = navigator.clipboard; + if (clipboard === undefined) { + toast("Can only copy over HTTPS (port 3443)"); + return; + } + + navigator.clipboard.writeText(webhook.info.url); + copyText.select(); + copyText.setSelectionRange( + 0, + 99999, + ); /* For mobile devices */ + + /* Copy the text inside the text field */ + document.execCommand("copy"); + + toast("URL copied to clipboard"); + } + }} + > + + + + ) + } + /> + + + + + + ); + })} + + + {/*
+

Tenzir Pipelines

+ + Controls a pipeline to run things.{" "} + + Learn more + + +
+ + */} +
+) : null; const appCategoryView = curTab === 8 ? ( From 9e34930718e0780a79c15a8a639a0bd0936d5d30 Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 26 Apr 2024 17:45:07 +0200 Subject: [PATCH 04/16] Loads of minor updates with cloud/onprem sync --- frontend/src/components/Billing.jsx | 2 +- frontend/src/components/Branding.jsx | 4 +- frontend/src/components/CacheView.jsx | 224 ++++++++++++------------- frontend/src/components/Files.jsx | 31 ++-- frontend/src/components/NewHeader.jsx | 2 +- frontend/src/components/Priorities.jsx | 21 +-- frontend/src/components/Priority.jsx | 6 +- frontend/src/views/AngularWorkflow.jsx | 26 ++- frontend/src/views/AppCreator.jsx | 112 +++++++------ 9 files changed, 229 insertions(+), 199 deletions(-) diff --git a/frontend/src/components/Billing.jsx b/frontend/src/components/Billing.jsx index c3687489..d3a65927 100644 --- a/frontend/src/components/Billing.jsx +++ b/frontend/src/components/Billing.jsx @@ -970,7 +970,7 @@ const Billing = (props) => { const isChildOrg = userdata.active_org.creator_org !== "" && userdata.active_org.creator_org !== undefined && userdata.active_org.creator_org !== null return ( -
+
{addDealModal} Billing & Licensing diff --git a/frontend/src/components/Branding.jsx b/frontend/src/components/Branding.jsx index 9dd40791..c3c32058 100644 --- a/frontend/src/components/Branding.jsx +++ b/frontend/src/components/Branding.jsx @@ -15,7 +15,7 @@ import { //import { useAlert const Branding = (props) => { - const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, } = props; + const { globalUrl, userdata, serverside, billingInfo,clickedFromOrgTab, stripeKey, selectedOrganization, handleGetOrg, } = props; //const alert = useAlert(); const [publishingInfo, setPublishingInfo] = useState(""); const [publishRequirements, setPublishRequirements] = useState([]) @@ -103,7 +103,7 @@ const Branding = (props) => { } return ( -
+

Branding

diff --git a/frontend/src/components/CacheView.jsx b/frontend/src/components/CacheView.jsx index 4d4d02fe..4e4f2fdd 100644 --- a/frontend/src/components/CacheView.jsx +++ b/frontend/src/components/CacheView.jsx @@ -1,10 +1,10 @@ import React, { useState, useEffect } from "react"; import theme from "../theme.jsx"; -import { toast } from 'react-toastify'; -import ReactJson from "react-json-view"; - +import { toast } from 'react-toastify'; +import ReactJson from "react-json-view"; + import { - Typography, + Typography, Tooltip, Divider, TextField, @@ -22,8 +22,8 @@ import { } from "@mui/material"; import { - Link as LinkIcon, - AutoFixHigh as AutoFixHighIcon, + Link as LinkIcon, + AutoFixHigh as AutoFixHighIcon, Edit as EditIcon, FileCopy as FileCopyIcon, SelectAll as SelectAllIcon, @@ -46,7 +46,7 @@ import { Visibility as VisibilityIcon, VisibilityOff as VisibilityOffIcon, } from "@mui/icons-material"; -import { validateJson, } from "../views/Workflows.jsx"; +import { validateJson, } from "../views/Workflows.jsx"; const scrollStyle1 = { height: 100, @@ -64,9 +64,9 @@ const scrollStyle2 = { overflow: "scroll", } - + const CacheView = (props) => { - const { globalUrl, userdata, serverside, orgId } = props; + const { globalUrl, userdata, serverside, orgId, isSelectedDataStore } = props; const [orgCache, setOrgCache] = React.useState(""); const [listCache, setListCache] = React.useState([]); const [addCache, setAddCache] = React.useState(""); @@ -79,7 +79,7 @@ const CacheView = (props) => { const [dataValue, setDataValue] = React.useState({}); const [editCache, setEditCache] = React.useState(false); const [show, setShow] = useState({}); - + useEffect(() => { listOrgCache(orgId); }, []); @@ -155,22 +155,22 @@ const CacheView = (props) => { const deleteCache = (orgId, key) => { toast("Attempting to delete Cache"); - + // method: "DELETE", - const method = "POST" - //const url = `${globalUrl}/api/v1/orgs/${orgId}/cache/${key}` - const url = `${globalUrl}/api/v1/orgs/${orgId}/delete_cache` - const parsed = { - "org_id": orgId, - "key": key, - } - + const method = "POST" + //const url = `${globalUrl}/api/v1/orgs/${orgId}/cache/${key}` + const url = `${globalUrl}/api/v1/orgs/${orgId}/delete_cache` + const parsed = { + "org_id": orgId, + "key": key, + } + fetch(url, { - method: method, + method: method, headers: { Accept: "application/json", }, - body: JSON.stringify(parsed), + body: JSON.stringify(parsed), credentials: "include", }) .then((response) => { @@ -255,20 +255,20 @@ const CacheView = (props) => { }); }; - 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) - //return JSON.stringify(inputvalue); - } - } - + 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) + //return JSON.stringify(inputvalue); + } + } + const modalView = ( // console.log("key:", dataValue.key), //console.log("value:",dataValue.value), @@ -316,21 +316,21 @@ const CacheView = (props) => { />
-
- - Value - ({isValidJson.valid === true ? "Valid" : "Invalid"} JSON) - - - { - autoFixJson(value) - }} - > - - - -
+
+ + Value - ({isValidJson.valid === true ? "Valid" : "Invalid"} JSON) + + + { + autoFixJson(value) + }} + > + + + +
{ id="Valuefield" margin="normal" variant="outlined" - multiline - minRows={4} - maxRows={12} + multiline + minRows={4} + maxRows={12} //defaultValue={editCache ? dataValue.value : ""} - value={value} + value={value} onChange={(e) => setValue(e.target.value)} />
- - - + />} + + { if (index % 2 === 0) { bgColor = "#1f2023"; } - - const validate = validateJson(data.value); + + const validate = validateJson(data.value); return ( { primary={data.key} /> { - //handleReactJsonClipboard(copy); - }} - displayDataTypes={false} - onSelect={(select) => { - //HandleJsonCopy(showResult, select, data.action.label); - //console.log("SELECTED!: ", select); - }} - name={"value"} - /> - : - data.value + primary={validate.valid ? + { + //handleReactJsonClipboard(copy); + }} + displayDataTypes={false} + onSelect={(select) => { + //HandleJsonCopy(showResult, select, data.action.label); + //console.log("SELECTED!: ", select); + }} + name={"value"} + /> + : + data.value } /> { style={{ padding: "6px" }} onClick={() => { setEditCache(true) - setDataValue({ - "key": data.key, - "value":data.value - }) - setValue(data.value) + setDataValue({ + "key": data.key, + "value":data.value + }) + setValue(data.value) setModalOpen(true) }} > @@ -525,18 +525,18 @@ const CacheView = (props) => { { - window.open(`${globalUrl}/api/v1/orgs/${orgId}/cache/${data.key}?type=text&authorization=${data.public_authorization}`, "_blank"); - }} + window.open(`${globalUrl}/api/v1/orgs/${orgId}/cache/${data.key}?type=text&authorization=${data.public_authorization}`, "_blank"); + }} > - + diff --git a/frontend/src/components/Files.jsx b/frontend/src/components/Files.jsx index 106cd18d..c13bf067 100644 --- a/frontend/src/components/Files.jsx +++ b/frontend/src/components/Files.jsx @@ -41,7 +41,7 @@ import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx"; import theme from "../theme.jsx"; const Files = (props) => { - const { globalUrl, userdata, serverside, selectedOrganization, isCloud, } = props; + const { globalUrl, userdata, serverside, selectedOrganization, isCloud,isSelectedFiles } = props; const [files, setFiles] = React.useState([]); const [selectedNamespace, setSelectedNamespace] = React.useState("default"); @@ -617,16 +617,16 @@ const Files = (props) => { style={{ maxWidth: window.innerWidth > 1366 ? 1366 : 1200, margin: "auto", - padding: 20, + padding: isSelectedFiles ? null : 20, }} onDrop={uploadFile} > -
+
setLoadFileModalOpen(true)} > @@ -636,15 +636,15 @@ const Files = (props) => { {fileDownloadModal} -
-

Files

- +
+

Files

+ Files from Workflows are a way to store as well as edit files.{" "} Learn more @@ -659,6 +659,7 @@ const Files = (props) => { onClick={() => { upload.click(); }} + style={{backgroundColor: isSelectedFiles?'rgba(255, 132, 68, 0.2)':null, color:isSelectedFiles?"#FF8444":null, borderRadius:isSelectedFiles?200:null, width:isSelectedFiles?162:null, height:isSelectedFiles?40:null}} > Upload files @@ -678,7 +679,7 @@ const Files = (props) => { }} /> - - - ); - })} - - -
-

WebHooks

-
- - - - - - - - - - - - {webHooks === undefined || webHooks === null - ? null - : webHooks.map((webhook, index) => { - var bgColor = "#27292d"; - if (index % 2 === 0) { - bgColor = "#1f2023"; - } - - return ( - - - - + + - - - ); - })} - - - {/*
-

Tenzir Pipelines

- - Controls a pipeline to run things.{" "} - - Learn more - - + { + const elementName = "copy_element_shuffle"; + var copyText = document.getElementById(elementName); + if (copyText !== null && copyText !== undefined) { + const clipboard = navigator.clipboard; + if (clipboard === undefined) { + toast("Can only copy over HTTPS (port 3443)"); + return; + } + + navigator.clipboard.writeText(webhook.info.url); + copyText.select(); + copyText.setSelectionRange( + 0, + 99999, + ); /* For mobile devices */ + + /* Copy the text inside the text field */ + document.execCommand("copy"); + + toast("URL copied to clipboard"); + } + }} + > + + + + ) + } + /> + + + + + + ); + })} + + + {/*
+

Tenzir Pipelines

+ + Controls a pipeline to run things.{" "} + + Learn more + + +
+ + */}
- - */} -
-) : null; + ) : null; const appCategoryView = curTab === 8 ? ( From 1c97e6785568fa3ac024ac6c9d4d77f63d33556b Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Mon, 29 Apr 2024 07:35:37 +0000 Subject: [PATCH 07/16] adding pipelines to the ui --- frontend/src/views/Admin.jsx | 532 +++++++++++++++++++++-------------- 1 file changed, 327 insertions(+), 205 deletions(-) diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index eb4173ef..81feaaab 100755 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -203,7 +203,7 @@ const Admin = (props) => { const [selectedStatus, setSelectedStatus] = React.useState([]); const [webHooks, setWebHooks] = React.useState([]); const [allSchedules, setAllSchedules] = React.useState([]); - + const [pipelines, setPipelines] = React.useState([]); const [, forceUpdate] = React.useState(); const [showDeleteAccountTextbox, setShowDeleteAccountTextbox] = @@ -756,6 +756,7 @@ If you're interested, please let me know a time that works for you, or set up a .then((responseJson) => { setWebHooks(responseJson.webhooks || []); // Handling the case where the result is null or undefined setAllSchedules(responseJson.schedules || []); + setPipelines(responseJson.pipelines || []); }) .catch((error) => { toast(error.toString()); @@ -933,6 +934,14 @@ If you're interested, please let me know a time that works for you, or set up a }); }; + const changePipelineState = (pipeline, state) => { + if (state.trim() === ''){ + toast("state is not defined") + return + } + + } + if ( userdata.support === true && selectedOrganization.id !== "" && @@ -4573,94 +4582,112 @@ If you're interested, please let me know a time that works for you, or set up a backgroundColor: theme.palette.inputColor, }} /> - - - - - - - - - - {allSchedules === undefined || allSchedules === null - ? null - : allSchedules.map((schedule, index) => { - var bgColor = "#27292d"; - if (index % 2 === 0) { - bgColor = "#1f2023"; - } + {allSchedules === undefined || + allSchedules === null || + allSchedules.length === 0 ? ( +
+ No schedules found. +
+ ) : ( + + + + + + + + + + { allSchedules.map((schedule, index) => { + var bgColor = "#27292d"; + if (index % 2 === 0) { + bgColor = "#1f2023"; + } - return ( - - 0 ? ( - schedule.frequency - ) : ( - {schedule.seconds} seconds - ) - } - /> - - - {schedule.workflow_id} - - } - /> - - - - - - ); - })} - + + + + ); + })} +
+ )}

WebHooks

@@ -4673,126 +4700,140 @@ If you're interested, please let me know a time that works for you, or set up a backgroundColor: theme.palette.inputColor, }} /> + {webHooks === undefined || webHooks === null || webHooks.length === 0 ? ( +
+ No webhooks found. +
+ ) : ( + + + + + + + + + {webHooks.map((webhook, index) => { + var bgColor = "#27292d"; + if (index % 2 === 0) { + bgColor = "#1f2023"; + } - - - - - - - - - {webHooks === undefined || webHooks === null - ? null - : webHooks.map((webhook, index) => { - var bgColor = "#27292d"; - if (index % 2 === 0) { - bgColor = "#1f2023"; - } - - return ( - - - - - {webhook.workflows[0]} - - } - /> - - - { - const elementName = "copy_element_shuffle"; - var copyText = document.getElementById(elementName); - if (copyText !== null && copyText !== undefined) { - const clipboard = navigator.clipboard; - if (clipboard === undefined) { - toast("Can only copy over HTTPS (port 3443)"); - return; - } - - navigator.clipboard.writeText(webhook.info.url); - copyText.select(); - copyText.setSelectionRange( - 0, - 99999, - ); /* For mobile devices */ - - /* Copy the text inside the text field */ - document.execCommand("copy"); - - toast("URL copied to clipboard"); - } - }} - > - - - - ) - } - /> - - - - - - ); - })} - + {webhook.workflows[0]} + + } + /> - {/*
+ + { + const elementName = "copy_element_shuffle"; + var copyText = document.getElementById(elementName); + if (copyText !== null && copyText !== undefined) { + const clipboard = navigator.clipboard; + if (clipboard === undefined) { + toast("Can only copy over HTTPS (port 3443)"); + return; + } + + navigator.clipboard.writeText(webhook.info.url); + copyText.select(); + copyText.setSelectionRange( + 0, + 99999, + ); /* For mobile devices */ + + /* Copy the text inside the text field */ + document.execCommand("copy"); + + toast("URL copied to clipboard"); + } + }} + > + + + + ) + } + /> + + + + + + ); + })} + + )} + +

Tenzir Pipelines

Controls a pipeline to run things.{" "} @@ -4813,9 +4854,90 @@ If you're interested, please let me know a time that works for you, or set up a marginBottom: 20, backgroundColor: theme.palette.inputColor, }} - /> */} + /> + {pipelines === undefined || pipelines === null || pipelines.length === 0 ? ( +
+ No pipelines found. +
+ ) : ( + + + + + + + + {pipelines.map((pipeline, index) => { + var bgColor = "#27292d"; + if (index % 2 === 0) { + bgColor = "#1f2023"; + } + + return ( + + + + + {pipeline.workflow_id} + + } + /> + + + + + ); + })} + + )}
- ) : null; + ) : null; const appCategoryView = curTab === 8 ? ( From 8567b7e722d4a0171a650be080b0d3fed0cc1eb3 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 29 Apr 2024 11:14:04 +0200 Subject: [PATCH 08/16] Fixed a looping bug with SHUFFLE_NO_SPLITTER where it sometimes didn't do loops at all --- backend/app_sdk/app_base.py | 149 +++++++++++++++++++----------------- 1 file changed, 78 insertions(+), 71 deletions(-) diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index 54b238e3..6f9afbd5 100755 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -3434,95 +3434,102 @@ class AppBase: # Loop WITH variables go in else. handled = False + self.logger.info("ACTUALITEM: %s" % actualitem) + # Has a loop without a variable used inside if len(actualitem[0]) > 2 and actualitem[0][1] == "SHUFFLE_NO_SPLITTER": tmpitem = value - index = 0 - replacement = actualitem[index][2] - if replacement.endswith("}$"): - replacement = replacement[:-2] + #index = 0 + for index in range(len(actualitem)): + # Check if it's SHUFFLE_NO_SPLITTER + if actualitem[index][1] != "SHUFFLE_NO_SPLITTER": + continue - if replacement.startswith("\"") and replacement.endswith("\""): - replacement = replacement[1:len(replacement)-1] + replacement = actualitem[index][2] + if replacement.endswith("}$"): + replacement = replacement[:-2] - #json_replacement = tmpitem.replace(actualitem[index][0], replacement, 1) - json_replacement = replacement - try: - json_replacement = json.loads(replacement) - except json.decoder.JSONDecodeError as e: + if replacement.startswith("\"") and replacement.endswith("\""): + replacement = replacement[1:len(replacement)-1] + + #json_replacement = tmpitem.replace(actualitem[index][0], replacement, 1) + json_replacement = replacement try: - replacement = replacement.replace("\'", "\"", -1) json_replacement = json.loads(replacement) - except: - self.logger.info("JSON error singular: %s" % e) - - if len(json_replacement) > minlength: - minlength = len(json_replacement) - - self.logger.info("PRE new_replacement") - - new_replacement = [] - for i in range(len(json_replacement)): - if isinstance(json_replacement[i], dict) or isinstance(json_replacement[i], list): - tmp_replacer = json.dumps(json_replacement[i]) - newvalue = tmpitem.replace(str(actualitem[index][0]), str(tmp_replacer), 1) - else: - newvalue = tmpitem.replace(str(actualitem[index][0]), str(json_replacement[i]), 1) - - try: - newvalue = parse_liquid(newvalue, self) - except Exception as e: - self.logger.info(f"[WARNING] Failed liquid parsing in loop (2): {e}") - - try: - newvalue = json.loads(newvalue) except json.decoder.JSONDecodeError as e: - pass + try: + replacement = replacement.replace("\'", "\"", -1) + json_replacement = json.loads(replacement) + except: + self.logger.info("JSON error singular: %s" % e) - new_replacement.append(newvalue) + if len(json_replacement) > minlength: + minlength = len(json_replacement) + + self.logger.info("PRE new_replacement") + + new_replacement = [] + for i in range(len(json_replacement)): + if isinstance(json_replacement[i], dict) or isinstance(json_replacement[i], list): + tmp_replacer = json.dumps(json_replacement[i]) + newvalue = tmpitem.replace(str(actualitem[index][0]), str(tmp_replacer), 1) + else: + newvalue = tmpitem.replace(str(actualitem[index][0]), str(json_replacement[i]), 1) + + try: + newvalue = parse_liquid(newvalue, self) + except Exception as e: + self.logger.info(f"[WARNING] Failed liquid parsing in loop (2): {e}") + + try: + newvalue = json.loads(newvalue) + except json.decoder.JSONDecodeError as e: + pass + + new_replacement.append(newvalue) - # FIXME: Should this use new_replacement? - tmpitem = tmpitem.replace(actualitem[index][0], replacement, 1) + # FIXME: Should this use new_replacement? + tmpitem = tmpitem.replace(actualitem[index][0], replacement, 1) - # This code handles files. - resultarray = [] - isfile = False - try: - if parameter["schema"]["type"] == "file" and len(value) > 0: - self.logger.info("(1) SHOULD HANDLE FILE IN MULTI. Get based on value %s" % tmpitem) - # This is silly :) - # Q: Is there something wrong with the download system? - # It seems to return "FILE CONTENT: %s" with the ID as %s - for tmp_file_split in json.loads(tmpitem): - file_value = self.get_file(tmp_file_split) - resultarray.append(file_value) + # This code handles files. + resultarray = [] + isfile = False + try: + if parameter["schema"]["type"] == "file" and len(value) > 0: + self.logger.info("(1) SHOULD HANDLE FILE IN MULTI. Get based on value %s" % tmpitem) + # This is silly :) + # Q: Is there something wrong with the download system? + # It seems to return "FILE CONTENT: %s" with the ID as %s + for tmp_file_split in json.loads(tmpitem): + file_value = self.get_file(tmp_file_split) + resultarray.append(file_value) - isfile = True - except NameError as e: - self.logger.info("(1) SCHEMA NAMEERROR IN FILE HANDLING: %s" % e) - except KeyError as e: - self.logger.info("(1) SCHEMA KEYERROR IN FILE HANDLING: %s" % e) - except json.decoder.JSONDecodeError as e: - self.logger.info("(1) JSON ERROR IN FILE HANDLING: %s" % e) + isfile = True + except NameError as e: + self.logger.info("(1) SCHEMA NAMEERROR IN FILE HANDLING: %s" % e) + except KeyError as e: + self.logger.info("(1) SCHEMA KEYERROR IN FILE HANDLING: %s" % e) + except json.decoder.JSONDecodeError as e: + self.logger.info("(1) JSON ERROR IN FILE HANDLING: %s" % e) - if not isfile: - params[parameter["name"]] = tmpitem - multi_parameters[parameter["name"]] = new_replacement - else: - params[parameter["name"]] = resultarray - multi_parameters[parameter["name"]] = resultarray + if not isfile: + params[parameter["name"]] = tmpitem + multi_parameters[parameter["name"]] = new_replacement + else: + params[parameter["name"]] = resultarray + multi_parameters[parameter["name"]] = resultarray - #if len(resultarray) == 0: - # self.logger.info("[WARNING] Returning empty array because the array length to be looped is 0 (1)") - # action_result["status"] = "SUCCESS" - # action_result["result"] = "[]" - # self.send_result(action_result, headers, stream_path) - # return + #if len(resultarray) == 0: + # self.logger.info("[WARNING] Returning empty array because the array length to be looped is 0 (1)") + # action_result["status"] = "SUCCESS" + # action_result["result"] = "[]" + # self.send_result(action_result, headers, stream_path) + # return - multi_execution_lists.append(new_replacement) + multi_execution_lists.append(new_replacement) #self.logger.info("MULTI finished: %s" % json_replacement) else: # This is here to handle for loops within variables.. kindof From 7e92c8d399364ef7810d42af96d1e5be56286829 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 29 Apr 2024 11:35:21 +0200 Subject: [PATCH 09/16] Fixed duplicate parameter parsing. Requires full app update --- backend/app_sdk/app_base.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index 6f9afbd5..8dcfc01d 100755 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -2864,8 +2864,12 @@ class AppBase: # Handles for loops etc. # FIXME: Should it dump to string here? Doesn't that defeat the purpose? # Trying without string dumping. - + #self.logger.info("TO BE REPLACED: %s" % to_be_replaced) value, is_loop = get_json_value(fullexecution, to_be_replaced) + self.logger.info("OUTPUT (%s): %s" % (to_be_replaced, value)) + self.logger.info("PRE VALUE:\n%s" % parameter["value"]) + + #self.logger.info(f"\n\nType of value: {type(value)}") if isinstance(value, str): # Could we take it here? @@ -2879,26 +2883,25 @@ class AppBase: # returnvalue = fix_json_string_value(value) # value = returnvalue - - parameter["value"] = parameter["value"].replace(to_be_replaced, value) + parameter["value"] = parameter["value"].replace(to_be_replaced, value, 1) elif isinstance(value, dict) or isinstance(value, list): # Changed from JSON dump to str() 28.05.2021 # This makes it so the parameters gets lists and dicts straight up - parameter["value"] = parameter["value"].replace(to_be_replaced, json.dumps(value)) + parameter["value"] = parameter["value"].replace(to_be_replaced, json.dumps(value), 1) #try: - # parameter["value"] = parameter["value"].replace(to_be_replaced, str(value)) - #except: # parameter["value"] = parameter["value"].replace(to_be_replaced, json.dumps(value)) + #except: + # parameter["value"] = parameter["value"].replace(to_be_replaced, str(value)) # self.logger.info("Failed parsing value as string?") else: self.logger.error("[ERROR] Unknown type %s" % type(value)) try: - parameter["value"] = parameter["value"].replace(to_be_replaced, json.dumps(value)) + parameter["value"] = parameter["value"].replace(to_be_replaced, json.dumps(value), 1) except json.decoder.JSONDecodeError as e: - parameter["value"] = parameter["value"].replace(to_be_replaced, value) + parameter["value"] = parameter["value"].replace(to_be_replaced, value, 1) - #self.logger.info("VALUE: %s" % parameter["value"]) + self.logger.info("POST VALUE: \n%s" % parameter["value"]) else: #self.logger.info(f"[ERROR] Not running static variant regex parsing (slow) on value with length {len(parameter['value'])}. Max is 5Mb~.") pass @@ -3434,8 +3437,6 @@ class AppBase: # Loop WITH variables go in else. handled = False - self.logger.info("ACTUALITEM: %s" % actualitem) - # Has a loop without a variable used inside if len(actualitem[0]) > 2 and actualitem[0][1] == "SHUFFLE_NO_SPLITTER": From 5e789d4a536fb17be76f6898011718ecde8cb894 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 29 Apr 2024 12:40:23 +0200 Subject: [PATCH 10/16] Fixed SHUFFLE_NO_SPLITTER loop bugs --- backend/app_sdk/app_base.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index 8dcfc01d..7d20fa1a 100755 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -1127,6 +1127,8 @@ class AppBase: #param_multiplier = await self.get_param_multipliers(newparams) param_multiplier = self.get_param_multipliers(newparams) + #self.logger.info("PARAM MULTIPLIER: %s" % param_multiplier) + # FIXME: This does a deduplication of the data new_params = self.validate_unique_fields(param_multiplier) #self.logger.info(f"NEW PARAMS: {new_params}") @@ -2866,9 +2868,6 @@ class AppBase: # Trying without string dumping. #self.logger.info("TO BE REPLACED: %s" % to_be_replaced) value, is_loop = get_json_value(fullexecution, to_be_replaced) - self.logger.info("OUTPUT (%s): %s" % (to_be_replaced, value)) - self.logger.info("PRE VALUE:\n%s" % parameter["value"]) - #self.logger.info(f"\n\nType of value: {type(value)}") if isinstance(value, str): @@ -2901,7 +2900,6 @@ class AppBase: except json.decoder.JSONDecodeError as e: parameter["value"] = parameter["value"].replace(to_be_replaced, value, 1) - self.logger.info("POST VALUE: \n%s" % parameter["value"]) else: #self.logger.info(f"[ERROR] Not running static variant regex parsing (slow) on value with length {len(parameter['value'])}. Max is 5Mb~.") pass @@ -3469,8 +3467,6 @@ class AppBase: if len(json_replacement) > minlength: minlength = len(json_replacement) - self.logger.info("PRE new_replacement") - new_replacement = [] for i in range(len(json_replacement)): if isinstance(json_replacement[i], dict) or isinstance(json_replacement[i], list): @@ -3484,11 +3480,14 @@ class AppBase: except Exception as e: self.logger.info(f"[WARNING] Failed liquid parsing in loop (2): {e}") + tmpitem = str(newvalue) + try: newvalue = json.loads(newvalue) except json.decoder.JSONDecodeError as e: pass + # The list to use for the multi execution IF not a file list new_replacement.append(newvalue) @@ -3517,6 +3516,7 @@ class AppBase: self.logger.info("(1) JSON ERROR IN FILE HANDLING: %s" % e) if not isfile: + # Should be here in normal circumstances params[parameter["name"]] = tmpitem multi_parameters[parameter["name"]] = new_replacement else: @@ -3661,7 +3661,7 @@ class AppBase: except KeyError as e: self.logger.info("SCHEMA ERROR IN FILE HANDLING: %s" % e) - + #remove_params.append(parameter["name"]) # Fix lists here # FIXME: This doesn't really do anything anymore @@ -3940,7 +3940,8 @@ class AppBase: # 1. Use number of executions based on the arrays being similar # 2. Find the right value from the parsed multi_params - self.logger.info("[INFO] Running WITHOUT outer loop (looping)") + #self.logger.info("[INFO] Running WITH loop. MULTI: %s", multi_parameters) + self.logger.info("[INFO] Running WITH loop") json_object = False #results = await self.run_recursed_items(func, multi_parameters, {}) results = self.run_recursed_items(func, multi_parameters, {}) From 9837e155023489050081fde345e1c1838111e278 Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Mon, 29 Apr 2024 10:55:09 +0000 Subject: [PATCH 11/16] some minor nits --- frontend/src/views/Admin.jsx | 199 +++++++++++++++---------- frontend/src/views/AngularWorkflow.jsx | 30 ++-- 2 files changed, 136 insertions(+), 93 deletions(-) diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 81feaaab..327bfa55 100755 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -756,7 +756,7 @@ If you're interested, please let me know a time that works for you, or set up a .then((responseJson) => { setWebHooks(responseJson.webhooks || []); // Handling the case where the result is null or undefined setAllSchedules(responseJson.schedules || []); - setPipelines(responseJson.pipelines || []); + // setPipelines(responseJson.pipelines || []); }) .catch((error) => { toast(error.toString()); @@ -935,13 +935,54 @@ If you're interested, please let me know a time that works for you, or set up a }; const changePipelineState = (pipeline, state) => { - if (state.trim() === ''){ - toast("state is not defined") - return + if (state.trim() === "") { + toast("state is not defined"); + return; } - - } - + + const data = { + name: pipeline.name, + type: state, + environment: pipeline.environment, + workflow_id: pipeline.workflow_id, + trigger_id: pipeline.trigger_id, + }; + + if (state === "start") toast("starting the pipeline"); + else toast("stopping the pipeline"); + + const url = `${globalUrl}/api/v1/triggers/pipeline`; + fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(data), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for stream results :O!"); + toast("Failed to update the pipeline state"); + } + + return response.json(); + }) + .then((responseJson) => { + if (!responseJson.success) { + toast("Failed to update the pipeline: " + responseJson.reason); + } else { + if (state === "start") toast("Successfully created pipeline"); + else toast("Sucessfully stopped the pipeline"); + } + }) + .catch((error) => { + //toast(error.toString()); + console.log("Get schedule error: ", error.toString()); + }); + }; + if ( userdata.support === true && selectedOrganization.id !== "" && @@ -4618,74 +4659,72 @@ If you're interested, please let me know a time that works for you, or set up a - { allSchedules.map((schedule, index) => { - var bgColor = "#27292d"; - if (index % 2 === 0) { - bgColor = "#1f2023"; - } + {allSchedules.map((schedule, index) => { + var bgColor = "#27292d"; + if (index % 2 === 0) { + bgColor = "#1f2023"; + } - return ( - - 0 ? ( - schedule.frequency - ) : ( - {schedule.seconds} seconds - ) - } - /> - - - {schedule.workflow_id} - - } - /> - - - - - - ); - })} + return ( + + 0 ? ( + schedule.frequency + ) : ( + {schedule.seconds} seconds + ) + } + /> + + + {schedule.workflow_id} + + } + /> + + + + + + ); + })} )} @@ -4833,7 +4872,7 @@ If you're interested, please let me know a time that works for you, or set up a )} -
+ {/*

Tenzir Pipelines

Controls a pipeline to run things.{" "} @@ -4855,7 +4894,9 @@ If you're interested, please let me know a time that works for you, or set up a backgroundColor: theme.palette.inputColor, }} /> - {pipelines === undefined || pipelines === null || pipelines.length === 0 ? ( + {pipelines === undefined || + pipelines === null || + pipelines.length === 0 ? (
{pipeline.status === "running" - ? "Stop webhook" - : "Start Webhook"} + ? "Stop pipeline" + : "Start pipeline"} ); })} - )} + )}*/}
) : null; diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 76953e68..5832f76f 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -13124,7 +13124,7 @@ const AngularWorkflow = (defaultprops) => { if (trigger.id === undefined) { return; } - + fetch(globalUrl + "/api/v1/hooks/" + trigger.id + "/delete", { method: "DELETE", headers: { @@ -13137,32 +13137,34 @@ const AngularWorkflow = (defaultprops) => { if (response.status !== 200) { console.log("Status not 200 for stream results :O!"); } - + return response.json(); }) .then((responseJson) => { + if (!responseJson.success) { + if (responseJson.reason !== undefined) { + toast("Failed to stop webhook: " + responseJson.reason); + } + } else { + toast("Successfully stopped webhook"); + } if (workflow.triggers[triggerindex] !== undefined) { workflow.triggers[triggerindex].status = "stopped"; } - - if (responseJson.success) { - // Set the status - saveWorkflow(workflow); - } else { - if (responseJson.reason !== undefined) { - toast("Failed stopping webhook: " + responseJson.reason); - } - } - trigger.status = "stopped"; - setWorkflow(workflow); setSelectedTrigger(trigger); + setWorkflow(workflow); + saveWorkflow(workflow); + }) .catch((error) => { //toast(error.toString()); - toast("Delete webhook error. Contact support or check logs if this persists.") + toast( + "Delete webhook error. Contact support or check logs if this persists.", + ); }); }; + // POST to /api/v1/workflows const createWorkflow = (workflow, trigger_index) => { From 45a6089629fa3e04978c12e987b3a706df9bb0b0 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 29 Apr 2024 13:00:12 +0200 Subject: [PATCH 12/16] Removed custom handler for SHUFFLE_NO_SPLITTER loop handler and further debug logs --- backend/app_sdk/app_base.py | 263 +++++++++++------------------------- 1 file changed, 80 insertions(+), 183 deletions(-) diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index 7d20fa1a..d5194909 100755 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -2526,7 +2526,7 @@ class AppBase: return template if "${" in template and "}$" in template: - self.logger.info("[DEBUG] Shuffle loop shouldn't run in liquid. Data length: %d" % len(template)) + #self.logger.info("[DEBUG] Shuffle loop shouldn't run in liquid. Data length: %d" % len(template)) return template @@ -3436,203 +3436,101 @@ class AppBase: handled = False # Has a loop without a variable used inside - if len(actualitem[0]) > 2 and actualitem[0][1] == "SHUFFLE_NO_SPLITTER": + + # This is here to handle for loops within variables.. kindof + # 1. Find the length of the longest array + # 2. Build an array with the base values based on parameter["value"] + # 3. Get the n'th value of the generated list from values + # 4. Execute all n answers + replacements = {} + curminlength = 0 + for replace in actualitem: + try: + to_be_replaced = replace[0] + actualitem = replace[2] + if actualitem.endswith("}$"): + actualitem = actualitem[:-2] - tmpitem = value + except IndexError: + self.logger.info("[WARNING] Indexerror") + continue - #index = 0 - for index in range(len(actualitem)): - # Check if it's SHUFFLE_NO_SPLITTER - if actualitem[index][1] != "SHUFFLE_NO_SPLITTER": - continue + try: + itemlist = json.loads(actualitem) + if len(itemlist) > minlength: + minlength = len(itemlist) - replacement = actualitem[index][2] - if replacement.endswith("}$"): - replacement = replacement[:-2] + if len(itemlist) > curminlength: + curminlength = len(itemlist) + + except json.decoder.JSONDecodeError as e: + self.logger.info("JSON Error (replace): %s in %s" % (e, actualitem)) + + replacements[to_be_replaced] = actualitem + + + # Parses the data as string with length, split etc. before moving on. + #self.logger.info("In second part of else: %s" % (len(itemlist))) + # This is a result array for JUST this value.. + # What if there are more? + resultarray = [] + for i in range(0, curminlength): + tmpitem = json.loads(json.dumps(parameter["value"])) + for key, value in replacements.items(): + replacement = value + try: + replacement = json.dumps(json.loads(value)[i]) + except IndexError as e: + self.logger.info(f"[ERROR] Failed handling value parsing with index: {e}") + pass if replacement.startswith("\"") and replacement.endswith("\""): replacement = replacement[1:len(replacement)-1] + #except json.decoder.JSONDecodeError as e: - #json_replacement = tmpitem.replace(actualitem[index][0], replacement, 1) - json_replacement = replacement + #self.logger.info("REPLACING %s with %s" % (key, replacement)) + #replacement = parse_wrapper_start(replacement) + tmpitem = tmpitem.replace(key, replacement, -1) try: - json_replacement = json.loads(replacement) - except json.decoder.JSONDecodeError as e: - try: - replacement = replacement.replace("\'", "\"", -1) - json_replacement = json.loads(replacement) - except: - self.logger.info("JSON error singular: %s" % e) - - if len(json_replacement) > minlength: - minlength = len(json_replacement) - - new_replacement = [] - for i in range(len(json_replacement)): - if isinstance(json_replacement[i], dict) or isinstance(json_replacement[i], list): - tmp_replacer = json.dumps(json_replacement[i]) - newvalue = tmpitem.replace(str(actualitem[index][0]), str(tmp_replacer), 1) - else: - newvalue = tmpitem.replace(str(actualitem[index][0]), str(json_replacement[i]), 1) - - try: - newvalue = parse_liquid(newvalue, self) - except Exception as e: - self.logger.info(f"[WARNING] Failed liquid parsing in loop (2): {e}") - - tmpitem = str(newvalue) - - try: - newvalue = json.loads(newvalue) - except json.decoder.JSONDecodeError as e: - pass - - # The list to use for the multi execution IF not a file list - new_replacement.append(newvalue) + tmpitem = parse_liquid(tmpitem, self) + except Exception as e: + self.logger.info(f"[WARNING] Failed liquid parsing in loop (2): {e}") - # FIXME: Should this use new_replacement? - tmpitem = tmpitem.replace(actualitem[index][0], replacement, 1) + # This code handles files. + isfile = False + try: + if parameter["schema"]["type"] == "file" and len(value) > 0: + self.logger.info("(2) SHOULD HANDLE FILE IN MULTI. Get based on value %s" % parameter["value"]) - # This code handles files. - resultarray = [] - isfile = False - try: - if parameter["schema"]["type"] == "file" and len(value) > 0: - self.logger.info("(1) SHOULD HANDLE FILE IN MULTI. Get based on value %s" % tmpitem) - # This is silly :) - # Q: Is there something wrong with the download system? - # It seems to return "FILE CONTENT: %s" with the ID as %s - for tmp_file_split in json.loads(tmpitem): - file_value = self.get_file(tmp_file_split) - resultarray.append(file_value) - - isfile = True - except NameError as e: - self.logger.info("(1) SCHEMA NAMEERROR IN FILE HANDLING: %s" % e) - except KeyError as e: - self.logger.info("(1) SCHEMA KEYERROR IN FILE HANDLING: %s" % e) - except json.decoder.JSONDecodeError as e: - self.logger.info("(1) JSON ERROR IN FILE HANDLING: %s" % e) - - if not isfile: - # Should be here in normal circumstances - params[parameter["name"]] = tmpitem - multi_parameters[parameter["name"]] = new_replacement - else: - params[parameter["name"]] = resultarray - multi_parameters[parameter["name"]] = resultarray - - #if len(resultarray) == 0: - # self.logger.info("[WARNING] Returning empty array because the array length to be looped is 0 (1)") - # action_result["status"] = "SUCCESS" - # action_result["result"] = "[]" - # self.send_result(action_result, headers, stream_path) - # return - - multi_execution_lists.append(new_replacement) - #self.logger.info("MULTI finished: %s" % json_replacement) - else: - # This is here to handle for loops within variables.. kindof - # 1. Find the length of the longest array - # 2. Build an array with the base values based on parameter["value"] - # 3. Get the n'th value of the generated list from values - # 4. Execute all n answers - replacements = {} - curminlength = 0 - for replace in actualitem: - try: - to_be_replaced = replace[0] - actualitem = replace[2] - if actualitem.endswith("}$"): - actualitem = actualitem[:-2] - - except IndexError: - self.logger.info("[WARNING] Indexerror") - continue - - #self.logger.info(f"\n\nTMPITEM: {actualitem}\n\n") - #actualitem = parse_wrapper_start(actualitem) - #self.logger.info(f"\n\nTMPITEM2: {actualitem}\n\n") - - try: - itemlist = json.loads(actualitem) - if len(itemlist) > minlength: - minlength = len(itemlist) - - if len(itemlist) > curminlength: - curminlength = len(itemlist) - - except json.decoder.JSONDecodeError as e: - self.logger.info("JSON Error (replace): %s in %s" % (e, actualitem)) - - replacements[to_be_replaced] = actualitem + for tmp_file_split in json.loads(parameter["value"]): + file_value = self.get_file(tmp_file_split) + resultarray.append(file_value) - # Parses the data as string with length, split etc. before moving on. + isfile = True + except KeyError as e: + self.logger.info("(2) SCHEMA ERROR IN FILE HANDLING: %s" % e) + except json.decoder.JSONDecodeError as e: + self.logger.info("(2) JSON ERROR IN FILE HANDLING: %s" % e) + if not isfile: + tmpitem = tmpitem.replace("\\\\", "\\", -1) + resultarray.append(tmpitem) - #self.logger.info("In second part of else: %s" % (len(itemlist))) - # This is a result array for JUST this value.. - # What if there are more? - resultarray = [] - for i in range(0, curminlength): - tmpitem = json.loads(json.dumps(parameter["value"])) - for key, value in replacements.items(): - replacement = value - try: - replacement = json.dumps(json.loads(value)[i]) - except IndexError as e: - self.logger.info(f"[ERROR] Failed handling value parsing with index: {e}") - pass + # With this parameter ready, add it to... a greater list of parameters. Rofl + if len(resultarray) == 0: + self.logger.info("[WARNING] Returning empty array because the array length to be looped is 0 (0)") + self.action_result["status"] = "SUCCESS" + self.action_result["result"] = "[]" + self.send_result(self.action_result, headers, stream_path) + return - if replacement.startswith("\"") and replacement.endswith("\""): - replacement = replacement[1:len(replacement)-1] - #except json.decoder.JSONDecodeError as e: + #self.logger.info("RESULTARRAY: %s" % resultarray) + if resultarray not in multi_execution_lists: + multi_execution_lists.append(resultarray) - #self.logger.info("REPLACING %s with %s" % (key, replacement)) - #replacement = parse_wrapper_start(replacement) - tmpitem = tmpitem.replace(key, replacement, -1) - try: - tmpitem = parse_liquid(tmpitem, self) - except Exception as e: - self.logger.info(f"[WARNING] Failed liquid parsing in loop (2): {e}") - - - # This code handles files. - isfile = False - try: - if parameter["schema"]["type"] == "file" and len(value) > 0: - self.logger.info("(2) SHOULD HANDLE FILE IN MULTI. Get based on value %s" % parameter["value"]) - - for tmp_file_split in json.loads(parameter["value"]): - file_value = self.get_file(tmp_file_split) - resultarray.append(file_value) - - - isfile = True - except KeyError as e: - self.logger.info("(2) SCHEMA ERROR IN FILE HANDLING: %s" % e) - except json.decoder.JSONDecodeError as e: - self.logger.info("(2) JSON ERROR IN FILE HANDLING: %s" % e) - - if not isfile: - tmpitem = tmpitem.replace("\\\\", "\\", -1) - resultarray.append(tmpitem) - - # With this parameter ready, add it to... a greater list of parameters. Rofl - if len(resultarray) == 0: - self.logger.info("[WARNING] Returning empty array because the array length to be looped is 0 (0)") - self.action_result["status"] = "SUCCESS" - self.action_result["result"] = "[]" - self.send_result(self.action_result, headers, stream_path) - return - - #self.logger.info("RESULTARRAY: %s" % resultarray) - if resultarray not in multi_execution_lists: - multi_execution_lists.append(resultarray) - - multi_parameters[parameter["name"]] = resultarray + multi_parameters[parameter["name"]] = resultarray else: # Parses things like int(value) #self.logger.info("[DEBUG] Normal parsing (not looping)")#with data %s" % value) @@ -3662,7 +3560,6 @@ class AppBase: self.logger.info("SCHEMA ERROR IN FILE HANDLING: %s" % e) - #remove_params.append(parameter["name"]) # Fix lists here # FIXME: This doesn't really do anything anymore #self.logger.info("[DEBUG] CHECKING multi execution list: %d!" % len(multi_execution_lists)) @@ -3941,7 +3838,7 @@ class AppBase: # 2. Find the right value from the parsed multi_params #self.logger.info("[INFO] Running WITH loop. MULTI: %s", multi_parameters) - self.logger.info("[INFO] Running WITH loop") + self.logger.info("[INFO] Running WITH loop") json_object = False #results = await self.run_recursed_items(func, multi_parameters, {}) results = self.run_recursed_items(func, multi_parameters, {}) From d9d7611ccb9cc4827d1c5329d618bae100d4760f Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Mon, 29 Apr 2024 11:17:01 +0000 Subject: [PATCH 13/16] removing awful white background color --- frontend/src/views/Admin.jsx | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 327bfa55..a678bf3b 100755 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -4630,7 +4630,6 @@ If you're interested, please let me know a time that works for you, or set up a style={{ textAlign: "center", padding: "20px", - backgroundColor: "#f0f0f0", color: "#666", borderRadius: "5px", }} @@ -4741,14 +4740,13 @@ If you're interested, please let me know a time that works for you, or set up a /> {webHooks === undefined || webHooks === null || webHooks.length === 0 ? (
+ style={{ + textAlign: "center", + padding: "20px", + color: "#666", + borderRadius: "5px", + }} + > No webhooks found.
) : ( @@ -4901,7 +4899,6 @@ If you're interested, please let me know a time that works for you, or set up a style={{ textAlign: "center", padding: "20px", - backgroundColor: "#f0f0f0", color: "#666", borderRadius: "5px", }} From 74e90a083e8745bbb5680d55710c08cbdf4f0a5d Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 29 Apr 2024 14:37:26 +0200 Subject: [PATCH 14/16] New 1.4.0 page view looks and bugfixes --- frontend/src/components/AppGrid.jsx | 2300 ++++++++++++++++++++---- frontend/src/components/Billing.jsx | 18 +- frontend/src/components/Branding.jsx | 4 +- frontend/src/components/CacheView.jsx | 60 +- frontend/src/components/Files.jsx | 30 +- frontend/src/components/Priorities.jsx | 4 +- frontend/src/components/Priority.jsx | 4 +- frontend/src/views/Search.jsx | 460 +++-- 8 files changed, 2302 insertions(+), 578 deletions(-) diff --git a/frontend/src/components/AppGrid.jsx b/frontend/src/components/AppGrid.jsx index 5bb08119..c75f2449 100644 --- a/frontend/src/components/AppGrid.jsx +++ b/frontend/src/components/AppGrid.jsx @@ -1,289 +1,1912 @@ -import React, {useEffect, useState} from 'react'; +import React, { useEffect, useState, useRef } from "react"; -import theme from '../theme.jsx'; -import ReactGA from 'react-ga4'; -import {Link} from 'react-router-dom'; -import { removeQuery } from '../components/ScrollToTop.jsx'; +import theme from "../theme.jsx"; +import ReactGA from "react-ga4"; +import { Link } from "react-router-dom"; +import { removeQuery } from "../components/ScrollToTop.jsx"; +import { useMemo } from "react"; -import { - Search as SearchIcon, - CloudQueue as CloudQueueIcon, - Code as CodeIcon -} from '@mui/icons-material'; +import { Tabs, Tab } from "@mui/material"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; +import ExpandLessIcon from "@mui/icons-material/ExpandLess"; +import { + Search as SearchIcon, + CloudQueue as CloudQueueIcon, + Code as CodeIcon, +} from "@mui/icons-material"; +import { toast } from "react-toastify" +import ClearIcon from '@mui/icons-material/Clear'; +import Box from '@mui/material/Box'; -import algoliasearch from 'algoliasearch/lite'; -import { InstantSearch, Configure, connectSearchBox, connectHits, connectHitInsights } from 'react-instantsearch-dom'; +import noImage from "../no_image.png" -import aa from 'search-insights' +import CircularProgress from '@mui/material/CircularProgress'; -import { - Zoom, - Grid, - Paper, - TextField, - ButtonBase, - InputAdornment, - Typography, - Button, - Tooltip -} from '@mui/material'; +import algoliasearch from "algoliasearch/lite"; +import { + InstantSearch, + Configure, + connectSearchBox, + connectHits, + connectHitInsights, + RefinementList, + ClearRefinements, + connectStateResults +} from "react-instantsearch-dom"; -const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") +import aa from "search-insights"; + +import "./FilterCSS.css"; + +import { + Zoom, + Grid, + Paper, + TextField, + ButtonBase, + InputAdornment, + Typography, + Button, + Tooltip, +} from "@mui/material"; + +const searchClient = algoliasearch( + "JNSS5CFDZZ", + "db08e40265e2941b9a7d8f644b6e5240" +); //const searchClient = algoliasearch("L55H18ZINA", "a19be455e7e75ee8f20a93d26b9fc6d6") -const AppGrid = props => { - const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, userdata, isHeader } = props + +const AppGrid = (props) => { + const { + maxRows, + showName, + showSuggestion, + isMobile, + globalUrl, + parsedXs, + isHeader, + } = props; const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; - const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows - const xs = parsedXs === undefined || parsedXs === null ? isMobile ? 6 : 2 : parsedXs - //const [apps, setApps] = React.useState([]); - //const [filteredApps, setFilteredApps] = React.useState([]); - const [formMail, setFormMail] = React.useState(""); - const [message, setMessage] = React.useState(""); - const [formMessage, setFormMessage] = React.useState(""); + const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows; + const xs = + parsedXs === undefined || parsedXs === null ? (isMobile ? 6 : 3) : parsedXs; - const buttonStyle = {borderRadius: 30, height: 50, width: 220, margin: isMobile ? "15px auto 15px auto" : 20, fontSize: 18,} + const [formMail, setFormMail] = React.useState(""); + const [message, setMessage] = React.useState(""); + const [formMessage, setFormMessage] = React.useState(""); - const innerColor = "rgba(255,255,255,0.65)" - const borderRadius = 3 - window.title = "Shuffle | Apps | Find and integrate any app" + const buttonStyle = { + borderRadius: 30, + height: 50, + width: 220, + margin: isMobile ? "15px auto 15px auto" : 20, + fontSize: 18, + }; + const innerColor = "rgba(255,255,255,0.65)"; + const borderRadius = 3; + window.title = "Shuffle | Apps | Find and integrate any app"; - const submitContact = (email, message) => { - const data = { - "firstname": "", - "lastname": "", - "title": "", - "companyname": "", - "email": email, - "phone": "", - "message": message, - } - - const errorMessage = "Something went wrong. Please contact frikky@shuffler.io directly." - fetch(globalUrl+"/api/v1/contact", { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), - }) - .then(response => response.json()) - .then(response => { - if (response.success === true) { - setFormMessage(response.reason) - //toast("Thanks for submitting!") - } else { - setFormMessage(errorMessage) - } + const submitContact = (email, message) => { + const data = { + firstname: "", + lastname: "", + title: "", + companyname: "", + email: email, + phone: "", + message: message, + }; - setFormMail("") - setMessage("") + const errorMessage = + "Something went wrong. Please contact frikky@shuffler.io directly."; + + fetch(globalUrl + "/api/v1/contact", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), }) - .catch(error => { - setFormMessage(errorMessage) - console.log(error) - }); - } + .then((response) => response.json()) + .then((response) => { + if (response.success === true) { + setFormMessage(response.reason); + //toast("Thanks for submitting!") + } else { + setFormMessage(errorMessage); + } - const SearchBox = ({currentRefinement, refine, isSearchStalled} ) => { - var defaultSearch = "" - //useEffect(() => { - if (window !== undefined && window.location !== undefined && window.location.search !== undefined && window.location.search !== null) { - const urlSearchParams = new URLSearchParams(window.location.search) - const params = Object.fromEntries(urlSearchParams.entries()) - const foundQuery = params["q"] - if (foundQuery !== null && foundQuery !== undefined) { - console.log("Got query: ", foundQuery) - refine(foundQuery) - defaultSearch = foundQuery - } - } - //}, []) + setFormMail(""); + setMessage(""); + }) + .catch((error) => { + setFormMessage(errorMessage); + console.log(error); + }); + }; - return ( -
- - - - ), - }} - autoComplete='off' - type="search" - color="primary" - placeholder="Find Apps..." - id="shuffle_search_field" - onChange={(event) => { - // Remove "q" from URL - removeQuery("q") + const SearchBox = ({ currentRefinement, refine, isSearchStalled }) => { + var defaultSearch = ""; - refine(event.currentTarget.value) - }} - limit={5} - /> - {/*isSearchStalled ? 'My search is stalled' : ''*/} - - ) - } + var [searchQuery, setSearchQuery] = useState(""); - var workflowDelay = -50 - const Hits = ({ hits, insights }) => { - const [mouseHoverIndex, setMouseHoverIndex] = useState(-1) - var counted = 0 + //useEffect(() => { + if ( + window !== undefined && + window.location !== undefined && + window.location.search !== undefined && + window.location.search !== null + ) { + const urlSearchParams = new URLSearchParams(window.location.search); + const params = Object.fromEntries(urlSearchParams.entries()); + const foundQuery = params["q"]; + if (foundQuery !== null && foundQuery !== undefined) { + console.log("Got query: ", foundQuery); + refine(foundQuery); + defaultSearch = foundQuery; + searchQuery = foundQuery + } + } + //}, []) - //console.log(hits) - //var curhits = hits - //if (hits.length > 0 && defaultApps.length === 0) { - // setDefaultApps(hits) - //} - //const [defaultApps, setDefaultApps] = React.useState([]) - //console.log(hits) - //if (hits.length > 0 && hits.length !== innerHits.length) { - // setInnerHits(hits) - //} + const handleSearch = () => { + refine(searchQuery.trim()); + }; - return ( - - {hits.map((data, index) => { + return ( +
+ + + + ), + endAdornment: ( + + {searchQuery.length > 0 && ( + { + setSearchQuery('') + removeQuery("q"); + refine('') + }} + /> + )} + + + ), - workflowDelay += 50 + }} + autoComplete="off" + color="primary" + placeholder="Find Apps" + id="shuffle_search_field" + onChange={(event) => { + setSearchQuery(event.currentTarget.value); + removeQuery("q"); + refine(event.currentTarget.value); + }} + limit={5} + /> + {/*isSearchStalled ? 'My search is stalled' : ''*/} + + ); + }; - const paperStyle = { - backgroundColor: index === mouseHoverIndex ? "rgba(255,255,255,0.8)" : theme.palette.inputColor, - color: index === mouseHoverIndex ? theme.palette.inputColor : "rgba(255,255,255,0.8)", - border: `1px solid ${innerColor}`, - padding: isHeader ? null : 15, - cursor: "pointer", - position: "relative", - minHeight: 116, - } - - if (counted === 12/xs*rowHandler) { - return null - } + const [currTab, setCurrTab] = useState(0); - counted += 1 - var parsedname = "" - for (var key = 0; key < data.name.length; key++) { - var character = data.name.charAt(key) - if (character === character.toUpperCase()) { - //console.log(data.name[key], data.name[key+1]) - if (data.name.charAt(key+1) !== undefined && data.name.charAt(key+1) === data.name.charAt(key+1).toUpperCase()) { - } else { - parsedname += " " - } - } + const handleTabChange = (event, newValue) => { + setCurrTab(newValue); + }; - parsedname += character - } - - parsedname = (parsedname.charAt(0).toUpperCase()+parsedname.substring(1)).replaceAll("_", " ") - const appUrl = isCloud ? `/apps/${data.objectID}?queryID=${data.__queryID}` : `https://shuffler.io/apps/${data.objectID}?queryID=${data.__queryID}` - return ( - - - - { - setMouseHoverIndex(index) - /* - ReactGA.event({ - category: "app_grid_view", - action: `search_bar_click`, - label: "", - }) - */ - }} onMouseOut={() => { - setMouseHoverIndex(-1) - }} onClick={() => { - if (isCloud) { - ReactGA.event({ - category: "app_grid_view", - action: `app_${parsedname}_${data.id}_click`, - label: "", - }) - } + const [isLoggedIn, setIsLoggedIn] = useState(false); + const [userInfo, setUserInfo] = useState([]); - //const searchClient = algoliasearch("L55H18ZINA", "a19be455e7e75ee8f20a93d26b9fc6d6") - console.log(searchClient) - aa('init', { - appId: searchClient.appId, - apiKey: searchClient.transporter.queryParameters["x-algolia-api-key"] - }) + useEffect(() => { + var baseurl = globalUrl; + fetch(baseurl + "/api/v1/getinfo", { + credentials: "include", + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(response => response.json()) + .then(responseJson => { + if (responseJson.success) { + setIsLoggedIn(true); + setUserInfo(responseJson); + } + }) + .catch(error => { + console.log("Failed login check: ", error); + }); + }, []); - const timestamp = new Date().getTime() - aa('sendEvents', [ - { - eventType: 'click', - eventName: 'Product Clicked', - index: 'appsearch', - objectIDs: [data.objectID], - timestamp: timestamp, - queryID: data.__queryID, - positions: [data.__position], - userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id, - } - ]) + //Component to fetch all app from the algolia + const Hits = ({ + hits, + insights, + setIsAnyAppActivated + }) => { + const [mouseHoverIndex, setMouseHoverIndex] = useState(-1); + var counted = 0; + const [hoverEffect, setHoverEffect] = useState(-1); - }}> - - {data.name} - -
- {index === mouseHoverIndex || showName === true ? - parsedname - : - null - } - {data.generated ? - - {data.invalid ? - - : - - } - - : - - - - } - - - - - ) - })} - - ) - } + const normalizedString = (name) => { + if (typeof name === 'string') { + return name.replace(/_/g, ' '); + } else { + return name; + } + }; - const CustomSearchBox = connectSearchBox(SearchBox) - const CustomHits = connectHits(Hits) - //const CustomHits = connectHitInsights(aa)(Hits) - const selectButtonStyle = { - minWidth: 150, - maxWidth: 150, - minHeight: 50, - } + const [allActivatedAppIds, setAllActivatedAppIds] = useState(() => { + const storedApps = isLoggedIn && localStorage.getItem('allActivatedAppIds'); + return storedApps ? JSON.parse(storedApps) : userInfo.active_apps; + }); + const [isAppActivated, setIsAppActivated] = useState(false); + const [isActivateAppSuccess, setIsActivateAppSuccess] = useState(false); - return ( -
- {/* + //Function for activation and deactivation of app + const handleActivateButton = (event, data, type) => { + event.preventDefault(); + if (!isLoggedIn) { + toast.error("Please log in to your account to activate the app.") + return; + } + if (type === "activate") { + toast.success(`The ${normalizedString(data.name)} app is activating. Please wait...`); + } + if (type === "deactivate") { + toast.success(`The ${normalizedString(data.name)} app is deactivating. Please wait...`); + } + + const baseURL = globalUrl; + const url = `${baseURL}/api/v1/apps/${data.objectID}/${type}`; + + fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => response.json()) + .then((responseJson) => { + if (responseJson.success === false) { + toast.error(responseJson.reason); + } else { + toast.success(`App ${type}d Successfully!`); + if (type === 'activate') { + setAllActivatedAppIds(prev => [...prev, data.objectID]); + setIsAnyAppActivated(true); + } + if (type === 'deactivate') { + const updatedIds = allActivatedAppIds.filter(id => id !== data.objectID); + setAllActivatedAppIds(updatedIds); + } + setIsActivateAppSuccess(prev => !prev); + } + }) + .catch(error => { + console.log("app error: ", error.toString()); + }); + } + + useEffect(() => { + isLoggedIn && localStorage.setItem('allActivatedAppIds', JSON.stringify(allActivatedAppIds)); + }, [allActivatedAppIds]); + + + const memoizedHits = useMemo(() => { + return hits.map((data, index) => { + let workflowDelay = 0; + const isHeader = true; + const paperStyle = { + color: "rgba(241, 241, 241, 1)", + padding: isHeader ? null : 15, + cursor: "pointer", + maxWidth: 339, + maxHeight: 96, + borderRadius: 8, + transition: 'background-color 0.3s ease', + backgroundColor: "rgba(26, 26, 26, 1)", + }; + + const appUrl = + isCloud + ? `/apps/${data.objectID}?queryID=${data.__queryID}` + : `https://shuffler.io/apps/${data.objectID}?queryID=${data.__queryID}`; + + //check if appExist in userInfo.active_app or not. + return ( + + + + { + setMouseHoverIndex(index); + }} + onMouseOut={() => { + setMouseHoverIndex(-1); + }} + > + + ) : ( + + )} +
+ )} +
+
+
+ + + + + + ); + }); + }, [hits, mouseHoverIndex, isActivateAppSuccess, allActivatedAppIds]); + + return ( + +
+ {memoizedHits} +
+
+ ); + }; + + var workflowDelay = -50; + + const CustomClearRefinements = connectStateResults(({ searchResults, ...rest }) => { + const hasFilters = searchResults && searchResults.nbHits !== searchResults.nbSortedHits; + return Clear All }} {...rest} disabled={!hasFilters} />; + }); + + //Component to Filter all apps base on category + const FilterAllAppsByCategory = () => { + const [isRefinementListExpanded, setIsRefinementListExpanded] = + useState(true); + + const toggleRefinementList = () => { + setIsRefinementListExpanded((prevState) => !prevState); + }; + + const categoryButtonStyling = { + cursor: "pointer", + color: "white", + border: "none", + backgroundColor: "transparent", + fontSize: 16, + display: "flex", + width: "100%", + height: 30, + flexDirection: "row", + textTransform: 'none', + fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" + } + + return ( +
+ + + {isRefinementListExpanded && ( + <> + + + + )} +
+ ); + }; + + //Component to filter all apps base on Action label + const FilterByActionLabel = () => { + const [isActionLabelExpanded, setIsActionLabelExpanded] = useState(false); + useState(false); + + const toogleActionLabel = () => { + setIsActionLabelExpanded((prevState) => !prevState); + }; + + const actionLabelButtonStyling = { + cursor: "pointer", + color: "white", + border: "none", + backgroundColor: "transparent", + fontSize: 16, + display: "flex", + flexDirection: "row", + width: "100%", + height: 30, + textTransform: 'none', + fontWeight: 400 + } + + + return ( +
+ + + {isActionLabelExpanded && ( + <> + + + + )} +
+ ); + }; + + //component to filter all apps base on the created with like 'App Editor' or 'Python' + const FilterByCreatedWith = () => { + const [isCreatedWithExpanded, setIsCreatedWithExpanded] = useState(false); + + const toogleCreatedWith = () => { + setIsCreatedWithExpanded((prevState) => !prevState); + }; + + const transformRefinementListItems = items => + items.map(item => ({ + ...item, + label: item.label === 'true' ? 'App Editor' : 'Python', + })); + + const createdWithButtonStyling = { + cursor: "pointer", + color: "white", + border: "none", + backgroundColor: "transparent", + fontSize: 16, + display: "flex", + flexDirection: "row", + alignItems: "center", + width: "100%", + height: 30, + textTransform: 'none', + fontWeight: 400 + } + + return ( +
+ + + {isCreatedWithExpanded && ( + <> + + + + )} +
+ ); + }; + + const FilterCreatedBy = () => { + const [isCreatedByExpanded, setIscreatedByExpanded] = useState(false); + useState(false); + + const toogleCreatedBy = () => { + setIscreatedByExpanded((prevState) => !prevState); + }; + + const createdByButtonStyling = { + cursor: "pointer", + color: "white", + border: "none", + backgroundColor: "transparent", + fontSize: 16, + display: "flex", + flexDirection: "row", + alignItems: "center", + whiteSpace: "nowrap", + width: "100%", + height: 30, + textTransform: 'none', + opacity: '0.5' + } + + return ( +
+ + + {isCreatedByExpanded && ( + <> + {/* */} + + {/* */} + + + )} +
+ ); + }; + + const FilterApps = () => { + return ( +
+ + Filter By + + + + + +
+ ); + }; + + + + const boxStyle = { + color: "white", + flex: "1", + marginLeft: isHeader ? null : 10, + marginRight: isHeader ? null : 10, + paddingLeft: isHeader ? null : 30, + paddingRight: isHeader ? null : 30, + paddingBottom: isHeader ? null : 30, + display: "flex", + flexDirection: "column", + overflowX: "visible", + backgroundColor: "rgba(33, 33, 33, 1)", + borderRadius: 16, + marginTop: 24, + width: 741, + height: 741, + }; + + + //Component to display all apps. + const AllApps = ({ setIsAnyAppActivated }) => { + + return ( +
+ + +
+ ); + }; + + //Search box for the orgs and users apps + const SearchBoxForOrgsAndUsersApp = ({ searchQuery, setSearchQuery }) => { + + return ( +
+ + + + ), + endAdornment: ( + + {searchQuery.length > 0 && ( + setSearchQuery('')} + /> + )} + + + ), + }} + autoComplete="off" + color="primary" + placeholder="Find Apps" + id="shuffle_search_field" + onChange={(event) => { + setSearchQuery(event.currentTarget.value); + }} + limit={5} + /> + {/*isSearchStalled ? 'My search is stalled' : ''*/} + + ) + } + + + + const [selectedCategoryForUsersAndOgsApps, setselectedCategoryForUsersAndOgsApps] = useState([]); + const [selectedTagsForUserAndOrgApps, setSelectedTagsForUserAndOrgApps] = useState([]); + const [isCategoreListExpanded, setIsCategoryListExpanded] = useState(true); + + const toogleCategoryList = () => { + setIsCategoryListExpanded((prevState) => !prevState); + }; + + //Component to display category List for User and Orgs app + const FilterUsersAndOrgsAppByCategory = ({ userAndOrgsApp }) => { + + //Display top 9 category from the database + + const findTopCategories = () => { + const categoryCountMap = {}; + + // Check if userAndOrgsApp is an array before iterating over it and Find top 10 Category from the apps + if (Array.isArray(userAndOrgsApp)) { + userAndOrgsApp.forEach((app) => { + const categories = app.categories; + + if (categories && categories.length > 0) { + categories.forEach((category) => { + categoryCountMap[category] = (categoryCountMap[category] || 0) + 1; + }); + } + }); + + const categoryArray = Object.keys(categoryCountMap).map((category) => ({ + category, + count: categoryCountMap[category], + })); + + categoryArray.sort((a, b) => b.count - a.count); + + const topCategories = categoryArray.slice(0, 9); + + return topCategories; + } + }; + + const topCategories = findTopCategories(); + + const handleCheckboxChange = (category) => { + if (selectedCategoryForUsersAndOgsApps.includes(category)) { + setselectedCategoryForUsersAndOgsApps(selectedCategoryForUsersAndOgsApps.filter((item) => item !== category)); + } else { + setselectedCategoryForUsersAndOgsApps([...selectedCategoryForUsersAndOgsApps, category]); + } + }; + + const handleClearFilter = () => { + setselectedCategoryForUsersAndOgsApps([]); + }; + + const categorysButtonStyling = { + cursor: "pointer", + color: "white", + border: "none", + backgroundColor: "transparent", + fontSize: 16, + display: "flex", + width: "100%", + height: 30, + flexDirection: "row", + textTransform: 'none', + fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" + } + return ( +
+ + {isCategoreListExpanded && topCategories && topCategories.length > 0 && ( +
+ {topCategories.map((data, index) => ( + + + ))} + + +
+ )} +
+ ); + }; + + const [isActionLabelExpanded, setIsActionLabelExpanded] = useState(false); + const toogleActionLabel = () => { + setIsActionLabelExpanded((prevState) => !prevState); + }; + + const FilterUsersAndOrgsAppByActionLabel = ({ userAndOrgsApp }) => { + + const findTopTags = () => { + const tagCountMap = {}; + + // Check if userAndOrgsApp is an array before iterating over it and Find top 10 tags from the apps + if (Array.isArray(userAndOrgsApp)) { + userAndOrgsApp.forEach((app) => { + const tags = app.tags; + + if (tags && tags.length > 0) { + tags.forEach((tag) => { + tagCountMap[tag] = (tagCountMap[tag] || 0) + 1; + }); + } + }); + } + + const tagArray = Object.keys(tagCountMap).map((tag) => ({ + tag, + count: tagCountMap[tag], + })); + + tagArray.sort((a, b) => b.count - a.count); + + const topTags = tagArray.slice(0, 9); + + return topTags; + }; + + const topTags = findTopTags(); + const [selectedCategories, setSelectedCategories] = useState([]); + + const handleCheckboxChange = (index) => { + const category = topTags[index].tag; + const updatedCheckboxStates = [...selectedTagsForUserAndOrgApps]; + + if (updatedCheckboxStates.includes(category)) { + setSelectedTagsForUserAndOrgApps(updatedCheckboxStates.filter((item) => item !== category)); + } else { + setSelectedTagsForUserAndOrgApps([...updatedCheckboxStates, category]); + } + }; + + const handleClearFilter = () => { + setSelectedTagsForUserAndOrgApps([]); + }; + + const actionLabelButtonStyling = { + cursor: "pointer", + color: "white", + border: "none", + backgroundColor: "transparent", + fontSize: 16, + display: "flex", + flexDirection: "row", + width: "100%", + height: 30, + textTransform: 'none', + marginBottom: isActionLabelExpanded && 16, + fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" + } + + return ( +
+ + + {isActionLabelExpanded && topTags && topTags.length > 0 && ( + <> + {topTags.map((data, index) => ( + + + ))} + + + + )} +
+ ); + }; + + const [selectedOptionOfCreatedWith, setSelectedOptionOfCreatedWith] = useState([]); + const [isCreatedWithExpanded, setIsCreatedWithExpanded] = useState(false); + + const toogleCreatedWith = () => { + setIsCreatedWithExpanded((prevState) => !prevState); + }; + const FilterUsersAndOrgsAppByCreatedWith = () => { + + const AppCreatedWithOptions = ['App Editor', 'Python'] + + const handleCheckboxChange = (index) => { + const category = AppCreatedWithOptions[index]; + const updatedCheckboxStates = [...selectedOptionOfCreatedWith]; + if (updatedCheckboxStates.includes(category)) { + setSelectedOptionOfCreatedWith(updatedCheckboxStates.filter((item) => item !== category)); + } else { + setSelectedOptionOfCreatedWith([...updatedCheckboxStates, category]); + } + }; + + + const handleClearFilter = () => { + setSelectedOptionOfCreatedWith([]); + }; + + const createdWithButtonStyling = { + cursor: "pointer", + color: "white", + border: "none", + backgroundColor: "transparent", + fontSize: 16, + display: "flex", + flexDirection: "row", + alignItems: "center", + width: "100%", + height: 30, + textTransform: 'none', + marginBottom: isCreatedWithExpanded && 16, + fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" + } + + return ( +
+ + + {isCreatedWithExpanded && ( + <> + {AppCreatedWithOptions.map((data, index) => ( + + + ))} + + + + )} +
+ ); + }; + + const FilterUsersAndOrgsAppCreatedBy = () => { + + const [isCreatedByExpanded, setIscreatedByExpanded] = useState(false); + useState(false); + + const toogleCreatedBy = () => { + setIscreatedByExpanded((prevState) => !prevState); + }; + const [isButtonDisable, setIsButtonDisable] = useState(true) + + const createdByButtonStyling = { + cursor: "pointer", + color: "white", + border: "none", + backgroundColor: isButtonDisable ? '#3c3c3c. ' : "transparent", + fontSize: 16, + display: "flex", + flexDirection: "row", + alignItems: "center", + whiteSpace: "nowrap", + width: "100%", + height: 30, + textTransform: 'none', + opacity: '0.5' + } + + return ( +
+ + + {isCreatedByExpanded && ( + <> + + + + )} +
+ ); + }; + + const FilterUserAndOrgApps = () => { + + const [userAndOrgsApp, setUserAndOrgsApp] = useState([]); + + useEffect(() => { + if (currTab === 2) { + const baseUrl = globalUrl; + const userAppsUrl = `${baseUrl}/api/v1/users/apps`; + fetch(userAppsUrl, { + method: "GET", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => response.json()) + .then((data) => { + setUserAndOrgsApp(data); + }) + .catch((err) => { + console.error("Error fetching user apps:", err); + }); + } else if (currTab === 1) { + const baseUrl = globalUrl; + const appsUrl = `${baseUrl}/api/v1/apps`; + fetch(appsUrl, { + method: "GET", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => response.json()) + .then((data) => { + setUserAndOrgsApp(data); + }) + .catch((err) => { + console.error("Error fetching apps:", err); + }); + } + }, [currTab]); + + + return ( +
+ {isLoggedIn === true && ( +
+ + Filter By + + + + + +
+ )} +
+ ) + } + const [isLoading, setIsLoading] = useState(false) + useEffect(() => { + if (currTab) { + setselectedCategoryForUsersAndOgsApps([]); + setSelectedTagsForUserAndOrgApps([]); + setSelectedOptionOfCreatedWith([]); + setIsCategoryListExpanded(true); + setIsActionLabelExpanded(false); + setIsCreatedWithExpanded(false); + } + if (currTab === 1 || currTab === 2) { + setIsLoading(true); + } + + }, [currTab]) + + //Component to fetch all apps created by user and Org + const UserAndOrgApps = () => { + + const [searchQuery, setSearchQuery] = useState(""); + const [userAndOrgAppData, setUserAndOrgAppData] = useState([]) + + const allActivatedAppIdsString = localStorage.getItem('allActivatedAppIds'); + const allActivatedAppIds = allActivatedAppIdsString ? JSON.parse(allActivatedAppIdsString) : []; + const latestActivatedAppId = allActivatedAppIds.length > 0 ? allActivatedAppIds[allActivatedAppIds.length - 1] : null; + + useEffect(() => { + if (currTab === 2 && isLoggedIn != undefined && isLoggedIn != null && isLoggedIn === true) { + const baseUrl = globalUrl; + const URL = `${baseUrl}/api/v1/users/apps`; + fetch(URL, { + method: "GET", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => { + return response.json(); + }) + .then((data) => { + setUserAndOrgAppData(data) + setIsLoading(false) + }) + .catch((err) => { + console.error("Error fetching user apps:", err); + }); + } + else if (currTab === 1 && isLoggedIn != undefined && isLoggedIn != null && isLoggedIn === true) { + const baseUrl = globalUrl; + const URL = `${baseUrl}/api/v1/apps`; + fetch(URL, { + method: "GET", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => { + return response.json(); + }) + .then((data) => { + setUserAndOrgAppData(data) + setIsLoading(false); + }) + .catch((err) => { + console.error("Error fetching user apps:", err); + }); + } + }, []) + + //Search app base on app name, category and tag + const filteredUserAppdata = Array.isArray(userAndOrgAppData) ? userAndOrgAppData.filter((app) => { + const matchesSearchQuery = ( + searchQuery === "" || + app.name.toLowerCase().includes(searchQuery.toLowerCase()) || + (app.tags && app.tags.some(tag => + tag.toLowerCase().includes(searchQuery.toLowerCase()) + )) || + (app.categories && app.categories.some((category) => + category.toLowerCase().includes(searchQuery.toLowerCase()) + )) + ); + + const matchesSelectedCategories = ( + selectedCategoryForUsersAndOgsApps.length === 0 || + (app.categories && app.categories.some(category => + selectedCategoryForUsersAndOgsApps.includes(category) + )) + ); + const matchesSelectedTags = ( + selectedTagsForUserAndOrgApps.length === 0 || + (app.tags && selectedTagsForUserAndOrgApps.some(tag => + app.tags.includes(tag) + )) + ); + + const matchesSelectedOption = ( + selectedOptionOfCreatedWith.length === 0 || + selectedOptionOfCreatedWith.includes('App Editor') && app.generated === true || + selectedOptionOfCreatedWith.includes('Python') && app.generated === false + ); + + return matchesSearchQuery && matchesSelectedCategories && matchesSelectedTags && matchesSelectedOption; + }) : []; + + + const [mouseHoverIndex, setMouseHoverIndex] = useState(-1); + var counted = 0; + + const memoizedHits = useMemo(() => { + return filteredUserAppdata.map((data, index) => { + const isMouseOverOnCloudIcon = false; + const xs = 12; + const rowHandler = 12; + const searchClient = {}; + const userdata = {}; + + const paperStyle = { + backgroundColor: "#1A1A1A", + color: "rgba(241, 241, 241, 1)", + padding: isHeader ? null : 15, + cursor: "pointer", + position: "relative", + width: 339, + height: 96, + borderRadius: 8, + }; + + var parsedname = ""; + for (var key = 0; key < data.name.length; key++) { + var character = data.name.charAt(key); + if (character === character.toUpperCase()) { + if ( + data.name.charAt(key + 1) !== undefined && + data.name.charAt(key + 1) === + data.name.charAt(key + 1).toUpperCase() + ) { + } else { + parsedname += " "; + } + } + parsedname += character; + } + + parsedname = ( + parsedname.charAt(0).toUpperCase() + parsedname.substring(1) + ).replaceAll("_", " "); + + const normalizedString = (name) => { + if (typeof name === 'string') { + return name.replace(/_/g, ' '); + } else { + return name; + } + }; + + const appUrl = + isCloud === false + ? `/apps/${data.id}` + : `https://shuffler.io/apps/${data.id}`; + + return ( + + + + { + setMouseHoverIndex(index); + }} + onMouseOut={() => { + setMouseHoverIndex(-1); + }} + > + + {data.name} +
+
+ {normalizedString(data.name)} +
+
+ {data.categories !== null + ? normalizedString(data.categories).join(", ") + : "NA"} +
+
+ {data.tags && + data.tags.map((tag, tagIndex) => ( + + {normalizedString(tag)} + {tagIndex < data.tags.length - 1 ? ", " : ""} + + ))} +
+ {/* )} */} +
+
+
+
+
+
+ ); + }); + }, [filteredUserAppdata, latestActivatedAppId]); + + + return ( +
+ {isLoggedIn ? ( +
+ {isLoading ? : ( +
+ + +
+ {memoizedHits} +
+
+
+ )} +
+ ) : ( +
+ Please login to your account first to view {`${currTab === 1 ? "Organization" : "My"}`} Apps.
+ Or signup to create a new account.
+
+ )} +
+ ); + }; + + + const AppTab = () => { + + const [isAnyAppActivated, setIsAnyAppActivated] = useState(false); + + return ( +
+
+ + + {isAnyAppActivated && }Organization Apps
+ sx={{ + color: currTab === 1 ? "#F86743" : "inherit", + border: 'none', + height: 44, + fontSize: 16, + flex: 1, + textTransform: 'none', + fontWeight: 400, + paddingBottom: 3, + fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)", + }} + > + + + + {currTab === 0 ? ( + + ) : currTab === 1 || currTab === 2 ? ( + + ) : null} +
+
+ ); + }; + + const CustomSearchBox = connectSearchBox(SearchBox); + const CustomHits = connectHits(Hits); + return ( +
+ {/*
*/} -
- -
- -
- - -
- {showSuggestion === true ? -
- - Can't find what you're looking for? - -
- setFormMail(e.target.value)} - /> - setMessage(e.target.value)} - /> -
- - {formMessage} -
- : null - } - - - - Search by - - - Algolia logo - - -
-
- ) -} +
+ +
+ {currTab === 0 ? : } + +
+ {/* */} + +
+ {showSuggestion === true ? ( +
+ + Can't find what you're looking for? + +
+ setFormMail(e.target.value)} + /> + setMessage(e.target.value)} + /> +
+ + + {formMessage} + +
+ ) : null} +
+
+ ); +}; export default AppGrid; diff --git a/frontend/src/components/Billing.jsx b/frontend/src/components/Billing.jsx index d3a65927..8602911c 100644 --- a/frontend/src/components/Billing.jsx +++ b/frontend/src/components/Billing.jsx @@ -43,7 +43,7 @@ import BillingStats from "../components/BillingStats.jsx"; import { handlePayasyougo } from "../views/HandlePaymentNew.jsx" const Billing = (props) => { - const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, } = props; + const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, clickedFromOrgTab } = props; //const alert = useAlert(); let navigate = useNavigate(); @@ -970,21 +970,29 @@ const Billing = (props) => { const isChildOrg = userdata.active_org.creator_org !== "" && userdata.active_org.creator_org !== undefined && userdata.active_org.creator_org !== null return ( -
+
{addDealModal} + {clickedFromOrgTab? +

Billing & Licensing

: Billing & Licensing - + } + {clickedFromOrgTab? + {isCloud ? + "Get more out of Shuffle by adding your credit card, such as no App Run limitations, and priority support from our team. We use Stripe to manage subscriptions and do not store any of your billing information. You can manage your subscription and billing information below." + : + "Shuffle is an Open Source automation platform, and no license is required. We do however offer a Scale license with HA guarantees, along with support hours. By buying a license on https://shuffler.io, you can get access to the license immediately, and if Cloud Syncronisation is enabled, the UI in your local instance will also update." + }: {isCloud ? "Get more out of Shuffle by adding your credit card, such as no App Run limitations, and priority support from our team. We use Stripe to manage subscriptions and do not store any of your billing information. You can manage your subscription and billing information below." : "Shuffle is an Open Source automation platform, and no license is required. We do however offer a Scale license with HA guarantees, along with support hours. By buying a license on https://shuffler.io, you can get access to the license immediately, and if Cloud Syncronisation is enabled, the UI in your local instance will also update." } - + } {userdata.support === true ? -
+
For sales: Create  New Cloud Contract diff --git a/frontend/src/components/Branding.jsx b/frontend/src/components/Branding.jsx index c3c32058..48466476 100644 --- a/frontend/src/components/Branding.jsx +++ b/frontend/src/components/Branding.jsx @@ -103,8 +103,8 @@ const Branding = (props) => { } return ( -
-

+
+

Branding

diff --git a/frontend/src/components/CacheView.jsx b/frontend/src/components/CacheView.jsx index 4e4f2fdd..22500fe2 100644 --- a/frontend/src/components/CacheView.jsx +++ b/frontend/src/components/CacheView.jsx @@ -115,43 +115,6 @@ const CacheView = (props) => { }); }; - // const getCacheList = (orgId) => { - // fetch(`${globalUrl}/api/v1/orgs/${orgId}/get_cache`, { - // method: "GET", - // headers: { - // "Content-Type": "application/json", - // Accept: "application/json", - // }, - // credentials: "include", - // }) - // .then((response) => { - // if (response.status !== 200) { - // console.log("Status not 200 for WORKFLOW EXECUTION :O!"); - // } - - - // return response.json(); - // }) - // .then((responseJson) => { - // if (responseJson.success !== false) { - // console.log("Found cache: ", responseJson) - // setListCache(responseJson) - // } else { - // console.log("Couldn't find the creator profile (rerun?): ", responseJson) - // // If the current user is any of the Shuffle Creators - // // AND the workflow doesn't have an owner: allow editing. - // // else: Allow suggestions? - // //console.log("User: ", userdata) - // //if (rerun !== true) { - // // getUserProfile(userdata.id, true) - // //} - // } - // }) - // .catch((error) => { - // console.log("Get userprofile error: ", error); - // }) - // } - const deleteCache = (orgId, key) => { toast("Attempting to delete Cache"); @@ -403,7 +366,7 @@ const CacheView = (props) => {
@@ -679,7 +679,7 @@ const Files = (props) => { }} /> {priority.active === true ? -