Started adding optional cloud features

This commit is contained in:
frikky
2020-09-18 20:13:52 +02:00
parent 266e8143fa
commit 08c0f9b64f
5 changed files with 244 additions and 37 deletions
+69 -7
View File
@@ -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)
}
+7 -4
View File
@@ -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 {
+2 -2
View File
@@ -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
View File
@@ -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