From 877114f8dddd5864ea8bff2b4689d1542771ba9b Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 4 Jul 2024 00:11:20 +0200 Subject: [PATCH 1/3] Loads of minor fixes --- frontend/src/components/AppGrid.jsx | 87 +- frontend/src/components/Billing.jsx | 10 +- frontend/src/components/EditWorkflow.jsx | 13 +- frontend/src/components/LicencePopup.jsx | 22 +- frontend/src/components/NewHeader.jsx | 12 +- frontend/src/components/OrgHeaderexpanded.jsx | 1819 +++++++++-------- frontend/src/components/ParsedAction.jsx | 6 +- frontend/src/views/Admin.jsx | 6 +- frontend/src/views/AngularWorkflow.jsx | 35 +- 9 files changed, 1058 insertions(+), 952 deletions(-) diff --git a/frontend/src/components/AppGrid.jsx b/frontend/src/components/AppGrid.jsx index 11e25f0b..f15456c6 100644 --- a/frontend/src/components/AppGrid.jsx +++ b/frontend/src/components/AppGrid.jsx @@ -79,6 +79,7 @@ const AppGrid = (props) => { const [formMail, setFormMail] = React.useState(""); const [message, setMessage] = React.useState(""); const [formMessage, setFormMessage] = React.useState(""); + const [deactivatedIndexes, setDeactivatedIndexes] = React.useState([]); const buttonStyle = { borderRadius: 30, @@ -352,7 +353,7 @@ const AppGrid = (props) => { if (responseJson.success === false) { toast.error(responseJson.reason); } else { - toast.success(`App ${type}d Successfully!`); + //toast.success(`App ${type}d Successfully!`); if (type === 'activate') { setAllActivatedAppIds(prev => [...prev, data.objectID]); setIsAnyAppActivated(true); @@ -395,7 +396,7 @@ const AppGrid = (props) => { {!isLoading ? (
{hits.length === 0 && searchQuery.length >= 0 && showNoAppFound ? ( - No App Found + No Apps Found ) : (
{ Filter By + + + +
)} @@ -1722,6 +1727,10 @@ const AppGrid = (props) => { ? `/apps/${data.id}` : `https://shuffler.io/apps/${data.id}`; + if (data.name === "" && data.id === "") { + return null + } + return ( { width: 230, textAlign: 'start', marginLeft: 8, - color: "rgba(158, 158, 158, 1)" + color: "rgba(158, 158, 158, 1)", + display: "flex", }} > - {data.tags && - data.tags.map((tag, tagIndex) => ( - - {normalizedString(tag)} - {tagIndex < data.tags.length - 1 ? ", " : ""} - - ))} -
+
+ {data.generated !== true ? +
+ {data.tags && + data.tags.slice(0,2).map((tag, tagIndex) => ( + + {normalizedString(tag)} + {tagIndex < data.tags.length - 1 ? ", " : ""} + + )) + } +
+ : null} +
+ {currTab === 1 && !deactivatedIndexes.includes(index) && mouseHoverIndex === index && data.generated === true ? + + : null} + + {/* )} */} @@ -1946,6 +2009,7 @@ const AppGrid = (props) => { selectedOptionOfCreatedWith={selectedOptionOfCreatedWith} /> )} + { setSelectedTagsForUserAndOrgApps={setSelectedTagsForUserAndOrgApps} setSelectedOptionOfCreatedWith={setSelectedOptionOfCreatedWith} /> + diff --git a/frontend/src/components/Billing.jsx b/frontend/src/components/Billing.jsx index d5b33aca..f7598852 100644 --- a/frontend/src/components/Billing.jsx +++ b/frontend/src/components/Billing.jsx @@ -1623,12 +1623,12 @@ const Billing = (props) => { {userdata.support === true ?
For sales: Create  - - New Cloud Contract + + EU contract  or  - - New Onprem Contract + + NOT EU contract   -   @@ -1636,7 +1636,7 @@ const Billing = (props) => {   -   - Sales Process + Sales Process (old)
: diff --git a/frontend/src/components/EditWorkflow.jsx b/frontend/src/components/EditWorkflow.jsx index e2443664..46be4138 100644 --- a/frontend/src/components/EditWorkflow.jsx +++ b/frontend/src/components/EditWorkflow.jsx @@ -6,6 +6,7 @@ import UsecaseSearch from "../components/UsecaseSearch.jsx" import WorkflowGrid from "../components/WorkflowGrid.jsx" import dayjs from 'dayjs'; import WorkflowTemplatePopup from "./WorkflowTemplatePopup.jsx"; +import WorkflowValidationTimeline from "../components/WorkflowValidationTimeline.jsx" import { Badge, @@ -206,6 +207,16 @@ const EditWorkflow = (props) => { Workflows can be built from scratch, or from templates. Usecases can help you discover next steps, and you can search for them directly. Learn more + +
+ +
+ {showUpload === true ?
@@ -957,7 +968,7 @@ const EditWorkflow = (props) => { }} > {showMoreClicked ? : } - {showMoreClicked ? "Collapse": "Expand"} + {showMoreClicked ? "Less Options": "More Options"}
diff --git a/frontend/src/components/LicencePopup.jsx b/frontend/src/components/LicencePopup.jsx index 1c12d7fd..014fa46f 100644 --- a/frontend/src/components/LicencePopup.jsx +++ b/frontend/src/components/LicencePopup.jsx @@ -515,16 +515,16 @@ const LicencePopup = (props) => { } useEffect(() => { - console.log("New variant: ", shuffleVariant) + console.log("New variant: ", shuffleVariant) - if (shuffleVariant === 1) { - setCalculatedCost("$600") - setSelectedValue(8) - } else { - setCalculatedCost("$540") - setSelectedValue(300) - } - }, [shuffleVariant]) + if (shuffleVariant === 1) { + setCalculatedCost("$960") + setSelectedValue(8) + } else { + setCalculatedCost("$960") + setSelectedValue(300) + } + }, [shuffleVariant]) if (typeof window === 'undefined' || window.location === undefined) { return null @@ -680,7 +680,7 @@ const LicencePopup = (props) => { color: "white", } - + console.log("Priceitem: ", shuffleVariant) const isLoggedInHandler = () => { if (calculatedCost === payasyougo) { handlePayasyougo(props.userdata) @@ -690,7 +690,7 @@ const LicencePopup = (props) => { const priceItem = window.location.origin === "https://shuffler.io" ? shuffleVariant === 0 ? "app_executions" : "cores" : - shuffleVariant === 0 ? "price_1MROFrDzMUgUjxHShcSxgHO1" : "price_1NXjQqDzMUgUjxHSg690R4FP" + shuffleVariant === 0 ? "price_1PWI5zDzMUgUjxHSKkz0fGdN" : "price_1NXjQqDzMUgUjxHSg690R4FP" const successUrl = `${window.location.origin}/admin?admin_tab=billing&payment=success` const failUrl = `${window.location.origin}/pricing?admin_tab=billing&payment=failure` diff --git a/frontend/src/components/NewHeader.jsx b/frontend/src/components/NewHeader.jsx index 12bc756a..fa626601 100644 --- a/frontend/src/components/NewHeader.jsx +++ b/frontend/src/components/NewHeader.jsx @@ -517,12 +517,11 @@ const Header = (props) => { window.location.href = responseJson["url"] return }, 2000) - } - + } else { setTimeout(() => { window.location.reload() }, 2000); - + } toast("Successfully changed active organization - refreshing!"); } else { if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.length > 0) { @@ -1547,7 +1546,7 @@ const Header = (props) => {
{/* Shuffle 1.4.0 is out! Read more about  */} - Shuffle now offers  + Early Success! More  {/* { ReactGA.event({ @@ -1596,10 +1595,11 @@ const Header = (props) => { navigate("/training") - }} style={{ cursor: "pointer", textDecoration: "none", color: "rgba(255,255,255,0.8)" }}> - Public Training! + }} style={{ cursor: "pointer", textDecoration: "none", fontWeight:600, color: "rgba(255,255,255,0.8)" }}> + Public Trainings +  Ahead! { setShowTopbar(false) diff --git a/frontend/src/components/OrgHeaderexpanded.jsx b/frontend/src/components/OrgHeaderexpanded.jsx index 4cfbd690..58d1a244 100644 --- a/frontend/src/components/OrgHeaderexpanded.jsx +++ b/frontend/src/components/OrgHeaderexpanded.jsx @@ -1,516 +1,517 @@ -import React, { useEffect } from "react"; - -import { makeStyles } from "@mui/styles"; -import theme from '../theme.jsx'; -import { toast } from "react-toastify" -import Chip from '@mui/material/Chip'; -import Stack from '@mui/material/Stack'; -import SubflowSuggestions from "../components/SubflowSuggestions.jsx"; - -import { - FormControl, - InputLabel, - Paper, - OutlinedInput, - Checkbox, - Card, - Tooltip, - FormControlLabel, - Typography, - Switch, - Select, - MenuItem, - Divider, - TextField, - Button, - Tabs, - Tab, - Grid, - IconButton, - Autocomplete, - Dialog, - DialogTitle, - DialogActions, - DialogContent, - Box -} from "@mui/material"; - -import { - ExpandLess as ExpandLessIcon, - ExpandMore as ExpandMoreIcon, - Save as SaveIcon, -} from "@mui/icons-material"; - -const useStyles = makeStyles({ - notchedOutline: { - borderColor: "#f85a3e !important", - }, -}) - -const OrgHeaderexpanded = (props) => { - const { - userdata, - selectedOrganization, - setSelectedOrganization, - globalUrl, - isCloud, - adminTab, +import React, { useEffect } from "react"; + +import { makeStyles } from "@mui/styles"; +import theme from '../theme.jsx'; +import { toast } from "react-toastify" +import Chip from '@mui/material/Chip'; +import Stack from '@mui/material/Stack'; +import SubflowSuggestions from "../components/SubflowSuggestions.jsx"; + +import { + FormControl, + InputLabel, + Paper, + OutlinedInput, + Checkbox, + Card, + Tooltip, + FormControlLabel, + Typography, + Switch, + Select, + MenuItem, + Divider, + TextField, + Button, + Tabs, + Tab, + Grid, + IconButton, + Autocomplete, + Dialog, + DialogTitle, + DialogActions, + DialogContent, + Box +} from "@mui/material"; + +import { + ExpandLess as ExpandLessIcon, + ExpandMore as ExpandMoreIcon, + Save as SaveIcon, +} from "@mui/icons-material"; + +const useStyles = makeStyles({ + notchedOutline: { + borderColor: "#f85a3e !important", + }, +}) + +const OrgHeaderexpanded = (props) => { + const { + userdata, + selectedOrganization, + setSelectedOrganization, + globalUrl, + isCloud, + adminTab, selectedStatus, setSelectedStatus, isEditOrgTab - } = props; - - const classes = useStyles(); - const defaultBranch = "master"; - - const [orgName, setOrgName] = React.useState(selectedOrganization.name); - const [orgDescription, setOrgDescription] = React.useState( - selectedOrganization.description - ); - - const [appDownloadUrl, setAppDownloadUrl] = React.useState( - selectedOrganization.defaults === undefined - ? "https://github.com/frikky/shuffle-apps" - : selectedOrganization.defaults.app_download_repo === undefined || - selectedOrganization.defaults.app_download_repo.length === 0 - ? "https://github.com/frikky/shuffle-apps" - : selectedOrganization.defaults.app_download_repo - ); - const [appDownloadBranch, setAppDownloadBranch] = React.useState( - selectedOrganization.defaults === undefined - ? defaultBranch - : selectedOrganization.defaults.app_download_branch === undefined || - selectedOrganization.defaults.app_download_branch.length === 0 - ? defaultBranch - : selectedOrganization.defaults.app_download_branch - ); - const [workflowDownloadUrl, setWorkflowDownloadUrl] = React.useState( - selectedOrganization.defaults === undefined - ? "https://github.com/frikky/shuffle-apps" - : selectedOrganization.defaults.workflow_download_repo === undefined || - selectedOrganization.defaults.workflow_download_repo.length === 0 - ? "https://github.com/frikky/shuffle-workflows" - : selectedOrganization.defaults.workflow_download_repo - ); - const [workflowDownloadBranch, setWorkflowDownloadBranch] = React.useState( - selectedOrganization.defaults === undefined - ? defaultBranch - : selectedOrganization.defaults.workflow_download_branch === undefined || - selectedOrganization.defaults.workflow_download_branch.length === 0 - ? defaultBranch - : selectedOrganization.defaults.workflow_download_branch - ); - const [ssoEntrypoint, setSsoEntrypoint] = React.useState( - selectedOrganization.sso_config === undefined - ? "" - : selectedOrganization.sso_config.sso_entrypoint === undefined || - selectedOrganization.sso_config.sso_entrypoint.length === 0 - ? "" - : selectedOrganization.sso_config.sso_entrypoint - ); - const [ssoCertificate, setSsoCertificate] = React.useState( - selectedOrganization.sso_config === undefined - ? "" - : selectedOrganization.sso_config.sso_certificate === undefined || - selectedOrganization.sso_config.sso_certificate.length === 0 - ? "" - : selectedOrganization.sso_config.sso_certificate - ); - const [SSORequired, setSSORequired] = React.useState(selectedOrganization.sso_config === undefined - ? false - : selectedOrganization.sso_config.SSORequired === undefined - ? false - : selectedOrganization.sso_config.SSORequired); - - const [notificationWorkflow, setNotificationWorkflow] = React.useState( - selectedOrganization.defaults === undefined - ? "" - : selectedOrganization.defaults.notification_workflow === undefined || - selectedOrganization.defaults.notification_workflow.length === 0 - ? "" - : selectedOrganization.defaults.notification_workflow - ); - - const [documentationReference, setDocumentationReference] = React.useState( - selectedOrganization.defaults === undefined - ? "" - : selectedOrganization.defaults.documentation_reference === undefined || - selectedOrganization.defaults.documentation_reference.length === 0 - ? "" - : selectedOrganization.defaults.documentation_reference - ); - const [openidClientId, setOpenidClientId] = React.useState( - selectedOrganization.sso_config === undefined - ? "" - : selectedOrganization.sso_config.client_id === undefined || - selectedOrganization.sso_config.client_id.length === 0 - ? "" - : selectedOrganization.sso_config.client_id - ); - const [openidClientSecret, setOpenidClientSecret] = React.useState( - selectedOrganization.sso_config === undefined - ? "" - : selectedOrganization.sso_config.client_secret === undefined || - selectedOrganization.sso_config.client_secret.length === 0 - ? "" - : selectedOrganization.sso_config.client_secret - ); - const [openidAuthorization, setOpenidAuthorization] = React.useState( - selectedOrganization.sso_config === undefined - ? "" - : selectedOrganization.sso_config.openid_authorization === undefined || - selectedOrganization.sso_config.openid_authorization.length === 0 - ? "" - : selectedOrganization.sso_config.openid_authorization - ); - const [openidToken, setOpenidToken] = React.useState( - selectedOrganization.sso_config === undefined - ? "" - : selectedOrganization.sso_config.openid_token === undefined || - selectedOrganization.sso_config.openid_token.length === 0 - ? "" - : selectedOrganization.sso_config.openid_token - ) + } = props; + + const classes = useStyles(); + const defaultBranch = "master"; + + const [orgName, setOrgName] = React.useState(selectedOrganization.name); + const [orgDescription, setOrgDescription] = React.useState( + selectedOrganization.description + ); + + const [appDownloadUrl, setAppDownloadUrl] = React.useState( + selectedOrganization.defaults === undefined + ? "https://github.com/frikky/shuffle-apps" + : selectedOrganization.defaults.app_download_repo === undefined || + selectedOrganization.defaults.app_download_repo.length === 0 + ? "https://github.com/frikky/shuffle-apps" + : selectedOrganization.defaults.app_download_repo + ); + const [appDownloadBranch, setAppDownloadBranch] = React.useState( + selectedOrganization.defaults === undefined + ? defaultBranch + : selectedOrganization.defaults.app_download_branch === undefined || + selectedOrganization.defaults.app_download_branch.length === 0 + ? defaultBranch + : selectedOrganization.defaults.app_download_branch + ); + const [workflowDownloadUrl, setWorkflowDownloadUrl] = React.useState( + selectedOrganization.defaults === undefined + ? "https://github.com/frikky/shuffle-apps" + : selectedOrganization.defaults.workflow_download_repo === undefined || + selectedOrganization.defaults.workflow_download_repo.length === 0 + ? "https://github.com/frikky/shuffle-workflows" + : selectedOrganization.defaults.workflow_download_repo + ); + const [workflowDownloadBranch, setWorkflowDownloadBranch] = React.useState( + selectedOrganization.defaults === undefined + ? defaultBranch + : selectedOrganization.defaults.workflow_download_branch === undefined || + selectedOrganization.defaults.workflow_download_branch.length === 0 + ? defaultBranch + : selectedOrganization.defaults.workflow_download_branch + ); + const [ssoEntrypoint, setSsoEntrypoint] = React.useState( + selectedOrganization.sso_config === undefined + ? "" + : selectedOrganization.sso_config.sso_entrypoint === undefined || + selectedOrganization.sso_config.sso_entrypoint.length === 0 + ? "" + : selectedOrganization.sso_config.sso_entrypoint + ); + const [ssoCertificate, setSsoCertificate] = React.useState( + selectedOrganization.sso_config === undefined + ? "" + : selectedOrganization.sso_config.sso_certificate === undefined || + selectedOrganization.sso_config.sso_certificate.length === 0 + ? "" + : selectedOrganization.sso_config.sso_certificate + ); + const [SSORequired, setSSORequired] = React.useState(selectedOrganization.sso_config === undefined + ? false + : selectedOrganization.sso_config.SSORequired === undefined + ? false + : selectedOrganization.sso_config.SSORequired); + + const [notificationWorkflow, setNotificationWorkflow] = React.useState( + selectedOrganization.defaults === undefined + ? "" + : selectedOrganization.defaults.notification_workflow === undefined || + selectedOrganization.defaults.notification_workflow.length === 0 + ? "" + : selectedOrganization.defaults.notification_workflow + ); + + const [documentationReference, setDocumentationReference] = React.useState( + selectedOrganization.defaults === undefined + ? "" + : selectedOrganization.defaults.documentation_reference === undefined || + selectedOrganization.defaults.documentation_reference.length === 0 + ? "" + : selectedOrganization.defaults.documentation_reference + ); + const [openidClientId, setOpenidClientId] = React.useState( + selectedOrganization.sso_config === undefined + ? "" + : selectedOrganization.sso_config.client_id === undefined || + selectedOrganization.sso_config.client_id.length === 0 + ? "" + : selectedOrganization.sso_config.client_id + ); + const [openidClientSecret, setOpenidClientSecret] = React.useState( + selectedOrganization.sso_config === undefined + ? "" + : selectedOrganization.sso_config.client_secret === undefined || + selectedOrganization.sso_config.client_secret.length === 0 + ? "" + : selectedOrganization.sso_config.client_secret + ); + const [openidAuthorization, setOpenidAuthorization] = React.useState( + selectedOrganization.sso_config === undefined + ? "" + : selectedOrganization.sso_config.openid_authorization === undefined || + selectedOrganization.sso_config.openid_authorization.length === 0 + ? "" + : selectedOrganization.sso_config.openid_authorization + ); + const [openidToken, setOpenidToken] = React.useState( + selectedOrganization.sso_config === undefined + ? "" + : selectedOrganization.sso_config.openid_token === undefined || + selectedOrganization.sso_config.openid_token.length === 0 + ? "" + : selectedOrganization.sso_config.openid_token + ) const [uploadRepo, setUploadRepo] = React.useState(selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.workflow_upload_repo === undefined || selectedOrganization.defaults.workflow_upload_repo.length === 0 ? "" : selectedOrganization.defaults.workflow_upload_repo) const [uploadBranch, setUploadBranch] = React.useState(selectedOrganization.defaults === undefined ? defaultBranch : selectedOrganization.defaults.workflow_upload_branch === undefined || selectedOrganization.defaults.workflow_upload_branch.length === 0 ? defaultBranch : selectedOrganization.defaults.workflow_upload_branch) const [uploadUsername, setUploadUsername] = React.useState(selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.workflow_upload_username === undefined || selectedOrganization.defaults.workflow_upload_username.length === 0 ? "" : selectedOrganization.defaults.workflow_upload_username) const [uploadToken, setUploadToken] = React.useState(selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.workflow_upload_token === undefined || selectedOrganization.defaults.workflow_upload_token.length === 0 ? "" : selectedOrganization.defaults.workflow_upload_token) - - const [workflows, setWorkflows] = React.useState([]) - const [workflow, setWorkflow] = React.useState({}) - - const getAvailableWorkflows = (trigger_index) => { - fetch(globalUrl + "/api/v1/workflows", { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for workflows :O!"); - return; - } - return response.json(); - }) - .then((responseJson) => { - if (responseJson !== undefined) { - setWorkflows(responseJson) - - if (selectedOrganization.defaults !== undefined && selectedOrganization.defaults.notification_workflow !== undefined) { - - const workflow = responseJson.find((workflow) => workflow.id === selectedOrganization.defaults.notification_workflow) - if (workflow !== undefined && workflow !== null) { - setWorkflow(workflow) - } - } - } - }) - .catch((error) => { - console.log("Error getting workflows: " + error); - }) - } - - useEffect(() => { - getAvailableWorkflows() - }, []) - - const handleEditOrg = ( - name, - description, - orgId, - image, - defaults, - sso_config - ) => { - - const data = { - name: name, - description: description, - org_id: orgId, - image: image, - defaults: defaults, - sso_config: sso_config, - }; - - const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`; - fetch(url, { - mode: "cors", - method: "POST", - body: JSON.stringify(data), - credentials: "include", - crossDomain: true, - withCredentials: true, - headers: { - "Content-Type": "application/json; charset=utf-8", - }, - }) - .then((response) => - response.json().then((responseJson) => { - if (responseJson["success"] === false) { - toast("Failed updating org: ", responseJson.reason); - } else { - toast("Successfully edited org!"); - } - }) - ) - .catch((error) => { - toast("Err: " + error.toString()); - }); - }; - - - const handleWorkflowSelectionUpdate = (e, isUserinput) => { - if (e.target.value === undefined || e.target.value === null || e.target.value.id === undefined) { - console.log("Returning as there's no id") - return null - } - - setWorkflow(e.target.value) - setNotificationWorkflow(e.target.value.id) - toast("Updated notification workflow. Don't forget to save!") - } - - const orgSaveButton = ( - -
- -
-
- ); - - const toggleBetweenRequiredOrOptional = (event) => { - setSSORequired(event.target.checked); - }; - - return ( -
- - - - Notification Workflow - - {/* - - */} - - -
- {workflows !== undefined && workflows !== null && workflows.length > 0 ? - { - if ( - option === undefined || - option === null || - option.name === undefined || - option.name === null - ) { - return "No Workflow Selected"; - } - - const newname = ( - option.name.charAt(0).toUpperCase() + option.name.substring(1) - ).replaceAll("_", " "); - return newname; - }} - options={workflows} - fullWidth - style={{ - backgroundColor: theme.palette.inputColor, - height: 50, - borderRadius: theme.palette.borderRadius, - }} - onChange={(event, newValue) => { - console.log("Found value: ", newValue) - - var parsedinput = { target: { value: newValue } } - - // For variables - if (typeof newValue === 'string' && newValue.startsWith("$")) { - parsedinput = { - target: { - value: { - "name": newValue, - "id": newValue, - "actions": [], - "triggers": [], - } - } - } - } - - handleWorkflowSelectionUpdate(parsedinput) - }} - renderOption={(props, data, state) => { - if (data.id === workflow.id) { - data = workflow; - } - - return ( - - {data.image !== undefined && data.image !== null && data.image.length > 0 ? - {data.name} - : null} - - Choose {data.name} - - - } placement="bottom"> - { - var parsedinput = { target: { value: data } } - handleWorkflowSelectionUpdate(parsedinput) - }} - > - {data.name} - - - ) - }} - renderInput={(params) => { - return ( - - ); - }} - /> - : - { - setNotificationWorkflow(e.target.value); - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - } -
- {orgSaveButton} -
-
-
-
- - - Org Documentation reference - { - setDocumentationReference(e.target.value); - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - + + const [workflows, setWorkflows] = React.useState([]) + const [workflow, setWorkflow] = React.useState({}) + + const getAvailableWorkflows = (trigger_index) => { + fetch(globalUrl + "/api/v1/workflows", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for workflows :O!"); + return; + } + return response.json(); + }) + .then((responseJson) => { + if (responseJson !== undefined) { + setWorkflows(responseJson) + + if (selectedOrganization.defaults !== undefined && selectedOrganization.defaults.notification_workflow !== undefined) { + + const workflow = responseJson.find((workflow) => workflow.id === selectedOrganization.defaults.notification_workflow) + if (workflow !== undefined && workflow !== null) { + setWorkflow(workflow) + } + } + } + }) + .catch((error) => { + console.log("Error getting workflows: " + error); + }) + } + + useEffect(() => { + getAvailableWorkflows() + }, []) + + const handleEditOrg = ( + name, + description, + orgId, + image, + defaults, + sso_config + ) => { + + const data = { + name: name, + description: description, + org_id: orgId, + image: image, + defaults: defaults, + sso_config: sso_config, + }; + + const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`; + fetch(url, { + mode: "cors", + method: "POST", + body: JSON.stringify(data), + credentials: "include", + crossDomain: true, + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then((response) => + response.json().then((responseJson) => { + if (responseJson["success"] === false) { + toast("Failed updating org: ", responseJson.reason); + } else { + toast("Successfully edited org!"); + } + }) + ) + .catch((error) => { + toast("Err: " + error.toString()); + }); + }; + + + const handleWorkflowSelectionUpdate = (e, isUserinput) => { + if (e.target.value === undefined || e.target.value === null || e.target.value.id === undefined) { + console.log("Returning as there's no id") + return null + } + + setWorkflow(e.target.value) + setNotificationWorkflow(e.target.value.id) + toast("Updated notification workflow. Don't forget to save!") + } + + const orgSaveButton = ( + +
+ +
+
+ ); + + const toggleBetweenRequiredOrOptional = (event) => { + setSSORequired(event.target.checked); + }; + + return ( +
+ + + + Notification Workflow + + {/* + + */} + + +
+ {workflows !== undefined && workflows !== null && workflows.length > 0 ? + { + if ( + option === undefined || + option === null || + option.name === undefined || + option.name === null + ) { + return "No Workflow Selected"; + } + + const newname = ( + option.name.charAt(0).toUpperCase() + option.name.substring(1) + ).replaceAll("_", " "); + return newname; + }} + options={workflows} + fullWidth + style={{ + backgroundColor: theme.palette.inputColor, + height: 50, + borderRadius: theme.palette.borderRadius, + }} + onChange={(event, newValue) => { + console.log("Found value: ", newValue) + + var parsedinput = { target: { value: newValue } } + + // For variables + if (typeof newValue === 'string' && newValue.startsWith("$")) { + parsedinput = { + target: { + value: { + "name": newValue, + "id": newValue, + "actions": [], + "triggers": [], + } + } + } + } + + handleWorkflowSelectionUpdate(parsedinput) + }} + renderOption={(props, data, state) => { + if (data.id === workflow.id) { + data = workflow; + } + + return ( + + {data.image !== undefined && data.image !== null && data.image.length > 0 ? + {data.name} + : null} + + Choose {data.name} + + + } placement="bottom"> + { + var parsedinput = { target: { value: data } } + handleWorkflowSelectionUpdate(parsedinput) + }} + > + {data.name} + + + ) + }} + renderInput={(params) => { + return ( + + ); + }} + /> + : + { + setNotificationWorkflow(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + } +
+ {orgSaveButton} +
+
+
+
+ + + Org Documentation reference + { + setDocumentationReference(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + Workflow Backup Repository @@ -663,409 +664,409 @@ const OrgHeaderexpanded = (props) => { SSO Configuration -
- Make SAML SSO or OpenID Authentication Required or Optional for Your Organization. -
- - {SSORequired ? 'Required' : 'Optional'} -
-
- - OpenID connect - - - - Client ID - 0 - } - id="outlined-with-placeholder" - margin="normal" - variant="outlined" - placeholder="The OpenID client ID from the identity provider" - value={openidClientId} - onChange={(e) => { - setOpenidClientId(e.target.value); - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - - - Client Secret (optional) - 0 - } - id="outlined-with-placeholder" - margin="normal" - variant="outlined" - placeholder="The OpenID client secret - DONT use this if dealing with implicit auth / PKCE" - value={openidClientSecret} - onChange={(e) => { - setOpenidClientSecret(e.target.value); - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - - - - - Authorization URL - { - setOpenidAuthorization(e.target.value) - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - - - Token URL - { - setOpenidToken(e.target.value) - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - - - {/* } */} - {/*isCloud ? null : */} - - SAML SSO (v1.1) - - - - SSO Entrypoint (IdP) - 0 - } - id="outlined-with-placeholder" - margin="normal" - variant="outlined" - placeholder="The entrypoint URL from your provider" - value={ssoEntrypoint} - onChange={(e) => { - setSsoEntrypoint(e.target.value); - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - - - SSO Certificate (X509) - { - setSsoCertificate(e.target.value); - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - - {isCloud ? - - IdP URL for Shuffle: https://shuffler.io/api/v1/login_sso - - : null} - - {isCloud ? null : ( - - - App Download URL - { - setAppDownloadUrl(e.target.value); - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - )} - {isCloud ? null : ( - - - App Download Branch - { - setAppDownloadBranch(e.target.value); - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - )} - {isCloud ? null : ( - - - Workflow Download URL - { - setWorkflowDownloadUrl(e.target.value); - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - )} - {isCloud ? null : ( - - - Workflow Download Branch - { - setWorkflowDownloadBranch(e.target.value); - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> - - - )} - -
- {orgSaveButton} -
- {/* - - {expanded ? - - : - - } - - */} -
-
- ) -} - +
+ Make SAML SSO or OpenID Authentication Required or Optional for Your Organization. +
+ + {SSORequired ? 'Required' : 'Optional'} +
+
+ + OpenID connect + + + + Client ID + 0 + } + id="outlined-with-placeholder" + margin="normal" + variant="outlined" + placeholder="The OpenID client ID from the identity provider" + value={openidClientId} + onChange={(e) => { + setOpenidClientId(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + + Client Secret (optional) + 0 + } + id="outlined-with-placeholder" + margin="normal" + variant="outlined" + placeholder="The OpenID client secret - DONT use this if dealing with implicit auth / PKCE" + value={openidClientSecret} + onChange={(e) => { + setOpenidClientSecret(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + + + + Authorization URL + { + setOpenidAuthorization(e.target.value) + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + + Token URL + { + setOpenidToken(e.target.value) + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + + {/* } */} + {/*isCloud ? null : */} + + SAML SSO (v1.1) + + + + SSO Entrypoint (IdP) + 0 + } + id="outlined-with-placeholder" + margin="normal" + variant="outlined" + placeholder="The entrypoint URL from your provider" + value={ssoEntrypoint} + onChange={(e) => { + setSsoEntrypoint(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + + SSO Certificate (X509) + { + setSsoCertificate(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + {isCloud ? + + IdP URL for Shuffle: https://shuffler.io/api/v1/login_sso + + : null} + + {isCloud ? null : ( + + + App Download URL + { + setAppDownloadUrl(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + )} + {isCloud ? null : ( + + + App Download Branch + { + setAppDownloadBranch(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + )} + {isCloud ? null : ( + + + Workflow Download URL + { + setWorkflowDownloadUrl(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + )} + {isCloud ? null : ( + + + Workflow Download Branch + { + setWorkflowDownloadBranch(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + )} + +
+ {orgSaveButton} +
+ {/* + + {expanded ? + + : + + } + + */} +
+
+ ) +} + export default OrgHeaderexpanded; diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index e3cf400c..09635e31 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -307,7 +307,6 @@ const ParsedAction = (props) => { (action) => action.name.toLowerCase() === selectedAction.name.toLowerCase() ); - console.log("FOUNDACTION: ", foundAction); if (foundAction !== null && foundAction !== undefined) { var foundparams = []; for (let [paramkey,paramkeyval] in Object.entries(foundAction.parameters)) { @@ -549,6 +548,11 @@ const ParsedAction = (props) => { } } } + + if (parentNode.label === undefined) { + parentNode.label = "" + } + newActionList.push({ type: "action", id: parentNode.id, diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 15382695..d7ee12b6 100755 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -5385,13 +5385,17 @@ If you're interested, please let me know a time that works for you, or set up a + {validIcon} style={{ minWidth: 65, maxWidth: 65, }} onClick={() => { + if (data.validation === null || data.validation === undefined) { + return + } + if (data.validation.workflow_id === undefined || data.validation.workflow_id === null || data.validation.workflow_id.length === 0) { toast.warn("No workflow runs found for this auth yet. Check back later.") return diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 7bfd4d0f..d0ef9308 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -139,6 +139,7 @@ import Draggable from "react-draggable"; import cytoscapestyle from "../defaultCytoscapeStyle.jsx"; import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx"; +import WorkflowValidationTimeline from "../components/WorkflowValidationTimeline.jsx" import { validateJson, GetIconInfo } from "../views/Workflows.jsx"; import { GetParsedPaths, internalIds, } from "../views/Apps.jsx"; import ConfigureWorkflow from "../components/ConfigureWorkflow.jsx"; @@ -319,6 +320,7 @@ export function SetJsonDotnotation(jsonInput, inputKey) { export const green = "#86c142"; export const yellow = "#FECC00"; +export const red = "red"; export function removeParam(key, sourceURL) { if (sourceURL === undefined) { @@ -1641,7 +1643,6 @@ const releaseToConnectLabel = "Release to Connect" setExecutionData(responseJson) } else { if (responseJson.status === "ABORTED" || responseJson.status === "STOPPED" || responseJson.status === "FAILURE" || responseJson.status === "WAITING" || responseJson.status === "FINISHED") { - console.log("DONE!") stop() } @@ -6732,7 +6733,6 @@ const releaseToConnectLabel = "Release to Connect" const parentlabel = parentNode.data("label").toLowerCase().replace(" ", "_") const parentname = parentNode.data("app_name").toLowerCase().replace(" ", "_") if (!parentlabel.startsWith(parentname)+"_") { - console.log("Return 1") return } @@ -7135,7 +7135,6 @@ const releaseToConnectLabel = "Release to Connect" // Find how many executions it has var executions = 0 const matchingExecutions = workflowExecutions.filter((execution => execution.execution_source === nodedata.app_name.toLowerCase())) - console.log("Matches: ", matchingExecutions.length) const color = matchingExecutions.length > 0 ? "#34a853" : "#ea4436" const decoratorNode = { position: { @@ -10204,6 +10203,7 @@ const releaseToConnectLabel = "Release to Connect" // Starts on current node and climbs UP the tree to the root object. // Sends back everything in it's path + // FIXME: Use the GetParentNodes in WorkflowValidationTimeline.jsx instead const getParents = (action) => { if (action === undefined || action === null) { return [] @@ -18717,6 +18717,7 @@ const releaseToConnectLabel = "Release to Connect" : null}
+ {executionData.workflow !== undefined && executionData.workflow !== null && executionData.workflow.actions !== undefined && executionData.workflow.actions !== null && executionData.workflow.actions.length > 0 ?
@@ -18828,12 +18829,31 @@ const releaseToConnectLabel = "Release to Connect" {new Date(executionData.completed_at * 1000).toLocaleString("en-GB")}
+ ) : null} + + {executionData.workflow !== undefined && executionData.workflow !== null && executionData.status !== "EXECUTING" ? +
+ +
+ : null} +
- {executionData.execution_argument !== undefined && - executionData.execution_argument.length > 0 + + {executionData.execution_argument !== undefined && executionData.execution_argument !== null && + executionData.execution_argument.length > 1 ? parsedExecutionArgument() - : null} + : + null} + + {executionData.results !== undefined && executionData.results !== null && executionData.results.length > 1 && @@ -19534,7 +19555,7 @@ const releaseToConnectLabel = "Release to Connect" if (stringjson.includes("\n") && !stringjson.includes("\n")) { return "Looks like you have a newline problem. Consider using the | replace: '\n', '\\n' }} filter in Liquid." } else { - return "The result looks like it should be JSON, but is invalid. Look for potential" + return "The result looks like it should be JSON, but is invalid. Look for potential single quotes instead of double quotes, missing commas or newlines" } } } From 98224e424615e1fcbcadcaf5d0bab84135902837 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 4 Jul 2024 00:12:14 +0200 Subject: [PATCH 2/3] Added first timeline tracker --- .../components/WorkflowValidationTimeline.jsx | 443 ++++++++++++++++++ 1 file changed, 443 insertions(+) create mode 100644 frontend/src/components/WorkflowValidationTimeline.jsx diff --git a/frontend/src/components/WorkflowValidationTimeline.jsx b/frontend/src/components/WorkflowValidationTimeline.jsx new file mode 100644 index 00000000..cbbb7bd6 --- /dev/null +++ b/frontend/src/components/WorkflowValidationTimeline.jsx @@ -0,0 +1,443 @@ +import React, { useState, } from "react"; +import { makeStyles, createStyles } from "@mui/styles"; +import { toast } from "react-toastify" + +import { + Tooltip, + Typography, + + Avatar, + AvatarGroup, +} from "@mui/material" + +import { + green, + yellow, + red, +} from "../views/AngularWorkflow.jsx" + +import { validateJson, GetIconInfo } from "../views/Workflows.jsx"; +import theme from "../theme.jsx"; +const itemHeight = 40 + +export const getParentNodes = (workflow, action) => { + if (action === undefined || action === null) { + return [] + } + + if (workflow.actions === undefined || workflow.actions === null) { + workflow.actions = [] + } + + if (workflow.triggers === undefined || workflow.triggers === null) { + workflow.triggers = [] + } + + if (workflow.branches === undefined || workflow.branches === null) { + workflow.branches = [] + } + + var allkeys = [action.id]; + var handled = []; + var results = []; + + // maxiter = max amount of parent nodes to loop + // also handles breaks if there are issues + var iterations = 0; + var maxiter = 10; + while (true) { + for (let parentkey in allkeys) { + if (allkeys[parentkey] === undefined) { + continue + } + + var currentnode = workflow.actions.find((element) => element.id === allkeys[parentkey]) + if (currentnode === undefined) { + currentnode = workflow.triggers.find((element) => element.id === allkeys[parentkey]) + + if (currentnode === undefined) { + console.log("Could not find parent node for: ", allkeys[parentkey]) + continue + } + } + + if (handled.includes(currentnode.id)) { + continue + } else { + handled.push(currentnode.id); + results.push(currentnode); + } + + // Get the name / label here too? + if (currentnode.length === 0) { + continue; + } + + // FIXME: This part is only handling first level, + // but needs to recurse + var incomingEdges = [] + for (var branchkey in workflow.branches) { + const branch = workflow.branches[branchkey] + if (branch.destination_id !== currentnode.id) { + continue + } + + // Go up in the levels + const parents = getParentNodes(workflow, { + id: branch.source_id, + }) + if (parents.length > 0) { + incomingEdges = incomingEdges.concat(parents) + } + + incomingEdges.push(branch) + } + + for (let i = 0; i < incomingEdges.length; i++) { + var tmp = incomingEdges[i]; + if (tmp.decorator === true) { + continue + } + + if (!allkeys.includes(tmp.source_id)) { + allkeys.push(tmp.source_id) + } + } + } + + if (results.length === allkeys.length || iterations === maxiter) { + break + } + + iterations += 1 + } + + // Remove on the end as we don't want to remove everything + results = results.filter((data) => data.id !== action.id) + results = results.filter((data) => data.type === "ACTION" || data.app_name === "Shuffle Workflow" || data.app_name === "User Input" || data.app_name === "shuffle-subflow") + results.push({ label: "Execution Argument", type: "INTERNAL" }) + + return results +} + +const WorkflowValidationTimeline = (props) => { + const { workflow, originalWorkflow, apps, getParents, execution} = props + + + if (workflow === undefined || workflow === null) { + return null + } + + if (workflow.validation === undefined || workflow.validation === null) { + return null + } + + if (workflow.actions === undefined || workflow.actions === null) { + workflow.actions = [] + } + + if (workflow.triggers === undefined || workflow.triggers === null) { + workflow.triggers = [] + + } + + if (workflow.branches === undefined || workflow.branches === null) { + workflow.branches = [] + + } + + var results = [] + if (execution !== undefined) { + results = execution.results + } + + // 1. Find startnode + // 2. Map childnodes from it + const startnodeId = workflow.start + + // Find parent of startnodeId and if it's a webhook + var relevantactions = [] + for (var key in workflow.branches) { + const branch = workflow.branches[key] + if (branch.destination_id !== startnodeId) { + continue + } + + for (var triggerkey in workflow.triggers) { + const trigger = workflow.triggers[triggerkey] + if (trigger.trigger_type !== "WEBHOOK") { + continue + } + + if (trigger.id === branch.source_id) { + trigger.order = -1 + relevantactions.push(trigger) + break + } + } + } + + + if (getParents !== undefined) { + for (var key in workflow.actions) { + const action = workflow.actions[key] + if (action.id === startnodeId) { + action.order = 0 + relevantactions.push(action) + continue + } + + const parents = getParents(action) + //const parents = getParentNodes(workflow, action) + //console.log("PARENTS", key, parents) + if (parents !== undefined && parents !== null) { + const parentfound = parents.find((element) => element.id === startnodeId) + if (parentfound !== undefined) { + + // FIXME: add order here based on how many steps away from the startnode + // This just has the parent count + action.order = parents.length + + relevantactions.push(action) + } + } + } + } else { + for (var key in workflow.triggers) { + const trigger = workflow.triggers[key] + if (trigger.trigger_type !== "SUBFLOW" && trigger.trigger_type !== "USERINPUT") { + continue + } + + if (workflow.actions.find((element) => element.id === trigger.id) === undefined) { + workflow.actions.push(trigger) + } + } + + relevantactions = workflow.actions + } + + // Sort according to how many parents a node has. MAY be wrong~ + relevantactions.sort((a, b) => { + if (a.order === undefined) { + return 1 + } + + if (b.order === undefined) { + return -1 + } + + return a.order - b.order + }) + + // FIXME: Add other relevant items as well from subflows (?) + var nodecolor = "grey" + var branchcolor = "grey" + var skipped = false + + var previousTools = false + + return ( +
+
+ {relevantactions.map((action, index) => { + action.result = {} + if (results !== undefined) { + const foundResult = results.find((element) => element.action.id === action.id) + if (foundResult !== undefined) { + action.result = foundResult + + action.status = foundResult.status + } + } + + const lastitem = index === relevantactions.length - 1 + if (!lastitem) { + if (action.app_name === "Shuffle Tools") { + if (action.status === "SUCCESS") { + branchcolor = red + + // Check action.result for the actual status + const validate = validateJson(action.result.result) + if (validate.valid) { + if (validate.result.success === true) { + branchcolor = green + } else { + branchcolor = "grey" + } + } + + + } else if (action.status === "SKIPPED") { + branchcolor = "grey" + } else { + if (action.status === undefined) { + branchcolor = green + } else { + branchcolor = red + } + } + + previousTools = true + return null + } else { + if (action.status === "SUCCESS") { + nodecolor = green + } else if (action.status === "SKIPPED") { + nodecolor = "grey" + } else { + if (action.status === undefined) { + nodecolor = green + } else { + nodecolor = red + } + } + } + } else { + nodecolor = "grey" + } + + if (action.status === "SKIPPED") { + skipped = true + } + + var image = "" + if (action.large_image !== undefined && action.large_image !== null && action.large_image !== "") { + image = action.large_image + } else { + if (originalWorkflow !== undefined) { + for (var key in originalWorkflow.actions) { + if (originalWorkflow.actions[key].id === action.id) { + image = originalWorkflow.actions[key].large_image + break + } + } + + if (image === "") { + for (var key in originalWorkflow.triggers) { + if (originalWorkflow.triggers[key].id === action.id) { + image = originalWorkflow.triggers[key].large_image + break + } + } + + } + } + } + + var founderror = "" + if (workflow.validation !== undefined && workflow.validation !== null && workflow.validation.errors !== undefined && workflow.validation.errors !== null) { + const foundError = workflow.validation.errors.find((element) => element.action_id === action.id) + if (foundError !== undefined) { + founderror = foundError.error + nodecolor = yellow + branchcolor = yellow + } + } + + if (skipped && !lastitem) { + nodecolor = "grey" + branchcolor = "grey" + } + + if (previousTools) { + previousTools = false + } else { + branchcolor = nodecolor + } + + var appgroup = [] + if (action.trigger_type === "WEBHOOK") { + nodecolor = green + branchcolor = green + } else if (action.app_name === "shuffle-subflow") { + if (action.status === "SUCCESS") { + nodecolor = green + branchcolor = green + } + + for (var subflowkey in workflow.validation.subflow_apps) { + const subflowApp = workflow.validation.subflow_apps[subflowkey] + if (subflowApp.error === action.id) { + appgroup.push(subflowApp) + } + } + + } + + var flex = index !== 0 && index !== relevantactions.length - 1 ? 1 : 3 + const branchTooltip = branchcolor === yellow ? "Check nodes for errors" : "" + + return ( +
+ {lastitem ? + +
+ + : null} + + {appgroup.length > 0 ? + + {appgroup.map((subflowApp, subflowIndex) => { + var appimage = "" + if (apps !== undefined && apps !== null && apps.length > 0) { + for (var key in apps) { + const app = apps[key] + if (app.name === subflowApp.app_name) { + appimage = apps[key].large_image + break + } + } + } + + return ( + + + + ) + })} + + : + + {founderror.length > 0 ? founderror : ``} + + } placement="top"> + + + {image !== "" ? + + : null} + + + } + + {lastitem ? null : + +
+ + } +
+ ) + })} +
+ +
+ ) +} + +export default WorkflowValidationTimeline From a60fad69d3a77e73b314bc203d9af7c4ed574876 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Thu, 4 Jul 2024 17:22:15 +0530 Subject: [PATCH 3/3] feat[k8s-cleanup]: adding a quick cleanup mechanism --- functions/onprem/orborus/orborus.go | 52 +++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index bb89a83f..6881c4a0 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -177,6 +177,51 @@ func getThisContainerId() { } func cleanupExistingNodes(ctx context.Context) error { + + if isKubernetes == "true" { + if kubernetesNamespace == "" { + kubernetesNamespace = "default" + } + + clientset, _, err := shuffle.GetKubernetesClient() + if err != nil { + log.Printf("[ERROR] Error getting kubernetes client:", err) + return err + } + + // Delete all pods + pods, err := clientset.CoreV1().Pods(kubernetesNamespace).List(context.Background(), metav1.ListOptions{}) + if err != nil { + log.Printf("[ERROR] Failed listing pods: %s", err) + return err + } + + for _, pod := range pods.Items { + err := clientset.CoreV1().Pods(kubernetesNamespace).Delete(context.Background(), pod.Name, metav1.DeleteOptions{}) + if err != nil { + log.Printf("[ERROR] Failed deleting pod %s: %s", pod.Name, err) + } + } + + // Delete all services + services, err := clientset.CoreV1().Services(kubernetesNamespace).List(context.Background(), metav1.ListOptions{}) + if err != nil { + log.Printf("[ERROR] Failed listing services: %s", err) + return err + } + + for _, service := range services.Items { + err := clientset.CoreV1().Services(kubernetesNamespace).Delete(context.Background(), service.Name, metav1.DeleteOptions{}) + if err != nil { + log.Printf("[ERROR] Failed deleting service %s: %s", service.Name, err) + } + } + + log.Printf("[INFO] Cleaned up all pods and services in namespace %s", kubernetesNamespace) + return nil + } + + serviceListOptions := types.ServiceListOptions{} services, err := dockercli.ServiceList( context.Background(), @@ -1661,11 +1706,12 @@ func main() { if swarmConfig == "run" || swarmConfig == "swarm" || isKubernetes == "true" { if isKubernetes != "true" { checkSwarmService(ctx) - log.Printf("[DEBUG] Cleaning up containers from previous run") - cleanupExistingNodes(ctx) - time.Sleep(time.Duration(5) * time.Second) } + log.Printf("[DEBUG] Cleaning up containers from previous run") + cleanupExistingNodes(ctx) + time.Sleep(time.Duration(5) * time.Second) + log.Printf("[DEBUG] Deploying worker image %s to swarm", workerImage) runString := "Run: \"docker service ls\" for more info"