Added org and suborg swapping POC

This commit is contained in:
frikky
2021-07-30 01:36:41 +02:00
parent 67453eb9f5
commit eceb3a2fca
8 changed files with 223 additions and 126 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ require (
github.com/docker/go-units v0.4.0 // indirect github.com/docker/go-units v0.4.0 // indirect
github.com/elastic/go-elasticsearch/v7 v7.13.1 // indirect github.com/elastic/go-elasticsearch/v7 v7.13.1 // indirect
github.com/frikky/kin-openapi v0.39.0 github.com/frikky/kin-openapi v0.39.0
github.com/frikky/shuffle-shared v0.0.72 github.com/frikky/shuffle-shared v0.0.74
github.com/fsouza/go-dockerclient v1.7.2 github.com/fsouza/go-dockerclient v1.7.2
github.com/ghodss/yaml v1.0.0 github.com/ghodss/yaml v1.0.0
github.com/go-git/go-billy/v5 v5.0.0 github.com/go-git/go-billy/v5 v5.0.0
+8 -4
View File
@@ -739,12 +739,15 @@ func handleRegister(resp http.ResponseWriter, request *http.Request) {
} }
} }
apikey := ""
if count != 0 { if count != 0 {
if user.Role != "admin" { if user.Role != "admin" {
resp.WriteHeader(401) resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "reason": "Can't register without being admin (2)"}`)) resp.Write([]byte(`{"success": false, "reason": "Can't register without being admin (2)"}`))
return return
} }
} else {
apikey = uuid.NewV4().String()
} }
// Gets a struct of Username, password // Gets a struct of Username, password
@@ -819,7 +822,7 @@ func handleRegister(resp http.ResponseWriter, request *http.Request) {
} }
} }
err = createNewUser(data.Username, data.Password, role, "", currentOrg) err = createNewUser(data.Username, data.Password, role, apikey, currentOrg)
if err != nil { if err != nil {
log.Printf("[WARNING] Failed registering user: %s", err) log.Printf("[WARNING] Failed registering user: %s", err)
resp.WriteHeader(401) resp.WriteHeader(401)
@@ -828,7 +831,7 @@ func handleRegister(resp http.ResponseWriter, request *http.Request) {
} }
resp.WriteHeader(200) resp.WriteHeader(200)
resp.Write([]byte(`{"success": true}`)) resp.Write([]byte(fmt.Sprintf(`{"success": true, "apikey": "%s"}`, apikey)))
log.Printf("[INFO] %s Successfully registered.", data.Username) log.Printf("[INFO] %s Successfully registered.", data.Username)
} }
@@ -4147,12 +4150,12 @@ func runInitEs(ctx context.Context) {
r, err := git.Clone(storer, fs, cloneOptions) r, err := git.Clone(storer, fs, cloneOptions)
if err != nil { if err != nil {
log.Printf("Failed loading repo into memory (init): %s", err) log.Printf("[WARNING] Failed loading repo into memory (init): %s", err)
} }
dir, err := fs.ReadDir("") dir, err := fs.ReadDir("")
if err != nil { if err != nil {
log.Printf("Failed reading folder: %s", err) log.Printf("[WARNING] Failed reading folder (init): %s", err)
} }
_ = r _ = r
//iterateAppGithubFolders(fs, dir, "", "testing") //iterateAppGithubFolders(fs, dir, "", "testing")
@@ -5837,6 +5840,7 @@ func initHandlers() {
r.HandleFunc("/api/v1/orgs/{orgId}", shuffle.HandleGetOrg).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/orgs/{orgId}", shuffle.HandleGetOrg).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}", shuffle.HandleEditOrg).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/orgs/{orgId}", shuffle.HandleEditOrg).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/create_sub_org", shuffle.HandleCreateSubOrg).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/orgs/{orgId}/create_sub_org", shuffle.HandleCreateSubOrg).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/change", shuffle.HandleChangeUserOrg).Methods("POST", "OPTIONS") // Swaps to the org
// This is a new API that validates if a key has been seen before. // This is a new API that validates if a key has been seen before.
// Not sure what the best course of action is for it. // Not sure what the best course of action is for it.
+2 -2
View File
@@ -1,7 +1,7 @@
version: '3' version: '3'
services: services:
frontend: frontend:
#build: ./frontend build: ./frontend
image: ghcr.io/frikky/shuffle-frontend:nightly image: ghcr.io/frikky/shuffle-frontend:nightly
container_name: shuffle-frontend container_name: shuffle-frontend
hostname: shuffle-frontend hostname: shuffle-frontend
@@ -16,7 +16,7 @@ services:
depends_on: depends_on:
- backend - backend
backend: backend:
#build: ./backend build: ./backend
image: ghcr.io/frikky/shuffle-backend:nightly image: ghcr.io/frikky/shuffle-backend:nightly
container_name: shuffle-backend container_name: shuffle-backend
hostname: ${BACKEND_HOSTNAME} hostname: ${BACKEND_HOSTNAME}
+1 -1
View File
@@ -105,7 +105,7 @@ const App = (message, props) => {
</div> : </div> :
<div style={{ backgroundColor: "#1F2023", color: "rgba(255, 255, 255, 0.65)", minHeight: "100vh" }}> <div style={{ backgroundColor: "#1F2023", color: "rgba(255, 255, 255, 0.65)", minHeight: "100vh" }}>
<ScrollToTop setCurpath={setCurpath} /> <ScrollToTop setCurpath={setCurpath} />
<Header cookies={cookies} removeCookie={removeCookie} isLoaded={isLoaded} globalUrl={globalUrl} setIsLoggedIn={setIsLoggedIn} isLoggedIn={isLoggedIn} userdata={userdata} {...props} /> <Header checkLogin={checkLogin} cookies={cookies} removeCookie={removeCookie} isLoaded={isLoaded} globalUrl={globalUrl} setIsLoggedIn={setIsLoggedIn} isLoggedIn={isLoggedIn} userdata={userdata} {...props} />
<Route exact path="/login" render={props => <LoginPage isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} /> <Route exact path="/login" render={props => <LoginPage isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
<Route exact path="/admin" render={props => <Admin userdata={userdata} isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} /> <Route exact path="/admin" render={props => <Admin userdata={userdata} isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
<Route exact path="/admin/:key" render={props => <Admin isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} /> <Route exact path="/admin/:key" render={props => <Admin isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+48 -23
View File
@@ -390,10 +390,10 @@ const Admin = (props) => {
if (responseJson.reason !== undefined) { if (responseJson.reason !== undefined) {
alert.error(responseJson.reason) alert.error(responseJson.reason)
} else { } else {
alert.error("Failed setting new password") alert.error("Failed creating suborg")
} }
} else { } else {
alert.success("Successfully updated password!") alert.success("Successfully created suborg!")
setSelectedUserModalOpen(false) setSelectedUserModalOpen(false)
} }
@@ -499,6 +499,10 @@ const Admin = (props) => {
if (responseJson["success"] === false) { if (responseJson["success"] === false) {
alert.error("Failed getting org: ", responseJson.readon) alert.error("Failed getting org: ", responseJson.readon)
} else { } else {
if (responseJson.sync_features === undefined || responseJson.sync_features === null) {
responseJson.sync_features = {}
}
setSelectedOrganization(responseJson) setSelectedOrganization(responseJson)
var lists = { var lists = {
"active": { "active": {
@@ -1727,7 +1731,7 @@ const Admin = (props) => {
} }
<Typography style={{marginTop: 40, marginLeft: 10, marginBottom: 5,}}>Cloud sync features</Typography> <Typography style={{marginTop: 40, marginLeft: 10, marginBottom: 5,}}>Cloud sync features</Typography>
<Grid container style={{width: "100%", marginBottom: 15, }}> <Grid container style={{width: "100%", marginBottom: 15, }}>
{Object.keys(selectedOrganization.sync_features).map(function(key, index) { {selectedOrganization.sync_features === undefined || selectedOrganization.sync_features === null ? null : Object.keys(selectedOrganization.sync_features).map(function(key, index) {
if (key === "schedule") { if (key === "schedule") {
return null return null
} }
@@ -1813,7 +1817,7 @@ const Admin = (props) => {
}} }}
> >
<DialogTitle><span style={{ color: "white" }}> <DialogTitle><span style={{ color: "white" }}>
{curTab === 1 ? "Add user" : curTab === 7 ? "Add Sub-Organization" : "Add environment"} {curTab === 1 ? "Add user" : curTab === 6 ? "Add Sub-Organization" : "Add environment"}
</span></DialogTitle> </span></DialogTitle>
<DialogContent> <DialogContent>
{curTab === 1 && isCloud ? {curTab === 1 && isCloud ?
@@ -1821,7 +1825,7 @@ const Admin = (props) => {
We'll send an email to invite them to your organization. We'll send an email to invite them to your organization.
</Typography> </Typography>
: :
curTab === 7 ? curTab === 6 ?
<Typography variant="body1" style={{marginBottom: 10}}> <Typography variant="body1" style={{marginBottom: 10}}>
The organization created will become a child of your current organization, and be available to you. The organization created will become a child of your current organization, and be available to you.
</Typography> </Typography>
@@ -1876,7 +1880,7 @@ const Admin = (props) => {
</span> </span>
} }
</div> </div>
: curTab === 7 ? : curTab === 6 ?
<div> <div>
Name Name
<TextField <TextField
@@ -1938,7 +1942,7 @@ const Admin = (props) => {
} else { } else {
submitUser(modalUser) submitUser(modalUser)
} }
} else if (curTab === 7) { } else if (curTab === 6) {
createSubOrg(selectedOrganization.id, orgName) createSubOrg(selectedOrganization.id, orgName)
} else if (curTab === 5) { } else if (curTab === 5) {
submitEnvironment(modalUser) submitEnvironment(modalUser)
@@ -2513,6 +2517,7 @@ const Admin = (props) => {
bgColor = "#1f2023" bgColor = "#1f2023"
} }
return ( return (
<ListItem key={index} style={{backgroundColor: bgColor}}> <ListItem key={index} style={{backgroundColor: bgColor}}>
<ListItemText <ListItemText
@@ -2696,7 +2701,7 @@ const Admin = (props) => {
</div> </div>
: null : null
const organizationsTab = curTab === 7 ? const organizationsTab = curTab === 6 ?
<div> <div>
<div style={{marginTop: 20, marginBottom: 20,}}> <div style={{marginTop: 20, marginBottom: 20,}}>
<h2 style={{display: "inline",}}>Organizations</h2> <h2 style={{display: "inline",}}>Organizations</h2>
@@ -2716,17 +2721,21 @@ const Admin = (props) => {
<List> <List>
<ListItem> <ListItem>
<ListItemText <ListItemText
primary="Name" primary="Logo"
style={{minWidth: 150, maxWidth: 150}} style={{minWidth: 100, maxWidth: 100}}
/> />
<ListItemText <ListItemText
primary="id" primary="Name"
style={{minWidth: 200, maxWidth: 200}} style={{minWidth: 250, maxWidth: 250}}
/> />
<ListItemText <ListItemText
primary="Your role" primary="Your role"
style={{minWidth: 150, maxWidth: 150}} style={{minWidth: 150, maxWidth: 150}}
/> />
<ListItemText
primary="id"
style={{minWidth: 400, maxWidth: 400}}
/>
<ListItemText <ListItemText
primary="Selected" primary="Selected"
style={{minWidth: 150, maxWidth: 150}} style={{minWidth: 150, maxWidth: 150}}
@@ -2736,25 +2745,41 @@ const Admin = (props) => {
style={{minWidth: 150, maxWidth: 150}} style={{minWidth: 150, maxWidth: 150}}
/> />
</ListItem> </ListItem>
{organizations !== undefined && organizations !== null && organizations.length > 0 ? {userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 0 ?
<span> <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 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 ( return (
<ListItem key={index}> <ListItem key={index} style={{backgroundColor: bgColor,}}>
<ListItemText <ListItemText
primary={data.name} primary={image}
style={{minWidth: 150, maxWidth: 150}} style={{minWidth: 100, maxWidth: 100}}
/> />
<ListItemText <ListItemText
primary={data.id} primary={data.name}
style={{minWidth: 200, maxWidth: 200}} style={{minWidth: 250, maxWidth: 250}}
/> />
<ListItemText <ListItemText
primary={data.role} primary={data.role}
style={{minWidth: 150, maxWidth: 150}} style={{minWidth: 150, maxWidth: 150}}
/> />
<ListItemText
primary={data.id}
style={{minWidth: 400, maxWidth: 400}}
/>
<ListItemText <ListItemText
primary={isSelected} primary={isSelected}
style={{minWidth: 150, maxWidth: 150}} style={{minWidth: 150, maxWidth: 150}}
@@ -2776,7 +2801,7 @@ const Admin = (props) => {
</div> </div>
: null : null
const hybridTab = curTab === 6 ? const hybridTab = curTab === 7 ?
<div> <div>
<div style={{marginTop: 20, marginBottom: 20,}}> <div style={{marginTop: 20, marginBottom: 20,}}>
<h2 style={{display: "inline",}}>Hybrid</h2> <h2 style={{display: "inline",}}>Hybrid</h2>
@@ -2836,9 +2861,9 @@ const Admin = (props) => {
<Tab label=<span><DescriptionIcon style={iconStyle} />Files</span> /> <Tab label=<span><DescriptionIcon style={iconStyle} />Files</span> />
<Tab label=<span><ScheduleIcon style={iconStyle} />Schedules</span> /> <Tab label=<span><ScheduleIcon style={iconStyle} />Schedules</span> />
{isCloud ? null : <Tab label=<span><EcoIcon style={iconStyle} />Environments</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} {isCloud ? null : <Tab label=<span><BusinessIcon style={iconStyle} /> Organizations</span>/>}
{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><CloudIcon style={iconStyle} /> Hybrid</span>/> : null*/}
{window.location.protocol === "http:" && window.location.port === "3000" ? <Tab label=<span><LockIcon style={iconStyle} />Categories</span>/> : null} {/*window.location.protocol === "http:" && window.location.port === "3000" ? <Tab label=<span><LockIcon style={iconStyle} />Categories</span>/> : null*/}
</Tabs> </Tabs>
<Divider style={{marginTop: 0, marginBottom: 10, backgroundColor: "rgb(91, 96, 100)"}} /> <Divider style={{marginTop: 0, marginBottom: 10, backgroundColor: "rgb(91, 96, 100)"}} />
<div style={{padding: 15}}> <div style={{padding: 15}}>
+29 -1
View File
@@ -1,6 +1,7 @@
import React, { useEffect} from 'react'; import React, { useEffect} from 'react';
import { useInterval } from 'react-powerhooks'; import { useInterval } from 'react-powerhooks';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
import { useTheme } from '@material-ui/core/styles';
import {Avatar, Grid, Paper, Tooltip, Divider, Button, TextField, FormControl, IconButton, Menu, MenuItem, FormControlLabel, Chip, Switch, Typography, Zoom, CircularProgress, Dialog, DialogTitle, DialogActions, DialogContent} from '@material-ui/core'; import {Avatar, Grid, Paper, Tooltip, Divider, Button, TextField, FormControl, IconButton, Menu, MenuItem, FormControlLabel, Chip, Switch, Typography, Zoom, CircularProgress, Dialog, DialogTitle, DialogActions, DialogContent} from '@material-ui/core';
import {Close as CloseIcon, Compare as CompareIcon, Maximize as MaximizeIcon, Minimize as MinimizeIcon, AddCircle as AddCircleIcon, Toc as TocIcon, Send as SendIcon, Search as SearchIcon, FileCopy as FileCopyIcon, Delete as DeleteIcon, BubbleChart as BubbleChartIcon, Restore as RestoreIcon, Cached as CachedIcon, GetApp as GetAppIcon, Apps as AppsIcon, Edit as EditIcon, MoreVert as MoreVertIcon, PlayArrow as PlayArrowIcon, Add as AddIcon, Publish as PublishIcon, CloudUpload as CloudUploadIcon, CloudDownload as CloudDownloadIcon} from '@material-ui/icons'; import {Close as CloseIcon, Compare as CompareIcon, Maximize as MaximizeIcon, Minimize as MinimizeIcon, AddCircle as AddCircleIcon, Toc as TocIcon, Send as SendIcon, Search as SearchIcon, FileCopy as FileCopyIcon, Delete as DeleteIcon, BubbleChart as BubbleChartIcon, Restore as RestoreIcon, Cached as CachedIcon, GetApp as GetAppIcon, Apps as AppsIcon, Edit as EditIcon, MoreVert as MoreVertIcon, PlayArrow as PlayArrowIcon, Add as AddIcon, Publish as PublishIcon, CloudUpload as CloudUploadIcon, CloudDownload as CloudDownloadIcon} from '@material-ui/icons';
@@ -328,6 +329,7 @@ export const validateJson = (showResult) => {
const Workflows = (props) => { const Workflows = (props) => {
const { globalUrl, isLoggedIn, isLoaded, removeCookie, cookies, userdata} = props; const { globalUrl, isLoggedIn, isLoaded, removeCookie, cookies, userdata} = props;
document.title = "Shuffle - Workflows" document.title = "Shuffle - Workflows"
const theme = useTheme();
const referenceUrl = globalUrl+"/api/v1/hooks/" const referenceUrl = globalUrl+"/api/v1/hooks/"
const alert = useAlert() const alert = useAlert()
@@ -391,6 +393,10 @@ const Workflows = (props) => {
return true return true
} else if (curWorkflow.tags.includes(filter)) { } else if (curWorkflow.tags.includes(filter)) {
return true return true
} else if (curWorkflow.owner === filter) {
return true
} else if (curWorkflow.org_id === filter) {
return true
} else if (curWorkflow.actions !== null && curWorkflow.actions !== undefined) { } else if (curWorkflow.actions !== null && curWorkflow.actions !== undefined) {
const newfilter = filter.toLowerCase() const newfilter = filter.toLowerCase()
for (var key in curWorkflow.actions) { for (var key in curWorkflow.actions) {
@@ -1065,16 +1071,38 @@ const Workflows = (props) => {
parsedName = parsedName.slice(0,26)+".." parsedName = parsedName.slice(0,26)+".."
} }
const actions = data.actions !== null ? data.actions.length : 0 const actions = data.actions !== null ? data.actions.length : 0
const [triggers, schedules, webhooks, subflows] = getWorkflowMeta(data) const [triggers, schedules, webhooks, subflows] = getWorkflowMeta(data)
const imagesize = 22
const foundOrg = userdata.orgs.find(org => org.id === data["org_id"])
//position: "absolute", bottom: 5, right: -5,
const imageStyle = {width: imagesize, height: imagesize, pointerEvents: "none", marginRight: 10, marginLeft: data.creator_org !== undefined && data.creator_org.length > 0 ? 20 : 0, borderRadius: 10, border: foundOrg.id === userdata.active_org.id ? `4px solid ${boxColor}` : null, cursor: "pointer", marginRight: 10, }
//<Tooltip title={`Org: ${foundOrg.name}`} placement="bottom">
const image = foundOrg.image === "" ?
<img alt={foundOrg.name} src={theme.palette.defaultImage} style={imageStyle} />
:
<img alt={foundOrg.name} src={foundOrg.image} style={imageStyle} onClick={() => {
//setFilteredWorkflows(newWorkflows)
}}/>
return ( return (
<Grid item xs={4} style={{padding: "12px 10px 12px 10px",}}> <Grid item xs={4} style={{padding: "12px 10px 12px 10px",}}>
<Paper square style={paperAppStyle} > <Paper square style={paperAppStyle} >
<div style={{position: "absolute", bottom: 1, left: 1, height: 12, width: 12, backgroundColor: boxColor, borderRadius: "0 100px 0 0",}} /> <div style={{position: "absolute", bottom: 1, left: 1, height: 12, width: 12, backgroundColor: boxColor, borderRadius: "0 100px 0 0",}} />
<Grid item style={{display: "flex", flexDirection: "column", width: "100%"}}> <Grid item style={{display: "flex", flexDirection: "column", width: "100%"}}>
<Grid item style={{display: "flex", maxHeight: 34,}}> <Grid item style={{display: "flex", maxHeight: 34,}}>
<Tooltip title={`Org "${foundOrg.name}"`} placement="bottom">
<div styl={{cursor: "pointer"}} onClick={() => {
addFilter(foundOrg.id)
//setFilters(["Org "+foundOrg.name])
//setFilteredWorkflows(newWorkflows)
}}>
{image}
</div>
</Tooltip>
<Tooltip title={`Edit ${data.name}`} placement="bottom"> <Tooltip title={`Edit ${data.name}`} placement="bottom">
<Typography variant="body1" style={{marginBottom: 0, paddingBottom: 0, maxHeight: 30, flex: 10,}}> <Typography variant="body1" style={{marginBottom: 0, paddingBottom: 0, maxHeight: 30, flex: 10,}}>
<Link to={"/workflows/"+data.id} style={{textDecoration: "none", color: "inherit",}}> <Link to={"/workflows/"+data.id} style={{textDecoration: "none", color: "inherit",}}>