Added more management tools for workflow running and stat management
This commit is contained in:
@@ -1230,6 +1230,7 @@ const Billing = (props) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
<BillingStats
|
<BillingStats
|
||||||
|
isCloud={isCloud}
|
||||||
globalUrl={globalUrl}
|
globalUrl={globalUrl}
|
||||||
selectedOrganization={selectedOrganization}
|
selectedOrganization={selectedOrganization}
|
||||||
userdata={userdata}
|
userdata={userdata}
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
import classNames from "classnames";
|
|
||||||
import theme from '../theme.jsx';
|
import theme from '../theme.jsx';
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
|
||||||
|
|
||||||
|
import {
|
||||||
|
DatePicker,
|
||||||
|
DateTimePicker,
|
||||||
|
LocalizationProvider,
|
||||||
|
} from '@mui/x-date-pickers'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
@@ -52,12 +59,14 @@ import {
|
|||||||
LinearXAxisTickLabel,
|
LinearXAxisTickLabel,
|
||||||
} from 'reaviz';
|
} from 'reaviz';
|
||||||
|
|
||||||
|
import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx";
|
||||||
|
|
||||||
const LineChartWrapper = ({keys, inputname, height, width}) => {
|
const LineChartWrapper = ({keys, inputname, height, width}) => {
|
||||||
const [hovered, setHovered] = useState("");
|
const [hovered, setHovered] = useState("");
|
||||||
const inputdata = keys.data === undefined ? keys : keys.data
|
const inputdata = keys.data === undefined ? keys : keys.data
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{color: "white", border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, padding: 30, marginTop: 15, }}>
|
<div style={{color: "white", border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, padding: 30, marginTop: 15, backgroundColor: theme.palette.platformColor, overflow: "hidden", }}>
|
||||||
<Typography variant="h6" style={{marginBotton: 15, }}>
|
<Typography variant="h6" style={{marginBotton: 15, }}>
|
||||||
{inputname}
|
{inputname}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -75,16 +84,174 @@ const LineChartWrapper = ({keys, inputname, height, width}) => {
|
|||||||
|
|
||||||
|
|
||||||
const AppStats = (defaultprops) => {
|
const AppStats = (defaultprops) => {
|
||||||
const { globalUrl, selectedOrganization, userdata, } = defaultprops;
|
const { globalUrl, selectedOrganization, userdata, isCloud, } = defaultprops;
|
||||||
|
|
||||||
const [keys, setKeys] = useState([])
|
const [keys, setKeys] = useState([])
|
||||||
const [searches, setSearches] = useState([]);
|
const [searches, setSearches] = useState([]);
|
||||||
const [clickData, setClickData] = useState(undefined);
|
|
||||||
const [conversionData, setConversionData] = useState(undefined);
|
|
||||||
const [statistics, setStatistics] = useState(undefined);
|
|
||||||
const [appRuns, setAppruns] = useState(undefined);
|
const [appRuns, setAppruns] = useState(undefined);
|
||||||
|
const [appRunCosts, setApprunCosts] = useState(undefined);
|
||||||
const [workflowRuns, setWorkflowRuns] = useState(undefined);
|
const [workflowRuns, setWorkflowRuns] = useState(undefined);
|
||||||
const [subflowRuns, setSubflowRuns] = useState(undefined);
|
const [subflowRuns, setSubflowRuns] = useState(undefined);
|
||||||
|
|
||||||
|
const [endTime, setEndTime] = useState("")
|
||||||
|
const [startTime, setStartTime] = useState("")
|
||||||
|
const [statistics, setStatistics] = useState(undefined);
|
||||||
|
const [filteredStatistics, setFilteredStatistics] = useState(undefined);
|
||||||
|
|
||||||
|
const [apprunCost, setApprunCost] = useState(0)
|
||||||
|
const [monthToDateCost, setMonthToDateCost] = useState(0)
|
||||||
|
const [monthTotalCost, setMonthTotalCost] = useState(0)
|
||||||
|
|
||||||
|
const includedExecutions = selectedOrganization.sync_features.app_executions !== undefined ? selectedOrganization.sync_features.app_executions.limit : 0
|
||||||
|
|
||||||
|
// Cost in old contracts: 0.0009
|
||||||
|
// Old contracts also always included 150.000 executions
|
||||||
|
const invocationCost = includedExecutions === 150000 || includedExecutions === 250000 ? 0.0009 : typecost_single
|
||||||
|
const defaultAmount = 10000
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (statistics === undefined || statistics === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statistics["daily_statistics"] === undefined || statistics["daily_statistics"] === null) {
|
||||||
|
setFilteredStatistics(statistics)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate month to date cost
|
||||||
|
var mtd_cost = 0
|
||||||
|
for (let key in statistics["daily_statistics"]) {
|
||||||
|
const item = statistics["daily_statistics"][key]
|
||||||
|
if (item["date"] === undefined) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = new Date(item["date"])
|
||||||
|
const today = new Date()
|
||||||
|
if (date.getMonth() === today.getMonth()) {
|
||||||
|
mtd_cost += (item["app_executions"] * invocationCost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCloud && mtd_cost !== monthToDateCost) {
|
||||||
|
// Find how many days there have been in the current month
|
||||||
|
const today = new Date()
|
||||||
|
const daysInMonth = new Date(today.getFullYear(), today.getMonth()+1, 0).getDate()
|
||||||
|
// Find what day we are on
|
||||||
|
const day = today.getDate()
|
||||||
|
// Find how many days are left in the month
|
||||||
|
const daysLeft = daysInMonth - day
|
||||||
|
|
||||||
|
// Calculate the cost of the entire month
|
||||||
|
var monthTotalCost = mtd_cost/day*daysInMonth
|
||||||
|
monthTotalCost -= defaultAmount*invocationCost
|
||||||
|
monthTotalCost -= includedExecutions*invocationCost
|
||||||
|
|
||||||
|
// Remove included amount
|
||||||
|
//const defaultAmount = 10000
|
||||||
|
mtd_cost -= defaultAmount*invocationCost
|
||||||
|
mtd_cost -= includedExecutions*invocationCost
|
||||||
|
|
||||||
|
if (monthTotalCost > 0) {
|
||||||
|
setMonthTotalCost(monthTotalCost.toFixed(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mtd_cost > 0) {
|
||||||
|
setMonthToDateCost(mtd_cost.toFixed(2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a date at the 1st of the current month
|
||||||
|
var foundstarttime = (new Date())
|
||||||
|
foundstarttime.setDate(1)
|
||||||
|
if (startTime !== "" && startTime !== undefined && startTime !== null) {
|
||||||
|
foundstarttime = startTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set to tomorrow by default
|
||||||
|
var foundendtime = (new Date())
|
||||||
|
foundendtime.setDate(foundendtime.getDate() + 1)
|
||||||
|
|
||||||
|
// Check if endtime is after the daily statistics["date"] string
|
||||||
|
if (endTime !== "" && endTime !== undefined && endTime !== null) {
|
||||||
|
foundendtime = endTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if start time is before the daily statistics["date"] string
|
||||||
|
var newlist = []
|
||||||
|
for (let key in statistics["daily_statistics"]) {
|
||||||
|
const item = statistics["daily_statistics"][key]
|
||||||
|
if (item["date"] === undefined) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = new Date(item["date"])
|
||||||
|
if (date >= foundstarttime) {
|
||||||
|
if (date <= foundendtime) {
|
||||||
|
newlist.push(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If newlist is empty, set the timestamp to 1 year back and check if there are any statistics there
|
||||||
|
// If foundstarttime is more than 30 days back, don't do this
|
||||||
|
/*
|
||||||
|
if (newlist.length === 0 && foundstarttime.getDate() > 30) {
|
||||||
|
// Set the timestamp to be back
|
||||||
|
foundstarttime.setFullYear(foundstarttime.getFullYear() - 1)
|
||||||
|
setStartTime(foundstarttime)
|
||||||
|
|
||||||
|
console.log("IN HERE")
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
var tmpstats = JSON.parse(JSON.stringify(statistics))
|
||||||
|
|
||||||
|
var workflowexecutions = 0
|
||||||
|
var appexecutions = 0
|
||||||
|
var estimatedcost = 0
|
||||||
|
if (newlist.length > 0) {
|
||||||
|
tmpstats["daily_statistics"] = newlist
|
||||||
|
|
||||||
|
for (let key in newlist) {
|
||||||
|
const item = newlist[key]
|
||||||
|
if (item["workflow_executions"] === undefined) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
workflowexecutions += item["workflow_executions"]
|
||||||
|
appexecutions += item["app_executions"]
|
||||||
|
|
||||||
|
estimatedcost += (item["app_executions"] * invocationCost)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpstats["monthly_workflow_executions"] = workflowexecutions
|
||||||
|
tmpstats["monthly_app_executions"] = appexecutions
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make estimatedcost have max 2 decimals
|
||||||
|
if (isCloud) {
|
||||||
|
// Exclude includedExecutions*month
|
||||||
|
// const includedExecutions = 150000
|
||||||
|
//estimatedcost -= (includedExecutions * invocationCost)
|
||||||
|
|
||||||
|
setApprunCost(estimatedcost.toFixed(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
setFilteredStatistics(tmpstats)
|
||||||
|
handleDataSetting(tmpstats, "day")
|
||||||
|
|
||||||
|
}, [statistics, startTime, endTime])
|
||||||
|
|
||||||
|
const handleStartTimeChange = (date) => {
|
||||||
|
setStartTime(date)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEndTimeChange = (date) => {
|
||||||
|
setEndTime(date)
|
||||||
|
}
|
||||||
|
|
||||||
const handleDataSetting = (inputdata, grouping) => {
|
const handleDataSetting = (inputdata, grouping) => {
|
||||||
if (inputdata === undefined || inputdata === null) {
|
if (inputdata === undefined || inputdata === null) {
|
||||||
return
|
return
|
||||||
@@ -95,8 +262,6 @@ const AppStats = (defaultprops) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Looking at daily data: ", inputdata)
|
|
||||||
|
|
||||||
var appRuns = {
|
var appRuns = {
|
||||||
"key": "App Runs",
|
"key": "App Runs",
|
||||||
"data": []
|
"data": []
|
||||||
@@ -112,6 +277,11 @@ const AppStats = (defaultprops) => {
|
|||||||
"data": []
|
"data": []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var appcostRuns = {
|
||||||
|
"key": "Cost of App Runs",
|
||||||
|
"data": []
|
||||||
|
}
|
||||||
|
|
||||||
for (let key in dailyStats) {
|
for (let key in dailyStats) {
|
||||||
// Always skips first one as it has accumulated data in it
|
// Always skips first one as it has accumulated data in it
|
||||||
if (key === 0) {
|
if (key === 0) {
|
||||||
@@ -119,7 +289,6 @@ const AppStats = (defaultprops) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const item = dailyStats[key]
|
const item = dailyStats[key]
|
||||||
|
|
||||||
if (item["date"] === undefined) {
|
if (item["date"] === undefined) {
|
||||||
console.log("No date: ", item)
|
console.log("No date: ", item)
|
||||||
continue
|
continue
|
||||||
@@ -131,6 +300,12 @@ const AppStats = (defaultprops) => {
|
|||||||
key: new Date(item["date"]),
|
key: new Date(item["date"]),
|
||||||
data: item["app_executions"]
|
data: item["app_executions"]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Add number
|
||||||
|
appcostRuns["data"].push({
|
||||||
|
key: new Date(item["date"]),
|
||||||
|
data: (item["app_executions"] * invocationCost).toFixed(2)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if workflow_executions key in item
|
// Check if workflow_executions key in item
|
||||||
@@ -150,12 +325,16 @@ const AppStats = (defaultprops) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Adds data for today
|
// Adds data for today
|
||||||
console.log("Inputdata: ", inputdata)
|
|
||||||
if (inputdata["daily_app_executions"] !== undefined && inputdata["daily_app_executions"] !== null) {
|
if (inputdata["daily_app_executions"] !== undefined && inputdata["daily_app_executions"] !== null) {
|
||||||
appRuns["data"].push({
|
appRuns["data"].push({
|
||||||
key: new Date(),
|
key: new Date(),
|
||||||
data: inputdata["daily_app_executions"]
|
data: inputdata["daily_app_executions"]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
appcostRuns["data"].push({
|
||||||
|
key: new Date(),
|
||||||
|
data: (inputdata["daily_app_executions"] * invocationCost).toFixed(2)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputdata["daily_workflow_executions"] !== undefined && inputdata["daily_workflow_executions"] !== null) {
|
if (inputdata["daily_workflow_executions"] !== undefined && inputdata["daily_workflow_executions"] !== null) {
|
||||||
@@ -175,6 +354,7 @@ const AppStats = (defaultprops) => {
|
|||||||
setSubflowRuns(subflowRuns)
|
setSubflowRuns(subflowRuns)
|
||||||
setWorkflowRuns(workflowRuns)
|
setWorkflowRuns(workflowRuns)
|
||||||
setAppruns(appRuns)
|
setAppruns(appRuns)
|
||||||
|
setApprunCosts(appcostRuns)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStats = () => {
|
const getStats = () => {
|
||||||
@@ -186,25 +366,25 @@ const AppStats = (defaultprops) => {
|
|||||||
},
|
},
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
console.log("Status not 200 for workflows :O!: ", response.status);
|
console.log("Status not 200 for workflows :O!: ", response.status);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
if (responseJson["success"] === false) {
|
if (responseJson["success"] === false) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setStatistics(responseJson)
|
setStatistics(responseJson)
|
||||||
handleDataSetting(responseJson, "day")
|
handleDataSetting(responseJson, "day")
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.log("error: ", error)
|
console.log("error: ", error)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -215,40 +395,126 @@ const AppStats = (defaultprops) => {
|
|||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
padding: 40,
|
padding: 40,
|
||||||
margin: 5,
|
margin: 5,
|
||||||
backgroundColor: theme.palette.surfaceColor,
|
backgroundColor: theme.palette.platformColor,
|
||||||
|
border: "1px solid rgba(255,255,255,0.3)",
|
||||||
maxWidth: 300,
|
maxWidth: 300,
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = (
|
const data = (
|
||||||
<div className="content" style={{width: "100%", margin: "auto", }}>
|
<div className="content" style={{width: "100%", margin: "auto", }}>
|
||||||
<Typography variant="body1" style={{margin: "auto", marginLeft: 10, marginBottom: 20, }}>
|
<Typography variant="body1" style={{margin: "auto", marginLeft: 10, marginBottom: 20, }}>
|
||||||
All Stat widgets are monthly and gathered from <a
|
All shown statistics are gathered from <a
|
||||||
href={`${globalUrl}/api/v1/orgs/${selectedOrganization.id}/stats`}
|
href={`${globalUrl}/api/v1/orgs/${selectedOrganization.id}/stats`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
style={{ textDecoration: "none", color: "#f85a3e",}}
|
style={{ textDecoration: "none", color: "#f85a3e",}}
|
||||||
>Your Organization Statistics. </a>
|
>Your Organization Statistics </a>
|
||||||
This is a feature to help give you more insight into Shuffle, and will be populating over time.
|
This is a feature to help give you more insight into Shuffle, and to understand your utilization of the Shuffle platform. <b>The billing tracker is in Beta, and is always calculated manually before being invoiced.</b>
|
||||||
</Typography>
|
</Typography>
|
||||||
{statistics !== undefined ?
|
|
||||||
<div style={{display: "flex", textAlign: "center",}}>
|
<div style={{display: "flex", textAlign: "center",}}>
|
||||||
<Paper style={paperStyle}>
|
{filteredStatistics !== undefined ?
|
||||||
<Typography variant="h4">
|
<div style={{flex: 1, display: "flex", textAlign: "center",}}>
|
||||||
{statistics.monthly_workflow_executions}
|
<Tooltip title={
|
||||||
</Typography>
|
<Typography variant="body1" style={{padding: 10, }}>
|
||||||
<Typography variant="h6">
|
The cost of app runs in the selected period based on {filteredStatistics.monthly_app_executions} App Runs. These numbers do not exclude your included 10.000/month or {includedExecutions} App Runs per month. App Run cost: ${invocationCost}.
|
||||||
Workflow Runs
|
</Typography>
|
||||||
</Typography>
|
}>
|
||||||
</Paper>
|
<Paper style={paperStyle}>
|
||||||
<Paper style={paperStyle}>
|
<Typography variant="h4">
|
||||||
<Typography variant="h4">
|
${apprunCost}
|
||||||
{statistics.monthly_app_executions}
|
</Typography>
|
||||||
</Typography>
|
<Typography variant="h6">
|
||||||
<Typography variant="h6">
|
Period Cost
|
||||||
App Runs
|
</Typography>
|
||||||
</Typography>
|
</Paper>
|
||||||
</Paper>
|
</Tooltip>
|
||||||
</div>
|
<Tooltip title={
|
||||||
: null}
|
<Typography variant="body1" style={{padding: 10, }}>
|
||||||
|
App runs in the selected period
|
||||||
|
</Typography>
|
||||||
|
}>
|
||||||
|
<Paper style={paperStyle}>
|
||||||
|
<Typography variant="h4">
|
||||||
|
{filteredStatistics.monthly_app_executions === null || filteredStatistics.monthly_app_executions === undefined ? 0 : filteredStatistics.monthly_app_executions}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h6">
|
||||||
|
App Runs
|
||||||
|
</Typography>
|
||||||
|
</Paper>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title={
|
||||||
|
<Typography variant="body1" style={{padding: 10, }}>
|
||||||
|
Workflow runs in the selected period
|
||||||
|
</Typography>
|
||||||
|
}>
|
||||||
|
<Paper style={paperStyle}>
|
||||||
|
<Typography variant="h4">
|
||||||
|
{filteredStatistics.monthly_workflow_executions === null || filteredStatistics.monthly_workflow_executions === undefined ? 0 : filteredStatistics.monthly_workflow_executions}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h6">
|
||||||
|
Workflow Runs
|
||||||
|
</Typography>
|
||||||
|
</Paper>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title={
|
||||||
|
<Typography variant="body1" style={{padding: 10, }}>
|
||||||
|
Estimated cost to be billed at the end of the current month. Subtracted contractually included app runs. Actual cost month to date: ${monthToDateCost}. App Run cost: ${invocationCost}.
|
||||||
|
</Typography>
|
||||||
|
}>
|
||||||
|
<Paper style={{
|
||||||
|
textAlign: "center",
|
||||||
|
padding: 40,
|
||||||
|
margin: 5,
|
||||||
|
marginLeft: 90,
|
||||||
|
backgroundColor: theme.palette.platformColor,
|
||||||
|
border: "1px solid rgba(255,255,255,0.3)",
|
||||||
|
maxWidth: 300,
|
||||||
|
}}>
|
||||||
|
<Typography variant="h4">
|
||||||
|
${monthTotalCost}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h6">
|
||||||
|
Estimated cost
|
||||||
|
</Typography>
|
||||||
|
</Paper>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
: null}
|
||||||
|
|
||||||
|
<LocalizationProvider dateAdapter={AdapterDayjs} style={{flex: 1, }}>
|
||||||
|
<div style={{display: "flex", flexDirection: "column", }}>
|
||||||
|
<DateTimePicker
|
||||||
|
sx={{
|
||||||
|
marginTop: 1,
|
||||||
|
marginLeft: 1,
|
||||||
|
minWidth: 240,
|
||||||
|
maxWidth: 240,
|
||||||
|
}}
|
||||||
|
ampm={false}
|
||||||
|
label="Search from"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
value={startTime}
|
||||||
|
onChange={handleStartTimeChange}
|
||||||
|
renderInput={(params) => <TextField {...params} />}
|
||||||
|
/>
|
||||||
|
<DateTimePicker
|
||||||
|
sx={{
|
||||||
|
marginTop: 1,
|
||||||
|
marginLeft: 1,
|
||||||
|
minWidth: 240,
|
||||||
|
maxWidth: 240,
|
||||||
|
}}
|
||||||
|
ampm={false}
|
||||||
|
label="Search until"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
value={endTime}
|
||||||
|
onChange={handleEndTimeChange}
|
||||||
|
renderInput={(params) => <TextField {...params} />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</LocalizationProvider>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
{appRuns === undefined ?
|
{appRuns === undefined ?
|
||||||
null
|
null
|
||||||
@@ -267,6 +533,12 @@ const AppStats = (defaultprops) => {
|
|||||||
:
|
:
|
||||||
<LineChartWrapper keys={subflowRuns} height={300} width={"100%"} inputname={"Subflow Runs"}/>
|
<LineChartWrapper keys={subflowRuns} height={300} width={"100%"} inputname={"Subflow Runs"}/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{/*appRunCosts === undefined ?
|
||||||
|
null
|
||||||
|
:
|
||||||
|
<LineChartWrapper keys={appRunCosts} height={300} width={"100%"} inputname={"Apprun cost - Cost per day"}/>
|
||||||
|
*/}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -830,6 +830,7 @@ const RuntimeDebugger = (props) => {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||||
<DateTimePicker
|
<DateTimePicker
|
||||||
sx={{
|
sx={{
|
||||||
@@ -859,8 +860,8 @@ const RuntimeDebugger = (props) => {
|
|||||||
onChange={handleEndTimeChange}
|
onChange={handleEndTimeChange}
|
||||||
renderInput={(params) => <TextField {...params} />}
|
renderInput={(params) => <TextField {...params} />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</LocalizationProvider>
|
</LocalizationProvider>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|||||||
@@ -448,7 +448,7 @@ const WorkflowTemplatePopup = (props) => {
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: "flex", maxWidth: isCloud ? 470 : isMobile? 345: 450, minWidth: isCloud ? 470 : isMobile? null: 450, height: 78, borderRadius: 8, }}>
|
<div style={{ display: "flex", maxWidth: isCloud ? isMobile ? null : 470 : isMobile? 345: 450, minWidth: isCloud ? isMobile ? null : 470 : isMobile? null: 450, height: 78, borderRadius: 8, justifyContent: isMobile ? null : "center" }}>
|
||||||
<ModalView />
|
<ModalView />
|
||||||
<div
|
<div
|
||||||
// variant={isActive === 1 ? "contained" : "outlined"}
|
// variant={isActive === 1 ? "contained" : "outlined"}
|
||||||
@@ -522,7 +522,7 @@ const WorkflowTemplatePopup = (props) => {
|
|||||||
<div style={{width: 50, }} />
|
<div style={{width: 50, }} />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 3, marginLeft: 20, }}>
|
<div style={{ flex: 3, marginLeft: 20, maxHeight: 50, overflow: "hidden", }}>
|
||||||
<Typography variant="body1" style={{ marginTop: parsedDescription.length === 0 ? 10 : 0, fontSize: isMobile ? 13 : 16,fontWeight: isHomePage? 600 : null,textTransform: 'capitalize', color: isHomePage ? "var(--White-text, #F1F1F1)" :"rgba(241, 241, 241, 1)"}} >
|
<Typography variant="body1" style={{ marginTop: parsedDescription.length === 0 ? 10 : 0, fontSize: isMobile ? 13 : 16,fontWeight: isHomePage? 600 : null,textTransform: 'capitalize', color: isHomePage ? "var(--White-text, #F1F1F1)" :"rgba(241, 241, 241, 1)"}} >
|
||||||
{parsedTitle}
|
{parsedTitle}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|||||||
+183
-26
@@ -88,7 +88,6 @@ import Priorities from "../components/Priorities.jsx";
|
|||||||
import Branding from "../components/Branding.jsx";
|
import Branding from "../components/Branding.jsx";
|
||||||
import Files from "../components/Files.jsx";
|
import Files from "../components/Files.jsx";
|
||||||
import { display, style } from "@mui/system";
|
import { display, style } from "@mui/system";
|
||||||
//import EnvironmentStats from "../components/EnvironmentStats.jsx";
|
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
notchedOutline: {
|
notchedOutline: {
|
||||||
@@ -181,10 +180,12 @@ const Admin = (props) => {
|
|||||||
const [secret2FA, setSecret2FA] = React.useState("");
|
const [secret2FA, setSecret2FA] = React.useState("");
|
||||||
const [show2faSetup, setShow2faSetup] = useState(false);
|
const [show2faSetup, setShow2faSetup] = useState(false);
|
||||||
|
|
||||||
const [adminTab, setAdminTab] = React.useState(2);
|
const [adminTab, setAdminTab] = React.useState(3);
|
||||||
const [showApiKey, setShowApiKey] = useState(false);
|
const [showApiKey, setShowApiKey] = useState(false);
|
||||||
const [billingInfo, setBillingInfo] = React.useState({});
|
const [billingInfo, setBillingInfo] = React.useState({});
|
||||||
const [selectedStatus, setSelectedStatus] = React.useState([]);
|
const [selectedStatus, setSelectedStatus] = React.useState([]);
|
||||||
|
|
||||||
|
const [, forceUpdate] = React.useState();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getUsers()
|
getUsers()
|
||||||
@@ -2254,35 +2255,124 @@ If you're interested, please let me know a time that works for you, or set up a
|
|||||||
|
|
||||||
const GridItem = (props) => {
|
const GridItem = (props) => {
|
||||||
const [expanded, setExpanded] = React.useState(false);
|
const [expanded, setExpanded] = React.useState(false);
|
||||||
|
const [showEdit, setShowEdit] = React.useState(false);
|
||||||
|
const [newValue, setNewValue] = React.useState(-100);
|
||||||
|
|
||||||
const primary = props.data.primary;
|
const primary = props.data.primary;
|
||||||
const secondary = props.data.secondary;
|
const secondary = props.data.secondary;
|
||||||
const primaryIcon = props.data.icon;
|
const primaryIcon = props.data.icon;
|
||||||
const secondaryIcon = props.data.active ? (
|
const secondaryIcon = props.data.active ?
|
||||||
<CheckCircleIcon style={{ color: "green" }} />
|
<CheckCircleIcon style={{ color: "green" }} />
|
||||||
) : (
|
:
|
||||||
<CloseIcon style={{ color: "red" }} />
|
<CloseIcon style={{ color: "red" }} />
|
||||||
)
|
|
||||||
|
const submitFeatureEdit = (sync_features) => {
|
||||||
|
if (!userdata.support) {
|
||||||
|
console.log("User does not have support access and can't edit features");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sync_features.editing = true
|
||||||
|
const data = {
|
||||||
|
org_id: selectedOrganization.id,
|
||||||
|
sync_features: sync_features,
|
||||||
|
};
|
||||||
|
|
||||||
|
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 enableFeature = () => {
|
||||||
|
console.log("Enabling "+primary)
|
||||||
|
|
||||||
|
console.log(selectedOrganization.sync_features)
|
||||||
|
// Check if primary is in sync_features
|
||||||
|
var tmpprimary = primary.replaceAll(" ", "_")
|
||||||
|
if (!(tmpprimary in selectedOrganization.sync_features)) {
|
||||||
|
console.log("Primary not in sync_features: "+tmpprimary)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.data.active) {
|
||||||
|
selectedOrganization.sync_features[tmpprimary].active = false
|
||||||
|
} else {
|
||||||
|
selectedOrganization.sync_features[tmpprimary].active = true
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedOrganization(selectedOrganization)
|
||||||
|
forceUpdate(Math.random())
|
||||||
|
submitFeatureEdit(selectedOrganization.sync_features)
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitEdit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// Check if primary is in sync_features
|
||||||
|
var tmpprimary = primary.replaceAll(" ", "_")
|
||||||
|
if (!(tmpprimary in selectedOrganization.sync_features)) {
|
||||||
|
console.log("Primary not in sync_features: "+tmpprimary)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make it into a number
|
||||||
|
var tmp = parseInt(newValue)
|
||||||
|
if (isNaN(tmp)) {
|
||||||
|
console.log("Not a number: "+newValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedOrganization.sync_features[tmpprimary].limit = tmp
|
||||||
|
|
||||||
|
setSelectedOrganization(selectedOrganization)
|
||||||
|
forceUpdate(Math.random())
|
||||||
|
submitFeatureEdit(selectedOrganization.sync_features)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid
|
<Grid
|
||||||
item
|
item
|
||||||
xs={4}
|
xs={4}
|
||||||
style={{ cursor: "pointer" }}
|
|
||||||
onClick={() => {
|
|
||||||
setExpanded(!expanded);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Card
|
<Card
|
||||||
style={{
|
style={{
|
||||||
margin: 4,
|
margin: 4,
|
||||||
backgroundColor: theme.palette.surfaceColor,
|
backgroundColor: theme.palette.platformColor,
|
||||||
|
borderRadius: theme.palette.borderRadius,
|
||||||
|
border: "1px solid rgba(255,255,255,0.3)",
|
||||||
color: "white",
|
color: "white",
|
||||||
minHeight: expanded ? 250 : "inherit",
|
minHeight: expanded ? 250 : "inherit",
|
||||||
maxHeight: expanded ? 250 : "inherit",
|
maxHeight: expanded ? 300 : "inherit",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ListItem>
|
<ListItem
|
||||||
|
style={{cursor: "pointer", }}
|
||||||
|
onClick={() => {
|
||||||
|
setExpanded(!expanded);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<Avatar>{primaryIcon}</Avatar>
|
<Avatar>{primaryIcon}</Avatar>
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
@@ -2290,9 +2380,42 @@ If you're interested, please let me know a time that works for you, or set up a
|
|||||||
style={{ textTransform: "capitalize" }}
|
style={{ textTransform: "capitalize" }}
|
||||||
primary={primary}
|
primary={primary}
|
||||||
/>
|
/>
|
||||||
{secondaryIcon}
|
{isCloud && userdata.support === true ?
|
||||||
|
<Tooltip title="Edit features (support users only)">
|
||||||
|
<EditIcon
|
||||||
|
color="secondary"
|
||||||
|
style={{marginRight: 10, cursor: "pointer", }}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (showEdit) {
|
||||||
|
setShowEdit(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Edit")
|
||||||
|
|
||||||
|
setShowEdit(true)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
: null}
|
||||||
|
<Tooltip title={props.data.active ? "Disable feature" : "Enable feature"}>
|
||||||
|
<span
|
||||||
|
style={{cursor: "pointer", marginTop: 5, }}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
enableFeature()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{secondaryIcon}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
{expanded ? (
|
{expanded ?
|
||||||
<div style={{ padding: 15 }}>
|
<div style={{ padding: 15 }}>
|
||||||
<Typography>
|
<Typography>
|
||||||
<b>Usage: </b>
|
<b>Usage: </b>
|
||||||
@@ -2309,7 +2432,41 @@ If you're interested, please let me know a time that works for you, or set up a
|
|||||||
</Typography>*/}
|
</Typography>*/}
|
||||||
<Typography style={{maxHeight: 150, overflowX: "hidden", overflowY: "auto"}}><b>Description:</b> {secondary}</Typography>
|
<Typography style={{maxHeight: 150, overflowX: "hidden", overflowY: "auto"}}><b>Description:</b> {secondary}</Typography>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
: null}
|
||||||
|
|
||||||
|
|
||||||
|
{showEdit ?
|
||||||
|
<FormControl fullWidth onSubmit={(e) => {
|
||||||
|
console.log("Submit")
|
||||||
|
submitEdit(e)
|
||||||
|
}}>
|
||||||
|
<span style={{display: "flex", }}>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
style={{flex: 3, }}
|
||||||
|
color="primary"
|
||||||
|
label={"Edit value"}
|
||||||
|
defaultValue={props.data.limit}
|
||||||
|
style={{
|
||||||
|
}}
|
||||||
|
onChange={(event) => {
|
||||||
|
setNewValue(event.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
style={{flex: 1, }}
|
||||||
|
variant="contained"
|
||||||
|
disabled={newValue < -1}
|
||||||
|
onClick={(e) => {
|
||||||
|
console.log("Submit 2")
|
||||||
|
submitEdit(e)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
|
</FormControl>
|
||||||
|
: null}
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
@@ -2658,7 +2815,7 @@ If you're interested, please let me know a time that works for you, or set up a
|
|||||||
/>
|
/>
|
||||||
<Tab
|
<Tab
|
||||||
label=<span>
|
label=<span>
|
||||||
Licensing
|
Billing & Stats
|
||||||
</span>
|
</span>
|
||||||
/>
|
/>
|
||||||
<Tab
|
<Tab
|
||||||
@@ -2858,12 +3015,12 @@ If you're interested, please let me know a time that works for you, or set up a
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Typography variant="h6" style={{ marginLeft: 5, marginTop: 40, marginBottom: 5 }}>
|
<Typography variant="h6" style={{ marginLeft: 5, marginTop: 40, marginBottom: 5 }}>
|
||||||
Cloud sync features
|
Features
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="textSecondary" style={{marginBottom: 10, marginLeft: 5, }}>
|
<Typography variant="body2" color="textSecondary" style={{marginBottom: 10, marginLeft: 5, }}>
|
||||||
If not otherwise specified, Usage will reset monthly
|
Features and Limitations that are currently available to you in your Cloud or Hybrid Organization. App Executions (App Runs) reset monthly. If the organization is a customer or in a trial, these features limitations are not always enforced.
|
||||||
</Typography>
|
</Typography>
|
||||||
<Grid container style={{ width: "100%", marginBottom: 15 }}>
|
<Grid container style={{ width: "100%", marginBottom: 15, paddingBottom: 150, }}>
|
||||||
|
|
||||||
{selectedOrganization.sync_features === undefined ||
|
{selectedOrganization.sync_features === undefined ||
|
||||||
selectedOrganization.sync_features === null
|
selectedOrganization.sync_features === null
|
||||||
@@ -2872,15 +3029,15 @@ If you're interested, please let me know a time that works for you, or set up a
|
|||||||
key,
|
key,
|
||||||
index
|
index
|
||||||
) {
|
) {
|
||||||
// unnecessary parts
|
|
||||||
if (key === "schedule" || key === "apps" || key === "updates") {
|
if (key === "schedule" || key === "apps" || key === "updates" || key === "editing") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const item = selectedOrganization.sync_features[key];
|
const item = selectedOrganization.sync_features[key];
|
||||||
if (item === null) {
|
if (item === null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const newkey = key.replaceAll("_", " ");
|
const newkey = key.replaceAll("_", " ");
|
||||||
const griditem = {
|
const griditem = {
|
||||||
|
|||||||
@@ -1022,7 +1022,7 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
})
|
})
|
||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
console.log("GOT A RESPONSE??")
|
console.log("GOT A RESPONSE??")
|
||||||
getWorkflowExecutionCount(id);
|
// getWorkflowExecutionCount(id);
|
||||||
if (responseJson !== undefined && responseJson !== null && responseJson.executions !== undefined && responseJson.executions !== null) {
|
if (responseJson !== undefined && responseJson !== null && responseJson.executions !== undefined && responseJson.executions !== null) {
|
||||||
|
|
||||||
// - means it's opposite
|
// - means it's opposite
|
||||||
@@ -13758,6 +13758,40 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
</div>
|
</div>
|
||||||
: null
|
: null
|
||||||
|
|
||||||
|
const RightsideBar = () => {
|
||||||
|
const [hovered, setHovered] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "fixed", right: -5, top: "40%", width: 70, height: 235, border: "1px solid #f85a3e", cursor: "pointer", borderRadius: theme.palette.borderRadius,
|
||||||
|
padding: 10,
|
||||||
|
backgroundColor: hovered ? theme.palette.surfaceColor : theme.palette.platformColor,
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
onClick={() => {
|
||||||
|
setExecutionModalOpen(true);
|
||||||
|
getWorkflowExecution(props.match.params.key, "");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowLeftIcon style={{marginTop: 10, marginLeft: 10, marginBottom: 10, }}/>
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
style={{
|
||||||
|
writingMode: "vertical-rl",
|
||||||
|
textOrientation: "mixed",
|
||||||
|
marginLeft: 10,
|
||||||
|
fontWeight: "bold",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Explore runs
|
||||||
|
</Typography>
|
||||||
|
{/*<ArrowLeftIcon style={{marginTop: 10, marginLeft: 10, marginBottom: 10, }}/> */}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const BottomCytoscapeBar = () => {
|
const BottomCytoscapeBar = () => {
|
||||||
if (workflow.id === undefined || workflow.id === null || (!workflow.public && apps.length === 0)) {
|
if (workflow.id === undefined || workflow.id === null || (!workflow.public && apps.length === 0)) {
|
||||||
return null;
|
return null;
|
||||||
@@ -15069,7 +15103,7 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
>
|
>
|
||||||
<h2 style={{ color: "rgba(255,255,255,0.5)" }}>
|
<h2 style={{ color: "rgba(255,255,255,0.5)" }}>
|
||||||
<DirectionsRunIcon style={{ marginRight: 10 }} />
|
<DirectionsRunIcon style={{ marginRight: 10 }} />
|
||||||
All Workflow Runs: { workflowExecutionCount }
|
All Workflow Runs
|
||||||
</h2>
|
</h2>
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@@ -16514,6 +16548,7 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
{showErrors}
|
{showErrors}
|
||||||
<BottomCytoscapeBar />
|
<BottomCytoscapeBar />
|
||||||
<TopCytoscapeBar />
|
<TopCytoscapeBar />
|
||||||
|
<RightsideBar />
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
TextField,
|
TextField,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Breadcrumbs,
|
Breadcrumbs,
|
||||||
|
Drawer,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Chip,
|
Chip,
|
||||||
IconButton,
|
IconButton,
|
||||||
@@ -43,6 +44,7 @@ import {
|
|||||||
Loop as LoopIcon,
|
Loop as LoopIcon,
|
||||||
AddPhotoAlternate as AddPhotoAlternateIcon,
|
AddPhotoAlternate as AddPhotoAlternateIcon,
|
||||||
CallMerge as CallMergeIcon,
|
CallMerge as CallMergeIcon,
|
||||||
|
CloudDownload as CloudDownloadIcon,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
|
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
@@ -448,6 +450,8 @@ const AppCreator = (defaultprops) => {
|
|||||||
const [openApiData, setOpenApiData] = React.useState("");
|
const [openApiData, setOpenApiData] = React.useState("");
|
||||||
const [openApiModal, setOpenApiModal] = React.useState(false);
|
const [openApiModal, setOpenApiModal] = React.useState(false);
|
||||||
|
|
||||||
|
const [appDownloadData, setAppDownloadData] = React.useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log("In useEffect for openApiData: ", openApiData)
|
console.log("In useEffect for openApiData: ", openApiData)
|
||||||
}, [openApiData]);
|
}, [openApiData]);
|
||||||
@@ -900,8 +904,9 @@ const AppCreator = (defaultprops) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (newaction.url !== undefined && newaction.url !== null && newaction.url.includes("_shuffle_replace_")) {
|
if (newaction.url !== undefined && newaction.url !== null && newaction.url.includes("_shuffle_replace_")) {
|
||||||
const regex = /_shuffle_replace_\d/i;
|
//const regex = /_shuffle_replace_\d/i;
|
||||||
//console.log("NEW: ",
|
const regex = /_shuffle_replace_\d+/i
|
||||||
|
|
||||||
newaction.url = newaction.url.replaceAll(new RegExp(regex, 'g'), "")
|
newaction.url = newaction.url.replaceAll(new RegExp(regex, 'g'), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1205,9 +1210,9 @@ const AppCreator = (defaultprops) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Param Error: ", e, path)
|
console.log("Param Error: ", e, path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2569,6 +2574,8 @@ const AppCreator = (defaultprops) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setAppDownloadData(JSON.stringify(data, null, 4))
|
||||||
|
|
||||||
fetch(globalUrl + "/api/v1/verify_openapi", {
|
fetch(globalUrl + "/api/v1/verify_openapi", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -3687,48 +3694,76 @@ const AppCreator = (defaultprops) => {
|
|||||||
return errormessage;
|
return errormessage;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getBackgroundColor = (data) => {
|
||||||
|
var bgColor = "#61afee";
|
||||||
|
if (data === "POST") {
|
||||||
|
bgColor = "#49cc90";
|
||||||
|
} else if (data === "PUT") {
|
||||||
|
bgColor = "#fca130";
|
||||||
|
} else if (data === "PATCH") {
|
||||||
|
bgColor = "#50e3c2";
|
||||||
|
} else if (data === "DELETE") {
|
||||||
|
bgColor = "#f93e3e";
|
||||||
|
} else if (data === "HEAD") {
|
||||||
|
bgColor = "#9012fe";
|
||||||
|
}
|
||||||
|
|
||||||
|
return bgColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const newActionModal = (
|
const newActionModal = (
|
||||||
<Dialog
|
<Drawer
|
||||||
|
anchor={"right"}
|
||||||
open={actionsModalOpen}
|
open={actionsModalOpen}
|
||||||
fullWidth
|
fullWidth
|
||||||
PaperProps={{
|
PaperProps={{
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: surfaceColor,
|
backgroundColor: surfaceColor,
|
||||||
color: "white",
|
color: "white",
|
||||||
minWidth: 550,
|
minWidth: 700,
|
||||||
maxWidth: 550,
|
maxWidth: 700,
|
||||||
maxHeight: 750,
|
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setUrlPath("");
|
console.log("Closing modal");
|
||||||
setCurrentAction({
|
|
||||||
name: "",
|
// Old: This had some issue with arrays
|
||||||
description: "",
|
//setUrlPath("");
|
||||||
url: "",
|
//setCurrentAction({
|
||||||
file_field: "",
|
// name: "",
|
||||||
headers: "",
|
// description: "",
|
||||||
paths: [],
|
// url: "",
|
||||||
queries: [],
|
// file_field: "",
|
||||||
body: "",
|
// headers: "",
|
||||||
errors: [],
|
// paths: [],
|
||||||
method: actionNonBodyRequest[0],
|
// queries: [],
|
||||||
action_label: "No Label",
|
// body: "",
|
||||||
required_bodyfields: [],
|
// errors: [],
|
||||||
});
|
// method: actionNonBodyRequest[0],
|
||||||
setCurrentActionMethod(apikeySelection[0]);
|
// action_label: "No Label",
|
||||||
setUrlPathQueries([]);
|
// required_bodyfields: [],
|
||||||
setActionsModalOpen(false);
|
//});
|
||||||
setFileUploadEnabled(false);
|
//setCurrentActionMethod(apikeySelection[0]);
|
||||||
|
//setUrlPathQueries([]);
|
||||||
|
//setActionsModalOpen(false);
|
||||||
|
//setFileUploadEnabled(false);
|
||||||
|
|
||||||
|
console.log(currentAction);
|
||||||
|
const errors = getActionErrors();
|
||||||
|
addActionToView(errors);
|
||||||
|
setActionsModalOpen(false);
|
||||||
|
setUrlPathQueries([]);
|
||||||
|
setUrlPath("");
|
||||||
|
setFileUploadEnabled(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormControl style={{ backgroundColor: surfaceColor, color: "white" }}>
|
<FormControl style={{ backgroundColor: surfaceColor, color: "white" }}>
|
||||||
<DialogTitle>
|
<DialogTitle style={{marginTop: 30, }}>
|
||||||
<div style={{ color: "white" }}>New action</div>
|
<div style={{ color: "white" }}>New action</div>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent style={{paddingBottom: 100, }}>
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://shuffler.io/docs/app_creation#actions"
|
href="https://shuffler.io/docs/app_creation#actions"
|
||||||
@@ -3826,26 +3861,33 @@ const AppCreator = (defaultprops) => {
|
|||||||
id: "method-option",
|
id: "method-option",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{actionNonBodyRequest.map((data, index) => {
|
|
||||||
|
// Add actionBodyRequest to actionNonBodyRequest
|
||||||
|
{actionNonBodyRequest.concat(actionBodyRequest).map((data, index) => {
|
||||||
|
const backgroundColor = getBackgroundColor(data);
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key={index}
|
key={index}
|
||||||
style={{ backgroundColor: inputColor, color: "white" }}
|
style={{}}
|
||||||
value={data}
|
value={data}
|
||||||
>
|
>
|
||||||
{data}
|
<Chip
|
||||||
|
style={{
|
||||||
|
color: "white",
|
||||||
|
borderRadius: 5,
|
||||||
|
minWidth: 80,
|
||||||
|
marginRight: 10,
|
||||||
|
marginTop: 2,
|
||||||
|
cursor: "pointer",
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: "bold",
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
}}
|
||||||
|
label={data}
|
||||||
|
/>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{actionBodyRequest.map((data, index) => (
|
|
||||||
<MenuItem
|
|
||||||
key={index}
|
|
||||||
style={{ backgroundColor: inputColor, color: "white" }}
|
|
||||||
value={data}
|
|
||||||
>
|
|
||||||
{data}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
</Select>
|
||||||
<div style={{ marginTop: "15px" }} />
|
<div style={{ marginTop: "15px" }} />
|
||||||
URL path / Curl statement
|
URL path / Curl statement
|
||||||
@@ -4222,19 +4264,11 @@ const AppCreator = (defaultprops) => {
|
|||||||
/>
|
/>
|
||||||
{exampleResponse}
|
{exampleResponse}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<div style={{position: "fixed", backgroundColor: theme.palette.surfaceColor, bottom: 0, width: "100%", padding: 25, borderTop: "1px solid rgba(255,255,255,0.3)", }}>
|
||||||
<Button
|
|
||||||
style={{ borderRadius: "0px" }}
|
|
||||||
onClick={() => {
|
|
||||||
setActionsModalOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
variant={urlPath.length > 0 ? "contained" : "outlined"}
|
variant={urlPath.length > 0 ? "contained" : "outlined"}
|
||||||
style={{ borderRadius: "0px" }}
|
style={{ }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
//console.log(urlPathQueries)
|
//console.log(urlPathQueries)
|
||||||
//console.log(urlPath)
|
//console.log(urlPath)
|
||||||
@@ -4249,9 +4283,17 @@ const AppCreator = (defaultprops) => {
|
|||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
<Button
|
||||||
|
style={{ marginLeft: 10, }}
|
||||||
|
onClick={() => {
|
||||||
|
setActionsModalOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Dialog>
|
</Drawer>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
@@ -6103,20 +6145,68 @@ const AppCreator = (defaultprops) => {
|
|||||||
{testView}
|
{testView}
|
||||||
*/}
|
*/}
|
||||||
|
|
||||||
<Button
|
<div style={{display: "flex", marginTop: 35, }}>
|
||||||
disabled={appBuilding}
|
{appDownloadData.length > 0 ?
|
||||||
color="primary"
|
<Tooltip title="Download the OpenAPI specification for the App" placement="bottom">
|
||||||
variant="contained"
|
<IconButton
|
||||||
style={{ borderRadius: "0px", marginTop: "30px", height: "50px" }}
|
style={{marginRight: 25, }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
submitApp();
|
toast(`Downloading OpenAPI JSON data for for ${name}`)
|
||||||
}}
|
// Download as file
|
||||||
>
|
var blob = new Blob([appDownloadData], {
|
||||||
{appBuilding ? <CircularProgress /> : "Save"}
|
type: "application/octet-stream",
|
||||||
</Button>
|
});
|
||||||
<Typography style={{ marginTop: 5 }}>
|
|
||||||
{errorCode.length > 0 ? `Error: ${errorCode}` : null}
|
var url = URL.createObjectURL(blob);
|
||||||
</Typography>
|
var link = document.createElement("a");
|
||||||
|
link.setAttribute("href", url);
|
||||||
|
link.setAttribute("download", `${name}.json`);
|
||||||
|
var event = document.createEvent("MouseEvents");
|
||||||
|
event.initMouseEvent(
|
||||||
|
"click",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
window,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
link.dispatchEvent(event);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloudDownloadIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
: null}
|
||||||
|
<Button
|
||||||
|
disabled={appBuilding}
|
||||||
|
color="primary"
|
||||||
|
variant="contained"
|
||||||
|
fullWidth
|
||||||
|
style={{ height: "50px", flex: 1, }}
|
||||||
|
onClick={() => {
|
||||||
|
submitApp();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{appBuilding ? <CircularProgress /> : "Save"}
|
||||||
|
</Button>
|
||||||
|
{appDownloadData.length > 0 ?
|
||||||
|
<div style={{width: 50, }}/>
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Typography style={{ marginTop: 25, textAlign: "center", }}>
|
||||||
|
{errorCode.length > 0 ? `Upload Error: ${errorCode}` : null}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
</Paper>
|
</Paper>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
+45
-40
@@ -921,6 +921,7 @@ const Apps = (props) => {
|
|||||||
</Link>
|
</Link>
|
||||||
: null
|
: null
|
||||||
|
|
||||||
|
console.log("Sharing config: ", sharingConfiguration);
|
||||||
const activateButton =
|
const activateButton =
|
||||||
selectedApp.generated && !selectedApp.activated ? (
|
selectedApp.generated && !selectedApp.activated ? (
|
||||||
<div>
|
<div>
|
||||||
@@ -943,6 +944,7 @@ const Apps = (props) => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDeleteModalOpen(true);
|
setDeleteModalOpen(true);
|
||||||
}}
|
}}
|
||||||
|
disabled={sharingConfiguration === undefined || sharingConfiguration === null || sharingConfiguration == "public"}
|
||||||
>
|
>
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -957,7 +959,7 @@ const Apps = (props) => {
|
|||||||
(selectedApp.downloaded !== undefined && selectedApp.downloaded == true) ||
|
(selectedApp.downloaded !== undefined && selectedApp.downloaded == true) ||
|
||||||
!selectedApp.generated) &&
|
!selectedApp.generated) &&
|
||||||
activateButton === null ? (
|
activateButton === null ? (
|
||||||
<Tooltip title={"Delete app"}>
|
<Tooltip title={"Delete app (confirm box will show)"}>
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
component="label"
|
component="label"
|
||||||
@@ -966,6 +968,7 @@ const Apps = (props) => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDeleteModalOpen(true);
|
setDeleteModalOpen(true);
|
||||||
}}
|
}}
|
||||||
|
disabled={sharingConfiguration === undefined || sharingConfiguration === null || sharingConfiguration == "public"}
|
||||||
>
|
>
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -1240,48 +1243,50 @@ const Apps = (props) => {
|
|||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
{isCloud && (selectedApp.sharing === true || selectedApp.public === true || creatorProfile.github_avatar !== undefined) && !internalIds.includes(selectedApp.name.toLowerCase()) ?
|
{isCloud && (selectedApp.sharing === true || selectedApp.public === true || creatorProfile.github_avatar !== undefined) && !internalIds.includes(selectedApp.name.toLowerCase()) ?
|
||||||
<Button
|
<Tooltip title="Deactivates this app for the current organisation. This means the app will not be usable again until you re-activate it." placement="top">
|
||||||
variant="contained"
|
<Button
|
||||||
component="label"
|
variant="contained"
|
||||||
color="primary"
|
component="label"
|
||||||
onClick={() => {
|
color="primary"
|
||||||
const tmpurl = new URL(window.location.href);
|
onClick={() => {
|
||||||
const searchParams = tmpurl.searchParams;
|
const tmpurl = new URL(window.location.href);
|
||||||
const queryID = searchParams.get("queryID");
|
const searchParams = tmpurl.searchParams;
|
||||||
|
const queryID = searchParams.get("queryID");
|
||||||
|
|
||||||
if (queryID !== undefined && queryID !== null) {
|
if (queryID !== undefined && queryID !== null) {
|
||||||
aa("init", {
|
aa("init", {
|
||||||
appId: "JNSS5CFDZZ",
|
appId: "JNSS5CFDZZ",
|
||||||
apiKey: "db08e40265e2941b9a7d8f644b6e5240",
|
apiKey: "db08e40265e2941b9a7d8f644b6e5240",
|
||||||
});
|
});
|
||||||
|
|
||||||
const timestamp = new Date().getTime();
|
const timestamp = new Date().getTime();
|
||||||
aa("sendEvents", [
|
aa("sendEvents", [
|
||||||
{
|
{
|
||||||
eventType: "conversion",
|
eventType: "conversion",
|
||||||
eventName: "Public App Activated",
|
eventName: "Public App Activated",
|
||||||
index: "appsearch",
|
index: "appsearch",
|
||||||
objectIDs: [selectedApp.id],
|
objectIDs: [selectedApp.id],
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
queryID: queryID,
|
queryID: queryID,
|
||||||
userToken:
|
userToken:
|
||||||
userdata === undefined ||
|
userdata === undefined ||
|
||||||
userdata === null ||
|
userdata === null ||
|
||||||
userdata.id === undefined
|
userdata.id === undefined
|
||||||
? "unauthenticated"
|
? "unauthenticated"
|
||||||
: userdata.id,
|
: userdata.id,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
console.log("No query to handle when activating");
|
console.log("No query to handle when activating");
|
||||||
}
|
}
|
||||||
|
|
||||||
activateApp(selectedApp.id, true)
|
activateApp(selectedApp.id, true)
|
||||||
}}
|
}}
|
||||||
style={{ height: 35, marginTop: 0, marginLeft: 10, }}
|
style={{ height: 35, marginTop: 0, marginLeft: 10, }}
|
||||||
>
|
>
|
||||||
Deactivate
|
Deactivate
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
: null}
|
: null}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
Reference in New Issue
Block a user