diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index 62e0e907..ab3e88e5 100644 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -352,7 +352,7 @@ class AppBase: finished = True break else: - self.logger.info(f"[DEBUG] RESP: {ret.text}") + self.logger.info(f"[ERROR] RESP: {ret.text}") except requests.exceptions.RequestException as e: self.logger.info(f"[DEBUG] Request problem: {e}") diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index a5425e4d..ebc1cfba 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -2,7 +2,7 @@ module main go 1.16 -//replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared +replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared //replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi //replace github.com/frikky/go-elasticsearch => ../../../../git/go-elasticsearch diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 3e3d3630..d15e31f5 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -6100,6 +6100,10 @@ func initHandlers() { r.HandleFunc("/api/v1/users/notifications/clear", shuffle.HandleClearNotifications).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/users/notifications/{notificationId}/markasread", shuffle.HandleMarkAsRead).Methods("GET", "OPTIONS") + //r.HandleFunc("/api/v1/users/notifications/{notificationId}/markasread", shuffle.HandleMarkAsRead).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/dashboards/{key}/widgets", shuffle.HandleNewWidget).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/dashboards/{key}/widgets/{widget_id}", shuffle.HandleGetWidget).Methods("GET", "OPTIONS") + http.Handle("/", r) } diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index d152d9f2..545e111c 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -16,6 +16,7 @@ import Apps from "./views/Apps"; import AppCreator from "./views/AppCreator"; import Dashboard from "./views/Dashboard.jsx"; +import DashboardView from "./views/DashboardViews.jsx"; import AdminSetup from "./views/AdminSetup"; import Admin from "./views/Admin"; import Docs from "./views/Docs"; @@ -656,6 +657,18 @@ const App = (message, props) => { /> } /> + + } + /> '), + "template": true, + "actions": [{ + "name": "Create Alert", + "description": "Create a ticket", + "parameters": [{ + "name": "id", + }, + { + "name": "name", + }, + { + "name": "description", + "multiline": true, + }, + ], + }], +}] + +export default extraApps diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js index 380e329e..7a7ae609 100644 --- a/frontend/src/components/Header.js +++ b/frontend/src/components/Header.js @@ -856,7 +856,7 @@ const Header = (props) => { position: "fixed", minHeight: 60, top: 0, - //zIndex: 10000, + zIndex: 10000, backgroundColor: "inherit", }} > diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 7372b3b3..85e6f560 100644 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -1064,6 +1064,8 @@ const ParsedAction = (props) => { */ } + console.log("APP: ", selectedApp) + // FIXME: Issue #40 - selectedActionParameters not reset if ( Object.getOwnPropertyNames(selectedAction).length > 0 && @@ -1090,6 +1092,114 @@ const ParsedAction = (props) => { Parameters + {selectedAction.template === true && selectedAction.matching_actions !== undefined && selectedAction.matching_actions !== null && selectedAction.matching_actions.length > 0 ? +
+ + Select an app you want to use + + { + console.log("LABEL: ", option) + if ( + option === undefined || + option === null || + option.app_name === undefined || + option.app_name === null + ) { + return null; + } + + const newname = ( + option.app_name.charAt(0).toUpperCase() + option.app_name.substring(1) + ).replaceAll("_", " "); + return newname; + }} + options={selectedAction.matching_actions} + fullWidth + style={{ + backgroundColor: theme.palette.inputColor, + height: 50, + borderRadius: theme.palette.borderRadius, + }} + onChange={(event, newValue) => { + console.log("SELECT: ", event, newValue) + // Workaround with event lol + //if (newValue !== undefined && newValue !== null) { + // setNewSelectedAction({ target: { value: newValue.name } }); + //} + }} + renderOption={(data) => { + var newActionname = data.app_name; + if ( + data.label !== undefined && + data.label !== null && + data.label.length > 0 + ) { + newActionname = data.label; + } + + const iconInfo = GetIconInfo({ name: data.app_name }); + const useIcon = iconInfo.originalIcon; + + newActionname = ( + newActionname.charAt(0).toUpperCase() + + newActionname.substring(1) + ).replaceAll("_", " "); + + return ( +
+ + {useIcon} + + {newActionname} +
+ ); + }} + renderInput={(params) => { + if (params.inputProps !== undefined && params.inputProps !== null && params.inputProps.value !== undefined && params.inputProps.value !== null) { + const prefixes = ["Post", "Put", "Patch"] + for (var key in prefixes) { + if (params.inputProps.value.startsWith(prefixes[key])) { + params.inputProps.value = params.inputProps.value.replace(prefixes[key]+" ", "", -1) + if (params.inputProps.value.length > 1) { + params.inputProps.value = params.inputProps.value.charAt(0).toUpperCase()+params.inputProps.value.substring(1) + } + break + } + } + } + + return ( + + ); + }} + /> +
+ : null} {selectedAction.description !== undefined && selectedAction.description !== null && selectedAction.description.length > 0 && hiddenDescription === false ? (
{ const coverColor = "#82ccc3" //menuPosition.left -= 50 //menuPosition.top -= 250 - console.log(menuPosition) return parsedPaths.length > 0 ? ( { title="See previous results for this action" placement="top" > - + { title="Read app docs" placement="top" > - + { title="What are actions?" placement="top" > - + @@ -2448,7 +2557,7 @@ const ParsedAction = (props) => { title={selectedAction.run_magic_output === undefined || selectedAction.run_magic_output === null || selectedAction.run_magic_output === false ? "Click to enable magic parsing" : "Click to disable magic parsing"} placement="top" > - + @@ -2529,7 +2638,7 @@ const ParsedAction = (props) => { />
- Name + Name { placement="top" > - Delay + Delay { selectedAction.authentication !== null && selectedAction.authentication.length > 0 ? (
- Authentication + Authentication
{ > {liquidFilters.map((item, index) => { return ( - { + { handleClick(item) }}>{item.name} ) @@ -486,7 +486,7 @@ const CodeEditor = (props) => { > {mathFilters.map((item, index) => { return ( - { + { handleClick(item) }}>{item.name} ) @@ -521,7 +521,7 @@ const CodeEditor = (props) => { > {pythonFilters.map((item, index) => { return ( - { + { handleClick(item) }}>{item.name} ) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 60b7455b..5cba127e 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -111,6 +111,7 @@ import ConfigureWorkflow from "../components/ConfigureWorkflow.jsx"; import AuthenticationOauth2 from "../components/Oauth2Auth.jsx"; import ParsedAction from "../components/ParsedAction.jsx"; import PaperComponent from "../components/PaperComponent.jsx" +import ExtraApps from "../components/ExtraApps.jsx" const surfaceColor = "#27292D"; const inputColor = "#383B40"; @@ -451,7 +452,8 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - alert.error(error.toString()); + //alert.error(error.toString()); + console.log("Workflow error: ", error.toString()) }); }; @@ -539,7 +541,7 @@ const AngularWorkflow = (defaultprops) => { setUserSettings(responseJson); }) .catch((error) => { - console.log(error); + console.log("Apikey error: ", error); }); }; @@ -574,7 +576,7 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - console.log(error); + console.log("Settings error: ", error); }); }; @@ -608,7 +610,8 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - alert.error(error.toString()); + //alert.error(error.toString()); + console.log("New auth error: ", error.toString()); }); }; @@ -692,7 +695,8 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - alert.error(error.toString()); + //alert.error(error.toString()); + console.log("Get execution error: ", error.toString()); }); }; @@ -719,7 +723,7 @@ const AngularWorkflow = (defaultprops) => { handleUpdateResults(responseJson, executionRequest); }) .catch((error) => { - console.log("Error: ", error); + console.log("Execution result Error: ", error); stop(); }); }; @@ -751,7 +755,8 @@ const AngularWorkflow = (defaultprops) => { return response.json(); }) .catch((error) => { - alert.error(error.toString()); + //alert.error(error.toString()); + console.log("Abort error: ", error.toString()); }); }; @@ -1262,7 +1267,8 @@ const AngularWorkflow = (defaultprops) => { }) .catch((error) => { setSavingState(0); - alert.error(error.toString()); + //alert.error(error.toString()); + console.log("Save workflow error: ", error.toString()); }); return success; @@ -1387,7 +1393,8 @@ const AngularWorkflow = (defaultprops) => { start(); }) .catch((error) => { - alert.error(error.toString()); + //alert.error(error.toString()); + console.log("Execute workflow err: ", error.toString()); }); }) }; @@ -1501,7 +1508,8 @@ const AngularWorkflow = (defaultprops) => { }) .catch((error) => { setAuthLoaded(true); - alert.error("Auth loading error: " + error.toString()); + //alert.error("Auth loading error: " + error.toString()); + console.log("AppAuth error: " + error.toString()); }); }; @@ -1578,7 +1586,8 @@ const AngularWorkflow = (defaultprops) => { }) .catch((error) => { setAppsLoaded(true) - alert.error("App loading error: "+error.toString()); + //alert.error("App loading error: "+error.toString()); + console.log("App loading error: "+error.toString()); }); }; @@ -1607,7 +1616,7 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - console.log(error); + console.log("Get userprofile error: ", error); }) } @@ -1825,7 +1834,8 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - alert.error(error.toString()); + //alert.error(error.toString()); + console.log("Get workflows error: ", error.toString()); }); }; @@ -2500,6 +2510,32 @@ const AngularWorkflow = (defaultprops) => { (a.loop_versions !== null && a.loop_versions.includes(curaction.app_version))) ); + + if (curaction.template === true) { + //newapps. + const parsedname = curaction.name.replaceAll(" ", "_").toLowerCase() + console.log("FIND AN ACTION AMONG THE APPS THAT MATCHES NAME: ", parsedname) + + curaction.matching_actions = [] + for (var key in newapps) { + for (var subkey in newapps[key].actions) { + const tmpaction = newapps[key].actions[subkey] + if (tmpaction.name.replaceAll(" ", "_").toLowerCase() === parsedname) { + console.log("MATCH!: ", newapps[key]) + curaction.matching_actions.push({ + "app_name": newapps[key].name, + "app_version": newapps[key].app_version, + "app_id": newapps[key].id, + "action": tmpaction, + "large_image": newapps[key].large_image, + "app_index": key, + "action_index": subkey, + }) + } + } + } + } + if (!curapp || curapp === undefined) { console.log("APPS: ", newapps) //alert.error(`App ${curaction.app_name}:${curaction.app_version} not found. Is it activated?`); @@ -2709,7 +2745,8 @@ const AngularWorkflow = (defaultprops) => { } }) .catch(error => { - alert.error(error.toString()) + //alert.error(error.toString()) + console.log("Activate app error: ", error.toString()) }); } @@ -3658,7 +3695,8 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - alert.error(error.toString()); + //alert.error(error.toString()); + console.log("Get environments error: ", error.toString()); }); }; @@ -4563,7 +4601,8 @@ const AngularWorkflow = (defaultprops) => { saveWorkflow(workflow); }) .catch((error) => { - alert.error(error.toString()); + //alert.error(error.toString()); + console.log("Stop schedule error: ", error.toString()); }); }; @@ -4615,7 +4654,8 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - alert.error(error.toString()); + //alert.error(error.toString()); + console.log("Get schedule error: ", error.toString()); }); }; @@ -4970,6 +5010,7 @@ const AngularWorkflow = (defaultprops) => { var thisview = ( @@ -5063,14 +5104,14 @@ const AngularWorkflow = (defaultprops) => { is_valid: true, label: "Webhook", environment: "onprem", - description: "Simple HTTP webhook", + description: "Custom HTTP input", long_description: "Execute a workflow with an unauthicated POST request", }, { name: "Schedule", type: "TRIGGER", status: "uninitialized", - description: "Schedule execution time", + description: "Specify time", trigger_type: "SCHEDULE", errors: null, large_image: @@ -5091,7 +5132,7 @@ const AngularWorkflow = (defaultprops) => { is_valid: true, label: "Subflow", environment: "onprem", - description: "Control another workflow", + description: "Control a workflow", long_description: "Execute another workflow from this workflow", }, { @@ -5112,7 +5153,7 @@ const AngularWorkflow = (defaultprops) => { name: "Office365", type: "TRIGGER", status: "uninitialized", - description: "Starts upon O365 email", + description: "O365 email trigger", trigger_type: "EMAIL", errors: null, is_valid: cloudSyncEnabled || isCloud ? true : false, @@ -5126,7 +5167,7 @@ const AngularWorkflow = (defaultprops) => { name: "Gmail", type: "TRIGGER", status: "uninitialized", - description: "Trigger based on Gmail", + description: "Gmail email trigger", trigger_type: "EMAIL", errors: null, is_valid: cloudSyncEnabled || isCloud ? true : false, @@ -5203,10 +5244,11 @@ const AngularWorkflow = (defaultprops) => { display: "flex", flexDirection: "column", marginLeft: "20px", + overflow: "hidden", }} > - -

+ +

{trigger.name}

@@ -5323,7 +5365,7 @@ const AngularWorkflow = (defaultprops) => { } // AUTHENTICATION - if (app.authentication.required) { + if (app.authentication !== undefined && app.authentication !== null && app.authentication.required === true) { console.log("App auth is required!") // Setup auth here :) @@ -5448,6 +5490,7 @@ const AngularWorkflow = (defaultprops) => { var description = "" if ( + app.actions[0].parameters !== undefined && app.actions[0].parameters !== null && app.actions[0].parameters.length > 0 ) { @@ -5455,6 +5498,8 @@ const AngularWorkflow = (defaultprops) => { } if ( + app.actions[0].returns !== undefined && + app.actions[0].returns !== null && app.actions[0].returns.example !== undefined && app.actions[0].returns.example !== null && app.actions[0].returns.example.length > 0 @@ -5510,6 +5555,7 @@ const AngularWorkflow = (defaultprops) => { : "", authentication_id: "", finished: false, + template: app.template === true ? true : false, }; // FIXME: overwrite category if the ACTION chosen has a different category @@ -5534,10 +5580,12 @@ const AngularWorkflow = (defaultprops) => { }; const AppView = (props) => { - const { allApps, prioritizedApps, filteredApps } = props; + const { allApps, prioritizedApps, filteredApps, extraApps } = props; + //extraApps, const [visibleApps, setVisibleApps] = React.useState( - prioritizedApps.concat( - filteredApps.filter((innerapp) => !internalIds.includes(innerapp.id)) + Array.prototype.concat.apply( + prioritizedApps, + filteredApps.filter((innerapp) => !internalIds.includes(innerapp.id)), ) ); @@ -5827,7 +5875,7 @@ const AngularWorkflow = (defaultprops) => { runSearch(event.target.value); }} /> - {visibleApps.length > 0 ? ( + {visibleApps.length > extraApps.length ? (
{visibleApps.map((app, index) => { if (app.invalid) { @@ -7293,7 +7341,7 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - console.log(error.toString()); + console.log("Get gmail folder error: ", error.toString()); }); }; @@ -7347,7 +7395,7 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - console.log(error.toString()); + console.log("Get outlook folders error: ", error.toString()); }); }; @@ -7368,7 +7416,8 @@ const AngularWorkflow = (defaultprops) => { setTriggerAuthentication(responseJson); }) .catch((error) => { - console.log(error.toString()); + //console.log(error.toString()); + console.log("Set outlook auth error: ", error.toString()); }); }; @@ -7458,7 +7507,7 @@ const AngularWorkflow = (defaultprops) => { setGmailFolders(); }) .catch((error) => { - console.log(error.toString()); + console.log("Set gmail trigg error: ", error.toString()); }); }, 2500); @@ -7556,7 +7605,7 @@ const AngularWorkflow = (defaultprops) => { setOutlookFolders(); }) .catch((error) => { - console.log(error.toString()); + console.log("Set outlook trigger error: ", error.toString()); }); }, 2500); @@ -9476,7 +9525,8 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - alert.error(error.toString()); + //alert.error(error.toString()); + console.log("Stop mailsub error: ", error.toString()); }); }; @@ -9552,7 +9602,8 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - alert.error(error.toString()); + //alert.error(error.toString()); + console.log("Start mailsub error: ", error.toString()); }); }; @@ -9636,7 +9687,8 @@ const AngularWorkflow = (defaultprops) => { } }) .catch((error) => { - console.log(error.toString()); + //console.log(error.toString()); + console.log("New webhook error: ", error.toString()); }); }; @@ -9679,7 +9731,8 @@ const AngularWorkflow = (defaultprops) => { setSelectedTrigger(trigger); }) .catch((error) => { - alert.error(error.toString()); + //alert.error(error.toString()); + alert.error("Delete webhook error: ", error.toString()); }); }; diff --git a/frontend/src/views/AppCreator.jsx b/frontend/src/views/AppCreator.jsx index a8a85007..285ffc53 100644 --- a/frontend/src/views/AppCreator.jsx +++ b/frontend/src/views/AppCreator.jsx @@ -289,8 +289,8 @@ const AppCreator = (defaultprops) => { type: "header", example: "", }; - const [extraAuth, setExtraAuth] = useState([]); + const [extraAuth, setExtraAuth] = useState([]); const [app, setApp] = useState({}); const [appAuthentication, setAppAuthentication] = React.useState([]); const [selectedAction, setSelectedAction] = useState({}); @@ -1420,6 +1420,8 @@ const AppCreator = (defaultprops) => { //console.log("SECURITYSCHEMES: ", securitySchemes) if (securitySchemes !== undefined) { + console.log("NEWAUTH: ", securitySchemes) + var valueset = false // FIXME: Should add Oauth2 (Microsoft) and JWT (Wazuh) //console.log("SECURITY: ", securitySchemes) //if (Object.entries(securitySchemes) > 1 && @@ -1444,18 +1446,31 @@ const AppCreator = (defaultprops) => { setAuthenticationRequired(true); } else if (key === "ApiKeyAuth" || key === "Token" || ((value.in === "header" || value.in === "query") && value.name !== undefined)) { - setAuthenticationOption("API key"); + if (authenticationOption === "" || authenticationOption === "No authentication") { + setAuthenticationOption("API key"); + } - value.in = value.in.charAt(0).toUpperCase() + value.in.slice(1); - setParameterLocation(value.in); - if (!apikeySelection.includes(value.in)) { - console.log("APIKEY SELECT: ", apikeySelection); - alert.error("Might be error in setting up API key authentication"); - } + if (valueset === false) { + valueset = true + value.in = value.in.charAt(0).toUpperCase() + value.in.slice(1) - console.log("PARAM NAME: ", value.name); - setParameterName(value.name); - setAuthenticationRequired(true); + setParameterLocation(value.in); + if (!apikeySelection.includes(value.in)) { + console.log("APIKEY SELECT: ", apikeySelection); + alert.error("Might be error in setting up API key authentication"); + } + + console.log("PARAM NAME: ", value.name); + setParameterName(value.name); + setAuthenticationRequired(true); + } else { + newauth.push({ + "name": key, + "type": value.in.toLowerCase(), + "in": value.in.toLowerCase(), + "example": "", + }) + } if (value.description !== undefined && value.description !== null && value.description.length > 0) { // Don't want a real description - just the ones we're replacing with @@ -2365,6 +2380,7 @@ const AppCreator = (defaultprops) => { )}
+ {extraAuth.map((value, index) => { return ( { + const [hovered, setHovered] = useState(""); + + //console.log("Date: ", new Date("2019-11-14T08:00:00.000Z")) + console.log("Keys: ", keys) + var inputdata = keys.data + + /* + const inputdata = [{ + "key": "Intel", + "data": [ + { key: new Date('11/22/2019'), data: 3, metadata: {color: "orange", "name": "Intel"}}, + { key: new Date('11/24/2019'), data: 8, metadata: {color: "orange", "name": "Intel"}}, + { key: new Date('11/29/2019'), data: 2, metadata: {color: "orange", "name": "Intel"}}, + ]}, + { + "key": "Popper", + "data": [ + { key: new Date('11/24/2019'), data: 9, }, + { key: new Date('11/29/2019'), data: 3, }, + ] + } + ] + */ + + return ( +
+ + {keys.title} + + + } + colorScheme={(colorInput) => { + var color = "cybertron" + if (colorInput !== undefined && colorInput.length > 0) { + color = colorInput[0].metadata !== undefined && colorInput[0].metadata.color !== undefined ? colorInput[0].metadata.color : color + } + + return color + }} + tooltip={ + { + if (hovered !== event.value.x) { + //setHovered(event.value.x) + } + }} + tooltip={ + { + console.log("DATA: ", data) + const name = data.metadata !== undefined && data.metadata.name !== undefined ? data.metadata.name : "No" + + return ( +
+ + {name} + +
+ ) + /* + + ) + */ + } + } + /> + } + /> + } + /> + } + /> +
+ ) +} + +const RadialChart = ({keys, setSelectedCategory}) => { + const [hovered, setHovered] = useState(""); + + return ( +
{ + console.log("Click: ", hovered) + if (setSelectedCategory !== undefined) { + setSelectedCategory(hovered) + } + }}> + } + series={ + { + return '#f86a3e' + }} + animated={false} + id="workflow_series_id" + style={{cursor: "pointer",}} + line={ + { + console.log("INFO: ", data, color) + return ( + null + ) + }} + /> + } + tooltip={ + { + if (hovered !== event.value.x) { + setHovered(event.value.x) + } + }} + tooltip={ + { + return ( +
+ + {data.x} + +
+ ) + /* + + ) + */ + } + } + /> + } + /> + + } + /> + } + /> +
+ ) + //axis={} +} + +// This is the start of a dashboard that can be used. +// What data do we fill in here? Idk +const Dashboard = (props) => { + const { globalUrl, isLoggedIn } = props; + const alert = useAlert(); + const [bigChartData, setBgChartData] = useState("data1"); + const [dayAmount, setDayAmount] = useState(7); + const [firstRequest, setFirstRequest] = useState(true); + const [stats, setStats] = useState({}); + const [changeme, setChangeme] = useState(""); + const [statsRan, setStatsRan] = useState(false); + const [keys, setKeys] = useState([]) + const [treeKeys, setTreeKeys] = useState([]) + + const [selectedUsecaseCategory, setSelectedUsecaseCategory] = useState(""); + const [selectedUsecases, setSelectedUsecases] = useState([]); + const [usecases, setUsecases] = useState([]); + const [workflows, setWorkflows] = useState([]); + const [frameworkData, setFrameworkData] = useState(undefined); + + const [widgetData, setWidgetData] = useState(undefined); + + let navigate = useNavigate(); + const isCloud = + window.location.host === "localhost:3002" || + window.location.host === "shuffler.io"; + + + useEffect(() => { + if (selectedUsecaseCategory.length === 0) { + setSelectedUsecases(usecases) + } else { + const foundUsecase = usecases.find(data => data.name === selectedUsecaseCategory) + if (foundUsecase !== undefined && foundUsecase !== null) { + setSelectedUsecases([foundUsecase]) + } + } + }, [selectedUsecaseCategory]) + + const checkSelectedParams = () => { + const urlSearchParams = new URLSearchParams(window.location.search) + const params = Object.fromEntries(urlSearchParams.entries()) + + const curpath = typeof window === "undefined" || window.location === undefined ? "" : window.location.pathname; + const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search; + + const foundQuery = params["selected"] + if (foundQuery !== null && foundQuery !== undefined) { + setSelectedUsecaseCategory(foundQuery) + + const newitem = removeParam("selected", cursearch); + navigate(curpath + newitem) + } + + const foundQuery2 = params["selected_object"] + if (foundQuery2 !== null && foundQuery2 !== undefined) { + console.log("Got selected_object: ", foundQuery2) + + const queryName = foundQuery2.toLowerCase().replaceAll("_", " ") + // Waiting a bit for it to render + setTimeout(() => { + const foundItem = document.getElementById(queryName) + if (foundItem !== undefined && foundItem !== null) { + foundItem.click() + } else { + //console.log("Couldn't find item with name ", queryName) + } + }, 100); + } + + } + + useEffect(() => { + if (usecases.length > 0) { + console.log(usecases) + checkSelectedParams() + } + }, [usecases]) + + const getWidget = (dashboard, widget) => { + fetch(`${globalUrl}/api/v1/dashboards/${dashboard}/widgets/${widget}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for framework!"); + } + + return response.json(); + }) + .then((responseJson) => { + console.log("Resp: ", responseJson) + if (responseJson.success === false) { + if (responseJson.reason !== undefined) { + //alert.error("Failed loading: " + responseJson.reason) + } else { + //alert.error("Failed to load framework for your org.") + } + } else { + var tmpdata = responseJson + for (var key in tmpdata.data) { + for (var subkey in tmpdata.data[key].data) { + tmpdata.data[key].data[subkey].key = new Date(tmpdata.data[key].data[subkey].key) + } + } + + console.log("VAl: ", tmpdata) + + setWidgetData(tmpdata) + } + }) + .catch((error) => { + //alert.error(error.toString()); + }) + } + + document.title = "Shuffle - Dashboard"; + var dayGraphLabels = [60, 80, 65, 130, 80, 105, 90, 130, 70, 115, 60, 130]; + var dayGraphData = [60, 80, 65, 130, 80, 105, 90, 130, 70, 115, 60, 130]; + + const handleKeysetting = (categorydata) => { + var allCategories = [] + var treeCategories = [] + for (key in categorydata) { + const category = categorydata[key] + allCategories.push({"key": category.name, "data": category.list.length, "color": category.color}) + treeCategories.push({"key": category.name, "data": 100, "color": category.color,}) + for (var subkey in category.list) { + treeCategories.push({"key": category.list[subkey].name, "data": 20, "color": category.color}) + } + } + + setKeys(allCategories) + setTreeKeys(treeCategories) + } + + const fetchUsecases = (workflows) => { + fetch(globalUrl + "/api/v1/workflows/usecases", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for usecases"); + } + + return response.json(); + }) + .then((responseJson) => { + // Matching workflows with usecases + if (responseJson.success !== false) { + if (workflows !== undefined && workflows !== null && workflows.length > 0) { + var categorydata = responseJson + + var newcategories = [] + for (var key in categorydata) { + var category = categorydata[key] + category.matches = [] + + for (var subcategorykey in category.list) { + var subcategory = category.list[subcategorykey] + subcategory.matches = [] + + for (var workflowkey in workflows) { + const workflow = workflows[workflowkey] + + if (workflow.usecase_ids !== undefined && workflow.usecase_ids !== null) { + for (var usecasekey in workflow.usecase_ids) { + if (workflow.usecase_ids[usecasekey].toLowerCase() === subcategory.name.toLowerCase()) { + + category.matches.push({ + "workflow": workflow.id, + "category": subcategory.name, + }) + + subcategory.matches.push(workflow) + break + } + } + } + + if (subcategory.matches.length > 0) { + break + } + } + } + + newcategories.push(category) + } + + if (newcategories !== undefined && newcategories !== null && newcategories.length > 0) { + handleKeysetting(newcategories) + setUsecases(newcategories) + setSelectedUsecases(newcategories) + } else { + handleKeysetting(responseJson) + setUsecases(responseJson) + setSelectedUsecases(responseJson) + } + } else { + handleKeysetting(responseJson) + setUsecases(responseJson) + setSelectedUsecases(responseJson) + } + } + }) + .catch((error) => { + //alert.error("ERROR: " + error.toString()); + console.log("ERROR: " + error.toString()); + }); + }; + + useEffect(() => { + getWidget("main", "tmp") + //fetchUsecases() + }, []); + + const fetchdata = (stats_id) => { + fetch(globalUrl + "/api/v1/stats/" + stats_id, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for " + stats_id); + } + + return response.json(); + }) + .then((responseJson) => { + stats[stats_id] = responseJson; + setStats(stats); + // Used to force updates + setChangeme(stats_id); + }) + .catch((error) => { + //alert.error("ERROR: " + error.toString()); + console.log("ERROR: " + error.toString()); + }); + }; + + let chart1_2_options = { + maintainAspectRatio: false, + legend: { + display: false, + }, + tooltips: { + backgroundColor: "#f5f5f5", + titleFontColor: "#333", + bodyFontColor: "#666", + bodySpacing: 4, + xPadding: 12, + mode: "nearest", + intersect: 0, + position: "nearest", + }, + responsive: true, + scales: { + yAxes: [ + { + barPercentage: 1.6, + gridLines: { + drawBorder: false, + color: "rgba(29,140,248,0.0)", + zeroLineColor: "transparent", + }, + ticks: { + suggestedMin: 60, + suggestedMax: 125, + padding: 20, + fontColor: "#9a9a9a", + }, + }, + ], + xAxes: [ + { + barPercentage: 1.6, + gridLines: { + drawBorder: false, + color: "rgba(29,140,248,0.1)", + zeroLineColor: "transparent", + }, + ticks: { + padding: 20, + fontColor: "#9a9a9a", + }, + }, + ], + }, + }; + + const dayGraph = { + data: (canvas) => { + let ctx = canvas.getContext("2d"); + + let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50); + + gradientStroke.addColorStop(1, "rgba(29,140,248,0.2)"); + gradientStroke.addColorStop(0.4, "rgba(29,140,248,0.0)"); + gradientStroke.addColorStop(0, "rgba(29,140,248,0)"); //blue colors + + return { + labels: dayGraphLabels, + datasets: [ + { + label: "My First dataset", + fill: true, + backgroundColor: gradientStroke, + borderColor: "#1f8ef1", + borderWidth: 2, + borderDash: [], + borderDashOffset: 0.0, + pointBackgroundColor: "#1f8ef1", + pointBorderColor: "rgba(255,255,255,0)", + pointHoverBackgroundColor: "#1f8ef1", + pointBorderWidth: 20, + pointHoverRadius: 4, + pointHoverBorderWidth: 15, + pointRadius: 4, + data: dayGraphData, + }, + ], + }; + }, + options: chart1_2_options, + }; + + // All these are currently tracked. + const variables = [ + "backend_executions", + "workflow_executions", + "workflow_executions_aborted", + "workflow_executions_success", + "total_apps_created", + "total_apps_loaded", + "openapi_apps_created", + "total_apps_deleted", + "total_webhooks_ran", + "total_workflows", + "total_workflow_actions", + "total_workflow_triggers", + ]; + + const runUpdate = () => { + for (var key in variables) { + fetchdata(variables[key]); + } + }; + + // Refresh every 60 seconds + const autoUpdate = 60000; + const { start, stop } = useInterval({ + duration: autoUpdate, + startImmediate: false, + callback: () => { + runUpdate(); + }, + }); + + if (firstRequest) { + setFirstRequest(false); + //start(); + //runUpdate(); + } else if (!statsRan) { + // FIXME: Run this under runUpdate schedule? + // 1. Fix labels in dayGraphy.data + // 2. Add data to the daygraph + + // Every time there's an update :) + + // This should probably be done in the backend.. bleh + if ( + stats["workflow_executions"] !== undefined && + stats["workflow_executions"] !== null && + stats["workflow_executions"].data !== undefined + ) { + setStatsRan(true); + //console.log("NEW DATA?: ", stats) + console.log("SET WORKFLOW: ", stats["workflow_executions"]); + //var curday = startDate.getDate() + + // Index = what day are we on + + // 0 = today + var newDayGraphLabels = []; + var newDayGraphData = []; + for (var i = dayAmount; i > 0; i--) { + var enddate = new Date(); + enddate.setDate(-i); + enddate.setHours(23, 59, 59, 999); + + var startdate = new Date(); + startdate.setDate(-i); + startdate.setHours(0, 0, 0, 0); + + var endtime = enddate.getTime() / 1000; + var starttime = startdate.getTime() / 1000; + + console.log( + "START: ", + starttime, + "END: ", + endtime, + "Data: ", + stats["workflow_executions"] + ); + for (var key in stats["workflow_executions"].data) { + const item = stats["workflow_executions"]["data"][key]; + console.log("ITEM: ", item.timestamp, endtime); + console.log(endtime - starttime); + if ( + endtime - starttime > endtime - item.timestamp && + endtime.timestamp >= 0 + ) { + console.log("HIT? "); + } + console.log(item.timestamp - endtime); + //console.log(item.timestamp-endtime) + break; + if (item.timestamp > endtime && item.timestamp < starttime) { + if (newDayGraphData[i - 1] === undefined) { + newDayGraphData[i - 1] = 1; + } else { + newDayGraphData[i - 1] += 1; + } + + //break + } + } + + newDayGraphLabels.push(i); + } + + console.log(newDayGraphLabels); + console.log(newDayGraphData); + } + } + + const newdata = + Object.getOwnPropertyNames(stats).length > 0 ? ( +
+ Autoupdate every {autoUpdate / 1000} seconds + {variables.map((data) => { + if (stats[data] === undefined || stats[data] === null) { + return null; + } + + if (stats[data].total === undefined) { + return null; + } + + return ( +
+ {data}: {stats[data].total} +
+ ); + })} +
+ ) : null; + + const data = ( +
+
+ {keys.length > 0 ? + + + + : null} +
+ + {widgetData === undefined ? null : + + + + + + } +
+ ); + + const dataWrapper = ( +
{data}
+ ); + + return dataWrapper; +}; + +export default Dashboard; diff --git a/frontend/src/views/SettingsPage.jsx b/frontend/src/views/SettingsPage.jsx index a8e0e0e9..1bcd2344 100644 --- a/frontend/src/views/SettingsPage.jsx +++ b/frontend/src/views/SettingsPage.jsx @@ -733,7 +733,7 @@ const Settings = (props) => { {isCloud ? - By connecting your Github account, you agree to our Terms of Service, and acknowledge that your non-sensitive data will be turned into a creator account. This enables you to earn a passive income from Shuffle. This IS reversible. + By connecting your Github or Metamask account, you agree to our Terms of Service, and acknowledge that your non-sensitive data will be turned into a creator account. This enables you to earn a passive income from Shuffle. This IS reversible. Support: support@shuffler.io