Added loads more workflow ownership pieces
This commit is contained in:
@@ -3,6 +3,7 @@ module main
|
|||||||
go 1.15
|
go 1.15
|
||||||
|
|
||||||
replace github.com/shuffle/shuffle-shared => ../../../../git/shuffle-shared
|
replace github.com/shuffle/shuffle-shared => ../../../../git/shuffle-shared
|
||||||
|
|
||||||
//replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi
|
//replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi
|
||||||
//replace github.com/frikky/go-elasticsearch => ../../../../git/go-elasticsearch
|
//replace github.com/frikky/go-elasticsearch => ../../../../git/go-elasticsearch
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ require (
|
|||||||
github.com/h2non/filetype v1.1.1
|
github.com/h2non/filetype v1.1.1
|
||||||
github.com/satori/go.uuid v1.2.0
|
github.com/satori/go.uuid v1.2.0
|
||||||
github.com/shuffle/shuffle-shared v0.1.30
|
github.com/shuffle/shuffle-shared v0.1.30
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
|
||||||
go4.org v0.0.0-20201209231011-d4a079459e60 // indirect
|
go4.org v0.0.0-20201209231011-d4a079459e60 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
|
||||||
google.golang.org/api v0.58.0
|
google.golang.org/api v0.58.0
|
||||||
|
|||||||
@@ -5691,6 +5691,8 @@ func initHandlers() {
|
|||||||
r.HandleFunc("/api/v1/users/updateuser", shuffle.HandleUpdateUser).Methods("PUT", "OPTIONS")
|
r.HandleFunc("/api/v1/users/updateuser", shuffle.HandleUpdateUser).Methods("PUT", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/users/{user}", shuffle.DeleteUser).Methods("DELETE", "OPTIONS")
|
r.HandleFunc("/api/v1/users/{user}", shuffle.DeleteUser).Methods("DELETE", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/users/passwordchange", shuffle.HandlePasswordChange).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/users/passwordchange", shuffle.HandlePasswordChange).Methods("POST", "OPTIONS")
|
||||||
|
r.HandleFunc("/api/v1/users/{key}/get2fa", shuffle.HandleGet2fa).Methods("GET", "OPTIONS")
|
||||||
|
r.HandleFunc("/api/v1/users/{key}/set2fa", shuffle.HandleSet2fa).Methods("POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/users", shuffle.HandleGetUsers).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/users", shuffle.HandleGetUsers).Methods("GET", "OPTIONS")
|
||||||
|
|
||||||
// General - duplicates and old.
|
// General - duplicates and old.
|
||||||
@@ -5739,6 +5741,10 @@ func initHandlers() {
|
|||||||
r.HandleFunc("/api/v1/apps/authentication/{appauthId}/config", shuffle.SetAuthenticationConfig).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/apps/authentication/{appauthId}/config", shuffle.SetAuthenticationConfig).Methods("POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/apps/authentication/{appauthId}", shuffle.DeleteAppAuthentication).Methods("DELETE", "OPTIONS")
|
r.HandleFunc("/api/v1/apps/authentication/{appauthId}", shuffle.DeleteAppAuthentication).Methods("DELETE", "OPTIONS")
|
||||||
|
|
||||||
|
// Related to
|
||||||
|
r.HandleFunc("/api/v1/workflows/collections/load", shuffle.LoadCollections).Methods("POST", "OPTIONS")
|
||||||
|
r.HandleFunc("/api/v1/workflows/collections/{key}", shuffle.HandleGetCollection).Methods("GET", "OPTIONS")
|
||||||
|
|
||||||
// Legacy app things
|
// Legacy app things
|
||||||
r.HandleFunc("/api/v1/workflows/apps/validate", validateAppInput).Methods("POST", "OPTIONS")
|
r.HandleFunc("/api/v1/workflows/apps/validate", validateAppInput).Methods("POST", "OPTIONS")
|
||||||
r.HandleFunc("/api/v1/workflows/apps", getWorkflowApps).Methods("GET", "OPTIONS")
|
r.HandleFunc("/api/v1/workflows/apps", getWorkflowApps).Methods("GET", "OPTIONS")
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ const App = (message, props) => {
|
|||||||
// Handling Ethereum update
|
// Handling Ethereum update
|
||||||
detectEthereumProvider()
|
detectEthereumProvider()
|
||||||
.then((provider) => {
|
.then((provider) => {
|
||||||
if (provider) {
|
if (provider && userInfo.eth_info !== undefined && userInfo.eth_info !== null) {
|
||||||
if (userInfo.eth_info.account !== undefined && userInfo.eth_info.account !== null && userInfo.eth_info.account.length === 0) {
|
if (userInfo.eth_info.account !== undefined && userInfo.eth_info.account !== null && userInfo.eth_info.account.length === 0) {
|
||||||
userInfo.eth_info = {}
|
userInfo.eth_info = {}
|
||||||
var method = "eth_requestAccounts"
|
var method = "eth_requestAccounts"
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+214
-36
@@ -11,7 +11,7 @@ import {Edit as EditIcon, FileCopy as FileCopyIcon, Publish as PublishIcon, Sele
|
|||||||
import { useAlert } from "react-alert";
|
import { useAlert } from "react-alert";
|
||||||
import Dropzone from '../components/Dropzone';
|
import Dropzone from '../components/Dropzone';
|
||||||
import HandlePayment from './HandlePayment'
|
import HandlePayment from './HandlePayment'
|
||||||
import OrgHeader from '../components/OrgHeader'
|
import OrgHeader from '../components/OrgHeader.jsx'
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
notchedOutline: {
|
notchedOutline: {
|
||||||
@@ -61,6 +61,11 @@ const Admin = (props) => {
|
|||||||
const [showArchived, setShowArchived] = React.useState(false)
|
const [showArchived, setShowArchived] = React.useState(false)
|
||||||
const [isDropzone, setIsDropzone] = React.useState(false);
|
const [isDropzone, setIsDropzone] = React.useState(false);
|
||||||
|
|
||||||
|
const [image2FA, setImage2FA] = React.useState("");
|
||||||
|
const [value2FA, setValue2FA] = React.useState("");
|
||||||
|
const [secret2FA, setSecret2FA] = React.useState("");
|
||||||
|
const [show2faSetup, setShow2faSetup] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isDropzone) {
|
if (isDropzone) {
|
||||||
//redirectOpenApi();
|
//redirectOpenApi();
|
||||||
@@ -69,6 +74,36 @@ const Admin = (props) => {
|
|||||||
}, [isDropzone]);
|
}, [isDropzone]);
|
||||||
|
|
||||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"
|
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"
|
||||||
|
|
||||||
|
const get2faCode = (userId) => {
|
||||||
|
fetch(`${globalUrl}/api/v1/users/${userId}/get2fa`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log("Status not 200 for apps :O!")
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
//console.log("RESPONSE: ", responseJson)
|
||||||
|
if (responseJson.success === true) {
|
||||||
|
//alert.info(responseJson.reason)
|
||||||
|
setImage2FA(responseJson.reason)
|
||||||
|
setSecret2FA(responseJson.extra)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
alert.error(error.toString())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const getApps = () => {
|
const getApps = () => {
|
||||||
fetch(globalUrl+"/api/v1/apps", {
|
fetch(globalUrl+"/api/v1/apps", {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@@ -200,6 +235,58 @@ const Admin = (props) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleVerify2FA = (userId, code) => {
|
||||||
|
const data = {
|
||||||
|
"code": code,
|
||||||
|
"user_id": userId,
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(`${globalUrl}/api/v1/users/${userId}/set2fa`, {
|
||||||
|
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) {
|
||||||
|
} else {
|
||||||
|
//alert.info("Wrong code sent.")
|
||||||
|
//alert.info("Wrong code sent. Please try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
if (responseJson.success === true) {
|
||||||
|
alert.info("Successfully enabled 2fa")
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
getUsers()
|
||||||
|
|
||||||
|
setImage2FA("")
|
||||||
|
setValue2FA("")
|
||||||
|
setSecret2FA("")
|
||||||
|
setShow2faSetup(false)
|
||||||
|
setSelectedUserModalOpen(false)
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
alert.info("Wrong code sent. Please try again.")
|
||||||
|
//alert.error("Failed setting 2fa: ", responseJson.reason)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
alert.info("Wrong code sent. Please try again.")
|
||||||
|
//alert.error("Err: " + error.toString())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const handleStopOrgSync = (org_id) => {
|
const handleStopOrgSync = (org_id) => {
|
||||||
if (org_id === undefined || org_id === null) {
|
if (org_id === undefined || org_id === null) {
|
||||||
alert.error("Couldn't get org "+org_id)
|
alert.error("Couldn't get org "+org_id)
|
||||||
@@ -467,7 +554,7 @@ const Admin = (props) => {
|
|||||||
if (!responseJson.success && responseJson.reason !== undefined) {
|
if (!responseJson.success && responseJson.reason !== undefined) {
|
||||||
alert.error("Failed to deactivate user: "+responseJson.reason)
|
alert.error("Failed to deactivate user: "+responseJson.reason)
|
||||||
} else {
|
} else {
|
||||||
alert.success("Deactivated user "+data.id)
|
alert.success("Changed activation for user "+data.id)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1291,7 +1378,7 @@ const Admin = (props) => {
|
|||||||
<DialogTitle><span style={{ color: "white" }}>Edit authentication for {selectedAuthentication.app.name} ({selectedAuthentication.label})</span></DialogTitle>
|
<DialogTitle><span style={{ color: "white" }}>Edit authentication for {selectedAuthentication.app.name} ({selectedAuthentication.label})</span></DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
{selectedAuthentication.fields.map((data, index) => {
|
{selectedAuthentication.fields.map((data, index) => {
|
||||||
console.log("DATA: ", data, selectedAuthentication)
|
//console.log("DATA: ", data, selectedAuthentication)
|
||||||
return (
|
return (
|
||||||
<div key={index}>
|
<div key={index}>
|
||||||
<Typography style={{marginBottom: 0, marginTop: 10}}>{data.key}</Typography>
|
<Typography style={{marginBottom: 0, marginTop: 10}}>{data.key}</Typography>
|
||||||
@@ -1356,7 +1443,7 @@ const Admin = (props) => {
|
|||||||
: null
|
: null
|
||||||
|
|
||||||
const editUserModal =
|
const editUserModal =
|
||||||
<Dialog modal
|
<Dialog
|
||||||
open={selectedUserModalOpen}
|
open={selectedUserModalOpen}
|
||||||
onClose={() => { setSelectedUserModalOpen(false) }}
|
onClose={() => { setSelectedUserModalOpen(false) }}
|
||||||
PaperProps={{
|
PaperProps={{
|
||||||
@@ -1368,7 +1455,7 @@ const Admin = (props) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogTitle><span style={{ color: "white" }}><EditIcon style={{marginTop: 5}}/> Editing {selectedUser.username}</span></DialogTitle>
|
<DialogTitle style={{maxWidth: 450, margin: "auto"}}><span style={{ color: "white" }}><EditIcon style={{marginTop: 5}}/> Editing {selectedUser.username}</span></DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
{isCloud ?
|
{isCloud ?
|
||||||
null
|
null
|
||||||
@@ -1444,25 +1531,91 @@ const Admin = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<Divider style={{ marginTop: 20, marginBottom: 20, backgroundColor: theme.palette.inputColor }} />
|
<Divider style={{ marginTop: 20, marginBottom: 20, backgroundColor: theme.palette.inputColor }} />
|
||||||
<Button
|
<div style={{margin: "auto", maxWidth: 400}}>
|
||||||
style={{}}
|
<Button
|
||||||
variant="outlined"
|
style={{}}
|
||||||
color="primary"
|
variant="outlined"
|
||||||
disabled={selectedUser.role === "admin"}
|
color="primary"
|
||||||
onClick={() => deleteUser(selectedUser)}
|
disabled={selectedUser.username === userdata.username}
|
||||||
>
|
onClick={() => deleteUser(selectedUser)}
|
||||||
{selectedUser.active ? "Deactivate" : "Activate"}
|
>
|
||||||
</Button>
|
{selectedUser.active ? "Deactivate" : "Activate"}
|
||||||
<Button
|
</Button>
|
||||||
style={{}}
|
<Button
|
||||||
variant="outlined"
|
style={{}}
|
||||||
color="primary"
|
variant="outlined"
|
||||||
disabled={selectedUser.role === "admin" && selectedUser.username !== userdata.username}
|
color="primary"
|
||||||
onClick={() => generateApikey(selectedUser)}
|
disabled={selectedUser.role === "admin" && selectedUser.username !== userdata.username}
|
||||||
>
|
onClick={() => generateApikey(selectedUser)}
|
||||||
Get new API key
|
>
|
||||||
</Button>
|
Renew API-key
|
||||||
</DialogContent>
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
run2FASetup(userdata)
|
||||||
|
}}
|
||||||
|
disabled={(selectedUser.role === "admin" && selectedUser.username !== userdata.username) || selectedUser.active === false}
|
||||||
|
variant="outlined"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
{selectedUser.mfa_info !== undefined && selectedUser.mfa_info !== null && selectedUser.mfa_info.active === true ? "Disable 2FA" : "Enable 2FA"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{show2faSetup && isCloud ?
|
||||||
|
<div style={{margin: "auto", maxWidth: 300, minWidth: 300, marginTop: 25, }}>
|
||||||
|
{/*<Divider style={{marginTop: 20, marginBottom: 20}} />*/}
|
||||||
|
|
||||||
|
{secret2FA !== undefined && secret2FA !== null && secret2FA.length > 0 ?
|
||||||
|
<span>
|
||||||
|
<Typography variant="body2" color="textSecondary">
|
||||||
|
Scan the image below with the two-factor authentication app on your phone. If you can’t use a QR code, use the code {secret2FA} instead.
|
||||||
|
</Typography>
|
||||||
|
</span>
|
||||||
|
: null}
|
||||||
|
{image2FA !== undefined && image2FA !== null && image2FA.length > 0 ?
|
||||||
|
<img alt={"2 factor img"} src={image2FA} style={{margin: "auto", marginTop: 25, maxHeight: 200, maxWidth: 200, minWidth: 200, maxWidth: 200, }} />
|
||||||
|
:
|
||||||
|
<CircularProgress />
|
||||||
|
}
|
||||||
|
|
||||||
|
<Typography variant="body2" color="textSecondary">
|
||||||
|
After scanning the QR code image, the app will display a code that you can enter below.
|
||||||
|
</Typography>
|
||||||
|
<div style={{display: "flex"}}>
|
||||||
|
<TextField
|
||||||
|
color="primary"
|
||||||
|
style={{flex: 2, backgroundColor: theme.palette.inputColor, marginRight: 10, }}
|
||||||
|
InputProps={{
|
||||||
|
style: {
|
||||||
|
height: 50,
|
||||||
|
color: "white",
|
||||||
|
fontSize: "1em",
|
||||||
|
},
|
||||||
|
maxLength: 6,
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
fullWidth={true}
|
||||||
|
id="2fa_key"
|
||||||
|
margin="normal"
|
||||||
|
placeholder="6-digit code"
|
||||||
|
variant="outlined"
|
||||||
|
onChange={(event) => {
|
||||||
|
if (event.target.value.length > 6) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue2FA(event.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button disabled={value2FA.length !== 6} variant="contained" style={{marginTop: 15, height: 50, flex: 1,}} onClick={() => {
|
||||||
|
handleVerify2FA(userdata.id, value2FA)
|
||||||
|
}} color="primary">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
: null}
|
||||||
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
const GridItem = (props) => {
|
const GridItem = (props) => {
|
||||||
@@ -1574,14 +1727,17 @@ const Admin = (props) => {
|
|||||||
setCloudSyncApikey(event.target.value)
|
setCloudSyncApikey(event.target.value)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button disabled={(!selectedOrganization.cloud_sync && cloudSyncApikey.length === 0) || loading} variant="contained" style={{ marginLeft: 15, height: 50, borderRadius: "0px" }} onClick={() => {
|
<Button disabled={(!selectedOrganization.cloud_sync && cloudSyncApikey.length === 0) || loading} style={{ marginLeft: 15, height: 50, borderRadius: "0px" }} onClick={() => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
enableCloudSync(
|
enableCloudSync(
|
||||||
cloudSyncApikey,
|
cloudSyncApikey,
|
||||||
selectedOrganization,
|
selectedOrganization,
|
||||||
selectedOrganization.cloud_sync,
|
selectedOrganization.cloud_sync,
|
||||||
)
|
)
|
||||||
}} color="primary">
|
}}
|
||||||
|
color="primary"
|
||||||
|
variant={selectedOrganization.cloud_sync === true ? "outlined" : "contained"}
|
||||||
|
>
|
||||||
{selectedOrganization.cloud_sync ?
|
{selectedOrganization.cloud_sync ?
|
||||||
"Stop sync"
|
"Stop sync"
|
||||||
:
|
:
|
||||||
@@ -1698,7 +1854,7 @@ const Admin = (props) => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{selectedOrganization.name.length > 0 ?
|
{selectedOrganization.name.length > 0 ?
|
||||||
<OrgHeader userdata={userdata} setSelectedOrganization={setSelectedOrganization} globalUrl={globalUrl} selectedOrganization={selectedOrganization}/>
|
<OrgHeader isCloud={isCloud} userdata={userdata} setSelectedOrganization={setSelectedOrganization} globalUrl={globalUrl} selectedOrganization={selectedOrganization}/>
|
||||||
:
|
:
|
||||||
<div style={{paddingTop: 250, width: 250, margin: "auto", textAlign: "center"}}>
|
<div style={{paddingTop: 250, width: 250, margin: "auto", textAlign: "center"}}>
|
||||||
<CircularProgress />
|
<CircularProgress />
|
||||||
@@ -1751,7 +1907,7 @@ const Admin = (props) => {
|
|||||||
{selectedOrganization.cloud_sync_active ?
|
{selectedOrganization.cloud_sync_active ?
|
||||||
<Button
|
<Button
|
||||||
style={{ width: 150, height: 50, marginLeft: 10, marginTop: 17, }}
|
style={{ width: 150, height: 50, marginLeft: 10, marginTop: 17, }}
|
||||||
variant="contained"
|
variant={selectedOrganization.cloud_sync_active === true ? "outlined" : "contained"}
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleStopOrgSync(selectedOrganization.id)
|
handleStopOrgSync(selectedOrganization.id)
|
||||||
@@ -1788,14 +1944,17 @@ const Admin = (props) => {
|
|||||||
setCloudSyncApikey(event.target.value)
|
setCloudSyncApikey(event.target.value)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button disabled={(!selectedOrganization.cloud_sync && cloudSyncApikey.length === 0) || loading} variant="contained" style={{marginTop: 15, height: 50, width: 150,}} onClick={() => {
|
<Button disabled={(!selectedOrganization.cloud_sync && cloudSyncApikey.length === 0) || loading} style={{marginTop: 15, height: 50, width: 150,}} onClick={() => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
enableCloudSync(
|
enableCloudSync(
|
||||||
cloudSyncApikey,
|
cloudSyncApikey,
|
||||||
selectedOrganization,
|
selectedOrganization,
|
||||||
selectedOrganization.cloud_sync,
|
selectedOrganization.cloud_sync,
|
||||||
)
|
)
|
||||||
}} color="primary">
|
}}
|
||||||
|
color="primary"
|
||||||
|
variant={selectedOrganization.cloud_sync === true ? "outlined" : "contained"}
|
||||||
|
>
|
||||||
{selectedOrganization.cloud_sync ?
|
{selectedOrganization.cloud_sync ?
|
||||||
"Stop sync"
|
"Stop sync"
|
||||||
:
|
:
|
||||||
@@ -2077,11 +2236,15 @@ const Admin = (props) => {
|
|||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="Active"
|
primary="Active"
|
||||||
style={{ minWidth: 150, maxWidth: 150 }}
|
style={{ minWidth: 100, maxWidth: 100, marginLeft: 5,}}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="Type"
|
primary="Type"
|
||||||
style={{ minWidth: 150 , maxWidth: 150 }}
|
style={{ minWidth: 100 , maxWidth: 100 }}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary="MFA"
|
||||||
|
style={{ minWidth: 100, maxWidth: 100 }}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="Actions"
|
primary="Actions"
|
||||||
@@ -2158,11 +2321,15 @@ const Admin = (props) => {
|
|||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={data.active ? "True" : "False"}
|
primary={data.active ? "True" : "False"}
|
||||||
style={{ minWidth: 150, maxWidth: 150}}
|
style={{ minWidth: 100, maxWidth: 100}}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={data.login_type === undefined || data.login_type === null || data.login_type.length === 0 ? "Normal" : data.login_type}
|
primary={data.login_type === undefined || data.login_type === null || data.login_type.length === 0 ? "Normal" : data.login_type}
|
||||||
style={{ minWidth: 150, maxWidth: 150}}
|
style={{ minWidth: 100, maxWidth: 100}}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary={data.mfa_info !== undefined && data.mfa_info !== null && data.mfa_info.active === true ? "Active" : "Inactive"}
|
||||||
|
style={{ minWidth: 100, maxWidth: 100}}
|
||||||
/>
|
/>
|
||||||
<ListItemText style={{ display: "flex" }}>
|
<ListItemText style={{ display: "flex" }}>
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -2173,7 +2340,7 @@ const Admin = (props) => {
|
|||||||
>
|
>
|
||||||
<EditIcon color="primary"/>
|
<EditIcon color="primary"/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Button
|
{/*<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
generateApikey(data)
|
generateApikey(data)
|
||||||
}}
|
}}
|
||||||
@@ -2182,7 +2349,7 @@ const Admin = (props) => {
|
|||||||
color="primary"
|
color="primary"
|
||||||
>
|
>
|
||||||
New apikey
|
New apikey
|
||||||
</Button>
|
</Button>*/}
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)
|
)
|
||||||
@@ -2191,6 +2358,17 @@ const Admin = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
: null
|
: null
|
||||||
|
|
||||||
|
const run2FASetup = (data) => {
|
||||||
|
console.log("2fa: ", data)
|
||||||
|
if (!show2faSetup) {
|
||||||
|
get2faCode(data.id)
|
||||||
|
} else {
|
||||||
|
// Should remove?
|
||||||
|
}
|
||||||
|
|
||||||
|
setShow2faSetup(!show2faSetup)
|
||||||
|
}
|
||||||
|
|
||||||
const uploadFiles = (files) => {
|
const uploadFiles = (files) => {
|
||||||
for (var key in files) {
|
for (var key in files) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ const LoginDialog = props => {
|
|||||||
const [loginViewLoading, setLoginViewLoading] = useState(false);
|
const [loginViewLoading, setLoginViewLoading] = useState(false);
|
||||||
const [ssoUrl, setSSOUrl] = useState("")
|
const [ssoUrl, setSSOUrl] = useState("")
|
||||||
|
|
||||||
|
const [MFAField, setMFAField] = useState(false);
|
||||||
|
const [MFAValue, setMFAValue] = useState("");
|
||||||
|
|
||||||
// Used to swap from login to register. True = login, false = register
|
// Used to swap from login to register. True = login, false = register
|
||||||
|
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
@@ -111,7 +114,11 @@ const LoginDialog = props => {
|
|||||||
// FIXME - add some check here ROFL
|
// FIXME - add some check here ROFL
|
||||||
|
|
||||||
// Just use this one?
|
// Just use this one?
|
||||||
var data = { "username": username, "password": password }
|
var data = {"username": username, "password": password}
|
||||||
|
if (MFAValue !== undefined && MFAValue !== null && MFAValue.length > 0) {
|
||||||
|
data["mfa_code"] = MFAValue
|
||||||
|
}
|
||||||
|
|
||||||
var baseurl = globalUrl
|
var baseurl = globalUrl
|
||||||
if (register) {
|
if (register) {
|
||||||
var url = baseurl + '/api/v1/users/login';
|
var url = baseurl + '/api/v1/users/login';
|
||||||
@@ -132,6 +139,12 @@ const LoginDialog = props => {
|
|||||||
if (responseJson["success"] === false) {
|
if (responseJson["success"] === false) {
|
||||||
setLoginInfo(responseJson["reason"])
|
setLoginInfo(responseJson["reason"])
|
||||||
} else {
|
} else {
|
||||||
|
if (responseJson["reason"] === "MFA_REDIRECT") {
|
||||||
|
setLoginInfo("MFA required. Please the 6-digit code from your authenticator")
|
||||||
|
setMFAField(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setLoginInfo("Successful login, rerouting")
|
setLoginInfo("Successful login, rerouting")
|
||||||
for (var key in responseJson["cookies"]) {
|
for (var key in responseJson["cookies"]) {
|
||||||
setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" })
|
setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" })
|
||||||
@@ -255,7 +268,7 @@ const LoginDialog = props => {
|
|||||||
<div>
|
<div>
|
||||||
<TextField
|
<TextField
|
||||||
color="primary"
|
color="primary"
|
||||||
style={{ backgroundColor: theme.palette.inputColor }}
|
style={{ backgroundColor: theme.palette.inputColor, marginTop: 5, }}
|
||||||
autoFocus
|
autoFocus
|
||||||
InputProps={{
|
InputProps={{
|
||||||
classes: {
|
classes: {
|
||||||
@@ -281,7 +294,7 @@ const LoginDialog = props => {
|
|||||||
<div>
|
<div>
|
||||||
<TextField
|
<TextField
|
||||||
color="primary"
|
color="primary"
|
||||||
style={{ backgroundColor: theme.palette.inputColor }}
|
style={{ backgroundColor: theme.palette.inputColor, marginTop: 5,}}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
classes: {
|
classes: {
|
||||||
notchedOutline: classes.notchedOutline,
|
notchedOutline: classes.notchedOutline,
|
||||||
@@ -303,6 +316,35 @@ const LoginDialog = props => {
|
|||||||
onChange={onChangePass}
|
onChange={onChangePass}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{MFAField === true ?
|
||||||
|
<div style={{marginTop: 15}}>
|
||||||
|
5-factor code
|
||||||
|
<TextField
|
||||||
|
color="primary"
|
||||||
|
style={{backgroundColor: theme.palette.inputColor, marginTop: 5, }}
|
||||||
|
InputProps={{
|
||||||
|
classes: {
|
||||||
|
notchedOutline: classes.notchedOutline,
|
||||||
|
},
|
||||||
|
style:{
|
||||||
|
height: "50px",
|
||||||
|
color: "white",
|
||||||
|
fontSize: "1em",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
id="outlined-password-input"
|
||||||
|
fullWidth={true}
|
||||||
|
type="text"
|
||||||
|
placeholder="6-digit code"
|
||||||
|
margin="normal"
|
||||||
|
variant="outlined"
|
||||||
|
onChange={(event) => {
|
||||||
|
setMFAValue(event.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
: null}
|
||||||
<div style={{ display: "flex", marginTop: "15px" }}>
|
<div style={{ display: "flex", marginTop: "15px" }}>
|
||||||
<Button color="primary" variant="contained" type="submit" style={{ flex: "1", }} disabled={!handleValidateForm() || loginLoading}>
|
<Button color="primary" variant="contained" type="submit" style={{ flex: "1", }} disabled={!handleValidateForm() || loginLoading}>
|
||||||
{loginLoading ? <CircularProgress color="secondary" style={{color: "white",}} /> : "SUBMIT"}
|
{loginLoading ? <CircularProgress color="secondary" style={{color: "white",}} /> : "SUBMIT"}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, {useState, useEffect} from 'react';
|
import React, {useState, useEffect} from 'react';
|
||||||
|
|
||||||
import {Typography, Paper, Button, Divider, TextField} from '@material-ui/core';
|
import {Grid, Typography, Paper, Button, Divider, TextField} from '@material-ui/core';
|
||||||
import {Link} from 'react-router-dom';
|
import {Link} from 'react-router-dom';
|
||||||
import { useAlert } from "react-alert";
|
import { useAlert } from "react-alert";
|
||||||
import { useTheme } from '@material-ui/core/styles';
|
import { useTheme } from '@material-ui/core/styles';
|
||||||
@@ -24,6 +24,9 @@ const Settings = (props) => {
|
|||||||
const [newPassword2, setNewPassword2] = useState("");
|
const [newPassword2, setNewPassword2] = useState("");
|
||||||
const [file, setFile] = React.useState("")
|
const [file, setFile] = React.useState("")
|
||||||
const [fileBase64, setFileBase64] = React.useState(userdata.image === undefined || userdata.image === null ? theme.palette.defaultImage : userdata.image)
|
const [fileBase64, setFileBase64] = React.useState(userdata.image === undefined || userdata.image === null ? theme.palette.defaultImage : userdata.image)
|
||||||
|
const [loadedValidationWorkflows, setLoadedValidationWorkflows] = React.useState([])
|
||||||
|
const [selfOwnedWorkflows, setSelfOwnedWorkflows] = React.useState([])
|
||||||
|
const [loadedWorkflowCollections, setLoadedWorkflowCollections] = React.useState([])
|
||||||
|
|
||||||
// Used for error messages etc
|
// Used for error messages etc
|
||||||
const [formMessage, ] = useState("");
|
const [formMessage, ] = useState("");
|
||||||
@@ -32,7 +35,6 @@ const Settings = (props) => {
|
|||||||
const [firstrequest, setFirstRequest] = useState(true)
|
const [firstrequest, setFirstRequest] = useState(true)
|
||||||
|
|
||||||
const [userSettings, setUserSettings] = useState({})
|
const [userSettings, setUserSettings] = useState({})
|
||||||
console.log(userdata)
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -83,6 +85,24 @@ const Settings = (props) => {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column"
|
flexDirection: "column"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const checkOwner = (data, userdata) => {
|
||||||
|
var currentOwner = false
|
||||||
|
if (data.owner.address === userdata.eth_info.account) {
|
||||||
|
currentOwner = true
|
||||||
|
} else {
|
||||||
|
if (data.top_ownerships !== undefined && data.top_ownerships !== null && data.top_ownerships.length === 1) {
|
||||||
|
for (var key in data.top_ownerships) {
|
||||||
|
if (data.top_ownerships[key].owner.address === userdata.eth_info.account) {
|
||||||
|
currentOwner = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentOwner
|
||||||
|
}
|
||||||
|
|
||||||
const onPasswordChange = () => {
|
const onPasswordChange = () => {
|
||||||
const data = {"username": userSettings.username, "currentpassword": currentPassword, "newpassword": newPassword, "newpassword2": newPassword2}
|
const data = {"username": userSettings.username, "currentpassword": currentPassword, "newpassword": newPassword, "newpassword2": newPassword2}
|
||||||
@@ -113,6 +133,50 @@ const Settings = (props) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadWorkflowOwnership = () => {
|
||||||
|
fetch(globalUrl+"/api/v1/workflows/collections/untitled-collection-103712081", {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log("Status not 200 for WORKFLOW EXECUTION :O!")
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
//console.log("Values: ", responseJson)
|
||||||
|
|
||||||
|
// const [selfOwnedWorkflows, setSelfOwnedWorkflows] = React.useState([])
|
||||||
|
if (responseJson !== undefined && responseJson !== null) {
|
||||||
|
const filteredOwnerships = responseJson.filter(data => checkOwner(data, userdata) === true)
|
||||||
|
if (filteredOwnerships !== undefined && filteredOwnerships !== null && filteredOwnerships.length > 0) {
|
||||||
|
setSelfOwnedWorkflows(filteredOwnerships)
|
||||||
|
}
|
||||||
|
|
||||||
|
var collections = []
|
||||||
|
for (var key in responseJson) {
|
||||||
|
var collectionname = responseJson[key].collection.name
|
||||||
|
if (!collections.includes(collectionname)) {
|
||||||
|
collections.push(collectionname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(collections)
|
||||||
|
setLoadedWorkflowCollections(collections)
|
||||||
|
setLoadedValidationWorkflows(responseJson)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const generateApikey = () => {
|
const generateApikey = () => {
|
||||||
fetch(globalUrl+"/api/v1/generateapikey", {
|
fetch(globalUrl+"/api/v1/generateapikey", {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@@ -258,6 +322,37 @@ const Settings = (props) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const ParsedWorkflowView = (props) => {
|
||||||
|
const { data } = props;
|
||||||
|
|
||||||
|
var innerPaperStyle = {
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
padding: "0px 0px 12px 0px",
|
||||||
|
borderRadius: theme.palette.borderRadius,
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentOwner = checkOwner(data, userdata)
|
||||||
|
if (currentOwner === true) {
|
||||||
|
innerPaperStyle.border = "3px solid #f86a3e"
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid item xs={4} style={{borderRadius: theme.palette.borderRadius,}}>
|
||||||
|
<Paper style={innerPaperStyle}>
|
||||||
|
<img src={data.image_thumbnail_url} alt={data.name} style={{width: "100%", marginBottom: 10, }}/>
|
||||||
|
<Typography variant="body2" color="textSecondary">
|
||||||
|
{data.collection.name}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2">
|
||||||
|
{data.name}
|
||||||
|
</Typography>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Random names for type & autoComplete. Didn't research :^)
|
// Random names for type & autoComplete. Didn't research :^)
|
||||||
var imageData = file.length > 0 ? file : fileBase64
|
var imageData = file.length > 0 ? file : fileBase64
|
||||||
imageData = imageData === undefined || imageData.length === 0 ? theme.palette.defaultImage : imageData
|
imageData = imageData === undefined || imageData.length === 0 ? theme.palette.defaultImage : imageData
|
||||||
@@ -529,45 +624,96 @@ const Settings = (props) => {
|
|||||||
</Button>
|
</Button>
|
||||||
<h3>{passwordFormMessage}</h3>
|
<h3>{passwordFormMessage}</h3>
|
||||||
<Divider style={{marginTop: "40px"}}/>
|
<Divider style={{marginTop: "40px"}}/>
|
||||||
{userdata !== undefined && userdata.eth_info !== undefined && userdata.eth_info.account.length > 0 ?
|
<h2>Platform Earnings</h2>
|
||||||
<Button
|
<div style={{display: "flex", width: "100%", }}>
|
||||||
style={{height: 40, marginTop: 10}}
|
<div style={{flex: 1, display: "flex",}}>
|
||||||
variant="contained"
|
<div>
|
||||||
color="primary"
|
{userdata.eth_info !== undefined && userdata.eth_info.account !== undefined && userdata.eth_info.account.length > 0 && userdata.eth_info.parsed_balance !== undefined ?
|
||||||
fullWidth={true}
|
<div style={{marginTop: 10, display: "flex", maxHeight: 163.75, }}>
|
||||||
onClick={() => {
|
<Paper square style={{borderRadius: theme.palette.borderRadius, padding: 50, backgroundColor: theme.palette.inputColor}}>
|
||||||
handleEthereumTokenCreation()
|
<Typography>
|
||||||
}}
|
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/6/6f/Ethereum-icon-purple.svg/480px-Ethereum-icon-purple.svg.png" style={{height: 30 }}/>
|
||||||
>
|
</Typography>
|
||||||
Create token
|
<Typography>
|
||||||
</Button>
|
{/*window.ethereum.fromWei(userdata.eth_info.balance, "ether")*/}
|
||||||
:
|
{userdata.eth_info.parsed_balance.toFixed(4)} ETH
|
||||||
<Button
|
</Typography>
|
||||||
style={{height: 40, marginTop: 10}}
|
</Paper>
|
||||||
variant="outlined"
|
</div>
|
||||||
color="primary"
|
: null}
|
||||||
fullWidth={true}
|
</div>
|
||||||
onClick={() => {
|
<div style={{marginTop: 10, display: "flex", maxHeight: 163.75, marginLeft: 10, }}>
|
||||||
handleEthereumConnection()
|
<Paper square style={{borderRadius: theme.palette.borderRadius, padding: 50, backgroundColor: theme.palette.inputColor}}>
|
||||||
}}
|
<Typography variant="body2">
|
||||||
>
|
Owned Workflows
|
||||||
Authenticate
|
</Typography>
|
||||||
</Button>
|
<Typography variant="h6">
|
||||||
}
|
{selfOwnedWorkflows.length}
|
||||||
|
</Typography>
|
||||||
{userdata.eth_info !== undefined && userdata.eth_info.account !== undefined && userdata.eth_info.account.length > 0 && userdata.eth_info.parsed_balance !== undefined ?
|
</Paper>
|
||||||
<div style={{marginTop: 10, display: "flex",}}>
|
</div>
|
||||||
<Paper square style={{borderRadius: theme.palette.borderRadius, padding: 50, backgroundColor: theme.palette.inputColor}}>
|
|
||||||
<Typography>
|
|
||||||
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/6/6f/Ethereum-icon-purple.svg/480px-Ethereum-icon-purple.svg.png" style={{height: 30 }}/>
|
|
||||||
</Typography>
|
|
||||||
<Typography>
|
|
||||||
{/*window.ethereum.fromWei(userdata.eth_info.balance, "ether")*/}
|
|
||||||
{userdata.eth_info.parsed_balance.toFixed(4)} ETH
|
|
||||||
</Typography>
|
|
||||||
</Paper>
|
|
||||||
</div>
|
</div>
|
||||||
: null}
|
<div style={{flex: 1, marginTop: 20, }}>
|
||||||
|
{userdata !== undefined && userdata.eth_info !== undefined && userdata.eth_info.account.length > 0 ?
|
||||||
|
<div style={{width: "100%", textAlign: "left",}}>
|
||||||
|
<Typography variant="body2">
|
||||||
|
Address: {userdata.eth_info.account}
|
||||||
|
</Typography>
|
||||||
|
{loadedWorkflowCollections.length > 0 ?
|
||||||
|
<Typography variant="body2">
|
||||||
|
Collections:
|
||||||
|
{loadedWorkflowCollections.map((data, index) => {
|
||||||
|
var collectionname = data.toLowerCase()
|
||||||
|
collectionname = collectionname.replaceAll("#", "")
|
||||||
|
collectionname = collectionname.replaceAll(" ", "-")
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span key={index}>
|
||||||
|
<a rel="noopener noreferrer" target="_blank" href={`https://opensea.io/collection/${collectionname}`} style={{textDecoration: "none", color: "#f85a3e"}}>{data}</a>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Typography>
|
||||||
|
: null}
|
||||||
|
<Button
|
||||||
|
style={{height: 40, marginTop: 10}}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
fullWidth={true}
|
||||||
|
onClick={() => {
|
||||||
|
//handleEthereumTokenCreation()
|
||||||
|
loadWorkflowOwnership()
|
||||||
|
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Validate ownership
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<Button
|
||||||
|
style={{height: 40, marginTop: 10}}
|
||||||
|
variant="outlined"
|
||||||
|
color="primary"
|
||||||
|
fullWidth={true}
|
||||||
|
onClick={() => {
|
||||||
|
handleEthereumConnection()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Authenticate
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{loadedValidationWorkflows !== undefined && loadedValidationWorkflows !== null ?
|
||||||
|
loadedValidationWorkflows.map((data, index) => {
|
||||||
|
return (
|
||||||
|
<Grid container spacing={3} style={{marginTop: 15}}>
|
||||||
|
<ParsedWorkflowView key={index} data={data} />
|
||||||
|
</Grid>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
: null}
|
||||||
</Paper>
|
</Paper>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user