From ab7436b5279d4e6bc9871219eaafef6a52d5fe49 Mon Sep 17 00:00:00 2001 From: frikky Date: Thu, 27 Aug 2020 20:23:51 +0200 Subject: [PATCH] Added sharing and fixed OpenAPI issue --- backend/go-app/codegen.go | 2 +- backend/go-app/main.go | 32 +++--- backend/go-app/walkoff.go | 177 ++++++++++++++++++++++++------- frontend/src/App.jsx | 3 +- frontend/src/views/Admin.jsx | 147 +++++++++++++++++++++++++ frontend/src/views/Apps.jsx | 148 +++++++++++++++++++++++--- frontend/src/views/Workflows.jsx | 53 ++++++--- 7 files changed, 474 insertions(+), 88 deletions(-) diff --git a/backend/go-app/codegen.go b/backend/go-app/codegen.go index 88644465..2b3be803 100644 --- a/backend/go-app/codegen.go +++ b/backend/go-app/codegen.go @@ -459,7 +459,7 @@ func generateYaml(swagger *openapi3.Swagger, newmd5 string) (*openapi3.Swagger, // Jesus what a clusterfuck. // Handles parsing of categories from OpenApi3 custom field if val, ok := swagger.Info.ExtensionProps.Extensions["x-categories"]; ok { - log.Printf("Categories: %#v", val) + //log.Printf("Categories: %#v", val) j, err := json.Marshal(&val) if err == nil { if j[0] == 0x22 && j[len(j)-1] == 0x22 { diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 7e96623f..75fba54f 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -141,7 +141,7 @@ type UserLimits struct { // Saves some data, not sure what to have here lol type UserAuth struct { - Description string `json:"description" datastore:"description" yaml:"description"` + Description string `json:"description" datastore:"description,noindex" yaml:"description"` Name string `json:"name" datastore:"name" yaml:"name"` Workflows []string `json:"workflows" datastore:"workflows"` Username string `json:"username" datastore:"username"` @@ -210,7 +210,7 @@ type Translator struct { Src struct { Name string `json:"name" datastore:"name"` Value string `json:"value" datastore:"value"` - Description string `json:"description" datastore:"description"` + Description string `json:"description" datastore:"description,noindex"` Required string `json:"required" datastore:"required"` Type string `json:"type" datastore:"type"` Schema struct { @@ -221,7 +221,7 @@ type Translator struct { Name string `json:"name" datastore:"name"` Value string `json:"value" datastore:"value"` Type string `json:"type" datastore:"type"` - Description string `json:"description" datastore:"description"` + Description string `json:"description" datastore:"description,noindex"` Required string `json:"required" datastore:"required"` Schema struct { Type string `json:"type" datastore:"type"` @@ -284,7 +284,7 @@ type ApiYaml struct { Name string `json:"name" yaml:"name" required:"true datastore:"name"` Foldername string `json:"foldername" yaml:"foldername" required:"true datastore:"foldername"` Id string `json:"id" yaml:"id",required:"true, datastore:"id"` - Description string `json:"description" datastore:"description" yaml:"description"` + Description string `json:"description" datastore:"description,noindex" yaml:"description"` AppVersion string `json:"app_version" yaml:"app_version",datastore:"app_version"` ContactInfo struct { Name string `json:"name" datastore:"name" yaml:"name"` @@ -293,10 +293,10 @@ type ApiYaml struct { Types []string `json:"types" datastore:"types" yaml:"types"` Input []struct { Name string `json:"name" datastore:"name" yaml:"name"` - Description string `json:"description" datastore:"description" yaml:"description"` + Description string `json:"description" datastore:"description,noindex" yaml:"description"` InputParameters []struct { Name string `json:"name" datastore:"name" yaml:"name"` - Description string `json:"description" datastore:"description" yaml:"description"` + Description string `json:"description" datastore:"description,noindex" yaml:"description"` Required string `json:"required" datastore:"required" yaml:"required"` Schema struct { Type string `json:"type" datastore:"type" yaml:"type"` @@ -304,7 +304,7 @@ type ApiYaml struct { } `json:"inputparameters" datastore:"inputparameters" yaml:"inputparameters"` OutputParameters []struct { Name string `json:"name" datastore:"name" yaml:"name"` - Description string `json:"description" datastore:"description" yaml:"description"` + Description string `json:"description" datastore:"description,noindex" yaml:"description"` Required string `json:"required" datastore:"required" yaml:"required"` Schema struct { Type string `json:"type" datastore:"type" yaml:"type"` @@ -312,7 +312,7 @@ type ApiYaml struct { } `json:"outputparameters" datastore:"outputparameters" yaml:"outputparameters"` Config []struct { Name string `json:"name" datastore:"name" yaml:"name"` - Description string `json:"description" datastore:"description" yaml:"description"` + Description string `json:"description" datastore:"description,noindex" yaml:"description"` Required string `json:"required" datastore:"required" yaml:"required"` Schema struct { Type string `json:"type" datastore:"type" yaml:"type"` @@ -321,10 +321,10 @@ type ApiYaml struct { } `json:"input" datastore:"input" yaml:"input"` Output []struct { Name string `json:"name" datastore:"name" yaml:"name"` - Description string `json:"description" datastore:"description" yaml:"description"` + Description string `json:"description" datastore:"description,noindex" yaml:"description"` Config []struct { Name string `json:"name" datastore:"name" yaml:"name"` - Description string `json:"description" datastore:"description" yaml:"description"` + Description string `json:"description" datastore:"description,noindex" yaml:"description"` Required string `json:"required" datastore:"required" yaml:"required"` Schema struct { Type string `json:"type" datastore:"type" yaml:"type"` @@ -332,7 +332,7 @@ type ApiYaml struct { } `json:"config" datastore:"config" yaml:"config"` InputParameters []struct { Name string `json:"name" datastore:"name" yaml:"name"` - Description string `json:"description" datastore:"description" yaml:"description"` + Description string `json:"description" datastore:"description,noindex" yaml:"description"` Required string `json:"required" datastore:"required" yaml:"required"` Schema struct { Type string `json:"type" datastore:"type" yaml:"type"` @@ -340,7 +340,7 @@ type ApiYaml struct { } `json:"inputparameters" datastore:"inputparameters" yaml:"inputparameters"` OutputParameters []struct { Name string `json:"name" datastore:"name" yaml:"name"` - Description string `json:"description" datastore:"description" yaml:"description"` + Description string `json:"description" datastore:"description,noindex" yaml:"description"` Required string `json:"required" datastore:"required" yaml:"required"` Schema struct { Type string `json:"type" datastore:"type" yaml:"type"` @@ -357,7 +357,7 @@ type Hooks struct { type Info struct { Url string `json:"url" datastore:"url"` Name string `json:"name" datastore:"name"` - Description string `json:"description" datastore:"description"` + Description string `json:"description" datastore:"description,noindex"` } // Actions to be done by webhooks etc @@ -1697,10 +1697,11 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { "success": true, "admin": %s, "tutorials": [], + "id": "%s", "orgs": [{"name": "Shuffle", "id": "123", "role": "admin"}], "selected_org": {"name": "Shuffle", "id": "123", "role": "admin"}, "cookies": [{"key": "session_token", "value": "%s", "expiration": %d}] - }`, parsedAdmin, userInfo.Session, expiration.Unix()) + }`, parsedAdmin, userInfo.Id, userInfo.Session, expiration.Unix()) resp.WriteHeader(200) resp.Write([]byte(returnData)) @@ -2505,7 +2506,7 @@ func handleCors(resp http.ResponseWriter, request *http.Request) bool { resp.Header().Set("Vary", "Origin") resp.Header().Set("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With, remember-me") - resp.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE") + resp.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, PATCH") resp.Header().Set("Access-Control-Allow-Credentials", "true") resp.Header().Set("Access-Control-Allow-Origin", allowedOrigins) @@ -6497,6 +6498,7 @@ func init() { r.HandleFunc("/api/v1/apps/run_hotload", handleAppHotloadRequest).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/apps/get_existing", loadSpecificApps).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/apps/download_remote", loadSpecificApps).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/apps/{appId}", updateWorkflowAppConfig).Methods("PATCH", "OPTIONS") r.HandleFunc("/api/v1/apps/validate", validateAppInput).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/apps/{appId}", deleteWorkflowApp).Methods("DELETE", "OPTIONS") r.HandleFunc("/api/v1/apps/{appId}/config", getWorkflowAppConfig).Methods("GET", "OPTIONS") diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index a72d6a32..d9fae1ba 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -96,25 +96,26 @@ type AuthenticationUsage struct { // An app inside Shuffle // Source string `json:"source" datastore:"soure" yaml:"source"` - downloadlocation type WorkflowApp struct { - Name string `json:"name" yaml:"name" required:true datastore:"name"` - IsValid bool `json:"is_valid" yaml:"is_valid" required:true datastore:"is_valid"` - ID string `json:"id" yaml:"id,omitempty" required:false datastore:"id"` - Link string `json:"link" yaml:"link" required:false datastore:"link,noindex"` - AppVersion string `json:"app_version" yaml:"app_version" required:true datastore:"app_version"` - Generated bool `json:"generated" yaml:"generated" required:false datastore:"generated"` - Downloaded bool `json:"downloaded" yaml:"downloaded" required:false datastore:"downloaded"` - Sharing bool `json:"sharing" yaml:"sharing" required:false datastore:"sharing"` - Verified bool `json:"verified" yaml:"verified" required:false datastore:"verified"` - Activated bool `json:"activated" yaml:"activated" required:false datastore:"activated"` - Tested bool `json:"tested" yaml:"tested" required:false datastore:"tested"` - Owner string `json:"owner" datastore:"owner" yaml:"owner"` - Hash string `json:"hash" datastore:"hash" yaml:"hash"` // api.yaml+dockerfile+src/app.py for apps - PrivateID string `json:"private_id" yaml:"private_id" required:false datastore:"private_id"` - Description string `json:"description" datastore:"description,noindex" required:false yaml:"description"` - Environment string `json:"environment" datastore:"environment" required:true yaml:"environment"` - SmallImage string `json:"small_image" datastore:"small_image,noindex" required:false yaml:"small_image"` - LargeImage string `json:"large_image" datastore:"large_image,noindex" yaml:"large_image" required:false` - ContactInfo struct { + Name string `json:"name" yaml:"name" required:true datastore:"name"` + IsValid bool `json:"is_valid" yaml:"is_valid" required:true datastore:"is_valid"` + ID string `json:"id" yaml:"id,omitempty" required:false datastore:"id"` + Link string `json:"link" yaml:"link" required:false datastore:"link,noindex"` + AppVersion string `json:"app_version" yaml:"app_version" required:true datastore:"app_version"` + SharingConfig string `json:"sharing_config" yaml:"sharing_config" datastore:"sharing_config"` + Generated bool `json:"generated" yaml:"generated" required:false datastore:"generated"` + Downloaded bool `json:"downloaded" yaml:"downloaded" required:false datastore:"downloaded"` + Sharing bool `json:"sharing" yaml:"sharing" required:false datastore:"sharing"` + Verified bool `json:"verified" yaml:"verified" required:false datastore:"verified"` + Activated bool `json:"activated" yaml:"activated" required:false datastore:"activated"` + Tested bool `json:"tested" yaml:"tested" required:false datastore:"tested"` + Owner string `json:"owner" datastore:"owner" yaml:"owner"` + Hash string `json:"hash" datastore:"hash" yaml:"hash"` // api.yaml+dockerfile+src/app.py for apps + PrivateID string `json:"private_id" yaml:"private_id" required:false datastore:"private_id"` + Description string `json:"description" datastore:"description,noindex" required:false yaml:"description"` + Environment string `json:"environment" datastore:"environment" required:true yaml:"environment"` + SmallImage string `json:"small_image" datastore:"small_image,noindex" required:false yaml:"small_image"` + LargeImage string `json:"large_image" datastore:"large_image,noindex" yaml:"large_image" required:false` + ContactInfo struct { Name string `json:"name" datastore:"name" yaml:"name"` Url string `json:"url" datastore:"url" yaml:"url"` } `json:"contact_info" datastore:"contact_info" yaml:"contact_info" required:false` @@ -144,7 +145,7 @@ type SchemaDefinition struct { } type WorkflowAppAction struct { - Description string `json:"description" datastore:"description"` + Description string `json:"description" datastore:"description,noindex"` ID string `json:"id" datastore:"id" yaml:"id,omitempty"` Name string `json:"name" datastore:"name"` Label string `json:"label" datastore:"label"` @@ -157,7 +158,7 @@ type WorkflowAppAction struct { Tested bool `json:"tested" datastore:"tested" yaml:"tested"` Parameters []WorkflowAppActionParameter `json:"parameters" datastore: "parameters"` ExecutionVariable struct { - Description string `json:"description" datastore:"description"` + Description string `json:"description" datastore:"description,noindex"` ID string `json:"id" datastore:"id"` Name string `json:"name" datastore:"name"` Value string `json:"value" datastore:"value"` @@ -192,7 +193,7 @@ type WorkflowExecution struct { Workflow Workflow `json:"workflow" datastore:"workflow,noindex"` Results []ActionResult `json:"results" datastore:"results,noindex"` ExecutionVariables []struct { - Description string `json:"description" datastore:"description"` + Description string `json:"description" datastore:"description,noindex"` ID string `json:"id" datastore:"id"` Name string `json:"name" datastore:"name"` Value string `json:"value" datastore:"value,noindex"` @@ -217,7 +218,7 @@ type Action struct { Name string `json:"name" datastore:"name"` Parameters []WorkflowAppActionParameter `json:"parameters" datastore: "parameters,noindex"` ExecutionVariable struct { - Description string `json:"description" datastore:"description"` + Description string `json:"description" datastore:"description,noindex"` ID string `json:"id" datastore:"id"` Name string `json:"name" datastore:"name"` Value string `json:"value" datastore:"value,noindex"` @@ -235,7 +236,7 @@ type Action struct { // Added environment for location to execute type Trigger struct { AppName string `json:"app_name" datastore:"app_name"` - Description string `json:"description" datastore:"description"` + Description string `json:"description" datastore:"description,noindex"` LongDescription string `json:"long_description" datastore:"long_description"` Status string `json:"status" datastore:"status"` AppVersion string `json:"app_version" datastore:"app_version"` @@ -294,20 +295,20 @@ type Workflow struct { ID string `json:"id" datastore:"id"` IsValid bool `json:"is_valid" datastore:"is_valid"` Name string `json:"name" datastore:"name"` - Description string `json:"description" datastore:"description"` + Description string `json:"description" datastore:"description,noindex"` Start string `json:"start" datastore:"start"` Owner string `json:"owner" datastore:"owner"` Sharing string `json:"sharing" datastore:"sharing"` Org []Org `json:"org,omitempty" datastore:"org"` ExecutingOrg Org `json:"execution_org,omitempty" datastore:"execution_org"` WorkflowVariables []struct { - Description string `json:"description" datastore:"description"` + Description string `json:"description" datastore:"description,noindex"` ID string `json:"id" datastore:"id"` Name string `json:"name" datastore:"name"` Value string `json:"value" datastore:"value"` } `json:"workflow_variables" datastore:"workflow_variables"` ExecutionVariables []struct { - Description string `json:"description" datastore:"description"` + Description string `json:"description" datastore:"description,noindex"` ID string `json:"id" datastore:"id"` Name string `json:"name" datastore:"name"` Value string `json:"value" datastore:"value,noindex"` @@ -330,7 +331,7 @@ type Authentication struct { } type AuthenticationParams struct { - Description string `json:"description" datastore:"description" yaml:"description"` + Description string `json:"description" datastore:"description,noindex" yaml:"description"` ID string `json:"id" datastore:"id" yaml:"id"` Name string `json:"name" datastore:"name" yaml:"name"` Example string `json:"example" datastore:"example" yaml:"example"` @@ -3408,9 +3409,6 @@ func getWorkflowAppConfig(resp http.ResponseWriter, request *http.Request) { return } - // log.Printf("%#v", parsedApi) - // log.Printf("API LEN: %d, ID: %s", len(parsedApi.Body), fileId) - //log.Printf("Parsed API: %#v", parsedApi) if len(parsedApi.ID) > 0 { parsedApi.Success = true @@ -3629,6 +3627,90 @@ func getAppAuthentication(resp http.ResponseWriter, request *http.Request) { }` */ } +func updateWorkflowAppConfig(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, userErr := handleApiAuthentication(resp, request) + if userErr != nil { + log.Printf("Api authentication failed in get all apps: %s", userErr) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + location := strings.Split(request.URL.String(), "/") + var fileId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + fileId = location[4] + } + + ctx := context.Background() + app, err := getApp(ctx, fileId) + if err != nil { + log.Printf("Error getting app: %s (update app)", app.Name) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if user.Id != app.Owner && user.Role != "admin" { + log.Printf("Wrong user (%s) for app %s in update app", user.Username, app.Name) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Printf("Error with body read in update app: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + type updatefields struct { + Sharing bool `json:"sharing"` + SharingConfig string `json:"sharing_config"` + } + + var tmpfields updatefields + err = json.Unmarshal(body, &tmpfields) + if err != nil { + log.Printf("Error with unmarshal body in update app: %s\n%s", err, string(body)) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if tmpfields.Sharing != app.Sharing { + app.Sharing = tmpfields.Sharing + } + + if tmpfields.SharingConfig != app.SharingConfig { + app.SharingConfig = tmpfields.SharingConfig + } + + err = setWorkflowAppDatastore(ctx, *app, app.ID) + if err != nil { + log.Printf("Failed patching workflowapp: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + log.Printf("Changed workflow app %s", app.ID) + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) +} func getWorkflowApps(resp http.ResponseWriter, request *http.Request) { cors := handleCors(resp, request) @@ -3691,12 +3773,12 @@ func getWorkflowApps(resp http.ResponseWriter, request *http.Request) { newapps := []WorkflowApp{} baseApps := []WorkflowApp{} - if len(user.PrivateApps) > 0 { - newapps = append(newapps, user.PrivateApps...) - } - for _, workflowapp := range workflowapps { - if !workflowapp.Sharing { + if !workflowapp.Activated && workflowapp.Generated { + continue + } + + if workflowapp.Owner != user.Id && user.Role != "admin" && !workflowapp.Sharing { continue } @@ -3716,6 +3798,25 @@ func getWorkflowApps(resp http.ResponseWriter, request *http.Request) { baseApps = append(baseApps, workflowapp) } + if len(user.PrivateApps) > 0 { + found := false + for _, item := range user.PrivateApps { + for _, app := range newapps { + if item.ID == app.ID { + found = true + break + } + } + + if !found { + newapps = append(newapps, item) + } + } + } + + log.Printf("User: %d", len(user.PrivateApps)) + log.Printf("All: %d", len(workflowapps)) + // Double unmarshal because of user apps newbody, err := json.Marshal(newapps) //newbody, err := json.Marshal(workflowapps) @@ -4381,14 +4482,14 @@ func iterateOpenApiGithub(fs billy.Filesystem, dir []os.FileInfo, extra string, readFile, err := ioutil.ReadAll(fileReader) if err != nil { - log.Printf("Filereader error yaml: %s", err) + log.Printf("Filereader error yaml for %s: %s", filename, err) continue } // 1. This parses OpenAPI v2 to v3 etc, for use. parsedOpenApi, err := handleSwaggerValidation(readFile) if err != nil { - log.Printf("Validation error: %s", err) + log.Printf("Validation error for %s: %s", filename, err) continue } diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index f7a408d1..ea4224f0 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -129,13 +129,14 @@ const App = (message, props) => { } /> } /> } /> + } /> } /> } /> } /> } /> } /> } /> - } /> + } /> } /> } /> } /> diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index ae1756c3..640c46e8 100644 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -49,6 +49,71 @@ const Admin = (props) => { const [selectedAuthenticationModalOpen, setSelectedAuthenticationModalOpen] = React.useState(false) const [showArchived, setShowArchived] = React.useState(false) + const getApps = () => { + fetch(globalUrl+"/api/v1/workflows/apps", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for apps :O!") + } + + return response.json() + }) + .then((responseJson) => { + console.log("apps: ", responseJson) + //setApps(responseJson) + //setFilteredApps(responseJson) + //if (responseJson.length > 0) { + // setSelectedApp(responseJson[0]) + // if (responseJson[0].actions !== null && responseJson[0].actions.length > 0) { + // setSelectedAction(responseJson[0].actions[0]) + // } else { + // setSelectedAction({}) + // } + //} + }) + .catch(error => { + alert.error(error.toString()) + }); + } + + const categories = [ + { + "name": "Ticketing", + "apps": [ + "TheHive", + "Service-Now", + "SecureWorks", + ], + "categories": ["tickets", "ticket", "ticketing"] + }, + ] + /* + "SIEM", + "Active Directory", + "Firewalls", + "Proxies web", + "SIEM", + "SOAR", + "Mail", + "EDR", + "AV", + "MDM/MAM", + "DNS", + "Ticketing platform", + "TIP", + "Communication", + "DDOS protection", + "VMS", + ] + */ + const alert = useAlert() const deleteAuthentication = (data) => { @@ -905,6 +970,82 @@ const Admin = (props) => { : null + const appCategoryView = curTab === 6 ? +
+
+

Categories

+ + Categories are the categories supported by Shuffle, which are mapped to apps and workflows + +
+ + + + + + + + + + {categories.map(data => { + if (data.apps.length === 0) { + return null + } + + return ( + + + + + + + + + + ) + })} + +
+ : null + const authenticationView = curTab === 1 ?
@@ -1182,6 +1323,10 @@ const Admin = (props) => { getSchedules() } + if (newValue === 6) { + console.log("Should get apps for categories.") + } + setModalUser({}) setCurTab(newValue) } @@ -1202,10 +1347,12 @@ const Admin = (props) => { Schedules /> {window.location.protocol == "http:" && window.location.port === "3000" ? Hybrid/> : null} {window.location.protocol == "http:" && window.location.port === "3000" ? Organizations/> : null} + {window.location.protocol === "http:" && window.location.port === "3000" ? Categories/> : null}
{authenticationView} + {appCategoryView} {usersView} {environmentView} {schedulesView} diff --git a/frontend/src/views/Apps.jsx b/frontend/src/views/Apps.jsx index 3ae8b7db..1bdbb68c 100644 --- a/frontend/src/views/Apps.jsx +++ b/frontend/src/views/Apps.jsx @@ -112,6 +112,7 @@ const Apps = (props) => { const [openApiError, setOpenApiError] = React.useState("") const [field1, setField1] = React.useState("") const [field2, setField2] = React.useState("") + const [sharingConfiguration, setSharingConfiguration] = React.useState("you") const { start, stop } = useInterval({ duration: 5000, @@ -134,6 +135,24 @@ const Apps = (props) => { } }) + function sortByKey(array, key) { + return array.sort(function(a, b) { + var x = a[key]; + var y = b[key]; + + if (typeof x == "string") + { + x = (""+x).toLowerCase(); + } + if (typeof y == "string") + { + y = (""+y).toLowerCase(); + } + + return ((x < y) ? 1 : ((x > y) ? -1 : 0)); + }); + } + const appViewStyle = { color: "#ffffff", width: "100%", @@ -169,6 +188,8 @@ const Apps = (props) => { return response.json() }) .then((responseJson) => { + responseJson = sortByKey(responseJson, "large_image") + setApps(responseJson) setFilteredApps(responseJson) if (responseJson.length > 0) { @@ -290,12 +311,18 @@ const Apps = (props) => { { if (selectedApp.id !== data.id) { setSelectedApp(data) + console.log(data) if (data.actions !== undefined && data.actions !== null && data.actions.length > 0) { setSelectedAction(data.actions[0]) } else { setSelectedAction({}) } + + console.log("Sharing: ", data.sharing_config) + if (data.sharing) { + setSharingConfiguration("everyone") + } } }}> @@ -314,9 +341,20 @@ const Apps = (props) => { {description}
- - Sharing: {sharing} - , Valid: {valid} + + {data.tags === null || data.tags === undefined ? null : data.tags.map((tag, index) => { + if (index >= 3) { + return null + } + + return ( + + ) + })} @@ -324,7 +362,7 @@ const Apps = (props) => { {data.activated && data.private_id !== undefined && data.private_id.length > 0 && data.generated ? {downloadApp(data)}}> - + @@ -450,6 +488,7 @@ const Apps = (props) => { ) })} + {/* { displayDataTypes={true} name={"Example return value"} /> + */}
) } return ( -
+
Example return
{selectedAction.returns.example}
) } - //fetch(globalUrl+"/api/v1/get_openapi/"+urlParams.get("id"), { + const userRoles = [ + "you", + "everyone", + ] + + //fetch(globalUrl+"/api/v1/get_openapi/"+urlParams.get("id"), + console.log("User: ", props.userdata) var baseInfo = newAppname.length > 0 ?
@@ -482,15 +528,23 @@ const Apps = (props) => {
{activateButton} - {downloadButton} - {editButton} - {deleteButton} + {props.userdata.role === "admin" || props.userdata.id === selectedApp.owner ? +
+ {downloadButton} + {editButton} + {deleteButton} +
+ : null} {selectedApp.tags !== undefined && selectedApp.tags !== null ?
- {selectedApp.tags.map(tag => { + {selectedApp.tags.map((tag, index) => { + if (index >= 3) { + return null + } + return ( @@ -498,11 +552,41 @@ const Apps = (props) => { })}
: null} - - {selectedApp.link.length > 0 ?

URL: {selectedApp.link}

: null} -

ID: {selectedApp.id}

+ {props.userdata.id === selectedApp.owner ? +
+ {/*

ID: {selectedApp.id}

*/} + Sharing: + +
+ : null} + {/*

Owner: {selectedApp.owner}

*/} {selectedApp.privateId !== undefined && selectedApp.privateId.length > 0 ?

PrivateID: {selectedApp.privateId}

: null} - + + {selectedApp.link.length > 0 ?

URL: {selectedApp.link}

: null}
Actions {selectedApp.actions !== null && selectedApp.actions.length > 0 ? @@ -884,6 +968,36 @@ const Apps = (props) => { }); } + const updateAppField = (app_id, fieldname, fieldvalue) => { + const data = {} + data[fieldname] = fieldvalue + + fetch(globalUrl+"/api/v1/apps/"+app_id, { + method: 'PATCH', + headers: { + 'Accept': 'application/json', + }, + body: JSON.stringify(data), + credentials: "include", + }) + .then((response) => { + //setAppSearchLoading(false) + return response.json() + }) + .then((responseJson) => { + //console.log(responseJson) + //alert.info(responseJson) + if (responseJson.success) { + alert.info("Success") + } else { + alert.error("Error updating app") + } + }) + .catch(error => { + alert.error(error.toString()) + }); + } + const runAppSearch = (searchterm) => { const data = {"search": searchterm} @@ -1165,11 +1279,13 @@ const Apps = (props) => { placeholder="OpenAPI URI" fullWidth /> + {/*
Example:
https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v2.0/json/uber.json -

or paste the yaml/JSON directly below

+ */} +

Or paste the yaml/JSON directly below

{ marginTop: "10px", overflow: "scroll", height: "90%", - overflowX: "auto", + overflowX: "hidden", overflowY: "auto", } @@ -212,6 +213,8 @@ const Workflows = (props) => { marginTop: "5px", color: "white", backgroundColor: surfaceColor, + borderRadius: 5, + padding: 10, cursor: "pointer", display: "flex", } @@ -241,6 +244,8 @@ const Workflows = (props) => { setWorkflowExecutions(responseJson) } else { alert.info("Couldn't find executions for the workflow") + setSelectedExecution({}) + setWorkflowExecutions([]) } } }) @@ -393,6 +398,7 @@ const Workflows = (props) => { } // dropdown with copy etc I guess + console.log("Why reset?") const WorkflowPaper = (props) => { const { data } = props; const [open, setOpen] = React.useState(false); @@ -419,7 +425,6 @@ const Workflows = (props) => { var webhookImg = "" var scheduleImg = "" if (data.triggers !== undefined && data.triggers !== null && data.triggers.length > 0) { - console.log("Triggers: ", data.triggers) for (var key in data.triggers) { if (data.triggers[key].app_name === "Webhook") { webhooks += 1 @@ -434,14 +439,9 @@ const Workflows = (props) => { const imgSize = 25 return ( { - }}> - - - {webhooks > 0 ? {data.title} : null} - {schedules > 0 ? {data.title} : null} - -
- + }}> +
+
{ @@ -450,9 +450,11 @@ const Workflows = (props) => { getWorkflowExecution(data.id) } }}> -

{data.name}

+ + {data.name} +
-
+
{ getWorkflowExecution(data.id) } }}> - + + + + {data.tags !== undefined ? data.tags.map(tag => { @@ -533,6 +537,22 @@ const Workflows = (props) => {
+ + + + + + {webhooks > 0 ? + + {data.title} + + : null} + {schedules > 0 ? + + {data.title} + + : null} + ) } @@ -938,7 +958,6 @@ const Workflows = (props) => { setLoadWorkflowsModalOpen(false) } - console.log("WOrkflowtags: ", newWorkflowTags) const modalView = modalOpen ?