Started adding optional cloud features
This commit is contained in:
+69
-7
@@ -1692,16 +1692,25 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
|
||||
Expires: expiration,
|
||||
})
|
||||
|
||||
// Migrate this to real Org
|
||||
// Need to create org endpoints (create, delete etc)
|
||||
currentOrg := `{
|
||||
"name": "Shuffle",
|
||||
"id": "123",
|
||||
"role": "admin",
|
||||
"cloud_sync": false
|
||||
}`
|
||||
|
||||
returnData := fmt.Sprintf(`
|
||||
{
|
||||
"success": true,
|
||||
"admin": %s,
|
||||
"tutorials": [],
|
||||
"id": "%s",
|
||||
"orgs": [{"name": "Shuffle", "id": "123", "role": "admin"}],
|
||||
"selected_org": {"name": "Shuffle", "id": "123", "role": "admin"},
|
||||
"orgs": [%s],
|
||||
"selected_org": %s,
|
||||
"cookies": [{"key": "session_token", "value": "%s", "expiration": %d}]
|
||||
}`, parsedAdmin, userInfo.Id, userInfo.Session, expiration.Unix())
|
||||
}`, parsedAdmin, userInfo.Id, currentOrg, currentOrg, userInfo.Session, expiration.Unix())
|
||||
|
||||
resp.WriteHeader(200)
|
||||
resp.Write([]byte(returnData))
|
||||
@@ -6449,6 +6458,55 @@ func runInit(ctx context.Context) {
|
||||
log.Printf("Finished INIT")
|
||||
}
|
||||
|
||||
func handleCloudSetup(resp http.ResponseWriter, request *http.Request) {
|
||||
cors := handleCors(resp, request)
|
||||
if cors {
|
||||
return
|
||||
}
|
||||
|
||||
user, err := handleApiAuthentication(resp, request)
|
||||
if err != nil {
|
||||
log.Printf("Api authentication failed in verify swagger: %s", err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false}`))
|
||||
return
|
||||
}
|
||||
|
||||
if user.Role != "admin" {
|
||||
log.Printf("Not admin.")
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false, "reason": "Not admin"}`))
|
||||
return
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false, "reason": "Failed reading body"}`))
|
||||
return
|
||||
}
|
||||
|
||||
type ReturnData struct {
|
||||
Apikey string `datastore:"apikey"`
|
||||
Organization Org `datastore:"organization"`
|
||||
}
|
||||
|
||||
var tmpData ReturnData
|
||||
err = json.Unmarshal(body, &tmpData)
|
||||
if err != nil {
|
||||
log.Printf("Failed unmarshalling test: %s", err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false}`))
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Apidata: %s", tmpData.Apikey)
|
||||
// FIXME: https://docs.google.com/drawings/d/1JJebpPeEVEbmH_qsAC6zf9Noygp7PytvesrkhE19QrY/edit
|
||||
|
||||
resp.WriteHeader(200)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
|
||||
}
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
ctx := context.Background()
|
||||
@@ -6501,9 +6559,14 @@ func init() {
|
||||
// Queuebuilder and Workflow streams. First is to update a stream, second to get a stream
|
||||
// Changed from workflows/streams to streams, as appengine was messing up
|
||||
// This does not increase the API counter
|
||||
// Used by frontend
|
||||
r.HandleFunc("/api/v1/streams", handleWorkflowQueue).Methods("POST")
|
||||
r.HandleFunc("/api/v1/streams/results", handleGetStreamResults).Methods("POST", "OPTIONS")
|
||||
|
||||
// Used by orborus
|
||||
r.HandleFunc("/api/v1/workflows/queue", handleGetWorkflowqueue).Methods("GET")
|
||||
r.HandleFunc("/api/v1/workflows/queue/confirm", handleGetWorkflowqueueConfirm).Methods("POST")
|
||||
|
||||
// App specific
|
||||
r.HandleFunc("/api/v1/apps/run_hotload", handleAppHotloadRequest).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/apps/get_existing", loadSpecificApps).Methods("POST", "OPTIONS")
|
||||
@@ -6530,8 +6593,6 @@ func init() {
|
||||
/* Everything below here increases the counters*/
|
||||
r.HandleFunc("/api/v1/workflows", getWorkflows).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/workflows", setNewWorkflow).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/workflows/queue", handleGetWorkflowqueue).Methods("GET")
|
||||
r.HandleFunc("/api/v1/workflows/queue/confirm", handleGetWorkflowqueueConfirm).Methods("POST")
|
||||
r.HandleFunc("/api/v1/workflows/schedules", handleGetSchedules).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/workflows/download_remote", loadSpecificWorkflows).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/workflows/{key}/execute", executeWorkflow).Methods("GET", "POST", "OPTIONS")
|
||||
@@ -6552,7 +6613,7 @@ func init() {
|
||||
r.HandleFunc("/api/v1/hooks/{key}/delete", handleDeleteHook).Methods("DELETE", "OPTIONS")
|
||||
|
||||
// Trigger hmm
|
||||
r.HandleFunc("/api/v1/triggers/{key}", handleGetSpecificTrigger).Methods("GET", "OPTIONS")
|
||||
//r.HandleFunc("/api/v1/triggers/{key}", handleGetSpecificTrigger).Methods("GET", "OPTIONS")
|
||||
|
||||
r.HandleFunc("/api/v1/stats/{key}", handleGetSpecificStats).Methods("GET", "OPTIONS")
|
||||
|
||||
@@ -6563,7 +6624,8 @@ func init() {
|
||||
r.HandleFunc("/api/v1/validate_openapi", validateSwagger).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/get_openapi/{key}", getOpenapi).Methods("GET", "OPTIONS")
|
||||
|
||||
r.HandleFunc("/api/v1/execution_cleanup", cleanupExecutions).Methods("GET", "OPTIONS")
|
||||
r.HandleFunc("/api/v1/cloud/setup", handleCloudSetup).Methods("POST", "OPTIONS")
|
||||
//r.HandleFunc("/api/v1/execution_cleanup", cleanupExecutions).Methods("GET", "OPTIONS")
|
||||
|
||||
http.Handle("/", r)
|
||||
}
|
||||
|
||||
@@ -66,11 +66,14 @@ type ExecutionRequest struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// Role is just used for feedback for a user
|
||||
type Org struct {
|
||||
Name string `json:"name"`
|
||||
Org string `json:"org"`
|
||||
Users []User `json:"users"`
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Org string `json:"org"`
|
||||
Users []User `json:"users"`
|
||||
Id string `json:"id"`
|
||||
Role string `json:"role"`
|
||||
CloudSync bool `json:"cloud_sync"`
|
||||
}
|
||||
|
||||
type AppAuthenticationStorage struct {
|
||||
|
||||
@@ -128,7 +128,7 @@ const App = (message, props) => {
|
||||
<Route exact path="/oauth2" render={props => <Oauth2 isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
||||
<Route exact path="/contact" render={props => <Contact isLoaded={isLoaded} globalUrl={globalUrl} {...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 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="/settings" render={props => <SettingsPage isLoaded={isLoaded} userdata={userdata} globalUrl={globalUrl} {...props} />} />
|
||||
<Route exact path="/AdminSetup" render={props => <AdminSetup isLoaded={isLoaded} userdata={userdata} globalUrl={globalUrl} {...props} />} />
|
||||
@@ -141,7 +141,7 @@ const App = (message, props) => {
|
||||
<Route exact path="/apps/edit/:appid" render={props => <AppCreator isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} {...props} />} />
|
||||
<Route exact path="/schedules/:key" render={props => <EditSchedule globalUrl={globalUrl} {...props} />} />
|
||||
<Route exact path="/workflows" render={props => <Workflows isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} cookies={cookies} {...props} />} />
|
||||
<Route exact path="/workflows/:key" render={props => <AngularWorkflow globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} {...props} />} />
|
||||
<Route exact path="/workflows/:key" render={props => <AngularWorkflow userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} {...props} />} />
|
||||
<Route exact path="/docs/:key" render={props => <Docs isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
||||
<Route exact path="/docs" render={props => { window.location.pathname = "/docs/about" }} />
|
||||
<Route exact path="/introduction" render={props => <Introduction isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
||||
|
||||
+158
-18
@@ -36,6 +36,12 @@ const Admin = (props) => {
|
||||
const [firstRequest, setFirstRequest] = React.useState(true);
|
||||
const [modalUser, setModalUser] = React.useState({});
|
||||
const [modalOpen, setModalOpen] = React.useState(false);
|
||||
|
||||
const [cloudSyncModalOpen, setCloudSyncModalOpen] = React.useState(false);
|
||||
const [cloudSyncApikey, setCloudSyncApikey] = React.useState("");
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
|
||||
const [selectedOrganization, setSelectedOrganization] = React.useState({});
|
||||
const [loginInfo, setLoginInfo] = React.useState("");
|
||||
const [curTab, setCurTab] = React.useState(0);
|
||||
const [users, setUsers] = React.useState([]);
|
||||
@@ -175,6 +181,41 @@ const Admin = (props) => {
|
||||
});
|
||||
}
|
||||
|
||||
const enableCloudSync = (apikey, organization) => {
|
||||
const data = {
|
||||
apikey: apikey,
|
||||
organization: organization,
|
||||
}
|
||||
|
||||
const url = globalUrl + '/api/v1/cloud/setup';
|
||||
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 => {
|
||||
setLoading(false)
|
||||
console.log(responseJson)
|
||||
if (responseJson["success"] === false) {
|
||||
alert.error("Failed setting up cloud sync")
|
||||
} else {
|
||||
alert.success("Set up cloud sync!")
|
||||
}
|
||||
}),
|
||||
)
|
||||
.catch(error => {
|
||||
setLoading(false)
|
||||
alert.error("Err: " + error.toString())
|
||||
});
|
||||
}
|
||||
|
||||
const onPasswordChange = () => {
|
||||
const data = { "username": selectedUser.username, "newpassword": newPassword }
|
||||
const url = globalUrl + '/api/v1/users/passwordchange';
|
||||
@@ -713,8 +754,69 @@ const Admin = (props) => {
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
const cloudSyncModal =
|
||||
<Dialog
|
||||
open={cloudSyncModalOpen}
|
||||
onClose={() => { setCloudSyncModalOpen(false) }}
|
||||
PaperProps={{
|
||||
style: {
|
||||
backgroundColor: theme.palette.surfaceColor,
|
||||
color: "white",
|
||||
minWidth: "800px",
|
||||
minHeight: "320px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle><span style={{ color: "white" }}>
|
||||
Enable cloud features
|
||||
</span></DialogTitle>
|
||||
<DialogContent>
|
||||
What does <a href="https://shuffler.io/docs/workflows#workflow_variables" target="_blank" style={{textDecoration: "none", color: "#f85a3e"}}>cloud sync</a> do?
|
||||
<div style={{marginTop: 5}}/>
|
||||
Cloud Apikey
|
||||
<div style={{display: "flex", marginBottom: 20, }}>
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{backgroundColor: theme.palette.inputColor, marginRight: 10, }}
|
||||
InputProps={{
|
||||
style: {
|
||||
height: "50px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
required
|
||||
fullWidth={true}
|
||||
autoComplete="cloud apikey"
|
||||
id="apikey_field"
|
||||
margin="normal"
|
||||
placeholder="Cloud Apikey"
|
||||
variant="outlined"
|
||||
onChange={(event) => {
|
||||
setCloudSyncApikey(event.target.value)
|
||||
}}
|
||||
/>
|
||||
<Button disabled={cloudSyncApikey.length === 0 || loading} variant="contained" style={{ marginLeft: 15, height: 60, margin: "auto", borderRadius: "0px" }} onClick={() => {
|
||||
setLoading(true)
|
||||
enableCloudSync(
|
||||
cloudSyncApikey,
|
||||
selectedOrganization,
|
||||
)
|
||||
}} color="primary">
|
||||
Test sync
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
* New triggers (userinput, hotmail realtime)<div/>
|
||||
* Execute in the cloud rather than onprem<div/>
|
||||
* Apps can be built in the cloud<div/>
|
||||
* Easily share apps and workflows<div/>
|
||||
* Access to powerful cloud search
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
const modalView =
|
||||
<Dialog modal
|
||||
<Dialog
|
||||
open={modalOpen}
|
||||
onClose={() => { setModalOpen(false) }}
|
||||
PaperProps={{
|
||||
@@ -1219,6 +1321,7 @@ const Admin = (props) => {
|
||||
</div>
|
||||
: null
|
||||
|
||||
console.log("Userdata: ", props.userdata)
|
||||
const organizationsTab = curTab === 5 ?
|
||||
<div>
|
||||
<div style={{marginTop: 20, marginBottom: 20,}}>
|
||||
@@ -1229,7 +1332,10 @@ const Admin = (props) => {
|
||||
style={{}}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => setModalOpen(true)}
|
||||
disabled
|
||||
onClick={() => {
|
||||
setModalOpen(true)
|
||||
}}
|
||||
>
|
||||
Add organization
|
||||
</Button>
|
||||
@@ -1241,28 +1347,61 @@ const Admin = (props) => {
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Orborus running (TBD)"
|
||||
primary="id"
|
||||
style={{minWidth: 200, maxWidth: 200}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Actions"
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary="Enabled"
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="false"
|
||||
style={{minWidth: 200, maxWidth: 200}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary=<Switch checked={false} onChange={() => {console.log("INVERT")}} />
|
||||
primary="Your role"
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Selected"
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Cloud Sync"
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
/>
|
||||
</ListItem>
|
||||
{props.userdata !== undefined && props.userdata.orgs !== null && props.userdata.orgs !== undefined && props.userdata.orgs.length > 0 ?
|
||||
<span>
|
||||
{props.userdata.orgs.map((data, index) => {
|
||||
const isSelected = props.userdata.selected_org === undefined ? "False" : props.userdata.selected_org.id === data.id ? "True" : "False"
|
||||
|
||||
return (
|
||||
<ListItem key={index}>
|
||||
<ListItemText
|
||||
primary={data.name}
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={data.id}
|
||||
style={{minWidth: 200, maxWidth: 200}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={data.role}
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={isSelected}
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary=<Switch checked={data.cloud_sync} onChange={() => {
|
||||
setCloudSyncModalOpen(true)
|
||||
setSelectedOrganization(data)
|
||||
console.log("INVERT CLOUD SYNC")
|
||||
}} />
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
/>
|
||||
</ListItem>
|
||||
)
|
||||
|
||||
})}
|
||||
</span>
|
||||
:
|
||||
null
|
||||
}
|
||||
</List>
|
||||
</div>
|
||||
: null
|
||||
@@ -1360,6 +1499,7 @@ const Admin = (props) => {
|
||||
return (
|
||||
<div>
|
||||
{modalView}
|
||||
{cloudSyncModal}
|
||||
{editUserModal}
|
||||
{editAuthenticationModal}
|
||||
{data}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user