Fixed relevant stats & license trackers to make it easier to use

This commit is contained in:
Frikky
2024-03-18 02:48:53 +01:00
parent c36ed18a04
commit 833a8d53e0
13 changed files with 513 additions and 273 deletions
+2
View File
@@ -969,6 +969,7 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
tutorialsFinished = append(tutorialsFinished, tutorial)
}
licensed := shuffle.IsLicensed(ctx, *currentOrg)
returnValue := shuffle.HandleInfo{
Success: true,
@@ -989,6 +990,7 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
Tutorials: tutorialsFinished,
Priorities: orgPriorities,
Licensed: licensed,
}
returnData, err := json.Marshal(returnValue)
+26 -3
View File
@@ -293,12 +293,35 @@ func handleGetWorkflowqueue(resp http.ResponseWriter, request *http.Request) {
env, err := shuffle.GetEnvironment(ctx, orgId, "")
timeNow := time.Now().Unix()
if err == nil && len(env.Id) > 0 && len(env.Name) > 0 {
// Updates every 60 seconds~
if time.Now().Unix() > env.Edited+60 {
env.RunningIp = request.RemoteAddr
env.RunningIp = shuffle.GetRequestIp(request)
if len(orborusLabel) > 0 {
env.RunningIp = orborusLabel
}
if request.Method == "POST" {
body, err := ioutil.ReadAll(request.Body)
if err == nil {
var envData shuffle.OrborusStats
err = json.Unmarshal(body, &envData)
if err == nil {
if envData.Swarm {
env.Licensed = true
env.RunType = "docker"
}
if envData.Kubernetes {
env.RunType = "k8s"
}
}
}
}
env.Checkin = timeNow
err = shuffle.SetEnvironment(ctx, env)
err = shuffle.SetEnvironment(ctx, &env)
if err != nil {
log.Printf("[WARNING] Failed updating environment: %s", err)
log.Printf("[ERROR] Failed updating environment: %s", err)
}
}
}
+18 -8
View File
@@ -120,10 +120,11 @@ const Billing = (props) => {
minHeight: 280,
maxWidth: 400,
width: "100%",
backgroundColor: theme.palette.surfaceColor,
borderRadius: theme.palette.borderRadius,
backgroundColor: theme.palette.platformColor,
borderRadius: theme.palette.borderRadius*2,
border: "1px solid rgba(255,255,255,0.3)",
marginRight: 10,
marginTop: 15,
}
const isCloud =
@@ -273,6 +274,7 @@ const Billing = (props) => {
const [signatureOpen, setSignatureOpen] = React.useState(false);
const [tosChecked, setTosChecked] = React.useState(subscription.eula_signed)
const [hovered, setHovered] = React.useState(false)
var top_text = "Base Cloud Access"
if (subscription.limit === undefined && subscription.level === undefined || subscription.level === null || subscription.level === 0) {
@@ -321,8 +323,16 @@ const Billing = (props) => {
newPaperstyle.border = "1px solid #f85a3e"
}
if (hovered) {
newPaperstyle.backgroundColor = theme.palette.surfaceColor
}
return (
<Paper style={newPaperstyle}>
<Paper
style={newPaperstyle}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
<Dialog
open={signatureOpen}
PaperProps={{
@@ -960,16 +970,16 @@ const Billing = (props) => {
const isChildOrg = userdata.active_org.creator_org !== "" && userdata.active_org.creator_org !== undefined && userdata.active_org.creator_org !== null
return (
<div>
<div style={{}}>
{addDealModal}
<Typography variant="h4" style={{ marginTop: 20, marginBottom: 10 }}>
Billing
Billing & Licensing
</Typography>
<Typography variant="body1" color="textSecondary" style={{ marginTop: 0, marginBottom: 10}}>
{isCloud ?
"We use Stripe to manage subscriptions and do not store any of your billing information. You can manage your subscription and billing information below."
"Get more out of Shuffle by adding your credit card, such as no App Run limitations, and priority support from our team. We use Stripe to manage subscriptions and do not store any of your billing information. You can manage your subscription and billing information below."
:
"Shuffle is an Open Source automation platform, and no license is required to use it. You may however activate Cloud Sync, get our Scale license, get help with Kubernetes, or talk to Shuffle's Support team to get automation help."
"Shuffle is an Open Source automation platform, and no license is required. We do however offer a Scale license with HA guarantees, along with support hours. By buying a license on https://shuffler.io, you can get access to the license immediately, and if Cloud Syncronisation is enabled, the UI in your local instance will also update."
}
</Typography>
@@ -1315,7 +1325,7 @@ const Billing = (props) => {
</div>
) : null*/}
<div style={{ marginTop: 30, }}>
<div style={{ marginTop: 100, }}>
<Typography
style={{ marginTop: 40, marginLeft: 10, marginBottom: 5 }}
variant="h4"
+3 -3
View File
@@ -636,13 +636,13 @@ const AppStats = (defaultprops) => {
const data = (
<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, }} color="textSecondary">
All shown statistics are gathered from <a
href={`${globalUrl}/api/v1/orgs/${selectedOrganization.id}/stats`}
target="_blank"
style={{ textDecoration: "none", color: "#f85a3e",}}
>Your Organization Statistics </a>
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>
>Your Organisation Statistics. </a>
It exists to give you more insight into your workflows, 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>
<div style={{display: "flex", textAlign: "center",}}>
+149 -100
View File
@@ -57,6 +57,7 @@ import {
Publish as PublishIcon,
OpenInNew as OpenInNewIcon,
Add as AddIcon,
Remove as RemoveIcon,
} from "@mui/icons-material";
const EditWorkflow = (props) => {
@@ -76,7 +77,8 @@ const EditWorkflow = (props) => {
const [name, setName] = React.useState(workflow.name !== undefined ? workflow.name : "")
const [dueDate, setDueDate] = React.useState(workflow.due_date !== undefined && workflow.due_date !== null && workflow.due_date !== 0 ? dayjs(workflow.due_date*1000) : dayjs().subtract(1, 'day'))
const [inputFields, setInputFields] = React.useState([])
console.log("WORKFLOW: ", workflow)
const [inputQuestions, setInputQuestions] = React.useState(workflow.input_questions !== undefined && workflow.input_questions !== null ? JSON.parse(JSON.stringify(workflow.input_questions)) : [])
const classes = useStyles();
@@ -232,7 +234,91 @@ const EditWorkflow = (props) => {
</div>
</DialogTitle>
<FormControl>
<DialogContent style={{paddingTop: 10, display: "flex", minHeight: 300, zIndex: 1001, }}>
<div style={{width: 600, position: "fixed", left: 0, bottom: 0, zIndex: 1002, backgroundColor: "rgba(53,53,53,1)", height: 75, paddingTop: 20, paddingLeft: 75, }}>
{/*
<Button
style={{}}
onClick={() => {
if (setNewWorkflow !== undefined) {
setWorkflow({})
}
setModalOpen(false)
}}
color="primary"
>
Cancel
</Button>
*/}
<Button
variant="contained"
style={{}}
disabled={name.length === 0 || submitLoading === true}
onClick={() => {
setSubmitLoading(true)
// Loop inputfields
var validfields = []
for (var i = 0; i < inputQuestions.length; i++) {
if (inputQuestions[i].deleted === true) {
continue
}
if (inputQuestions[i].value.length === 0) {
continue
}
validfields.push(inputQuestions[i])
}
innerWorkflow.input_questions = validfields
innerWorkflow.name = name
innerWorkflow.description = description
if (newWorkflowTags.length > 0) {
innerWorkflow.tags = newWorkflowTags
}
if (selectedUsecases.length > 0) {
innerWorkflow.usecase_ids = selectedUsecases
}
if (dueDate > 0) {
innerWorkflow.due_date = new Date(`${dueDate["$y"]}-${dueDate["$M"]+1}-${dueDate["$D"]}`).getTime()/1000
}
if (setNewWorkflow !== undefined) {
setNewWorkflow(
innerWorkflow.name,
innerWorkflow.description,
innerWorkflow.tags,
innerWorkflow.default_return_value,
innerWorkflow,
newWorkflow,
innerWorkflow.usecase_ids,
innerWorkflow.blogpost,
innerWorkflow.status,
)
setWorkflow({})
} else {
setWorkflow(innerWorkflow)
console.log("editing workflow: ", innerWorkflow)
}
setSubmitLoading(true)
// If new workflow, don't close it
if (isEditing) {
setModalOpen(false)
}
}}
color="primary"
>
{submitLoading ? <CircularProgress color="secondary" /> : "Done"}
</Button>
</div>
<DialogContent style={{paddingTop: 10, display: "flex", minHeight: 300, zIndex: 1001, paddingBottom: 200, }}>
<div style={{minWidth: newWorkflow ? 500 : 550, maxWidth: newWorkflow ? 450 : 500, }}>
<TextField
onChange={(event) => {
@@ -477,41 +563,21 @@ const EditWorkflow = (props) => {
fullWidth
/>
{/*
<Typography variant="h6">
<Typography variant="h6" style={{marginTop: 50, }}>
Input fields
</Typography>
<Typography variant="body1">
<Typography variant="body1" color="textSecondary" style={{marginBottom: 20, }}>
Input fields are fields that will be used during the startup of the workflow. These will be formatted in JSON and is most commonly used from the <a href={`/workflows/${workflow.id}/run`} rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>workflow run page</a>.
</Typography>
{inputFields.length === 0 ?
<Button
color="primary"
style={{ maxWidth: 50, marginLeft: 15 }}
variant="outlined"
onClick={() => {
inputFields.push({
"name": "",
"required": false
})
setInputFields(inputFields)
setUpdate(Math.random());
}}
>
<AddIcon style={{}} />
</Button>
: null}
{inputFields.map((data, index) => {
{inputQuestions.map((data, index) => {
console.log("Inputfield: ", data)
return (
<div style={{display: "flex", }}>
<TextField
required={data.required}
disabled={data.deleted === true}
style={{
height: 50,
flex: 2,
@@ -521,14 +587,45 @@ const EditWorkflow = (props) => {
marginRight: 5,
}}
fullWidth={true}
placeholder="Name"
placeholder="Question"
id="standard-required"
margin="normal"
variant="outlined"
defaultValue={data.name}
onChange={(e) => {
inputQuestions[index].name = e.target.value
setInputQuestions(inputQuestions)
setUpdate(Math.random());
}}
InputProps={{
classes: {
notchedOutline: classes.notchedOutline,
},
style: {
color: "white",
minHeight: 50,
},
}}
/>
<TextField
disabled={data.deleted === true}
style={{
height: 50,
flex: 2,
marginTop: 0,
marginBottom: 0,
backgroundColor: theme.palette.inputColor,
marginRight: 5,
}}
fullWidth={true}
placeholder="JSON key"
id="standard-required"
margin="normal"
variant="outlined"
defaultValue={data.value}
onChange={(e) => {
inputFields[index].name = e.target.value
setInputFields(inputFields)
inputQuestions[index].value = e.target.value
setInputQuestions(inputQuestions)
setUpdate(Math.random());
}}
InputProps={{
@@ -544,28 +641,44 @@ const EditWorkflow = (props) => {
<Button
color="primary"
style={{ maxWidth: 50, marginLeft: 15 }}
disabled={data.deleted === true}
variant="outlined"
onClick={() => {
inputFields.push({
// Remove current index
console.log("Removing index: ", index)
inputQuestions[index].deleted = true
setUpdate(Math.random());
}}
>
<RemoveIcon style={{}} />
</Button>
</div>
)
})}
<Button
color="primary"
style={{ maxWidth: 50, marginLeft: 15, marginTop: 20, }}
variant="outlined"
onClick={() => {
inputQuestions.push({
"name": "",
"value": "",
"deleted": false,
"required": false
})
setInputFields(inputFields)
setInputQuestions(inputQuestions)
setUpdate(Math.random());
}}
>
<AddIcon style={{}} />
</Button>
</div>
)
})}
*/}
</span>
: null}
<Tooltip color="primary" title={"Add more details"} placement="top">
<IconButton
style={{ color: "white", margin: "auto", marginTop: 10, textAlign: "center", width: 50,}}
style={{ color: "white", margin: "auto", textAlign: "center", width: 50, marginTop: 50, }}
onClick={() => {
setShowMoreClicked(!showMoreClicked);
}}
@@ -577,71 +690,7 @@ const EditWorkflow = (props) => {
</DialogContent>
<DialogActions style={{paddingRight: 100, marginBottom: 100, }}>
<Button
style={{}}
onClick={() => {
if (setNewWorkflow !== undefined) {
setWorkflow({})
}
setModalOpen(false)
}}
color="primary"
>
Cancel
</Button>
<Button
variant="contained"
style={{}}
disabled={name.length === 0 || submitLoading === true}
onClick={() => {
setSubmitLoading(true)
innerWorkflow.name = name
innerWorkflow.description = description
if (newWorkflowTags.length > 0) {
innerWorkflow.tags = newWorkflowTags
}
if (selectedUsecases.length > 0) {
innerWorkflow.usecase_ids = selectedUsecases
}
if (dueDate > 0) {
innerWorkflow.due_date = new Date(`${dueDate["$y"]}-${dueDate["$M"]+1}-${dueDate["$D"]}`).getTime()/1000
}
if (setNewWorkflow !== undefined) {
setNewWorkflow(
innerWorkflow.name,
innerWorkflow.description,
innerWorkflow.tags,
innerWorkflow.default_return_value,
innerWorkflow,
newWorkflow,
innerWorkflow.usecase_ids,
innerWorkflow.blogpost,
innerWorkflow.status,
)
setWorkflow({})
} else {
setWorkflow(innerWorkflow)
console.log("editing workflow: ", innerWorkflow)
}
setSubmitLoading(true)
// If new workflow, don't close it
if (isEditing) {
setModalOpen(false)
}
}}
color="primary"
>
{submitLoading ? <CircularProgress color="secondary" /> : "Done"}
</Button>
</DialogActions>
{newWorkflow === true ?
<span style={{marginTop: 30, }}>
<Typography variant="h6" style={{marginLeft: 30, paddingBottom: 0, }}>
+28 -9
View File
@@ -70,6 +70,7 @@ const Header = (props) => {
const [anchorEl, setAnchorEl] = React.useState(null);
const [anchorElAvatar, setAnchorElAvatar] = React.useState(null);
const [subAnchorEl, setSubAnchorEl] = React.useState(null);
const [upgradeHovered, setUpgradeHovered] = React.useState(false);
let navigate = useNavigate();
const handleClick = (event) => {
@@ -1097,10 +1098,10 @@ const Header = (props) => {
)}
{/* Show on cloud, if not suborg and if not customer/pov/internal */}
{isCloud &&
(userdata.org_status === undefined ||
userdata.org_status === null ||
userdata.org_status.length === 0) ? (
{
userdata.licensed !== undefined &&
userdata.licensed !== null &&
userdata.licensed === false ?
<ListItem
style={{
textAlign: "center",
@@ -1109,11 +1110,13 @@ const Header = (props) => {
marginTop: 0,
}}
>
<Link to="/pricing?tab=cloud&highlight=true" style={hrefStyle}>
<Link to="/admin?tab=billing" style={hrefStyle}>
<Button
variant="contained"
color="primary"
style={{ textTransform: "none" }}
onMouseOver={() => {setUpgradeHovered(true)}}
onMouseOut={() => {setUpgradeHovered(false)}}
onClick={() => {
if (isCloud) {
ReactGA.event({
@@ -1122,13 +1125,30 @@ const Header = (props) => {
label: "",
});
}
if (window.drift !== undefined) {
if (isCloud) {
window.drift.api.startInteraction({
interactionId: 386404,
})
} else {
window.drift.api.startInteraction({
interactionId: 386403,
})
}
}
}}
>
Upgrade
{upgradeHovered ?
isCloud ? "Upgrade License" : "Upgrade License"
:
isCloud ? "Upgrade" : "Upgrade"
}
</Button>
</Link>
</ListItem>
) : null}
: null}
{userdata === undefined ||
userdata.app_execution_limit === undefined ||
@@ -1145,7 +1165,6 @@ const Header = (props) => {
textAlign: "center",
cursor: "pointer",
borderRadius: theme.palette.borderRadius,
marginRight: 10,
marginTop: 5,
backgroundColor: theme.palette.surfaceColor,
minWidth: 60,
@@ -1154,7 +1173,7 @@ const Header = (props) => {
userdata.app_execution_usage /
userdata.app_execution_limit >=
0.9
? "#f86a3e"
? "2px solid #f86a3e"
: null,
}}
onClick={() => {
-1
View File
@@ -3258,7 +3258,6 @@ const ParsedAction = (props) => {
}
}
console.log("DID NAME REPLACE ACTUALLY WORK? - may be missing it in certain triggers");
setWorkflow(workflow);
setUpdate(Math.random());
baselabel = name
+101 -38
View File
@@ -70,6 +70,9 @@ import {
Business as BusinessIcon,
Visibility as VisibilityIcon,
VisibilityOff as VisibilityOffIcon,
Cancel as CancelIcon,
Dns as DnsIcon,
Help as HelpIcon,
Flag as FlagIcon,
FmdGood as FmdGoodIcon,
@@ -194,8 +197,25 @@ const Admin = (props) => {
useEffect(() => {
getUsers()
setTimeout(() => {
if (adminTab === 3) {
window.scroll({
top: 450,
left: 0,
behavior: 'smooth'
})
}
}, 1500)
}, []);
useEffect(() => {
window.scroll({
top: 450,
left: 0,
behavior: 'smooth'
})
}, [adminTab]);
useEffect(() => {
if (isDropzone) {
@@ -970,6 +990,8 @@ If you're interested, please let me know a time that works for you, or set up a
.then((responseJson) => {
if (!responseJson.success && responseJson.reason !== undefined) {
toast("Failed to deactivate user: " + responseJson.reason);
} else if (responseJson.success === false) {
toast("Failed to deactivate user. Please contact support@shuffler.io if this persists.")
} else {
toast("Changed activation for user " + data.id);
}
@@ -1146,7 +1168,7 @@ If you're interested, please let me know a time that works for you, or set up a
});
};
const handleClickChangeOrg = (orgId) => {
const handleClickChangeOrg = (orgId) => {
// Don't really care about the logout
//name: org.name,
//orgId = "asd"
@@ -1177,7 +1199,6 @@ const handleClickChangeOrg = (orgId) => {
}).then(function(responseJson) {
if (responseJson.success === true) {
if (responseJson.region_url !== undefined && responseJson.region_url !== null && responseJson.region_url.length > 0) {
console.log("Region Change: ", responseJson.region_url)
localStorage.setItem("globalUrl", responseJson.region_url)
//globalUrl = responseJson.region_url
}
@@ -1187,14 +1208,14 @@ const handleClickChangeOrg = (orgId) => {
}, 2000)
toast("Successfully changed active organization - refreshing!")
} else {
toast("Failed changing org: ", responseJson.reason)
toast("Failed changing org: "+responseJson.reason)
}
})
.catch(error => {
console.log("error changing: ", error)
//removeCookie("session_token", {path: "/"})
})
}
}
const inviteUser = (data) => {
@@ -1850,6 +1871,8 @@ const handleClickChangeOrg = (orgId) => {
.then((responseJson) => {
if (!responseJson.success && responseJson.reason !== undefined) {
toast("Failed setting user: " + responseJson.reason);
} else if (responseJson.success === false) {
toast("Failed to update user")
} else {
//toast("Set the user field " + field + " to " + value);
toast("Successfully updated user field " + field)
@@ -2912,6 +2935,7 @@ const handleClickChangeOrg = (orgId) => {
)}
<Tabs
id="admin_tabs"
value={adminTab}
indicatorColor="primary"
textColor="secondary"
@@ -4448,8 +4472,7 @@ const handleClickChangeOrg = (orgId) => {
<div style={{ marginTop: 20, marginBottom: 20 }}>
<h2 style={{ display: "inline" }}>Environments</h2>
<span style={{ marginLeft: 25 }}>
Decides what Orborus environment to execute an action in a workflow
in.{" "}
Decides what Orborus environment to run your workflow actions. If you have scale problems, talk to our team: support@shuffler.io.&nbsp;
<a
target="_blank"
rel="noopener noreferrer"
@@ -4491,25 +4514,33 @@ const handleClickChangeOrg = (orgId) => {
}}
/>
<List>
<ListItem>
<ListItem style={{paddingLeft: 10, }}>
<ListItemText
primary="Type"
style={{ minWidth: 50, maxWidth: 50, }}
/>
<ListItemText
primary="License"
style={{ minWidth: 85, maxWidth: 85, }}
/>
<ListItemText
primary="Name"
style={{ minWidth: 150, maxWidth: 150 }}
/>
<ListItemText
primary="Orborus running"
style={{ minWidth: 200, maxWidth: 200 }}
primary="Orborus label"
style={{ minWidth: 150, maxWidth: 150 }}
/>
<ListItemText
primary="Command"
style={{ minWidth: 100, maxWidth: 100 }}
/>
<ListItemText
primary="Type"
primary="Location"
style={{ minWidth: 100, maxWidth: 100, }}
/>
<ListItemText
primary={"In Queue"}
primary={"Queue"}
style={{ minWidth: 100, maxWidth: 100 }}
/>
<ListItemText
@@ -4558,7 +4589,53 @@ const handleClickChangeOrg = (orgId) => {
return (
<span key={index}>
<ListItem key={index} style={{ backgroundColor: bgColor }}>
<ListItem key={index} style={{ backgroundColor: bgColor, marginLeft: 0, }}>
<ListItemText
primary={environment.run_type === "cloud" ?
<Tooltip title="Cloud" placement="top">
<CloudIcon style={{ color: "rgba(255,255,255,0.8)" }} />
</Tooltip>
: environment.run_type === "docker" ?
<Tooltip title="Docker" placement="top">
<img src="/icons/docker.svg" style={{ width: 30, height: 30, }} />
</Tooltip>
: environment.run_type === "k8s" ?
<Tooltip title="Kubernetes" placement="top">
<img src="/icons/k8s.svg" style={{ width: 30, height: 30, }} />
</Tooltip>
:
<Tooltip title="Unknown" placement="top">
<HelpIcon style={{ color: "rgba(255,255,255,0.8)" }} />
</Tooltip>
}
style={{
minWidth: 50,
maxWidth: 50,
overflow: "hidden",
}}
/>
<ListItemText
primary={environment.licensed ?
<Tooltip title="Licensed" placement="top">
<CheckCircleIcon style={{ color: "#4caf50", }} />
</Tooltip>
:
<Tooltip title="Not licensed, and can't scale.. This may cause service disruption." placement="top">
<a
href="/admin?tab=billing"
target="_blank"
rel="noopener noreferrer"
>
<CancelIcon style={{ color: "#f85a3e", }} />
</a>
</Tooltip>
}
style={{
minWidth: 85,
maxWidth: 85,
overflow: "hidden",
}}
/>
<ListItemText
primary={environment.Name}
style={{
@@ -4581,8 +4658,8 @@ const handleClickChangeOrg = (orgId) => {
: "N/A"
}
style={{
minWidth: 200,
maxWidth: 200,
minWidth: 150,
maxWidth: 150,
overflow: "hidden",
}}
/>
@@ -4849,11 +4926,7 @@ const handleClickChangeOrg = (orgId) => {
/>
<ListItemText
primary="Name"
style={{ minWidth: 250, maxWidth: 250 }}
/>
<ListItemText
primary="Your role"
style={{ minWidth: 150, maxWidth: 150 }}
style={{ minWidth: 350, maxWidth: 350}}
/>
<ListItemText
primary="id"
@@ -4867,11 +4940,7 @@ const handleClickChangeOrg = (orgId) => {
/>
<ListItemText
primary={parentOrg.name}
style={{ minWidth: 250, maxWidth: 250 }}
/>
<ListItemText
primary={parentOrg.role}
style={{ minWidth: 150, maxWidth: 150 }}
style={{ minWidth: 350, maxWidth: 350}}
/>
<ListItemText
primary={parentOrg.id}
@@ -4881,11 +4950,12 @@ const handleClickChangeOrg = (orgId) => {
<Button
variant="contained"
color="primary"
disabled={parentOrg.id === selectedOrganization.id}
onClick={() => {
handleClickChangeOrg(parentOrg.id);
}}
>
Switch To Parent
Switch to Parent
</Button>
</ListItem>
</List>
@@ -4920,7 +4990,7 @@ const handleClickChangeOrg = (orgId) => {
letterSpacing: "1px",
}}
>
Sub Organizations of the Current Organization
Sub Organizations of the Current Organization ({subOrgs.length})
</h3>
</div>
@@ -4940,11 +5010,7 @@ const handleClickChangeOrg = (orgId) => {
/>
<ListItemText
primary="Name"
style={{ minWidth: 250, maxWidth: 250 }}
/>
<ListItemText
primary="Your role"
style={{ minWidth: 150, maxWidth: 150 }}
style={{ minWidth: 350, maxWidth: 350 }}
/>
<ListItemText primary="id" style={{ minWidth: 400, maxWidth: 400 }} />
</ListItem>
@@ -4974,11 +5040,7 @@ const handleClickChangeOrg = (orgId) => {
/>
<ListItemText
primary={data.name}
style={{ minWidth: 250, maxWidth: 250 }}
/>
<ListItemText
primary={data.role}
style={{ minWidth: 150, maxWidth: 150 }}
style={{ minWidth: 350, maxWidth: 350 }}
/>
<ListItemText
primary={data.id}
@@ -4986,8 +5048,9 @@ const handleClickChangeOrg = (orgId) => {
/>
<Button
variant="contained"
variant="outlined"
color="primary"
disabled={data.id === selectedOrganization.id}
onClick={() => {
handleClickChangeOrg(data.id);
}}
@@ -5247,7 +5310,7 @@ const handleClickChangeOrg = (orgId) => {
disabled={userdata.admin !== "true"}
label=<span>
<ScheduleIcon style={iconStyle} />
Schedules
Triggers
</span>
/>
<Tab
+10
View File
@@ -16255,6 +16255,16 @@ const AngularWorkflow = (defaultprops) => {
</a>
)
) : (
executionData.execution_source === "questions" || executionData.execution_source === "web" ?
<a
rel="noopener noreferrer"
href={`/workflows/${executionData.workflow.id}/run`}
target="_blank"
style={{ textDecoration: "none", color: "#f85a3e" }}
>
Questions
</a>
:
executionData.execution_source
)}
</Typography>
+95 -25
View File
@@ -141,11 +141,30 @@ const RunWorkflow = (defaultprops) => {
return (
<div style={{marginTop: executionMargin, }}>
<Typography variant="h6" style={{color: theme.palette.primaryColor}}>
Results
{/*executionData !== undefined && executionData !== null && executionData !== {} && executionData.status !== undefined && (answer === undefined || answer === null) ?
<div style={{ marginBottom: 5, display: "flex" }}>
<Typography variant="body1">
<b>Status&nbsp;</b>
</Typography>
<Typography variant="body1" color="textSecondary" style={{ marginRight: 15, }}>
{executionData.status}
</Typography>
</div>
: null*/}
{executionData.results.map((data, index) => {
{/*
<Typography variant="h6" style={{color: theme.palette.primaryColor}}>
Results: {executionData.results.length}/{executionData.workflow.actions.length}
</Typography>
*/}
{executionData.result !== undefined && executionData.result !== null && executionData.result.length > 0 ?
<Typography variant="h6">
{executionData.result}
</Typography>
: null}
{/*executionData.results.map((data, index) => {
if (executionData.results.length !== 1 && (data.status === "SKIPPED")) {
return null;
}
@@ -311,7 +330,7 @@ const RunWorkflow = (defaultprops) => {
</div>
</div>
)
})}
})*/}
</div>
)
}
@@ -321,8 +340,6 @@ const RunWorkflow = (defaultprops) => {
event.preventDefault()
}
console.log("In submit!")
stop()
setMessage("")
setExecutionLoading(true)
@@ -330,12 +347,22 @@ const RunWorkflow = (defaultprops) => {
setExecutionInfo("")
var data = {
"execution_argument": executionArgument
"execution_argument": executionArgument,
"execution_source": "questions",
}
if (workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0) {
try {
data["execution_argument"] = JSON.stringify(executionArgument)
} catch (e) {
console.log("Error parsing execution argument: ", e)
}
}
if (workflow.start !== undefined && workflow.start !== null && workflow.start.length > 0) {
data.start = workflow.start
//data.start = workflow.start
} else {
/*
if (workflow.actions !== undefined && workflow.actions !== null && workflow.actions.length > 0) {
for (let actionkey in workflow.actions) {
if (workflow.actions[actionkey].isStartNode) {
@@ -344,6 +371,7 @@ const RunWorkflow = (defaultprops) => {
}
}
}
*/
}
var url = `${globalUrl}/api/v1/workflows/${props.match.params.key}/execute`
@@ -369,7 +397,6 @@ const RunWorkflow = (defaultprops) => {
console.log("Pre request: ", url, fetchBody)
fetch(url, fetchBody)
.then((response) => {
console.log("Got answer 1")
if (response.status !== 200 && response.status !== 201) {
if (answer !== undefined && execution_id !== undefined && authorization !== undefined) {
@@ -385,11 +412,9 @@ const RunWorkflow = (defaultprops) => {
}
}
console.log("Got answer 2")
return response.json();
})
.then(responseJson => {
console.log("Got answer 3")
setExecutionLoading(false)
if (responseJson["success"] === false) {
console.log("Failed sending execution request")
@@ -404,7 +429,6 @@ const RunWorkflow = (defaultprops) => {
start();
}
}
console.log("Got answer 4")
})
.catch(error => {
//setExecutionInfo("Error in workflow startup: " + error)
@@ -446,6 +470,16 @@ const RunWorkflow = (defaultprops) => {
responseJson.triggers = [];
}
if (responseJson.input_questions !== undefined && responseJson.input_questions !== null && responseJson.input_questions.length > 0) {
var newexec = {}
for (let questionkey in responseJson.input_questions) {
const question = responseJson.input_questions[questionkey]
newexec[question.value] = ""
}
setExecutionArgument(newexec)
}
handleGetOrg(responseJson.org_id)
setWorkflow(responseJson);
})
@@ -475,6 +509,18 @@ const RunWorkflow = (defaultprops) => {
// Doesn't work because this is some async garbage
if (executionData.execution_id === undefined || (responseJson.execution_id === executionData.execution_id && responseJson.results !== undefined && responseJson.results !== null)) {
if (executionData.status !== responseJson.status || executionData.result !== responseJson.result || (executionData.results !== undefined && responseJson.results !== null && executionData.results.length !== responseJson.results.length)) {
if (responseJson.result !== undefined && responseJson.result !== null && responseJson.result.length > 0) {
if (responseJson.result.startsWith("[") && responseJson.result.endsWith("]")) {
try {
responseJson.result = JSON.parse(responseJson.result).length
console.log("Set length to: ", responseJson.result)
} catch (e) {
console.log("Error parsing length: ", e)
}
}
}
//console.log("Updating data!")
setExecutionData(responseJson)
@@ -685,27 +731,50 @@ const RunWorkflow = (defaultprops) => {
<Typography color="textSecondary">{message}</Typography>
{answer !== undefined && answer !== null ? null :
<Typography variant="h6" style={{marginBottom: 15, }}><b>Workflow: </b>{workflow.name}</Typography>
<Typography variant="h6" style={{marginBottom: 15, }}><b>{workflow.name}</b></Typography>
}
{executionData !== undefined && executionData !== null && executionData !== {} && executionData.status !== undefined && (answer === undefined || answer === null) ?
<div style={{ marginBottom: 5, display: "flex" }}>
<Typography variant="body1">
<b>Status&nbsp;</b>
</Typography>
<Typography variant="body1" color="textSecondary" style={{ marginRight: 15, }}>
{executionData.status}
</Typography>
</div>
: null}
{workflowQuestion.length > 0 ?
<Typography variant="body1" style={{ marginBottom: 35, marginTop: 30, marginRight: 15, textAlign: "center", whiteSpace: "pre-line", }}>
{workflowQuestion}
</Typography>
: null}
{answer !== undefined && answer !== null ? null :
{workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ?
<div style={{marginBottom: 5, }}>
{workflow.input_questions.map((question, index) => {
return (
<div style={{marginBottom: 5}}>
{question.name}
<TextField
color="primary"
style={{backgroundColor: theme.palette.inputColor, marginTop: 5, }}
multiLine
maxRows={2}
InputProps={{
style:{
height: "50px",
color: "white",
fontSize: "1em",
},
}}
fullWidth={true}
placeholder=""
id="emailfield"
margin="normal"
variant="outlined"
onChange={(e) => {
//setExecutionArgument(e.target.value)
executionArgument[question.value] = e.target.value
}}
/>
</div>
)
})}
</div>
:
answer !== undefined && answer !== null ? null :
<span>
Runtime Argument
<div style={{marginBottom: 5}}>
@@ -733,6 +802,7 @@ const RunWorkflow = (defaultprops) => {
</div>
</span>
}
{executionRunning ?
<span style={{width: 50, height: 50, margin: "auto", alignItems: "center", justifyContent: "center", textAlign: "center", }}>
<CircularProgress style={{marginTop: 20, marginBottom: 20, marginLeft: 185, }}/>
+1 -1
View File
@@ -7,7 +7,7 @@ go 1.19
require (
github.com/docker/docker v23.0.3+incompatible
github.com/satori/go.uuid v1.2.0
github.com/shuffle/shuffle-shared v0.5.68
github.com/shuffle/shuffle-shared v0.5.93
k8s.io/api v0.28.1
k8s.io/apimachinery v0.28.1
k8s.io/client-go v0.28.1
+2
View File
@@ -361,6 +361,8 @@ github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shuffle/shuffle-shared v0.5.68 h1:2ok4SsLojHvFIQMfe2upm3CLEvSbUPMJPIBaBZO1bL8=
github.com/shuffle/shuffle-shared v0.5.68/go.mod h1:oIZkx93Z7EvtiTXty7xO+ax63Fjz8MQvjXMxDN0Qws0=
github.com/shuffle/shuffle-shared v0.5.93 h1:fZf9s2cEgDoyYXXvPYVeNh8UysuSlywQkc54IXkNL0k=
github.com/shuffle/shuffle-shared v0.5.93/go.mod h1:Lg6/+qjQlWzNKwj4/4ATpvScyP2JQGLkTPlNlRM6RJk=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ=
+10 -17
View File
@@ -1065,26 +1065,10 @@ func getOrborusStats(ctx context.Context) shuffle.OrborusStats {
Timestamp: time.Now().Unix(),
}
// FIXME: Returning for now due to this causing network congestion
// and database fillup. The backend api also has it disabled.
return newStats
// Disable orborus stats
if os.Getenv("SHUFFLE_STATS_DISABLED") == "true" {
return newStats
}
if swarmConfig == "run" || swarmConfig == "swarm" {
if (swarmConfig == "run" || swarmConfig == "swarm") && strings.Contains(newWorkerImage, "scale") {
newStats.Swarm = true
}
// Run this 1/10 times
//if rand.Intn(10) != 1 {
// return newStats
//}
newStats.PollTime = sleepTime
newStats.MaxQueue = maxConcurrency
newStats.Queue = executionCount
@@ -1094,6 +1078,15 @@ func getOrborusStats(ctx context.Context) shuffle.OrborusStats {
return newStats
}
// Disable orborus stats
if os.Getenv("SHUFFLE_STATS_DISABLED") == "true" {
return newStats
}
// FIXME: Returning for now due to this causing network congestion
// and database fillup. The backend api also has it disabled.
return newStats
// Use the docker API to get the CPU usage of the docker engine machine
pers, err := dockercli.Info(ctx)
if err != nil {