From a9cce3e4e8280cf6935974da076e5c9ae6f90f9e Mon Sep 17 00:00:00 2001 From: frikky Date: Sat, 7 Nov 2020 10:35:56 +0100 Subject: [PATCH] Added organizational EDIT possibility --- backend/go-app/main.go | 127 ++++++++++++++++++++++++++- backend/go-app/walkoff.go | 3 +- frontend/src/views/Admin.jsx | 162 ++++++++++++++++++++++++++++------- 3 files changed, 258 insertions(+), 34 deletions(-) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 413d3ec8..4fa7b193 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -72,7 +72,9 @@ var gceProject = "shuffle" var bucketName = "shuffler.appspot.com" var baseAppPath = "/home/frikky/git/shaffuru/tmp/apps" var baseDockerName = "frikky/shuffle" -var syncUrl = "http://192.168.3.6:5002" + +//var syncUrl = "http://192.168.102.54:5002" +var syncUrl = "http://localhost:5002" var dbclient *datastore.Client @@ -2825,6 +2827,11 @@ func fixUserOrg(ctx context.Context, user *User) *User { } if userFound { + user.PrivateApps = []WorkflowApp{} + user.Executions = ExecutionInfo{} + user.Limits = UserLimits{} + user.Authentication = []UserAuth{} + org.Users[orgIndex] = *user } else { org.Users = append(org.Users, *user) @@ -7500,6 +7507,123 @@ func handleStopCloudSync(syncUrl string, org Org) error { return nil } +func handleEditOrg(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 cloud setup: %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 { + Image string `json:"image" datastore:"image"` + Name string `json:"name" datastore:"name"` + Description string `json:"description" datastore:"description"` + OrgId string `json:"org_id" datastore:"org_id"` + } + + 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 + } + + var fileId string + location := strings.Split(request.URL.String(), "/") + if location[1] == "api" { + if len(location) <= 4 { + log.Printf("Path too short: %d", len(location)) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + fileId = location[4] + } + + if tmpData.OrgId != user.ActiveOrg.Id || fileId != user.ActiveOrg.Id { + log.Printf("User can't edit the org") + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "No permission to edit this org"}`)) + return + } + + ctx := context.Background() + org, err := getOrg(ctx, tmpData.OrgId) + if err != nil { + log.Printf("Organization doesn't exist: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + admin := false + userFound := false + for _, inneruser := range org.Users { + if inneruser.Id == user.Id { + userFound = true + if inneruser.Role == "admin" { + admin = true + } + + break + } + } + + if !userFound { + log.Printf("User %s doesn't exist in organization for edit %s", user.Id, org.Id) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if !admin { + log.Printf("User %s doesn't have edit rights to %s", user.Id, org.Id) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + org.Image = tmpData.Image + org.Name = tmpData.Name + org.Description = tmpData.Description + //log.Printf("Org: %#v", org) + err = setOrg(ctx, *org, org.Id) + if err != nil { + log.Printf("User %s doesn't have edit rights to %s", user.Id, org.Id) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "Successfully updated org"}`))) + +} + // INFO: https://docs.google.com/drawings/d/1JJebpPeEVEbmH_qsAC6zf9Noygp7PytvesrkhE19QrY/edit /* This is here to both enable and disable cloud sync features for an organization @@ -8023,6 +8147,7 @@ func initHandlers() { r.HandleFunc("/api/v1/cloud/setup", handleCloudSetup).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/orgs", handleGetOrgs).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/orgs/{orgId}", handleGetOrg).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/orgs/{orgId}", handleEditOrg).Methods("POST", "OPTIONS") //r.HandleFunc("/api/v1/orgs/{orgId}", handleEditOrg).Methods("POST", "OPTIONS") // Important for email, IDS etc. Create this by: diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 6ce5a77b..5315c75b 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -81,6 +81,7 @@ type SyncFeatures struct { type SyncData struct { Active bool `json:"active" datastore:"active"` + Type string `json:"type" datastore:"type"` Name string `json:"name" datastore:"name"` Description string `json:"description" datastore:"description"` Limit int64 `json:"limit" datastore:"limit"` @@ -95,7 +96,7 @@ type SyncConfig struct { type Org struct { Name string `json:"name" datastore:"name"` Description string `json:"description" datastore:"description"` - Image string `json:"image" datastore:"image"` + Image string `json:"image" datastore:"image,noindex"` Id string `json:"id" datastore:"id"` Org string `json:"org" datastore:"org"` Users []User `json:"users" datastore:"users"` diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 817a20e2..1e65e5b2 100644 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -30,6 +30,7 @@ import { useAlert } from "react-alert"; import { Dialog, DialogTitle, DialogActions, DialogContent } from '@material-ui/core'; import { useTheme } from '@material-ui/core/styles'; +import HandlePayment from './HandlePayment' import PolymerIcon from '@material-ui/icons/Polymer'; import CheckCircleIcon from '@material-ui/icons/CheckCircle'; @@ -253,7 +254,7 @@ const Admin = (props) => { if (disableSync) { alert.success("Successfully disabled sync!") } else { - alert.success("Sync successfully set up!") + alert.success("Cloud Syncronization successfully set up!") } selectedOrganization.cloud_sync = !selectedOrganization.cloud_sync @@ -269,6 +270,51 @@ const Admin = (props) => { }) } + const orgSaveButton = + + + const handleEditOrg = (name, description, orgId, image) => { + const data = { + "name": name, + "description": description, + "org_id": orgId, + "image": image, + } + + const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`; + 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) { + alert.error("Failed updating org") + } else { + alert.success("Successfully edited org!") + setSelectedUserModalOpen(false) + } + }), + ) + .catch(error => { + alert.error("Err: " + error.toString()) + }); + } + const onPasswordChange = () => { const data = { "username": selectedUser.username, "newpassword": newPassword } const url = globalUrl + '/api/v1/users/passwordchange'; @@ -360,9 +406,13 @@ const Admin = (props) => { } Object.keys(responseJson.sync_features).map(function(key, index) { - console.log(responseJson.sync_features[key]) + //console.log(responseJson.sync_features[key]) }) + + if (responseJson.image !== undefined && responseJson.image !== null && responseJson.image.length > 0) { + setFileBase64(responseJson.image) + } setOrganizationFeatures(lists) } }), @@ -668,6 +718,22 @@ const Admin = (props) => { if (firstRequest) { setFirstRequest(false) getUsers() + + const views = { + "organization": 0, + "users": 1, + "app_auth": 2, + "environments": 3, + "schedules": 4, + "categories": 5, + } + + if (props.match.params.key !== undefined) { + const tmpitem = views[props.match.params.key] + if (tmpitem !== undefined) { + setCurTab(tmpitem) + } + } } if (selectedOrganization.id === undefined && userdata !== undefined && userdata.active_org !== undefined) { @@ -1044,25 +1110,33 @@ const Admin = (props) => { const canvasUrl = canvas.toDataURL() if (canvasUrl !== fileBase64) { - //console.log("SET URL TO: ", canvasUrl) setFileBase64(canvasUrl) + selectedOrganization.image = canvasUrl + setSelectedOrganization(selectedOrganization) } } } - const imageData = file.length > 0 ? file : fileBase64 + if (imageData !== undefined && imageData.length === 0) { + //imageData + } + const imageInfo = const organizationView = curTab === 0 ?
- Organization overview - Click here to learn more about organization editing +
+

