Merge pull request #538 from frikky/launch

0.9.25 :D
This commit is contained in:
Frikky
2021-10-15 01:23:57 +02:00
committed by GitHub
85 changed files with 9387 additions and 3795 deletions
+335 -115
View File
@@ -4,7 +4,7 @@ import { makeStyles } from '@material-ui/styles';
import { useTheme } from '@material-ui/core/styles';
import {Link} from 'react-router-dom';
import {Paper, Card, Tooltip, FormControlLabel, Typography, Switch, Select, MenuItem, Divider, TextField, Button, Tabs, Tab, Grid, List, ListItem, ListItemText, ListItemAvatar, ListItemSecondaryAction, IconButton, Avatar, Zoom, Dialog, DialogTitle, DialogActions, DialogContent, CircularProgress } from '@material-ui/core';
import {FormControl, InputLabel, Paper, Card, Tooltip, FormControlLabel, Typography, Switch, Select, MenuItem, Divider, TextField, Button, Tabs, Tab, Grid, List, ListItem, ListItemText, ListItemAvatar, ListItemSecondaryAction, IconButton, Avatar, Zoom, Dialog, DialogTitle, DialogActions, DialogContent, CircularProgress } from '@material-ui/core';
import {Edit as EditIcon, FileCopy as FileCopyIcon, Publish as PublishIcon, SelectAll as SelectAllIcon, OpenInNew as OpenInNewIcon, CloudDownload as CloudDownloadIcon, Description as DescriptionIcon, Polymer as PolymerIcon, CheckCircle as CheckCircleIcon, Close as CloseIcon, Apps as AppsIcon, Image as ImageIcon, Delete as DeleteIcon, Cached as CachedIcon, AccessibilityNew as AccessibilityNewIcon, Lock as LockIcon, Eco as EcoIcon, Schedule as ScheduleIcon, Cloud as CloudIcon, Business as BusinessIcon} from '@material-ui/icons';
@@ -29,6 +29,7 @@ const Admin = (props) => {
const [firstRequest, setFirstRequest] = React.useState(true);
const [orgRequest, setOrgRequest] = React.useState(true);
const [modalUser, setModalUser] = React.useState({});
const [orgName, setOrgName] = React.useState("")
const [modalOpen, setModalOpen] = React.useState(false);
const [cloudSyncModalOpen, setCloudSyncModalOpen] = React.useState(false);
@@ -48,7 +49,10 @@ const Admin = (props) => {
const [authentication, setAuthentication] = React.useState([]);
const [schedules, setSchedules] = React.useState([])
const [files, setFiles] = React.useState([])
const [selectedNamespace, setSelectedNamespace] = React.useState("default")
const [fileNamespaces, setFileNamespaces] = React.useState([]);
const [selectedUser, setSelectedUser] = React.useState({})
const [newUsername, setNewUsername] = React.useState("");
const [newPassword, setNewPassword] = React.useState("");
const [selectedUserModalOpen, setSelectedUserModalOpen] = React.useState(false)
const [selectedAuthentication, setSelectedAuthentication] = React.useState({})
@@ -155,7 +159,7 @@ const Admin = (props) => {
setTimeout(() => {
getAppAuthentication()
}, 1000)
alert.success("Successfully deleted authentication!")
//alert.success("Successfully deleted authentication!")
}
}),
)
@@ -184,8 +188,10 @@ const Admin = (props) => {
if (responseJson["success"] === false) {
alert.error("Failed stopping schedule")
} else {
getSchedules()
alert.success("Successfully stopped schedule!")
setTimeout(() => {
getSchedules()
}, 1500)
//alert.success("Successfully stopped schedule!")
}
}),
)
@@ -365,6 +371,45 @@ const Admin = (props) => {
});
}
const createSubOrg = (currentOrgId, name) => {
const data = { "name": name, "org_id": currentOrgId}
console.log(data)
const url = globalUrl + `/api/v1/orgs/${currentOrgId}/create_sub_org`
fetch(url, {
mode: 'cors',
method: 'POST',
body: JSON.stringify(data),
credentials: 'include',
crossDomain: true,
withCredentials: true,
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
})
.then(response =>
response.json().then(responseJson => {
if (responseJson["success"] === false) {
if (responseJson.reason !== undefined) {
alert.error(responseJson.reason)
} else {
alert.error("Failed creating suborg")
}
} else {
alert.success("Successfully created suborg!")
setSelectedUserModalOpen(false)
}
setOrgName("")
setModalOpen(false)
}),
)
.catch(error => {
alert.error("Err: " + error.toString())
});
}
const onPasswordChange = () => {
const data = { "username": selectedUser.username, "newpassword": newPassword }
const url = globalUrl + '/api/v1/users/passwordchange';
@@ -457,6 +502,10 @@ const Admin = (props) => {
if (responseJson["success"] === false) {
alert.error("Failed getting org: ", responseJson.readon)
} else {
if (responseJson.sync_features === undefined || responseJson.sync_features === null) {
responseJson.sync_features = {}
}
setSelectedOrganization(responseJson)
var lists = {
"active": {
@@ -559,19 +608,19 @@ const Admin = (props) => {
}
// Horrible frontend fix for environments
const setDefaultEnvironment = (name) => {
// FIXME - add some check here ROFL
alert.info("Setting default env to " + name)
const setDefaultEnvironment = (environment) => {
// FIXME - add more checks to this
alert.info("Setting default env to " + environment.name)
var newEnv = []
for (var key in environments) {
if (environments[key].Name == name) {
if (environments[key].id == environment.id) {
if (environments[key].archived) {
alert.error("Can't set archived to default")
return
}
environments[key].default = true
} else if (environments[key].default == true && environments[key].name !== name) {
} else if (environments[key].default == true && environments[key].id !== environment.id) {
environments[key].default = false
}
@@ -592,11 +641,15 @@ const Admin = (props) => {
response.json().then(responseJson => {
if (responseJson["success"] === false) {
alert.error(responseJson.reason)
getEnvironments()
setTimeout(() => {
getEnvironments()
}, 1500)
} else {
setLoginInfo("")
setModalOpen(false)
getEnvironments()
setTimeout(() => {
getEnvironments()
}, 1500)
}
}),
)
@@ -632,23 +685,46 @@ const Admin = (props) => {
})
}
const deleteEnvironment = (name) => {
const deleteEnvironment = (environment) => {
// FIXME - add some check here ROFL
alert.info("Deleting environment " + name)
//const name = environment.name
//alert.info("Modifying environment " + name)
//var newEnv = []
//for (var key in environments) {
// if (environments[key].Name == name) {
// if (environments[key].default) {
// alert.error("Can't modify the default environment")
// return
// }
// if (environments[key].type === "cloud" && !environments[key].archived) {
// alert.error("Can't modify cloud environments")
// return
// }
// environments[key].archived = !environments[key].archived
// }
// newEnv.push(environments[key])
//}
const id = environment.id
//alert.info("Modifying environment " + environment.Name)
var newEnv = []
for (var key in environments) {
if (environments[key].Name == name) {
if (environments[key].id == id) {
if (environments[key].default) {
alert.error("Can't delete the default environment")
alert.error("Can't modify the default environment")
return
}
if (environments[key].type === "cloud") {
alert.error("Can't delete the cloud environments")
if (environments[key].type === "cloud" && !environments[key].archived) {
alert.error("Can't modify cloud environments")
return
}
environments[key].archived = true
environments[key].archived = !environments[key].archived
}
newEnv.push(environments[key])
@@ -795,8 +871,16 @@ const Admin = (props) => {
return response.json()
})
.then((responseJson) => {
//console.log(responseJson)
setFiles(responseJson)
if (responseJson.files !== undefined && responseJson.files !== null) {
setFiles(responseJson.files)
} else {
setFiles([])
}
console.log("NAMESPACES: ", responseJson.namespaces)
if (responseJson.namespaces !== undefined && responseJson.namespaces !== null) {
setFileNamespaces(responseJson.namespaces)
}
})
.catch(error => {
alert.error(error.toString())
@@ -931,6 +1015,9 @@ const Admin = (props) => {
}
const getOrgs = () => {
// API no longer in use, as it's in handleInfo request
return
fetch(globalUrl + "/api/v1/orgs", {
method: 'GET',
headers: {
@@ -1118,6 +1205,7 @@ const Admin = (props) => {
alert.error("Failed setting user: " + responseJson.reason)
} else {
alert.success("Set the user field " + field + " to " + value)
setSelectedUserModalOpen(false)
}
})
.catch(error => {
@@ -1257,6 +1345,44 @@ const Admin = (props) => {
>
<DialogTitle><span style={{ color: "white" }}><EditIcon style={{marginTop: 5}}/> Editing {selectedUser.username}</span></DialogTitle>
<DialogContent>
{isCloud ?
null
:
<div style={{ display: "flex" }}>
<TextField
style={{ marginTop: 0, backgroundColor: theme.palette.inputColor, flex: 3 , marginRight: 10,}}
InputProps={{
style: {
height: 50,
color: "white",
},
}}
color="primary"
required
fullWidth={true}
placeholder="New username"
type="text"
id="standard-required"
autoComplete="username"
margin="normal"
variant="outlined"
defaultValue={selectedUser.username}
onChange={e => {
setNewUsername(e.target.value)
}}
/>
<Button
style={{ maxHeight: 50, flex: 1 }}
variant="outlined"
color="primary"
onClick={() => {
setUser(selectedUser.id, "username", newUsername)
}}
>
Submit
</Button>
</div>
}
{isCloud ?
null
:
@@ -1543,7 +1669,7 @@ const Admin = (props) => {
</IconButton>
</Tooltip>
{selectedOrganization.name.length > 0 ?
<OrgHeader setSelectedOrganization={setSelectedOrganization} globalUrl={globalUrl} selectedOrganization={selectedOrganization}/>
<OrgHeader userdata={userdata} setSelectedOrganization={setSelectedOrganization} globalUrl={globalUrl} selectedOrganization={selectedOrganization}/>
:
<div style={{paddingTop: 250, width: 250, margin: "auto", textAlign: "center"}}>
<CircularProgress />
@@ -1657,65 +1783,65 @@ const Admin = (props) => {
</div>
}
<Typography style={{marginTop: 40, marginLeft: 10, marginBottom: 5,}}>Cloud sync features</Typography>
<Grid container style={{width: "100%", marginBottom: 15, }}>
{Object.keys(selectedOrganization.sync_features).map(function(key, index) {
if (key === "schedule") {
return null
}
<Grid container style={{width: "100%", marginBottom: 15, }}>
{selectedOrganization.sync_features === undefined || selectedOrganization.sync_features === null ? null : Object.keys(selectedOrganization.sync_features).map(function(key, index) {
if (key === "schedule") {
return null
}
const item = selectedOrganization.sync_features[key]
const newkey = key.replaceAll("_", " ")
const griditem = {
"primary": newkey,
"secondary": item.description === undefined || item.description === null || item.description.length === 0 ? "Not defined yet" : item.description,
"limit": item.limit,
"usage": 0,
"data_collection": "None",
"active": item.active,
"icon": <PolymerIcon style={{color: itemColor}}/>,
}
const item = selectedOrganization.sync_features[key]
const newkey = key.replaceAll("_", " ")
const griditem = {
"primary": newkey,
"secondary": item.description === undefined || item.description === null || item.description.length === 0 ? "Not defined yet" : item.description,
"limit": item.limit,
"usage": 0,
"data_collection": "None",
"active": item.active,
"icon": <PolymerIcon style={{color: itemColor}}/>,
}
return (
<Zoom key={index} >
<GridItem data={griditem} />
</Zoom>
)
})}
</Grid>
<Divider style={{ marginTop: 20, marginBottom: 20, backgroundColor: theme.palette.inputColor }} />
{isCloud && selectedOrganization.subscriptions !== undefined && selectedOrganization.subscriptions !== null && selectedOrganization.subscriptions.length > 0 ?
<div style={{marginTop: 30, marginBottom: 20}}>
<Typography style={{marginTop: 40, marginLeft: 10, marginBottom: 5,}}>
Your subscription{selectedOrganization.subscriptions.length > 1 ? "s" : ""}
</Typography>
<Grid container spacing={3} style={{marginTop: 15}}>
{selectedOrganization.subscriptions.reverse().map((sub, index) => {
return (
<Grid item key={index} xs={4}>
<Card elevation={6} style={{backgroundColor: theme.palette.inputColor, color: "white", padding: 25, textAlign: "left",}}>
<b>Type</b>: {sub.level}<div/>
<b>Recurrence</b>: {sub.recurrence}<div/>
{sub.active ?
<div>
<b>Started</b>: {new Date(sub.startdate*1000).toISOString()}<div/>
<Button variant="outlined" color="primary" style={{marginTop: 15}} onClick={() => {
cancelSubscriptions(sub.reference)
}}>
Cancel subscription
</Button>
</div>
:
<div>
<b>Cancelled</b>: {new Date(sub.cancellationdate*1000).toISOString()}<div/>
<Typography color="textSecondary">
<b>Status</b>: Deactivated
</Typography>
</div>
}
</Card>
</Grid>
)
return (
<Zoom key={index} >
<GridItem data={griditem} />
</Zoom>
)
})}
</Grid>
<Divider style={{ marginTop: 20, marginBottom: 20, backgroundColor: theme.palette.inputColor }} />
{isCloud && selectedOrganization.subscriptions !== undefined && selectedOrganization.subscriptions !== null && selectedOrganization.subscriptions.length > 0 ?
<div style={{marginTop: 30, marginBottom: 20}}>
<Typography style={{marginTop: 40, marginLeft: 10, marginBottom: 5,}}>
Your subscription{selectedOrganization.subscriptions.length > 1 ? "s" : ""}
</Typography>
<Grid container spacing={3} style={{marginTop: 15}}>
{selectedOrganization.subscriptions.reverse().map((sub, index) => {
return (
<Grid item key={index} xs={4}>
<Card elevation={6} style={{backgroundColor: theme.palette.inputColor, color: "white", padding: 25, textAlign: "left",}}>
<b>Type</b>: {sub.level}<div/>
<b>Recurrence</b>: {sub.recurrence}<div/>
{sub.active ?
<div>
<b>Started</b>: {new Date(sub.startdate*1000).toISOString()}<div/>
<Button variant="outlined" color="primary" style={{marginTop: 15}} onClick={() => {
cancelSubscriptions(sub.reference)
}}>
Cancel subscription
</Button>
</div>
:
<div>
<b>Cancelled</b>: {new Date(sub.cancellationdate*1000).toISOString()}<div/>
<Typography color="textSecondary">
<b>Status</b>: Deactivated
</Typography>
</div>
}
</Card>
</Grid>
)
})}
</Grid>
<Divider style={{ marginTop: 20, backgroundColor: theme.palette.inputColor }} />
</div>
@@ -1744,14 +1870,20 @@ const Admin = (props) => {
}}
>
<DialogTitle><span style={{ color: "white" }}>
{curTab === 1 ? "Add user" : "Add environment"}
{curTab === 1 ? "Add user" : curTab === 6 ? "Add Sub-Organization" : "Add environment"}
</span></DialogTitle>
<DialogContent>
{curTab === 1 && isCloud ?
<Typography variant="body1" style={{marginBottom: 10}}>
We'll send an email to invite them to your organization.
</Typography>
: null}
:
curTab === 6 ?
<Typography variant="body1" style={{marginBottom: 10}}>
The organization created will become a child of your current organization, and be available to you.
</Typography>
:
null }
{curTab === 1 ?
<div>
Username
@@ -1801,6 +1933,31 @@ const Admin = (props) => {
</span>
}
</div>
: curTab === 6 ?
<div>
Name
<TextField
color="primary"
style={{ backgroundColor: theme.palette.inputColor }}
autoFocus
InputProps={{
style: {
height: "50px",
color: "white",
fontSize: "1em",
},
}}
required
fullWidth={true}
placeholder={`${selectedOrganization.name} Copycat Inc.`}
id="orgname"
margin="normal"
variant="outlined"
onChange={(event) => {
setOrgName(event.target.value)
}}
/>
</div>
: curTab === 5 ?
<div>
Environment Name
@@ -1838,6 +1995,8 @@ const Admin = (props) => {
} else {
submitUser(modalUser)
}
} else if (curTab === 6) {
createSubOrg(selectedOrganization.id, orgName)
} else if (curTab === 5) {
submitEnvironment(modalUser)
}
@@ -1889,7 +2048,11 @@ const Admin = (props) => {
/>
<ListItemText
primary="Active"
style={{ minWidth: 180, maxWidth: 180 }}
style={{ minWidth: 150, maxWidth: 150 }}
/>
<ListItemText
primary="Type"
style={{ minWidth: 150 , maxWidth: 150 }}
/>
<ListItemText
primary="Actions"
@@ -1949,10 +2112,9 @@ const Admin = (props) => {
value={data.role}
fullWidth
onChange={(e) => {
console.log("VALUE: ", e.target.value)
setUser(data.id, "role", e.target.value)
}}
console.log("VALUE: ", e.target.value)
setUser(data.id, "role", e.target.value)
}}
style={{ backgroundColor: theme.palette.surfaceColor, color: "white", height: "50px" }}
>
<MenuItem style={{ backgroundColor: theme.palette.inputColor, color: "white" }} value={"admin"}>
@@ -1965,10 +2127,14 @@ const Admin = (props) => {
}
style ={{ minWidth: 135, maxWidth: 135, marginRight: 15,}}
/>
<ListItemText
primary={data.active ? "True" : "False"}
style={{ minWidth: 180, maxWidth: 180 }}
/>
<ListItemText
primary={data.active ? "True" : "False"}
style={{ minWidth: 150, maxWidth: 150}}
/>
<ListItemText
primary={data.login_type === undefined || data.login_type === null || data.login_type.length === 0 ? "Normal" : data.login_type}
style={{ minWidth: 150, maxWidth: 150}}
/>
<ListItemText style={{ display: "flex" }}>
<IconButton
onClick={() => {
@@ -2021,7 +2187,9 @@ const Admin = (props) => {
}
}
getFiles()
setTimeout(() => {
getFiles()
}, 2500)
}
const uploadFile = (e) => {
@@ -2061,6 +2229,30 @@ const Admin = (props) => {
>
<CachedIcon />
</Button>
{fileNamespaces !== undefined && fileNamespaces !== null && fileNamespaces.length > 1 ?
<FormControl>
<InputLabel id="input-namespace-label">Namespace</InputLabel>
<Select
labelId="input-namespace-select-label"
id="input-namespace-select-id"
style={{color: "white", minWidth: 100, float: "right",}}
value={selectedNamespace}
onChange={(event) => {
console.log("CHANGE NAMESPACE: ", event.target)
setSelectedNamespace(event.target.value)
}}
>
{fileNamespaces.map((data, index) => {
return (
<MenuItem key={index} value={data} style={{color: "white"}}>{data}</MenuItem>
)
})}
</Select>
</FormControl>
: null}
<Divider style={{marginTop: 20, marginBottom: 20, backgroundColor: theme.palette.inputColor}}/>
<List>
<ListItem>
@@ -2095,7 +2287,15 @@ const Admin = (props) => {
primary="File ID"
/>
</ListItem>
{files === undefined || files === null ? null : files.map((file, index) => {
{files === undefined || files === null || files.length === 0 ? null : files.map((file, index) => {
if (file.namespace === "") {
file.namespace = "default"
}
if (file.namespace !== selectedNamespace) {
return null
}
var bgColor = "#27292d"
if (index % 2 === 0) {
bgColor = "#1f2023"
@@ -2303,13 +2503,13 @@ const Admin = (props) => {
style={{minWidth: 150, maxWidth: 150, overflow: "hidden"}}
/>
</ListItem>
{categories.map(data => {
{categories.map((data, index) => {
if (data.apps.length === 0) {
return null
}
return (
<ListItem>
<ListItem key={index}>
<ListItemText
primary={data.name}
style={{minWidth: 150, maxWidth: 150}}
@@ -2409,6 +2609,7 @@ const Admin = (props) => {
bgColor = "#1f2023"
}
return (
<ListItem key={index} style={{backgroundColor: bgColor}}>
<ListItemText
@@ -2489,7 +2690,7 @@ const Admin = (props) => {
<div>
<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.<a target="_blank" href="https://shuffler.io/docs/organizations#environments" style={{textDecoration: "none", color: "#f85a3e"}}>Learn more</a></span>
<span style={{marginLeft: 25}}>Decides what Orborus environment to execute an action in a workflow in. <a target="_blank" href="https://shuffler.io/docs/organizations#environments" style={{textDecoration: "none", color: "#f85a3e"}}>Learn more</a></span>
</div>
<Button
style={{}}
@@ -2572,13 +2773,13 @@ const Admin = (props) => {
{environment.default ?
null
:
<Button variant="outlined" style={{borderRadius: "0px"}} onClick={() => setDefaultEnvironment(environment.Name)} color="primary">Set default</Button>
<Button variant="outlined" style={{borderRadius: "0px"}} onClick={() => setDefaultEnvironment(environment)} color="primary">Set default</Button>
}
</ListItemText>
<ListItemText
style={{minWidth: 150, maxWidth: 150, overflow: "hidden"}}
>
<Button disabled={environment.archived} variant="outlined" style={{borderRadius: "0px"}} onClick={() => deleteEnvironment(environment.Name)} color="primary">Archive</Button>
<Button variant={environment.archived ? "contained" : "outlined"} style={{borderRadius: "0px"}} onClick={() => deleteEnvironment(environment)} color="primary">{environment.archived ? "Activate" : "Disable"}</Button>
{/*<Button disabled={environment.archived} variant="outlined" style={{borderRadius: "0px"}} onClick={() => flushQueue(environment.Name)} color="primary">Flush Queue</Button>*/}
</ListItemText>
<ListItemText
@@ -2592,7 +2793,7 @@ const Admin = (props) => {
</div>
: null
const organizationsTab = curTab === 7 ?
const organizationsTab = curTab === 6 ?
<div>
<div style={{marginTop: 20, marginBottom: 20,}}>
<h2 style={{display: "inline",}}>Organizations</h2>
@@ -2602,28 +2803,31 @@ const Admin = (props) => {
style={{}}
variant="contained"
color="primary"
disabled
onClick={() => {
setModalOpen(true)
}}
>
Add organization
Add suborganization
</Button>
<Divider style={{marginTop: 20, marginBottom: 20, backgroundColor: theme.palette.inputColor}}/>
<List>
<ListItem>
<ListItemText
primary="Name"
style={{minWidth: 150, maxWidth: 150}}
primary="Logo"
style={{minWidth: 100, maxWidth: 100}}
/>
<ListItemText
primary="id"
style={{minWidth: 200, maxWidth: 200}}
primary="Name"
style={{minWidth: 250, maxWidth: 250}}
/>
<ListItemText
primary="Your role"
style={{minWidth: 150, maxWidth: 150}}
/>
<ListItemText
primary="id"
style={{minWidth: 400, maxWidth: 400}}
/>
<ListItemText
primary="Selected"
style={{minWidth: 150, maxWidth: 150}}
@@ -2633,25 +2837,41 @@ const Admin = (props) => {
style={{minWidth: 150, maxWidth: 150}}
/>
</ListItem>
{organizations !== undefined && organizations !== null && organizations.length > 0 ?
{userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 0 ?
<span>
{organizations.map((data, index) => {
{userdata.orgs.map((data, index) => {
const isSelected = props.userdata.active_org.id === undefined ? "False" : props.userdata.active_org.id === data.id ? "True" : "False"
const imagesize = 40
const imageStyle = {width: imagesize, height: imagesize, pointerEvents: "none", }
const image = data.image === "" ?
<img alt={data.name} src={theme.palette.defaultImage} style={imageStyle} />
:
<img alt={data.name} src={data.image} style={imageStyle} />
var bgColor = "#27292d"
if (index % 2 === 0) {
bgColor = "#1f2023"
}
return (
<ListItem key={index}>
<ListItem key={index} style={{backgroundColor: bgColor,}}>
<ListItemText
primary={data.name}
style={{minWidth: 150, maxWidth: 150}}
primary={image}
style={{minWidth: 100, maxWidth: 100}}
/>
<ListItemText
primary={data.id}
style={{minWidth: 200, maxWidth: 200}}
primary={data.name}
style={{minWidth: 250, maxWidth: 250}}
/>
<ListItemText
primary={data.role}
style={{minWidth: 150, maxWidth: 150}}
/>
<ListItemText
primary={data.id}
style={{minWidth: 400, maxWidth: 400}}
/>
<ListItemText
primary={isSelected}
style={{minWidth: 150, maxWidth: 150}}
@@ -2673,7 +2893,7 @@ const Admin = (props) => {
</div>
: null
const hybridTab = curTab === 6 ?
const hybridTab = curTab === 7 ?
<div>
<div style={{marginTop: 20, marginBottom: 20,}}>
<h2 style={{display: "inline",}}>Hybrid</h2>
@@ -2719,7 +2939,7 @@ const Admin = (props) => {
const iconStyle = {marginRight: 10}
const data =
<div style={{width: 1366, margin: "auto", overflowX: "hidden", marginTop: 25,}}>
<div style={{width: 1300, margin: "auto", overflowX: "hidden", marginTop: 25,}}>
<Paper style={paperStyle}>
<Tabs
value={curTab}
@@ -2733,21 +2953,21 @@ const Admin = (props) => {
<Tab label=<span><DescriptionIcon style={iconStyle} />Files</span> />
<Tab label=<span><ScheduleIcon style={iconStyle} />Schedules</span> />
{isCloud ? null : <Tab label=<span><EcoIcon style={iconStyle} />Environments</span>/>}
{window.location.protocol == "http:" && window.location.port === "3000" ? <Tab label=<span><CloudIcon style={iconStyle} /> Hybrid</span>/> : null}
{window.location.protocol == "http:" && window.location.port === "3000" ? <Tab label=<span><BusinessIcon style={iconStyle} /> Organizations</span>/> : null}
{window.location.protocol === "http:" && window.location.port === "3000" ? <Tab label=<span><LockIcon style={iconStyle} />Categories</span>/> : null}
{isCloud ? null : <Tab label=<span><BusinessIcon style={iconStyle} /> Organizations</span>/>}
{/*window.location.protocol == "http:" && window.location.port === "3000" ? <Tab label=<span><CloudIcon style={iconStyle} /> Hybrid</span>/> : null*/}
{/*window.location.protocol === "http:" && window.location.port === "3000" ? <Tab label=<span><LockIcon style={iconStyle} />Categories</span>/> : null*/}
</Tabs>
<Divider style={{marginTop: 0, marginBottom: 10, backgroundColor: "rgb(91, 96, 100)"}} />
<div style={{padding: 15}}>
{organizationView}
{authenticationView}
{appCategoryView}
{usersView}
{environmentView}
{schedulesView}
{filesView}
{hybridTab}
{organizationsTab}
{appCategoryView}
</div>
</Paper>
</div>
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+50 -44
View File
@@ -9,7 +9,6 @@ import { useTheme } from '@material-ui/core/styles';
import YAML from 'yaml'
import {Link} from 'react-router-dom';
import ReactJson from 'react-json-view'
import { useAlert } from "react-alert";
import Dropzone from '../components/Dropzone';
@@ -130,6 +129,7 @@ const Apps = (props) => {
const upload = React.useRef(null);
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" ? true : false
const borderRadius = 3
const viewWidth = 590
const { start, stop } = useInterval({
duration: 5000,
@@ -178,6 +178,7 @@ const Apps = (props) => {
color: "#ffffff",
width: "100%",
display: "flex",
margin: "auto",
}
const paperAppStyle = {
@@ -474,13 +475,14 @@ const Apps = (props) => {
const dividerColor = "rgb(225, 228, 232)"
const uploadViewPaperStyle = {
minWidth: 662.5,
maxWidth: 662.5,
minWidth: viewWidth,
maxWidth: viewWidth,
color: "white",
borderRadius: 5,
backgroundColor: surfaceColor,
display: "flex",
//display: "flex",
marginBottom: 10,
overflow: "hidden",
}
const UploadView = () => {
@@ -520,7 +522,7 @@ const Apps = (props) => {
<Link to={editUrl} style={{textDecoration: "none"}}>
<Tooltip title={"Edit OpenAPI app"}>
<Button
variant="outlined"
variant="contained"
component="label"
color="primary"
style={{marginTop: 10, marginRight: 10,}}
@@ -620,15 +622,6 @@ const Apps = (props) => {
</MenuItem>
)
})}
{/*
<ReactJson
src={JSON.parse(showResult)}
theme="solarized"
collapsed={false}
displayDataTypes={true}
name={"Example return value"}
/>
*/}
</div>
)
}
@@ -707,8 +700,8 @@ const Apps = (props) => {
{activateButton}
{(props.userdata !== undefined && (props.userdata.role === "admin" || props.userdata.id === selectedApp.owner) || !selectedApp.generated) ?
<div>
{downloadButton}
{editButton}
{downloadButton}
{deleteButton}
</div>
: null}
@@ -771,7 +764,7 @@ const Apps = (props) => {
{/*<p><b>Owner:</b> {selectedApp.owner}</p>*/}
{selectedApp.privateId !== undefined && selectedApp.privateId.length > 0 ? <p><b>PrivateID:</b> {selectedApp.privateId}</p> : null}
<Divider style={{marginBottom: 10, marginTop: 10, backgroundColor: dividerColor}}/>
<div style={{padding: 20}}>
<div style={{paddingTop: 20, paddingBottom: 20, }}>
{selectedApp.link.length > 0 ? <p><b>URL:</b> {selectedApp.link}</p> : null}
<div style={{marginTop: 15, marginBottom: 15}}>
<b>Actions</b>
@@ -852,7 +845,7 @@ const Apps = (props) => {
<h2>App Creator</h2>
<a rel="norefferer" href="https://shuffler.io/docs/apps" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">How it works</a>
&nbsp;- <a href="https://github.com/frikky/security-openapis" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">Security API's</a>
&nbsp;- <a href="https://apis.guru/browse-apis/" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">OpenAPI directory</a>
&nbsp;- <a href="https://github.com/APIs-guru/openapi-directory/tree/main/APIs" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">OpenAPI directory</a>
&nbsp;- <a href="https://editor.swagger.io/" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">OpenAPI Validator</a>
<div/>
<Typography variant="body2" color="textSecondary">
@@ -932,7 +925,11 @@ const Apps = (props) => {
console.log("Error in dropzone: ", e)
}
reader.readAsText(files[0]);
try {
reader.readAsText(files[0]);
} catch(error) {
alert.error("Failed to read file")
}
};
useEffect(() => {
@@ -950,9 +947,9 @@ const Apps = (props) => {
}, [appValidation, isDropzone]);
const appView = isLoggedIn ?
<Dropzone style={{maxWidth: window.innerWidth > 1366 ? 1366 : 1200, margin: "auto", padding: 20 }} onDrop={uploadFile}>
<Dropzone style={{width: viewWidth*2+20, margin: "auto", padding: 20 }} onDrop={uploadFile}>
<div style={appViewStyle}>
<div style={{flex: 1, }}>
<div style={{flex: 1, maxWidth: viewWidth, marginRight: 10,}}>
<Breadcrumbs aria-label="breadcrumb" separator="" style={{color: "white",}}>
<Link to="/apps" style={{textDecoration: "none", color: "inherit",}}>
<Typography variant="h6" style={{color: "rgba(255,255,255,0.5)"}}>
@@ -969,9 +966,9 @@ const Apps = (props) => {
: null}
</Breadcrumbs>
<div style={{marginTop: 15}} />
<UploadView/>
<UploadView />
</div>
<div style={{flex: 1, marginLeft: 10, marginRight: 10, }}>
<div style={{flex: 1, marginLeft: 10, maxWidth: viewWidth, }}>
<div style={{display: "flex",}}>
<div style={{flex: 1, marginBottom: 15, }}>
<Typography variant="h6">
@@ -980,35 +977,39 @@ const Apps = (props) => {
</div>
{isCloud ? null :
<span>
<Tooltip title={"Reload apps locally"} style={{marginTop: "28px", width: "100%"}} aria-label={"Upload"}>
<Button
variant="outlined"
component="label"
color="primary"
style={{margin: 5, maxHeight: 50, marginTop: 10}}
onClick={() => {
hotloadApps()
}}
>
<CachedIcon />
</Button>
</Tooltip>
{isLoading ? null :
<Tooltip title={"Reload apps locally"} style={{marginTop: "28px", width: "100%"}} aria-label={"Upload"}>
<Button
variant="outlined"
component="label"
color="primary"
style={{margin: 5, maxHeight: 50, marginTop: 10}}
disabled={isLoading}
onClick={() => {
hotloadApps()
}}
>
{isLoading ? <CircularProgress size={25} /> : <CachedIcon />}
</Button>
</Tooltip>
}
<Tooltip title={"Download from Github"} style={{marginTop: "28px", width: "100%"}} aria-label={"Upload"}>
<Button
variant="outlined"
component="label"
color="primary"
style={{margin: 5, maxHeight: 50, marginTop: 10}}
disabled={isLoading}
onClick={() => {
setOpenApi(baseRepository)
setLoadAppsModalOpen(true)
}}
>
<CloudDownloadIcon />
{isLoading ? <CircularProgress size={25} /> : <CloudDownloadIcon />}
</Button>
</Tooltip>
</span>
}
}
</div>
<div style={{height: 50}}>
<TextField
@@ -1072,9 +1073,12 @@ const Apps = (props) => {
<CircularProgress style={{width: 40, height: 40, margin: "auto"}}/>
:
<Paper square style={uploadViewPaperStyle}>
<h4 style={{margin: 10}}>
<Typography variant="body1" style={{margin: 10}}>
No apps have been created, uploaded or downloaded yet. Click "Load existing apps" above to get the baseline. This may take a while as its building docker images.
</h4>
</Typography>
<Typography variant="body1" style={{margin: 10}}>
If you're still not able to see any apps, please follow our <a href={"https://shuffler.io/docs/troubleshooting#load_all_apps_locally"} style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">troubleshooting guide for loading apps!</a>
</Typography>
</Paper>
}
</div>
@@ -1571,11 +1575,13 @@ const Apps = (props) => {
<Button style={{borderRadius: "0px"}} onClick={() => setLoadAppsModalOpen(false)} color="primary">
Cancel
</Button>
<Button style={{borderRadius: "0px"}} disabled={openApi.length === 0 || !openApi.includes("http")} onClick={() => {
handleGithubValidation(true)
}} color="primary">
Force update
</Button>
{isCloud ? null :
<Button style={{borderRadius: "0px"}} disabled={openApi.length === 0 || !openApi.includes("http")} onClick={() => {
handleGithubValidation(true)
}} color="primary">
Force update
</Button>
}
<Button variant="outlined" style={{float: "left", borderRadius: "0px"}} disabled={openApi.length === 0 || !openApi.includes("http")} onClick={() => {
handleGithubValidation(false)
}} color="primary">
+183 -21
View File
@@ -5,14 +5,15 @@ import ReactMarkdown from 'react-markdown';
import {BrowserView, MobileView} from "react-device-detect";
import {Link} from 'react-router-dom';
import {Divider, Button, Menu, MenuItem, Typography, Paper, List} from '@material-ui/core';
import {Tooltip, Divider, Button, Menu, MenuItem, Typography, Paper, List} from '@material-ui/core';
import {Link as LinkIcon, Edit as EditIcon} from '@material-ui/icons';
const Body = {
maxWidth: '1000px',
minWidth: '768px',
margin: 'auto',
display: "flex",
heigth: "100%",
height: "100%",
color: "white",
//textAlign: "center",
};
@@ -23,15 +24,24 @@ const hrefStyle = {
textDecoration: "none"
}
const innerHrefStyle = {
color: "rgba(255, 255, 255, 0.75)",
textDecoration: "none"
}
const Docs = (props) => {
const { globalUrl, selectedDoc, serverside, isMobile, } = props;
const theme = useTheme();
const [mobile, setMobile] = useState(isMobile === true ? true : false);
const [data, setData] = useState("");
const [firstrequest, setFirstrequest] = useState(true);
const [list, setList] = useState([]);
const [, setListLoaded] = useState(false);
const [anchorEl, setAnchorEl] = React.useState(null);
const [headingSet, setHeadingSet] = React.useState(false);
const [selectedMeta, setSelectedMeta] = React.useState({link: "hello", read_time: 2, });
const [tocLines, setTocLines] = React.useState([]);
const [baseUrl, setBaseUrl] = React.useState(serverside === true ? "" : window.location.href)
function handleClick(event) {
@@ -48,14 +58,14 @@ const Docs = (props) => {
position: "relative",
padding: 30,
paddingTop: 15,
borderRadius: 5,
height: "80vh",
marginTop: 15,
}
const SideBar = {
maxWidth: 250,
flex: "1",
position: "fixed",
marginTop: 35,
}
const fetchDocList = () => {
@@ -71,7 +81,7 @@ const Docs = (props) => {
if (responseJson.success) {
setList(responseJson.list)
} else {
setList(["error"])
setList(["# Error loading documentation. Please contact us if this persists."])
}
setListLoaded(true)
})
@@ -91,6 +101,58 @@ const Docs = (props) => {
if (responseJson.success) {
setData(responseJson.reason)
document.title = "Shuffle "+docId+" documentation"
if (responseJson.meta !== undefined) {
setSelectedMeta(responseJson.meta)
}
//console.log("TOC list: ", responseJson.reason)
if (responseJson.reason !== undefined && responseJson.reason !== null) {
const splitkey = responseJson.reason.split("\n")
var innerTocLines = []
var record = false
for (var key in splitkey) {
const line = splitkey[key]
//console.log("Line: ", line)
if (line.toLowerCase().includes("table of contents")) {
record = true
continue
}
if (record && line.length < 3) {
record = false
}
if (record) {
const parsedline = line.split("](")
if (parsedline.length > 1) {
parsedline[0] = parsedline[0].replaceAll("*", "")
parsedline[0] = parsedline[0].replaceAll("[", "")
parsedline[0] = parsedline[0].replaceAll("]", "")
parsedline[0] = parsedline[0].replaceAll("(", "")
parsedline[0] = parsedline[0].replaceAll(")", "")
parsedline[0] = parsedline[0].trim()
parsedline[1] = parsedline[1].replaceAll("*", "")
parsedline[1] = parsedline[1].replaceAll("[", "")
parsedline[1] = parsedline[1].replaceAll("]", "")
parsedline[1] = parsedline[1].replaceAll(")", "")
parsedline[1] = parsedline[1].replaceAll("(", "")
parsedline[1] = parsedline[1].trim()
//console.log(parsedline[0], parsedline[1])
innerTocLines.push({
"text": parsedline[0],
"link": parsedline[1]
})
} else {
console.log("Bad line for parsing: ", line)
}
}
}
setTocLines(innerTocLines)
}
} else {
setData("# Error\nThis page doesn't exist.")
}
@@ -100,14 +162,21 @@ const Docs = (props) => {
if (firstrequest) {
setFirstrequest(false)
if (!serverside) {
if (window.innerWidth < 768) {
setMobile(true)
}
}
if (selectedDoc !== undefined) {
setData(selectedDoc.reason)
setList(selectedDoc.list)
setListLoaded(true)
} else {
fetchDocList()
fetchDocs(props.match.params.key)
if (!serverside) {
fetchDocList()
fetchDocs(props.match.params.key)
}
}
}
@@ -118,6 +187,7 @@ const Docs = (props) => {
}
const parseElementScroll = () => {
const offset = 45
var parent = document.getElementById("markdown_wrapper_outer")
if (parent !== null) {
//console.log("IN PARENT")
@@ -135,7 +205,12 @@ const Docs = (props) => {
// Fix location..
if (element.innerHTML.toLowerCase() === name) {
//console.log(element.offsetTop)
element.scrollIntoView({behavior: "smooth"})
//element.scrollTo({
// top: element.offsetTop+offset,
// behavior: "smooth"
//})
found = true
//element.scrollTo({
// top: element.offsetTop-100,
@@ -147,7 +222,7 @@ const Docs = (props) => {
// H#
if (!found) {
elements = parent.getElementsByTagName('h3')
console.log(name)
//console.log("NAMe: ", name)
found = false
for (key in elements) {
const element = elements[key]
@@ -158,6 +233,10 @@ const Docs = (props) => {
// Fix location..
if (element.innerHTML.toLowerCase() === name) {
element.scrollIntoView({behavior: "smooth"})
//element.scrollTo({
// top: element.offsetTop-offset,
// behavior: "smooth"
//})
found = true
//element.scrollTo({
// top: element.offsetTop-100,
@@ -187,10 +266,10 @@ const Docs = (props) => {
const markdownStyle = {
color: "rgba(255, 255, 255, 0.65)",
flex: "1",
maxWidth: isMobile ? "100%" : 750,
maxWidth: mobile ? "100%" : 750,
overflow: "hidden",
paddingBottom: 200,
marginLeft: isMobile ? 0 : 275,
marginLeft: mobile ? 0 : 275,
}
function OuterLink(props) {
@@ -214,12 +293,65 @@ const Docs = (props) => {
)
}
function Heading(props) {
const element = React.createElement(`h${props.level}`, {style: {marginTop: 40}}, props.children)
const Heading = (props) => {
const element = React.createElement(`h${props.level}`, {style: {marginTop: props.level === 1 ? 20 : 50}}, props.children)
const [hover, setHover] = useState(false)
var extraInfo = ""
if (props.level === 1) {
extraInfo =
<div style={{backgroundColor: theme.palette.inputColor, padding: 15, borderRadius: theme.palette.borderRadius, marginBottom: 30, display: "flex",}}>
<div style={{flex: 3, display: "flex", vAlign: "center",}}>
{mobile ? null :
<Typography style={{display: "inline", marginTop: 6, }}>
<a rel="norefferer" target="_blank" href={selectedMeta.link} target="_blank" style={{textDecoration: "none", color: "#f85a3e"}}>
<Button style={{}} variant="outlined">
<EditIcon /> &nbsp;&nbsp;Edit
</Button>
</a>
</Typography>
}
{mobile ? null :
<div style={{height: "100%", width: 1, backgroundColor: "white", marginLeft: 50, marginRight: 50, }} />
}
<Typography style={{display: "inline", marginTop: 11, }}>
{selectedMeta.read_time} minute{selectedMeta.read_time === 1 ? "" : "s"} to read
</Typography>
</div>
<div style={{flex: 2}}>
{mobile || selectedMeta.contributors === undefined || selectedMeta.contributors === null ? "" :
<div style={{margin: 10, height: "100%", display: "inline",}}>
{selectedMeta.contributors.slice(0,7).map((data, index) => {
return (
<a rel="norefferer" target="_blank" href={data.url} target="_blank" style={{textDecoration: "none", color: "#f85a3e"}}>
<Tooltip title={data.url} placement="bottom">
<img alt={data.url} src={data.image} style={{marginTop: 5, marginRight: 10, height: 40, borderRadius: 40, }} />
</Tooltip>
</a>
)
})}
</div>
}
</div>
</div>
}
return (
<Typography>
<Typography
onMouseOver={() => {
setHover(true)
}} >
{props.level !== 1 ? <Divider style={{width: "90%", marginTop: 40, backgroundColor: theme.palette.inputColor}} /> : null}
{element}
{/*hover ? <LinkIcon onMouseOver={() => {setHover(true)}} style={{cursor: "pointer", display: "inline", }} onClick={() => {
window.location.href += "#hello"
console.log(window.location)
//window.history.pushState('page2', 'Title', '/page2.php');
//window.history.replaceState('page2', 'Title', '/page2.php');
}} />
: ""
*/}
{extraInfo}
</Typography>
)
}
@@ -234,19 +366,44 @@ const Docs = (props) => {
// );
//}
const postDataBrowser =
const postDataBrowser = list === undefined || list === null ? null :
<div style={Body}>
<div style={SideBar}>
<Paper style={SidebarPaperStyle}>
<List style={{listStyle: "none", paddingLeft: "0", }}>
{list.map((item, index) => {
{list.map((data, index) => {
const item = data.name
if (item === undefined) {
return null
}
const path = "/docs/"+item
const newname = item.charAt(0).toUpperCase()+item.substring(1).split("_").join(" ").split("-").join(" ")
const itemMatching = props.match.params.key.toLowerCase() === item.toLowerCase()
//const [tocLines, setTocLines] = React.useState([]);
return (
<li key={index} style={{marginTop: 15,}}>
<Link key={index} style={hrefStyle} to={path} onClick={() => {fetchDocs(item)}}>
<Typography variant="h6"><b>{newname}</b></Typography>
<li key={index} style={{marginTop: 10,}}>
<Link key={index} style={hrefStyle} to={path} onClick={() => {
setTocLines([])
fetchDocs(item)
}}>
<Typography style={{color: itemMatching ? "#f86a3e" : "inherit"}} variant="body1"><b>> {newname}</b></Typography>
</Link>
{itemMatching && tocLines !== null && tocLines !== undefined && tocLines.length > 0 ?
<div style={{marginLeft: 5}}>
{tocLines.map((data, index) => {
//console.log(data)
return (
<Link key={index} style={innerHrefStyle} to={data.link} onClick={() => {}}>
<Typography variant="body2" style={{cursor: "pointer"}}>
- {data.text}
</Typography>
</Link>
)
})}
</div>
: null}
</li>
)
})}
@@ -278,7 +435,7 @@ const Docs = (props) => {
flexDirection: "column",
}
const postDataMobile =
const postDataMobile = list === undefined || list === null ? null :
<div style={mobileStyle}>
<div>
<Button fullWidth aria-controls="simple-menu" aria-haspopup="true" variant="outlined" color="primary" onClick={handleClick}>
@@ -294,7 +451,12 @@ const Docs = (props) => {
open={Boolean(anchorEl)}
onClose={handleClose}
>
{list.map((item, index) => {
{list.map((data, index) => {
const item = data.name
if (item === undefined) {
return null
}
const path = "/docs/"+item
const newname = item.charAt(0).toUpperCase()+item.substring(1).split("_").join(" ").split("-").join(" ")
return (
@@ -344,7 +506,7 @@ const Docs = (props) => {
</div>
return (
<div>
<div style={{}}>
{loadedCheck}
</div>
)
+91 -4
View File
@@ -1,6 +1,7 @@
/* eslint-disable react/no-multi-comp */
import React, { useState } from 'react';
import { makeStyles } from '@material-ui/styles';
import { useInterval } from 'react-powerhooks';
import {CircularProgress, TextField, Button, Paper, Typography} from '@material-ui/core'
import { useTheme } from '@material-ui/core/styles';
@@ -27,11 +28,13 @@ const useStyles = makeStyles({
const LoginDialog = props => {
const theme = useTheme();
const { globalUrl, isLoaded, isLoggedIn, setIsLoggedIn, setCookie, register } = props;
const { globalUrl, isLoaded, isLoggedIn, setIsLoggedIn, setCookie, register, checkLogin } = props;
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("")
// Used to swap from login to register. True = login, false = register
@@ -47,7 +50,6 @@ const LoginDialog = props => {
window.location.pathname = "/workflows"
}
const checkAdmin = () => {
const url = globalUrl + '/api/v1/checkusers';
fetch(url, {
@@ -61,6 +63,20 @@ const LoginDialog = props => {
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)
}
}
if (responseJson.reason === "stay") {
window.location.pathname = "/adminsetup"
}
@@ -68,10 +84,21 @@ const LoginDialog = props => {
}),
)
.catch(error => {
setLoginInfo("Error logging in - please refresh in a minute ", error)
if (!loginViewLoading) {
setLoginViewLoading(true)
start()
}
})
}
const { start, stop } = useInterval({
duration: 3000,
startImmediate: false,
callback: () => {
checkAdmin()
}
})
if (firstRequest) {
setFirstRequest(false)
checkAdmin()
@@ -178,6 +205,50 @@ const LoginDialog = props => {
<div style={{position: "absolute", top: -imgsize/2-10, left: 250-imgsize/2, height: imgsize, width: imgsize, }}>
<img src="images/Shuffle_logo.png" style={{height: imgsize+10, width: imgsize+10, border: "2px solid rgba(255,255,255,0.6)", borderRadius: imgsize,}}/>
</div>
{loginViewLoading ?
<div style={{textAlign: "center", marginTop: 50, }}>
<Typography variant="body2" style={{marginBottom: 20, color: "white",}}>
Waiting for the Shuffle database to become available. This may take up to a minute.
</Typography>
{loginInfo === undefined || loginInfo === null || loginInfo.length === 0 ?
null
:
<div style={{ marginTop: "10px" }}>
Response: {loginInfo}
</div>
}
<CircularProgress color="secondary" style={{color: "white",}} />
<Paper style={{
paddingLeft: "30px",
paddingRight: "30px",
paddingBottom: "30px",
paddingTop: "30px",
position: "relative",
backgroundColor: theme.palette.inputColor,
textAlign: "left",
marginTop: 15,
}}>
<Typography variant="body2" style={{marginBottom: 20, color: "white",}}>
<b>Are you sure Shuffle is <a rel="norefferer" target="_blank" href="https://github.com/frikky/Shuffle/blob/master/.github/install-guide.md" style={{textDecoration: "none", color: "#f86a3e"}}>installed correctly</a>?</b>
</Typography>
<Typography variant="body2" style={{marginBottom: 20, color: "white",}}>
<b>1.</b> Make sure shuffle-database folder has correct access: <br/><br/>
sudo chown 1000:1000 -R shuffle-database
</Typography>
<Typography variant="body2" style={{marginBottom: 20, color: "white",}}>
<b>2</b>. Restart docker-compose:<br/><br/>
sudo docker-compose restart
</Typography>
</Paper>
<Typography variant="body2" style={{marginBottom: 10, color: "white", marginTop: 20, }}>
Need help? <a rel="norefferer" target="_blank" href="https://discord.gg/B2CBzUm" style={{textDecoration: "none", color: "#f86a3e"}}>Join the Discord!</a>
</Typography>
</div>
:
<form onSubmit={onSubmit} style={{ margin: "15px 15px 15px 15px", color: "white", }}>
<h2>{formtitle}</h2>
Username
@@ -233,14 +304,30 @@ const LoginDialog = props => {
/>
</div>
<div style={{ display: "flex", marginTop: "15px" }}>
<Button color="primary" variant="contained" type="submit" style={{ flex: "1", marginRight: "5px" }} disabled={!handleValidateForm() || loginLoading}>
<Button color="primary" variant="contained" type="submit" style={{ flex: "1", }} disabled={!handleValidateForm() || loginLoading}>
{loginLoading ? <CircularProgress color="secondary" style={{color: "white",}} /> : "SUBMIT"}
</Button>
</div>
<div style={{ marginTop: "10px" }}>
{loginInfo}
</div>
{ssoUrl !== undefined && ssoUrl !== null && ssoUrl.length > 0 ?
<div>
<Typography style={{textAlign: "center", }}>
Or
</Typography>
<div style={{textAlign: "center", margin: 10, }}>
<Button fullWidth color="secondary" variant="outlined" type="button" style={{ flex: "1", marginTop: 5}} onClick={() => {
console.log("CLICK")
window.location = ssoUrl
}}>
Use SSO
</Button>
</div>
</div>
: null}
</form>
}
</Paper>
</div>
+147
View File
@@ -0,0 +1,147 @@
import React, {useRef, useState, useEffect, useLayoutEffect} from 'react';
import { Typography, CircularProgress } from '@material-ui/core';
const SetAuthentication = (props) => {
const { globalUrl, isLoggedIn, isLoaded, userdata } = props;
const [firstRequest, setFirstRequest] = useState(true)
const [finished, setFinished] = useState(false)
const [response, setResponse] = useState("")
const [failed, setFailed] = useState(false)
if (firstRequest) {
setFirstRequest(false)
//code
//session_state
const urlSearchParams = new URLSearchParams(window.location.search);
const params = Object.fromEntries(urlSearchParams.entries());
const authenticationStore = []
var appAuthData = {
"label": "",
"app": {
"name": "",
"id": "",
"app_version": "",
},
"fields": [],
"type": "oauth2",
}
if (window !== undefined && window !== null) {
console.log(window.location)
appAuthData.fields.push({"key": "redirect_uri", "value": window.location.origin+window.location.pathname})
}
if (params.code !== undefined && params.code !== null) {
appAuthData.fields.push({"key": "code", "value": params.code})
}
if (params.session_state !== undefined && params.session_state !== null) {
appAuthData.fields.push({"key": "session_state", "value": params.session_state})
}
if (params.state !== undefined && params.state !== null) {
const paramsplit = params.state.split("&")
console.log(paramsplit)
for (var key in paramsplit) {
const query = paramsplit[key].split("=")
console.log(query)
if (query.length !== 2) {
console.log("INVALID QUERY: ", query)
continue
}
if (query[0] === "workflow_id") {
appAuthData.reference_workflow = query[1]
}
if (query[0] === "reference_action_id") {
//appAuthData.ReferenceWorkflow = query[1]
}
if (query[0] === "app_name") {
appAuthData.app.name = query[1]
appAuthData.label = "Oauth2 for "+query[1]
}
if (query[0] === "app_id") {
appAuthData.app.id = query[1]
}
if (query[0] === "app_version") {
appAuthData.app.app_version = query[1]
}
if (query[0] === "authentication_url") {
appAuthData.fields.push({"key": "authentication_url", "value": query[1]})
}
if (query[0] === "scope") {
appAuthData.fields.push({"key": "scope", "value": query[1]})
}
if (query[0] === "client_id") {
appAuthData.fields.push({"key": "client_id", "value": query[1]})
}
if (query[0] === "client_secret") {
appAuthData.fields.push({"key": "client_secret", "value": query[1]})
}
if (query[0] === "oauth_url") {
appAuthData.fields.push({"key": "oauth_url", "value": query[1]})
}
}
}
console.log(appAuthData)
fetch(globalUrl+"/api/v1/apps/authentication", {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
credentials: "include",
body: JSON.stringify(appAuthData),
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for oauth2 authentication")
setFailed(true)
}
return response.json()
})
.then((responseJson) => {
//setUserSettings(responseJson)
console.log("Resp: ", responseJson)
setFinished(true)
setResponse(responseJson.reason)
setTimeout(() => {
window.close()
}, 1000)
})
.catch(error => {
console.log(error)
});
}
return (
<div style={{width: 1000, margin: "auto", itemAlign: "center",}}>
<Typography variant="h6" style={{marginLeft: "auto", marginRight: "auto", marginTop: 200, }}>
{!finished ? <CircularProgress /> : "DONE WITH AUTH - this will close soon!!"}
<div />
{failed ? "Failed setup. Error: " : ""} {response}
</Typography>
</div>
)
}
export default SetAuthentication;
+143
View File
@@ -0,0 +1,143 @@
import React, {useRef, useState, useEffect, useLayoutEffect} from 'react';
import { Typography, CircularProgress } from '@material-ui/core';
const SetAuthentication = (props) => {
const { globalUrl, isLoggedIn, isLoaded, userdata } = props;
const [firstRequest, setFirstRequest] = useState(true)
const [finished, setFinished] = useState(false)
const [response, setResponse] = useState("")
const [failed, setFailed] = useState(false)
if (firstRequest) {
setFirstRequest(false)
//code
//session_state
const urlSearchParams = new URLSearchParams(window.location.search);
const params = Object.fromEntries(urlSearchParams.entries());
const authenticationStore = []
var appAuthData = {
"label": "",
"app": {
"name": "",
"id": "",
"app_version": "",
},
"fields": [],
"type": "oauth2",
}
if (window !== undefined && window !== null) {
console.log(window.location)
appAuthData.fields.push({"key": "redirect_uri", "value": window.location.origin+window.location.pathname})
}
if (params.code !== undefined && params.code !== null) {
appAuthData.fields.push({"key": "code", "value": params.code})
}
if (params.session_state !== undefined && params.session_state !== null) {
appAuthData.fields.push({"key": "session_state", "value": params.session_state})
}
if (params.state !== undefined && params.state !== null) {
const paramsplit = params.state.split("&")
console.log(paramsplit)
for (var key in paramsplit) {
const query = paramsplit[key].split("=")
console.log(query)
if (query.length !== 2) {
console.log("INVALID QUERY: ", query)
continue
}
if (query[0] === "workflow_id") {
appAuthData.reference_workflow = query[1]
}
if (query[0] === "reference_action_id") {
//appAuthData.ReferenceWorkflow = query[1]
}
if (query[0] === "app_name") {
appAuthData.app.name = query[1]
appAuthData.label = "Oauth2 for "+query[1]
}
if (query[0] === "app_id") {
appAuthData.app.id = query[1]
}
if (query[0] === "app_version") {
appAuthData.app.app_version = query[1]
}
if (query[0] === "authentication_url") {
appAuthData.fields.push({"key": "authentication_url", "value": query[1]})
}
if (query[0] === "scope") {
appAuthData.fields.push({"key": "scope", "value": query[1]})
}
if (query[0] === "client_id") {
appAuthData.fields.push({"key": "client_id", "value": query[1]})
}
if (query[0] === "client_secret") {
appAuthData.fields.push({"key": "client_secret", "value": query[1]})
}
}
}
console.log(appAuthData)
fetch(globalUrl+"/api/v1/apps/authentication", {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
credentials: "include",
body: JSON.stringify(appAuthData),
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for oauth2 authentication")
setFailed(true)
}
return response.json()
})
.then((responseJson) => {
//setUserSettings(responseJson)
console.log("Resp: ", responseJson)
setFinished(true)
setResponse(responseJson.reason)
setTimeout(() => {
window.close()
}, 1000)
})
.catch(error => {
console.log(error)
});
}
return (
<div style={{width: 1000, margin: "auto", itemAlign: "center",}}>
<Typography variant="h6" style={{marginLeft: "auto", marginRight: "auto", marginTop: 200, }}>
{!finished ? <CircularProgress /> : "DONE WITH AUTH - this will close soon!!"}
<div />
{failed ? "Failed setup. Error: " : ""} {response}
</Typography>
</div>
)
}
export default SetAuthentication;
File diff suppressed because it is too large Load Diff