@@ -1210,12 +1469,14 @@ const DocsWrapper = memo(({isLoggedIn, isLoaded, children })=>{
return (
{children}
diff --git a/frontend/src/views/LoginPage.jsx b/frontend/src/views/LoginPage.jsx
index 23f8e350..ede54153 100755
--- a/frontend/src/views/LoginPage.jsx
+++ b/frontend/src/views/LoginPage.jsx
@@ -1,547 +1,960 @@
/* eslint-disable react/no-multi-comp */
-import React, { useState, useEffect } from "react";
-import { makeStyles } from "@mui/styles";
+import React, { useState, useEffect, } from 'react';
+import { makeStyles } from '@mui/styles';
+import { useNavigate, Link, useParams } from "react-router-dom";
+import { isMobile } from "react-device-detect";
+import { toast } from 'react-toastify';
import { useInterval } from "react-powerhooks";
-import theme from '../theme.jsx';
+
import {
- CircularProgress,
- TextField,
- Button,
- Paper,
- Typography,
-} from "@mui/material";
+ ConnectingAirportsOutlined,
+ ConstructionOutlined,
+ DoneRounded,
+ Done as DoneIcon
+} from '@mui/icons-material';
-import { useNavigate } from "react-router-dom";
+import {
+ Checkbox,
+ CircularProgress,
+ TextField,
+ Button,
+ Paper,
+ Typography,
+ Tooltip,
+ TableHead,
+ TableRow,
+ TableContainer,
+ TableCell,
+ TableBody,
+ Table,
+} from '@mui/material';
const hrefStyle = {
- color: "white",
- textDecoration: "none",
-};
+ color: "#FF8444",
+ fontSize: "14px",
+ textDecoration: "none",
+ display: "flex",
+}
-const bodyDivStyle = {
- margin: "auto",
- marginTop: 150,
- width: "500px",
-};
+const googleLoginIcon = {
+ boxSizing: "border-box",
+ display: "flex",
+ flexDirection: "row",
+ justifyContent: "center",
+ alignItems: "center",
+ padding: "16px",
+ gap: "8px",
+ // position: "sticky",
+ width: 171,
+ height: "51px",
+ left: "352px",
+ top: "725px",
+ background: "#1A1A1A",
+ border: "1px solid #494949",
+ borderRadius: "8px",
+
+}
+
+const githubLoginIcon = {
+ boxSizing: "border-box",
+ display: "flex",
+ flexDirection: "row",
+ justifyContent: "center",
+ alignItems: "center",
+ padding: "16px",
+ gap: "8px",
+ position: "sticky",
+ width: "173px",
+ height: "51px",
+ left: "539px",
+ top: "725px",
+ backgroundColor: "#1A1A1A",
+ border: "1px solid #494949",
+ borderRadius: "8px",
+}
+
+const surfaceColor = "#27292D"
+const inputColor = "#383B40"
const useStyles = makeStyles({
- notchedOutline: {
- borderColor: "#f85a3e !important",
- },
+ notchedOutline: {
+ borderColor: "#f85a3e !important"
+ },
+ marketplaceButton: {
+ width: "100%",
+ justifyContent: "flex-start",
+ padding: "12px",
+ marginBottom: "12px",
+ backgroundColor: "#1A1A1A",
+ border: "1px solid #494949",
+ borderRadius: "8px",
+ color: "white",
+ opacity: 0.7,
+ '&:hover': {
+ opacity: 1,
+ cursor: "not-allowed",
+ },
+ },
+ marketplaceIcon: {
+ width: 28,
+ height: 28,
+ marginRight: 12
+ },
+ divider: {
+ display: "flex",
+ alignItems: "center",
+ margin: "0 20px",
+ '&::before, &::after': {
+ content: '""',
+ flex: 1,
+ borderBottom: "1px solid #494949"
+ },
+ '& span': {
+ margin: "0 10px",
+ color: "#9E9E9E"
+ }
+ },
+ freePlanCard: {
+ padding: "40px",
+ background: "#212121",
+ borderRadius: "12px",
+ width: "100%",
+ maxWidth: "500px"
+ },
+ freePlanTitle: {
+ fontSize: "28px",
+ fontWeight: 600,
+ color: "white",
+ marginTop: 0,
+ marginBottom: "32px"
+ },
+ featureItem: {
+ display: "flex",
+ alignItems: "center",
+ marginBottom: "20px",
+ color: "white",
+ fontSize: "16px",
+ fontWeight: 500
+ },
+ checkIcon: {
+ color: "#4CAF50",
+ marginRight: "16px",
+ width: "24px",
+ height: "24px"
+ }
});
-const LoginDialog = (props) => {
- const {
- globalUrl,
- isLoaded,
- isLoggedIn,
- setIsLoggedIn,
- setCookie,
- register,
- checkLogin,
- } = props;
+const FreePlanCard = ({ classes }) => {
+ const features = [
+ "Access to All Apps",
+ "10,000 App Runs",
+ "Monthly Runs Refresh",
+ "Unlimited Users & Workflows",
+ "Multi-Tenancy & -Region",
+ "Support & Discord Access"
+ ];
+
+ return (
+
+
+ The free plan includes:
+
+
+ {features.map((feature, index) => (
+
+
+ {feature}
+
+ ))}
+
+ );
+};
+
+const MarketplaceCard = ({ classes }) => {
+ const marketplaceOptions = [
+ {
+ name: "Amazon Web Services",
+ logo: "https://cdn.cdnlogo.com/logos/a/19/aws.svg",
+ tooltipText: "Coming soon to AWS Marketplace!",
+ valid: false,
+ },
+ {
+ name: "Microsoft Azure",
+ logo: "https://cdn.cdnlogo.com/logos/a/12/azure.svg",
+ tooltipText: "Coming soon to Azure Marketplace!",
+ valid: false,
+ },
+ {
+ name: "Google Cloud Platform",
+ logo: "https://cdn.cdnlogo.com/logos/g/75/google-cloud.svg",
+ tooltipText: "Coming soon to Google Cloud Marketplace!",
+ valid: false,
+ }
+ ];
+
+ return (
+
+
+ Self Host
+
+
+ {marketplaceOptions.map((option, index) => (
+
+
+
+
+ {option.name}
+
+
+
+ ))}
+
+
+
+
+ );
+};
+
+
+const LoginPage = props => {
+ const { globalUrl, isLoaded, isLoggedIn, setIsLoggedIn, setCookie, inregister, serverside, checkLogin, } = props;
let navigate = useNavigate();
- const classes = useStyles();
+ const [username, setUsername] = useState("");
+ const [password, setPassword] = useState("");
+ const [message, setMessage] = useState("");
+ const [loginLoading, setLoginLoading] = useState(false);
- const [username, setUsername] = useState("");
- const [password, setPassword] = useState("");
- const [firstRequest, setFirstRequest] = useState(true);
- const [loginLoading, setLoginLoading] = useState(false);
- const [loginViewLoading, setLoginViewLoading] = useState(false);
- const [ssoUrl, setSSOUrl] = useState("");
+ const [MFAField, setMFAField] = useState(false);
+ const [MFAValue, setMFAValue] = useState("");
+ const [register, setRegister] = useState(inregister);
+ const [checkboxClicked, setCheckboxClicked] = useState(false);
+ const [loginWithSSO, setLoginWithSSO] = useState(false)
+
+ const [ssoUrl, setSSOUrl] = useState("");
- const [MFAField, setMFAField] = useState(false);
- const [MFAValue, setMFAValue] = useState("");
-
-
- // Used to swap from login to register. True = login, false = register
+ const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" || window.location.host === "migration.shuffler.io";
+ const parsedsearch = serverside === true ? "" : window.location.search
useEffect(() => {
- checkAdmin()
- }, [loginViewLoading])
+ if (!isCloud) {
+ checkAdmin()
+ }
+ }, [])
- // Error messages etc
- const [loginInfo, setLoginInfo] = useState("");
-
- const handleValidateForm = () => {
- return username.length > 1 && password.length > 1;
- };
-
- if (isLoggedIn === true) {
- //window.location.pathname = "/workflows";
- navigate("/workflows")
- }
-
- const checkAdmin = () => {
- const url = globalUrl + "/api/v1/checkusers";
- fetch(url, {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
+ const { start, stop } = useInterval({
+ duration: 3000,
+ startImmediate: false,
+ callback: () => {
+ checkAdmin()
},
})
+
+ if (serverside !== true) {
+ const tmpMessage = new URLSearchParams(window.location.search).get("message")
+ if (tmpMessage !== undefined && tmpMessage !== null && message !== tmpMessage) {
+ setMessage(tmpMessage)
+ }
+ }
+
+ if (document !== undefined) {
+ if (register) {
+ document.title = "Login to Shuffle SaaS"
+ } else {
+ document.title = "Register to Shuffle SaaS"
+ }
+ }
+
+ // Just a way to force location loading properly
+ // Register & login should be split :3
+ if (window !== undefined) {
+ const path = window.location.pathname;
+ if (path.includes("/login") && register === false) {
+ console.log("Should register instead of login!")
+ setRegister(!register)
+ } else if (path.includes("/register") && register === true) {
+ if (!isCloud) {
+ setRegister(true)
+ navigate("/login")
+ }
+
+ console.log("Should login instead of register!")
+ setRegister(!register)
+ } else {
+ console.log("Path: " + path, "Register: " + register)
+ }
+ }
+
+ const bodyDivStyle = {
+ marginTop: 100,
+ width: isMobile ? "100%" : "100%",
+ maxWidth: "1200px", // Increased max-width to accommodate larger cards
+ background: "#1A1A1A",
+ margin: "auto",
+ display: isMobile ? "block" : "flex",
+ padding: "40px",
+ gap: "40px",
+ overflow: "hidden",
+ alignItems: "center"
+ };
+
+ const boxStyle = {
+ color: "white",
+ padding: "40px",
+ flex: 1,
+ maxWidth: isMobile ? "100%" : "550px",
+ background: "#212121",
+ borderRadius: "12px",
+ display: "flex",
+ // flexDirection: "column",
+ };
+
+
+
+ const paperStyleReg = {
+ width: 300,
+ height: 350,
+ background: "#212121",
+ borderRadius: "8px",
+ marginLeft: isMobile ? 80 : register ? "455px" : "485px",
+ marginTop: isMobile ? 10 : 110,
+ position: isMobile ? "" : "absolute",
+
+ }
+ const paperStyleLog = {
+ position: "absolute",
+ width: isMobile ? "400px" : "532px",
+ padding: "0px 30px 0px",
+ // marginTop: "135px",
+ // background: "#212121",
+ borderRadius: "8px",
+ }
+ const createData = (icon, title) => {
+ return {
+ icon,
+ title,
+ }
+ }
+
+ // Used to swap from login to register. True = login, false = register
+ const activeIcon =
+ const rows = [
+ createData(activeIcon, "Access to All Apps"),
+ createData(activeIcon, "10,000 App Runs"),
+ createData(activeIcon, "Monthly Runs Refresh"),
+ createData(activeIcon, "Unlimited Users & Workflows"),
+ createData(activeIcon, "Multi-Tenancy & -Region"),
+ createData(activeIcon, "Support & Discord Access"),
+ ];
+ const classes = useStyles();
+ // Error messages etc
+ const [loginInfo, setLoginInfo] = useState("");
+
+ const handleValidateForm = (username, password) => {
+ if (loginWithSSO) {
+ return username.length > 1
+ }
+
+ if (!isCloud) {
+ return (username.length > 0 && password.length > 0);
+ }
+
+ return (username.length > 1 && password.length > 8);
+ }
+
+ if (isLoggedIn === true && serverside !== true) {
+ const tmpView = new URLSearchParams(window.location.search).get("view")
+ if (tmpView !== undefined && tmpView !== null && tmpView === "pricing") {
+ window.location.pathname = "/pricing"
+ return
+ } else if (tmpView !== undefined && tmpView !== null) {
+ window.location.pathname = tmpView
+ return
+ }
+
+ window.location.pathname = "/workflows"
+ }
+
+ const checkAdmin = () => {
+ const url = globalUrl + "/api/v1/checkusers";
+ fetch(url, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
.then((response) =>
response.json().then((responseJson) => {
if (responseJson["success"] === false) {
setLoginInfo(responseJson["reason"]);
} else {
-
if (responseJson.sso_url !== undefined && responseJson.sso_url !== null) {
setSSOUrl(responseJson.sso_url);
}
- if (loginViewLoading) {
- setLoginViewLoading(false);
- checkLogin();
- stop();
-
- if (
- responseJson.reason !== undefined &&
- responseJson.reason !== null
- ) {
- setLoginInfo(responseJson.reason);
- }
- }
-
+ // Stay = 0 users
+ // Redirect = >1 user
if (responseJson.reason === "stay") {
- navigate("/adminsetup")
- }
+ setTimeout(() => {
+ navigate("/adminsetup")
+ }, 2500)
+ }
}
})
)
.catch((error) => {
- if (!loginViewLoading) {
- setLoginViewLoading(true);
- start();
- }
- });
- };
-
- const { start, stop } = useInterval({
- duration: 3000,
- startImmediate: false,
- callback: () => {
- checkAdmin();
- },
- });
-
- if (firstRequest) {
- setFirstRequest(false);
- checkAdmin();
- }
-
- const onSubmit = (e) => {
- setLoginLoading(true);
- e.preventDefault();
- setLoginInfo("");
- // FIXME - add some check here ROFL
-
- // Just use this one?
- var data = { username: username, password: password };
- if (MFAValue !== undefined && MFAValue !== null && MFAValue.length > 0) {
- data["mfa_code"] = MFAValue;
+ setTimeout(() => {
+ navigate("/adminsetup")
+ }, 2500)
+ })
}
- var baseurl = globalUrl;
- if (register) {
- var url = baseurl + "/api/v1/login";
- 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) => {
- setLoginLoading(false);
- if (responseJson["success"] === false) {
- setLoginInfo(responseJson["reason"]);
- } else {
- if (responseJson["reason"] === "MFA_REDIRECT") {
- setLoginInfo(
- "MFA required. Please enter the 6-digit code from your authenticator"
- );
- setMFAField(true);
- return;
- } else if (responseJson["reason"] === "MFA_SETUP") {
- window.location.href = `/login/${responseJson.url}/mfa-setup`;
- return;
- }
+ const onSubmit = (e) => {
+ //toast("Testing from login page")
- setLoginInfo("Successful login, rerouting");
- for (var key in responseJson["cookies"]) {
- setCookie(
- responseJson["cookies"][key].key,
- responseJson["cookies"][key].value,
- { path: "/" }
- );
- }
+ setMessage("")
+ setLoginLoading(true)
+ e.preventDefault()
- if (responseJson.tutorials === undefined || responseJson.tutorials === null || !responseJson.tutorials.includes("welcome")) {
- console.log("RUN Welcome!!")
- setTimeout(() => {
- navigate("/welcome?tab=2")
- },200)
- // window.location.pathname = ""
- return
- }
+ // Just use this one?
+ var data = { "username": username, "password": password }
+ if (MFAValue !== undefined && MFAValue !== null && MFAValue.length > 0) {
+ data["mfa_code"] = MFAValue
+ }
- const tmpView = new URLSearchParams(window.location.search).get("view")
- if (tmpView !== undefined && tmpView !== null) {
- //const newUrl = `/${tmpView}${decodeURIComponent(window.location.search)}`
- const newUrl = `/${tmpView}`
- window.location.pathname = newUrl
- } else {
- window.location.pathname = "/workflows"
- }
+ localStorage.setItem("globalUrl", "")
- setIsLoggedIn(true);
- }
- })
- )
- .catch((error) => {
- setLoginLoading(false);
- setLoginInfo("Error logging in: " + error);
- });
- } else {
- url = baseurl + "/api/v1/users/register";
- fetch(url, {
- method: "POST",
- body: JSON.stringify(data),
- headers: {
- "Content-Type": "application/json",
- },
- })
- .then((response) =>
- response.json().then((responseJson) => {
- if (responseJson["success"] === false) {
- setLoginInfo(responseJson["reason"]);
- } else {
- setLoginInfo("Successful register!");
- }
- })
- )
- .catch((error) => {
- setLoginInfo("Error in from backend: ", error);
- });
- }
- };
+ var baseurl = globalUrl
+ if (register) {
+ var url = baseurl + '/api/v1/login';
- const onChangeUser = (e) => {
- setUsername(e.target.value);
- };
-
- const onChangePass = (e) => {
- setPassword(e.target.value);
- };
-
- //const onClickRegister = () => {
- // if (props.location.pathname === "/login") {
- // window.location.pathname = "/register"
- // } else {
- // window.location.pathname = "/login"
- // }
-
- // setLoginCheck(!register)
- //}
-
- //var loginChange = register ? (
Want to register? Click here.
) : (
Go back to login? Click here.
);
- var formtitle = register ?
Login
:
Register
;
- const imgsize = 100;
- const basedata = (
-
-
-
-
-
-
- {loginViewLoading ? (
-
-
- Waiting for the Shuffle database to become available. This may
- take up to a minute.
-
-
- {loginInfo === undefined ||
- loginInfo === null ||
- loginInfo.length === 0 ? null : (
-
Database Response: {loginInfo}
- )}
-
-
-
-
-
- Are you sure Shuffle is{" "}
-
- installed correctly
-
- ?
-
-
-
- 1. Make sure shuffle-database folder has correct access, and that you have a minimum of 2Gb of RAM available :{" "}
-
-
- sudo chown -R 1000:1000 shuffle-database
-
-
- 2. Disable memory swap on the host:
-
-
- sudo swapoff -a
-
-
- 3 . Restart the database:
-
-
- sudo docker restart shuffle-opensearch
-
-
-
- Need help?{" "}
-
- Join the Discord!
-
-
-
- ) : (
-
- )}
-
-
- );
-
- const loadedCheck = isLoaded ?
{basedata}
:
;
-
- useEffect(() => {
- setTimeout(() => {
- if (ssoUrl !== undefined && ssoUrl !== null && ssoUrl.length > 0) {
- //id="sso_button"
- const ssoBtn = document.getElementById("sso_button");
- if (ssoBtn !== undefined && ssoBtn !== null) {
- //console.log("SSO BTN: ", ssoBtn)
- const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search;
- var tmpView = new URLSearchParams(cursearch).get("autologin");
- if (tmpView !== undefined && tmpView !== null) {
- if (tmpView === "true") {
- console.log("Tmp: ", tmpView)
- ssoBtn.click()
- }
- }
- }
+ if (loginWithSSO === true) {
+ url = baseurl + '/api/v1/login/sso'
+ setLoginInfo("Logging in with SSO. Please wait while we find a relevant org...")
}
- }, 200);
- }, [ssoUrl])
- return
{loadedCheck}
;
-};
+ 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) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for login:O!");
+ }
-export default LoginDialog;
+ return response.json();
+ })
+ .then((responseJson) => {
+
+ setLoginLoading(false)
+
+ console.log("Resp from backend: ", responseJson)
+
+ if (responseJson["success"] === false) {
+ setLoginInfo(responseJson["reason"])
+ }
+ else {
+
+ if (responseJson["reason"] === "MFA_REDIRECT") {
+ setLoginInfo("Enter the 6-digit MFA code.")
+ setMFAField(true)
+ return
+
+ }
+ else if (responseJson["reason"] === "MFA_SETUP") {
+ window.location.href = `/login/${responseJson.url}/mfa-setup`;
+ return;
+ }
+ else if (responseJson["reason"] === "SSO_REDIRECT") {
+ //navigate(responseJson["url"])
+ window.location.href = responseJson["url"]
+ return
+
+ }
+ else if (responseJson["reason"] !== undefined && responseJson["reason"] !== null && responseJson["reason"].includes("error")) {
+ setLoginInfo(responseJson["reason"])
+ return
+ }
+
+
+ setLoginInfo("Successful login! Redirecting you in 3 seconds...")
+ for (var key in responseJson["cookies"]) {
+ setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" })
+ }
+
+ const tmpView = new URLSearchParams(window.location.search).get("view")
+ if (tmpView !== undefined && tmpView !== null) {
+ //const newUrl = `/${tmpView}${decodeURIComponent(window.location.search)}`
+ // Check if slash in the url
+
+ var newUrl = `/${tmpView}`
+ if (tmpView.startsWith("/")) {
+ newUrl = `${tmpView}`
+ }
+
+ console.log("Found url: ", newUrl)
+
+ window.location.pathname = newUrl
+ return
+ }
+
+ console.log("LOGIN DATA: ", responseJson)
+ if (responseJson.tutorials !== undefined && responseJson.tutorials !== null) {
+ // Find welcome in responseJson.tutorials under key name
+ const welcome = responseJson.tutorials.find(function (element) {
+ return element.name === "welcome";
+ })
+
+ console.log("Welcome: ", welcome)
+ if (welcome === undefined || welcome === null) {
+ console.log("RUN login Welcome!!")
+ // window.location.pathname = "/welcome?tab=2"
+ // window.location = "/welcome?tab=2"
+ window.location.href = "/welcome?tab=2"
+ return
+ }
+ }
+
+ window.location.pathname = "/workflows"
+ }
+ })
+ .catch(error => {
+ setLoginInfo("Error from login API: " + error)
+ setLoginLoading(false)
+ });
+ } else {
+ url = baseurl + '/api/v1/register';
+ fetch(url, {
+ method: 'POST',
+ body: JSON.stringify(data),
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ })
+ .then(response =>
+ response.json().then(responseJson => {
+ if (responseJson["success"] === false) {
+ setLoginInfo(responseJson["reason"])
+ } else {
+ if (responseJson["reason"] === "shuffle_account") {
+ window.location.href = "/login?message=Please+login+with+your+Shuffle+account"
+ return
+ }
+
+ //setLoginInfo("Successful register!")
+ //var newpath = "/login?message=Successfully signed up. You can now sign in."
+ //const tmpMessage = new URLSearchParams(window.location.search).get("message")
+ setLoginInfo("Successful registration! Redirecting in 3 seconds...")
+ for (var key in responseJson["cookies"]) {
+ setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" })
+ }
+
+ setTimeout(() => {
+ console.log("LOGIN DATA: ", responseJson)
+
+ const tmpView = new URLSearchParams(window.location.search).get("view")
+ if (tmpView !== undefined && tmpView !== null) {
+ //const newUrl = `/${tmpView}${decodeURIComponent(window.location.search)}`
+ const newUrl = `/${tmpView}`
+ window.location.pathname = newUrl
+ return
+ }
+
+ //if (responseJson.tutorials === undefined || responseJson.tutorials === null || !responseJson.tutorials.includes("welcome")) {
+ console.log("RUN Welcome!!")
+ //window.location.pathname = "/welcome?tab=2"
+ window.location.href = "/welcome"
+ }, 1500);
+ }
+ setLoginLoading(false)
+ }),
+ )
+ .catch(error => {
+ setLoginInfo("Error in login. Please try again, or contact support@shuffler.io if the problem persists.")
+ setLoginLoading(false)
+ });
+ }
+ }
+
+ const onChangeUser = (e) => {
+ setUsername(e.target.value)
+ }
+
+ const onChangePass = (e) => {
+ setPassword(e.target.value)
+ }
+
+ const HandleLoginWithSSO = () => {
+ setPassword("")
+ setLoginInfo("")
+ setLoginWithSSO(true)
+ }
+
+ //const onClickRegister = () => {
+ // if (props.location.pathname === "/login") {
+ // window.location.pathname = "/register"
+ // } else {
+ // window.location.pathname = "/login"
+ // }
+
+ // setLoginCheck(!register)
+ //}
+
+ //var loginChange = register ? (
Want to register? Click here.
) : (
Go back to login? Click here.
);
+ var formtitle = register ?
Welcome Back!
:
Create your account
+ var formButton = !isCloud ? "" : register ?
Don’t have an account yet?
Register here
: <>
+
Already have an account?
Login here
+ >
+ //
Click here to Login
+
+ //
{formtitle}
+
+ const buttonBackground = "linear-gradient(89.83deg, #FF8444 0.13%, #F2643B 99.84%)"
+
+ const buttonStyle = { borderRadius: 25, height: 50, fontSize: 18, backgroundImage: handleValidateForm(username, password) || loginLoading || (checkboxClicked && register) ? buttonBackground : "grey", color: "white" }
+ //
+ const basedata =
+ (
+
+
+
+
+
+ {isMobile ? null : (
+ <>
+
+ OR
+
+
+ >
+ )}
+
+ );
+
+ const loadedCheck = isLoaded ?
+
+ {basedata}
+
+ :
+
+
+
+ return (
+
+ {loadedCheck}
+
+ )
+}
+
+export default LoginPage;
diff --git a/frontend/src/views/LoginPageOld.jsx b/frontend/src/views/LoginPageOld.jsx
new file mode 100755
index 00000000..541cd903
--- /dev/null
+++ b/frontend/src/views/LoginPageOld.jsx
@@ -0,0 +1,548 @@
+/* eslint-disable react/no-multi-comp */
+import React, { useState, useEffect } from "react";
+import { makeStyles } from "@mui/styles";
+import { useInterval } from "react-powerhooks";
+import theme from '../theme.jsx';
+
+import {
+ CircularProgress,
+ TextField,
+ Button,
+ Paper,
+ Typography,
+} from "@mui/material";
+
+import { useNavigate } from "react-router-dom";
+
+const hrefStyle = {
+ color: "white",
+ textDecoration: "none",
+};
+
+const bodyDivStyle = {
+ margin: "auto",
+ marginTop: 150,
+ width: "500px",
+};
+
+const useStyles = makeStyles({
+ notchedOutline: {
+ borderColor: "#f85a3e !important",
+ },
+});
+
+const LoginDialog = (props) => {
+ const {
+ globalUrl,
+ isLoaded,
+ isLoggedIn,
+ setIsLoggedIn,
+ setCookie,
+ register,
+ checkLogin,
+ } = props;
+
+ let navigate = useNavigate();
+ const classes = useStyles();
+
+ const [username, setUsername] = useState("");
+ const [password, setPassword] = useState("");
+ const [firstRequest, setFirstRequest] = useState(true);
+ const [loginLoading, setLoginLoading] = useState(false);
+ const [loginViewLoading, setLoginViewLoading] = useState(false);
+ const [ssoUrl, setSSOUrl] = useState("");
+
+ const [MFAField, setMFAField] = useState(false);
+ const [MFAValue, setMFAValue] = useState("");
+
+
+ // Used to swap from login to register. True = login, false = register
+
+ useEffect(() => {
+ checkAdmin()
+ }, [loginViewLoading])
+
+ // Error messages etc
+ const [loginInfo, setLoginInfo] = useState("");
+
+ const handleValidateForm = () => {
+ return username.length > 1 && password.length > 1;
+ };
+
+ if (isLoggedIn === true) {
+ //window.location.pathname = "/workflows";
+ navigate("/workflows")
+ }
+
+ const checkAdmin = () => {
+ const url = globalUrl + "/api/v1/checkusers";
+ fetch(url, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then((response) =>
+ response.json().then((responseJson) => {
+ if (responseJson["success"] === false) {
+ setLoginInfo(responseJson["reason"]);
+ } else {
+
+ if (responseJson.sso_url !== undefined && responseJson.sso_url !== null) {
+ setSSOUrl(responseJson.sso_url);
+ }
+
+ navigate("/login")
+
+ if (loginViewLoading) {
+ setLoginViewLoading(false);
+ checkLogin();
+ stop();
+
+ if (
+ responseJson.reason !== undefined &&
+ responseJson.reason !== null
+ ) {
+ setLoginInfo(responseJson.reason);
+ }
+ }
+
+ if (responseJson.reason === "stay") {
+ navigate("/adminsetup")
+ }
+ }
+ })
+ )
+ .catch((error) => {
+ if (!loginViewLoading) {
+ setLoginViewLoading(true);
+ start();
+ }
+ });
+ };
+
+ const { start, stop } = useInterval({
+ duration: 3000,
+ startImmediate: false,
+ callback: () => {
+ checkAdmin();
+ },
+ })
+
+ if (firstRequest) {
+ setFirstRequest(false);
+ checkAdmin();
+ }
+
+ const onSubmit = (e) => {
+ setLoginLoading(true);
+ e.preventDefault();
+ setLoginInfo("");
+ // FIXME - add some check here ROFL
+
+ // Just use this one?
+ var data = { username: username, password: password };
+ if (MFAValue !== undefined && MFAValue !== null && MFAValue.length > 0) {
+ data["mfa_code"] = MFAValue;
+ }
+
+ var baseurl = globalUrl;
+ if (register) {
+ var url = baseurl + "/api/v1/login";
+ 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) => {
+ setLoginLoading(false);
+ if (responseJson["success"] === false) {
+ setLoginInfo(responseJson["reason"]);
+ } else {
+ if (responseJson["reason"] === "MFA_REDIRECT") {
+ setLoginInfo(
+ "MFA required. Please enter the 6-digit code from your authenticator"
+ );
+ setMFAField(true);
+ return;
+ } else if (responseJson["reason"] === "MFA_SETUP") {
+ window.location.href = `/login/${responseJson.url}/mfa-setup`;
+ return;
+ }
+
+ setLoginInfo("Successful login, rerouting");
+ for (var key in responseJson["cookies"]) {
+ setCookie(
+ responseJson["cookies"][key].key,
+ responseJson["cookies"][key].value,
+ { path: "/" }
+ );
+ }
+
+ if (responseJson.tutorials === undefined || responseJson.tutorials === null || !responseJson.tutorials.includes("welcome")) {
+ console.log("RUN Welcome!!")
+ setTimeout(() => {
+ navigate("/welcome?tab=2")
+ },200)
+ // window.location.pathname = ""
+ return
+ }
+
+ const tmpView = new URLSearchParams(window.location.search).get("view")
+ if (tmpView !== undefined && tmpView !== null) {
+ //const newUrl = `/${tmpView}${decodeURIComponent(window.location.search)}`
+ const newUrl = `/${tmpView}`
+ window.location.pathname = newUrl
+ } else {
+ window.location.pathname = "/workflows"
+ }
+
+ setIsLoggedIn(true);
+ }
+ })
+ )
+ .catch((error) => {
+ setLoginLoading(false);
+ setLoginInfo("Error logging in: " + error);
+ });
+ } else {
+ url = baseurl + "/api/v1/users/register";
+ fetch(url, {
+ method: "POST",
+ body: JSON.stringify(data),
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then((response) =>
+ response.json().then((responseJson) => {
+ if (responseJson["success"] === false) {
+ setLoginInfo(responseJson["reason"]);
+ } else {
+ setLoginInfo("Successful register!");
+ }
+ })
+ )
+ .catch((error) => {
+ setLoginInfo("Error in from backend: ", error);
+ });
+ }
+ };
+
+ const onChangeUser = (e) => {
+ setUsername(e.target.value);
+ };
+
+ const onChangePass = (e) => {
+ setPassword(e.target.value);
+ };
+
+ //const onClickRegister = () => {
+ // if (props.location.pathname === "/login") {
+ // window.location.pathname = "/register"
+ // } else {
+ // window.location.pathname = "/login"
+ // }
+
+ // setLoginCheck(!register)
+ //}
+
+ //var loginChange = register ? (Want to register? Click here.
) : (Go back to login? Click here.
);
+ var formtitle = register ? Login
: Register
;
+ const imgsize = 100;
+ const basedata = (
+
+
+
+
+
+
+ {loginViewLoading ? (
+
+
+ Waiting for the Shuffle database to become available. This may
+ take up to two minutes.
+
+
+ {loginInfo === undefined ||
+ loginInfo === null ||
+ loginInfo.length === 0 ? null : (
+
Database Response: {loginInfo}
+ )}
+
+
+
+
+
+ Are you sure Shuffle is{" "}
+
+ installed correctly
+
+ ?
+
+
+
+ 1. Make sure shuffle-database folder has correct access, and that you have a minimum of 2Gb of RAM available :{" "}
+
+
+ sudo chown -R 1000:1000 shuffle-database
+
+
+ 2. Disable memory swap on the host:
+
+
+ sudo swapoff -a
+
+
+ 3 . Restart the database:
+
+
+ sudo docker restart shuffle-opensearch
+
+
+
+ Need help?{" "}
+
+ Join the Discord!
+
+
+
+ ) : (
+
+ )}
+
+
+ );
+
+ const loadedCheck = isLoaded ? {basedata}
:
;
+
+ useEffect(() => {
+ setTimeout(() => {
+ if (ssoUrl !== undefined && ssoUrl !== null && ssoUrl.length > 0) {
+ //id="sso_button"
+ const ssoBtn = document.getElementById("sso_button");
+ if (ssoBtn !== undefined && ssoBtn !== null) {
+ //console.log("SSO BTN: ", ssoBtn)
+ const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search;
+ var tmpView = new URLSearchParams(cursearch).get("autologin");
+ if (tmpView !== undefined && tmpView !== null) {
+ if (tmpView === "true") {
+ console.log("Tmp: ", tmpView)
+ ssoBtn.click()
+ }
+ }
+ }
+ }
+ }, 200);
+ }, [ssoUrl])
+
+ return {loadedCheck}
;
+};
+
+export default LoginDialog;
diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx
index 1eee8317..100eed13 100755
--- a/frontend/src/views/Workflows.jsx
+++ b/frontend/src/views/Workflows.jsx
@@ -1946,7 +1946,10 @@ const Workflows = (props) => {
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for setting workflows :O!");
- toast("Failed deleting workflow. Do you have access?");
+
+ if (bulk !== true) {
+ toast(`Failed deleting workflow ${id}. Do you have access?`);
+ }
} else {
if (bulk !== true) {
toast(`Deleted workflow ${id}. Child Workflows in Suborgs were also removed.`)
diff --git a/frontend/src/views/Workflows2.jsx b/frontend/src/views/Workflows2.jsx
index f03b8f97..ce7adb45 100644
--- a/frontend/src/views/Workflows2.jsx
+++ b/frontend/src/views/Workflows2.jsx
@@ -1758,9 +1758,17 @@ const Workflows2 = (props) => {
let exportFileDefaultName = data.name + ".json";
data["owner"] = "";
+ data["updated_by"] = "";
data["org"] = [];
data["org_id"] = "";
data["execution_org"] = {};
+ data["created"] = 0
+ data["due_date"] = 0
+ data["edited"] = 0
+
+ data["validation"] = {};
+ data["suborg_distribution"] = []
+ data["parentorg_workflow"] = ""
// These are backwards.. True = saved before. Very confuse.
data["previously_saved"] = false;
@@ -2035,7 +2043,10 @@ const Workflows2 = (props) => {
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for setting workflows :O!");
- toast("Failed deleting workflow. Do you have access?");
+
+ if (bulk !== true) {
+ toast("Failed deleting workflow. Do you have access?");
+ }
} else {
if (bulk !== true) {
toast(`Deleted workflow ${id}. Child Workflows in Suborgs were also removed.`)
@@ -2813,7 +2824,7 @@ const Workflows2 = (props) => {
{(data.sharing !== undefined && data.sharing !== null && data.sharing === "form") || (data?.form_control?.input_markdown !== undefined && data?.form_control?.input_markdown !== null && data?.form_control?.input_markdown !== "") && type !== "public" ?
-
+
{
: null}
{(data?.validation?.validation_ran === true && data?.validation?.valid === false && data?.validation?.errors?.length > 0 ) ?
-
-
@@ -3227,6 +3238,7 @@ const Workflows2 = (props) => {
}
+
../../../../shuffle-shared
+replace github.com/shuffle/shuffle-shared => ../../../../shuffle-shared
require (
github.com/docker/docker v27.5.0+incompatible
github.com/docker/go-connections v0.5.0
github.com/satori/go.uuid v1.2.0
- github.com/shuffle/shuffle-shared v0.8.3
+ github.com/shuffle/shuffle-shared v0.8.7
k8s.io/api v0.30.2
k8s.io/apimachinery v0.30.2
)
diff --git a/functions/onprem/orborus/go.sum b/functions/onprem/orborus/go.sum
index 2bb0c986..7fc88f31 100644
--- a/functions/onprem/orborus/go.sum
+++ b/functions/onprem/orborus/go.sum
@@ -123,12 +123,10 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
-
github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8=
github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
-
github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E=
github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
@@ -305,10 +303,11 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shuffle/shuffle-shared v0.6.99 h1:sPGmZo+8JMgUH9Q2O659za2w4sF/NXiWFCrSPm1nTAU=
github.com/shuffle/shuffle-shared v0.6.99/go.mod h1:bBXhEsPKjxln0mFnSeri7gIJ3tL/636Sh5NyTyNrvIQ=
+github.com/shuffle/shuffle-shared v0.8.7 h1:+UdRx7b/KUy/E92ODNr87UvCI2Dxfnv0samQWuggJu4=
+github.com/shuffle/shuffle-shared v0.8.7/go.mod h1:bBXhEsPKjxln0mFnSeri7gIJ3tL/636Sh5NyTyNrvIQ=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
-
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
@@ -369,7 +368,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -427,7 +425,6 @@ golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -437,7 +434,6 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
-
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -474,14 +470,12 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go
index 2f6af9fe..dbc79434 100755
--- a/functions/onprem/orborus/orborus.go
+++ b/functions/onprem/orborus/orborus.go
@@ -456,6 +456,7 @@ func deployServiceWorkers(image string) {
}
}
+ /*
isMemcachedRunning, err := checkMemcached(ctx, dockercli)
if err != nil {
log.Printf("[ERROR] Failed checking memcached: %s", err)
@@ -466,8 +467,10 @@ func deployServiceWorkers(image string) {
}
ip := "shuffle-cache"
-
- os.Setenv("SHUFFLE_MEMCACHED", fmt.Sprintf("%s:11211", ip))
+ if len(os.Getenv("SHUFFLE_MEMCACHED")) == 0 {
+ os.Setenv("SHUFFLE_MEMCACHED", fmt.Sprintf("%s:11211", ip))
+ }
+ */
defaultNetworkAttach := false
if containerId != "" {
@@ -608,7 +611,7 @@ func deployServiceWorkers(image string) {
fmt.Sprintf("SHUFFLE_LOGS_DISABLED=%s", os.Getenv("SHUFFLE_LOGS_DISABLED")),
fmt.Sprintf("DEBUG_MEMORY=%s", os.Getenv("DEBUG_MEMORY")),
fmt.Sprintf("SHUFFLE_APP_SDK_TIMEOUT=%s", os.Getenv("SHUFFLE_APP_SDK_TIMEOUT")),
- fmt.Sprintf("SHUFFLE_MAX_SWARM_NODES=%d", os.Getenv("SHUFFLE_MAX_SWARM_NODES")),
+ fmt.Sprintf("SHUFFLE_MAX_SWARM_NODES=%s", os.Getenv("SHUFFLE_MAX_SWARM_NODES")),
fmt.Sprintf("SHUFFLE_BASE_IMAGE_NAME=%s", os.Getenv("SHUFFLE_BASE_IMAGE_NAME")),
fmt.Sprintf("SHUFFLE_APP_REQUEST_TIMEOUT=%s", os.Getenv("SHUFFLE_APP_REQUEST_TIMEOUT")),
},
@@ -766,10 +769,8 @@ func handleBackendImageDownload(ctx context.Context, images string) error {
// Remove the image
handled := []string{}
- log.Printf("[DEBUG] Should remove existing image (s): %s. Waiting 30 seconds to ensure backend has the latest images built and ready to distribute.", images)
- removeOptions := image.RemoveOptions{}
-
- time.Sleep(time.Duration(30) * time.Second)
+ log.Printf("[DEBUG] Removing existing image (s): %s. Waiting 30 seconds before starting to ensure backend has the latest images built and ready to distribute.", images)
+ //time.Sleep(time.Duration(30) * time.Second)
newImages := []string{}
for _, image := range strings.Split(images, ",") {
@@ -785,23 +786,12 @@ func handleBackendImageDownload(ctx context.Context, images string) error {
newImages = append(newImages, image)
- // There is no real point in actual removal. This may however be a good idea, as Worker will force download the new one anyway
- resp, err := dockercli.ImageRemove(ctx, image, removeOptions)
+ log.Printf("[DEBUG] Downloading image: %s", image)
+ err := shuffle.DownloadDockerImageBackend(&http.Client{Timeout: imagedownloadTimeout}, image)
if err != nil {
- log.Printf("[ERROR] Failed removing image: %s. Resp: %#v", err, resp)
-
- // Goroutining images that don't already exist, as they are most likely not the correct one
- go shuffle.DownloadDockerImageBackend(&http.Client{Timeout: imagedownloadTimeout}, image)
+ log.Printf("[ERROR] Failed downloading image: %s", err)
} else {
- log.Printf("[DEBUG] Removed image: %s", image)
-
- err = shuffle.DownloadDockerImageBackend(&http.Client{Timeout: imagedownloadTimeout}, image)
- if err != nil {
- log.Printf("[ERROR] Failed downloading image: %s", err)
- } else {
- log.Printf("[DEBUG] Downloaded image: %s", image)
- //break
- }
+ log.Printf("[DEBUG] Downloaded image: %s", image)
}
}
@@ -820,20 +810,18 @@ func handleBackendImageDownload(ctx context.Context, images string) error {
)
if err != nil {
- log.Printf("[ERROR] Failed finding containers: %s", err)
+ log.Printf("[ERROR] Failed finding services: %s", err)
} else {
- log.Printf("[DEBUG] Found %d services", len(services))
-
+ found := false
for _, service := range services {
-
- log.Printf("Imagename: %s", service.Spec.TaskTemplate.ContainerSpec.Image)
+ //log.Printf("Service image: %s", service.Spec.TaskTemplate.ContainerSpec.Image)
for _, image := range newImages {
if !strings.Contains(service.Spec.TaskTemplate.ContainerSpec.Image, image) {
continue
}
- log.Printf("[DEBUG] Found service for image %#v: %#v", service.Spec.Annotations.Name)
+ log.Printf("[DEBUG] Found service for image: %#v", service.Spec.Annotations.Name)
// Update the service to run with the new image
//docker service update --image username/imagename:latest servicename --force
@@ -853,9 +841,20 @@ func handleBackendImageDownload(ctx context.Context, images string) error {
if !strings.Contains(fmt.Sprintf("%s", resp), "error") {
break
+ } else {
+ found = true
+ log.Printf("[ERROR] Failed updating service %s with the new image %s: %s. Resp: %#v", service.Spec.Annotations.Name, image, err, resp)
}
}
}
+
+ if found {
+ break
+ }
+ }
+
+ if !found {
+ log.Printf("[DEBUG] Failed to find service to update for service %s", newImages)
}
}
@@ -1479,29 +1478,34 @@ func initializeImages() {
}
// check whether they are the same first
- images := []string{
- fmt.Sprintf("frikky/shuffle:app_sdk"),
- fmt.Sprintf("shuffle/shuffle:app_sdk"),
- fmt.Sprintf("%s/%s/shuffle-app_sdk:%s", baseimageregistry, baseimagename, appSdkVersion),
- newWorker,
- }
- pullOptions := image.PullOptions{}
- for _, image := range images {
- if isKubernetes == "true" {
- log.Printf("[DEBUG] Skipping image pull of '%s' because Kubernetes does it in realtime instead", image)
- } else {
- log.Printf("[DEBUG] Pulling image %s", image)
- reader, err := dockercli.ImagePull(ctx, image, pullOptions)
- if err != nil {
- log.Printf("[ERROR] Failed getting image %s: %s", image, err)
-
- continue
- }
-
- io.Copy(os.Stdout, reader)
- log.Printf("[DEBUG] Successfully downloaded and built %s", image)
+ if os.Getenv("SHUFFLE_AUTO_IMAGE_DOWNLOAD") != "true" {
+ images := []string{
+ fmt.Sprintf("frikky/shuffle:app_sdk"),
+ fmt.Sprintf("shuffle/shuffle:app_sdk"),
+ fmt.Sprintf("%s/%s/shuffle-app_sdk:%s", baseimageregistry, baseimagename, appSdkVersion),
+ newWorker,
}
+
+ pullOptions := image.PullOptions{}
+ for _, image := range images {
+ if isKubernetes == "true" {
+ log.Printf("[DEBUG] Skipping image pull of '%s' because Kubernetes does it in realtime instead", image)
+ } else {
+ log.Printf("[DEBUG] Pulling image %s", image)
+ reader, err := dockercli.ImagePull(ctx, image, pullOptions)
+ if err != nil {
+ log.Printf("[ERROR] Failed getting image %s: %s", image, err)
+
+ continue
+ }
+
+ io.Copy(os.Stdout, reader)
+ log.Printf("[DEBUG] Successfully downloaded and built %s", image)
+ }
+ }
+ } else {
+ log.Printf("[DEBUG] Skipping image download as SHUFFLE_AUTO_IMAGE_DOWNLOAD is set to true")
}
}
@@ -2245,7 +2249,7 @@ func main() {
toBeRemoved.Data = append(toBeRemoved.Data, incRequest)
} else if incRequest.Type == "DOCKER_IMAGE_DOWNLOAD" {
- log.Printf("[INFO] Should delete -> download new images: %#v", incRequest.ExecutionArgument)
+ log.Printf("[INFO] Re-downloading new image(s): %#v", incRequest.ExecutionArgument)
if len(incRequest.ExecutionArgument) > 0 {
// FIXME: Wait X seconds before running this as the image build may not be done yet. This is shitty, but may be ok to do in Orborus. Easy fix for the future: Just let it run through jobs 5-10 times before actually picking it up
@@ -3462,7 +3466,7 @@ func sendPipelineHealthStatus() (shuffle.LakeConfig, error) {
} else {
tenzirDisabled = true
- log.Printf("[ERROR] Disabling pipelines: %s. You will need to restart the Orborus to fix this.", err)
+ log.Printf("[WARNING] Disabling pipelines: %s. You will need to restart the Orborus to fix this.", err)
}
return pipelinePayload, err
diff --git a/functions/onprem/worker/go.mod b/functions/onprem/worker/go.mod
index 64929892..5b8cbd22 100644
--- a/functions/onprem/worker/go.mod
+++ b/functions/onprem/worker/go.mod
@@ -8,7 +8,7 @@ require (
github.com/docker/docker v27.5.0+incompatible
github.com/gorilla/mux v1.8.1
github.com/satori/go.uuid v1.2.0
- github.com/shuffle/shuffle-shared v0.8.3
+ github.com/shuffle/shuffle-shared v0.8.7
k8s.io/api v0.30.2
k8s.io/apimachinery v0.30.2
k8s.io/client-go v0.30.2
diff --git a/functions/onprem/worker/worker.go b/functions/onprem/worker/worker.go
index dca31904..943dcd2f 100644
--- a/functions/onprem/worker/worker.go
+++ b/functions/onprem/worker/worker.go
@@ -106,9 +106,7 @@ var window = shuffle.NewTimeWindow(10 * time.Second)
// Images to be autodeployed in the latest version of Shuffle.
var autoDeploy = map[string]string{
"http:1.4.0": "frikky/shuffle:http_1.4.0",
- "http:1.3.0": "frikky/shuffle:http_1.3.0",
"shuffle-tools:1.2.0": "frikky/shuffle:shuffle-tools_1.2.0",
- "shuffle-subflow:1.0.0": "frikky/shuffle:shuffle-subflow_1.0.0",
"shuffle-subflow:1.1.0": "frikky/shuffle:shuffle-subflow_1.1.0",
// "shuffle-tools-fork:1.0.0": "frikky/shuffle:shuffle-tools-fork_1.0.0",
}