Organization overview

+ + On this page you can configure individual parts of your organization.Learn more. + +
{selectedOrganization.id === undefined ? null :
-
{upload.click()}}> +
0 ? null : "1px solid #f85a3e", cursor: "pointer", backgroundColor: imageData !== undefined && imageData.length > 0 ? null : theme.palette.inputColor, maxWidth: 174, maxHeight: 174}} onClick={() => {upload.click()}}> upload = ref} onChange={editHeaderImage} /> {imageInfo}
@@ -1111,32 +1185,38 @@ const Admin = (props) => { />
Description - { - selectedOrganization.description = e.target.value - setSelectedOrganization(selectedOrganization) - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style:{ - color: "white", - }, - }} - /> +
+ { + selectedOrganization.description = e.target.value + setSelectedOrganization(selectedOrganization) + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style:{ + color: "white", + }, + }} + /> +
+ {orgSaveButton} +
+
+ Cloud syncronization What does cloud sync do? Cloud syncronization is a way of getting more out of Shuffle. Shuffle will ALWAYS make every option open source, but features relying on other users can't be done without a collaborative approach.
@@ -1582,9 +1662,9 @@ const Admin = (props) => { primary="Actions" /> - {authentication === undefined ? null : authentication.map(data => { + {authentication === undefined ? null : authentication.map((data, index) => { return ( - + style={{minWidth: 150, maxWidth: 150}} @@ -1869,6 +1949,24 @@ const Admin = (props) => { console.log("Should get apps for categories.") } + const views = { + 0: "organization", + 1: "users", + 2: "app_auth", + 3: "environments", + 4: "schedules", + 5: "categories", + } + + //var theURL = window.location.pathname + //FIXME: Add url edits + //var theURL = window.location + //theURL.replace(`/${views[curTab]}`, `/${views[newValue]}`) + //window.history.pushState({"html":response.html,"pageTitle":response.pageTitle},"", urlPath); + + //console.log(newpath) + //window.location.pathame = newpath + setModalUser({}) setCurTab(newValue) }