From 4855b549376c57ddebc0655089ee07a513e0d14b Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Wed, 13 Nov 2024 13:29:50 +0530 Subject: [PATCH 1/6] added the basic ui structure for now --- frontend/src/App.jsx | 16 + frontend/src/views/Apps2.jsx | 562 +++++++++++++++++++++++++++++++++++ 2 files changed, 578 insertions(+) create mode 100644 frontend/src/views/Apps2.jsx diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 7349952b..c4d118d9 100755 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -14,6 +14,7 @@ import HealthPage from "./components/HealthPage.jsx"; //import Header from "./components/Header.jsx"; import theme from "./theme"; import Apps from "./views/Apps"; +import Apps2 from "./views/Apps2.jsx"; import AppCreator from "./views/AppCreator"; import DetectionDashBoard from "./views/DetectionDashboard.jsx"; @@ -408,6 +409,21 @@ const App = (message, props) => { {...props} /> } + /> + + } /> { + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; + const appUrl = isCloud ? `/apps/${data.id}` : `https://shuffler.io/apps/${data.id}`; + + const paperStyle = { + backgroundColor: mouseHoverIndex === index ? "rgba(26, 26, 26, 1)" : "#1A1A1A", + color: "rgba(241, 241, 241, 1)", + cursor: "pointer", + position: "relative", + width: 365, + height: 96, + borderRadius: 8, + boxShadow: "0px 0px 10px 0px rgba(0, 0, 0, 0.1)", + marginBottom: 20, + }; + + return ( + + + setMouseHoverIndex(index)} onMouseOut={() => setMouseHoverIndex(-1)}> + + {data.name} +
+
+ {data.name.replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase())} +
+
+ {data.categories ? data.categories.join(", ") : "NA"} +
+
+
+ {data.generated !== true && data.tags && data.tags.slice(0, 2).map((tag, tagIndex) => ( + + {tag} + {tagIndex < data.tags.length - 1 ? ", " : ""} + + ))} +
+ {/* Deactivate button logic */} + {data.generated === true ? ( + + ) : null} +
+
+
+
+
+
+ ); +}; + +const Apps2 = (props) => { + const { globalUrl, isLoaded, serverside, userdata } = props; + let navigate = useNavigate(); + const { leftSideBarOpenByClick } = useContext(Context); + const location = useLocation(); + const [searchQuery, setSearchQuery] = useState(""); + const [selectedCategory, setSelectedCategory] = useState(""); + const [selectedLabel, setSelectedLabel] = useState(""); + const [currTab, setCurrTab] = useState(0); + const [categories, setCategories] = useState([]); + const [labels, setLabels] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [userApps, setUserApps] = useState([]); + const [orgApps, setOrgApps] = useState([]); + const [selectedCategoryForUsersAndOgsApps, setselectedCategoryForUsersAndOgsApps] = useState([]); + const [selectedTagsForUserAndOrgApps, setSelectedTagsForUserAndOrgApps] = useState([]); + const [appsToShow, setAppsToShow] = useState([]); + const [deactivatedIndexes, setDeactivatedIndexes] = React.useState([]); + + + const [mouseHoverIndex, setMouseHoverIndex] = useState(-1); + var counted = 0; + const [showNoAppFound, setShowNoAppFound] = useState(false); + + const isCloud = + window.location.host === "localhost:3002" || + window.location.host === "shuffler.io"; + + useEffect(() => { + const timer = setTimeout(() => { + setShowNoAppFound(true); + }, 1000); + return () => clearTimeout(timer); + }, []); + + useEffect(() => { + const queryParams = new URLSearchParams(location.search); + const tabParam = queryParams.get('tab'); + if (tabParam !== null && tabParam !== undefined) { + if (tabParam === 'org_apps') { + setCurrTab(0); + } else if (tabParam === 'my_apps') { + setCurrTab(1); + } else { + setCurrTab(2); + } + } + }, [location.search]); + + + useEffect(() => { + if (currTab === 1) { + const baseUrl = globalUrl; + const userAppsUrl = `${baseUrl}/api/v1/users/me/apps`; + fetch(userAppsUrl, { + method: "GET", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => response.json()) + .then((data) => { + setUserApps(data); + setIsLoading(false); + }) + .catch((err) => { + console.error("Error fetching user apps:", err); + }); + } else if (currTab === 0) { + const baseUrl = globalUrl; + const appsUrl = `${baseUrl}/api/v1/apps`; + fetch(appsUrl, { + method: "GET", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => response.json()) + .then((data) => { + console.log("data of orgApps", data) + setOrgApps(data); + setIsLoading(false) + }) + .catch((err) => { + console.error("Error fetching apps:", err); + }); + } + }, [currTab]); + + + + + useEffect(() => { + if (serverside) { + return null; + } + }, [serverside]); + + + useEffect(() => { + + const findTopCategories = () => { + const categoryCountMap = {}; + + // Check if userAndOrgsApp is an array before iterating over it and Find top 10 Category from the apps + if (Array.isArray(appsToShow)) { + appsToShow.forEach((app) => { + const categories = app.categories; + + if (categories && categories.length > 0) { + categories.forEach((category) => { + categoryCountMap[category] = (categoryCountMap[category] || 0) + 1; + }); + } + }); + + const categoryArray = Object.keys(categoryCountMap).map((category) => ({ + category, + count: categoryCountMap[category], + })); + + categoryArray.sort((a, b) => b.count - a.count); + + const topCategories = categoryArray.slice(0, 7); + + return topCategories; + } + }; + + const findTopTags = () => { + const tagCountMap = {}; + + if (Array.isArray(appsToShow)) { + appsToShow.forEach((app) => { + const tags = app.tags; + if (tags && tags.length > 0) { + tags.forEach((tag) => { + tagCountMap[tag] = (tagCountMap[tag] || 0) + 1; + }); + } + }); + } + + const tagArray = Object.keys(tagCountMap).map((tag) => ({ + tag, + count: tagCountMap[tag], + })); + + tagArray.sort((a, b) => b.count - a.count); + + const topTags = tagArray.slice(0, 8); + + return topTags; + }; + + const categories = findTopCategories(); + if (categories) { + setCategories(categories) + } + console.log(categories) + const tags = findTopTags(); + if (tags) { + setLabels(tags); + } + console.log(tags) + }, [currTab]) + + const handleCreateApp = () => { + navigate('/create-app'); + }; + + + //Search app base on app name, category and tag + const filteredUserAppdata = Array.isArray(appsToShow) ? appsToShow.filter((app) => { + const matchesSearchQuery = ( + searchQuery === "" || + app.name.toLowerCase().includes(searchQuery.toLowerCase()) || + (app.tags && app.tags.some(tag => + tag.toLowerCase().includes(searchQuery.toLowerCase()) + )) || + (app.categories && app.categories.some((category) => + category.toLowerCase().includes(searchQuery.toLowerCase()) + )) + ); + + const matchesSelectedCategories = ( + selectedCategoryForUsersAndOgsApps.length === 0 || + (app.categories && app.categories.some(category => + selectedCategoryForUsersAndOgsApps.includes(category) + )) + ); + + const matchesSelectedTags = ( + selectedTagsForUserAndOrgApps.length === 0 || + (app.tags && selectedTagsForUserAndOrgApps.some(tag => + app.tags.includes(tag) + )) + ); + + + return matchesSearchQuery && matchesSelectedCategories && matchesSelectedTags; + }) : []; + + + const handleTabChange = (newTab) => { + setCurrTab(newTab); + if (currTab === 0) { + setAppsToShow(orgApps) + } + if (currTab === 1) { + setAppsToShow(userApps) + } + const newQueryParam = newTab === 0 ? 'org_apps' : newTab === 1 ? 'my_apps' : 'all_apps'; + const queryParams = new URLSearchParams(location.search); + queryParams.set('tab', newQueryParam); + queryParams.delete('q'); + window.history.replaceState({}, '', `${location.pathname}?${queryParams.toString()}`); + console.log("Current Tab", currTab) + console.log("Apps to show", appsToShow) + }; + + + + + const boxStyle = { + color: "white", + display: "flex", + flexDirection: "column", + width: "100%", + margin: "auto", + maxWidth: "60%", + // padding: '20px 380px', + }; + + const CustomClearRefinements = connectStateResults(({ searchResults, ...rest }) => { + const hasFilters = searchResults && searchResults.nbHits !== searchResults.nbSortedHits; + return Clear All }} {...rest} disabled={!hasFilters} />; + }); + + + const SearchBox = ({ refine, searchQuery, setSearchQuery }) => { + var defaultSearch = ""; + + + //useEffect(() => { + if ( + window !== undefined && + window.location !== undefined && + window.location.search !== undefined && + window.location.search !== null + ) { + const urlSearchParams = new URLSearchParams(window.location.search); + const params = Object.fromEntries(urlSearchParams.entries()); + const foundQuery = params["q"]; + if (foundQuery !== null && foundQuery !== undefined) { + console.log("Got query: ", foundQuery); + refine(foundQuery); + defaultSearch = foundQuery; + searchQuery = foundQuery + } + } + //}, []) + + + const handleSearch = () => { + refine(searchQuery.trim()); + }; + + return ( +
+ + + + ), + endAdornment: ( + + {searchQuery.length > 0 && ( + { + setSearchQuery('') + removeQuery("q"); + refine('') + }} + /> + ) + } + + + ), + + }} + autoComplete="off" + color="primary" + placeholder="Search more than 2500 Apps" + id="shuffle_search_field" + onChange={(event) => { + setSearchQuery(event.currentTarget.value); + removeQuery("q"); + refine(event.currentTarget.value); + }} + onKeyDown={(event) => { + if (event.key === "Enter") { + event.preventDefault(); + } + }} + limit={5} + /> + {/*isSearchStalled ? 'My search is stalled' : ''*/} + + ); + }; + + + + + const CustomSearchBox = connectSearchBox(SearchBox); + // const CustomHits = connectHits(Hits); + + + + + return ( +
+ + Apps + +
+ handleTabChange(newTab)} + TabIndicatorProps={{ style: { height: '3px', borderRadius: 10 } }} + > + + + + +
+
+
+ { + setSearchQuery(event.currentTarget.value); + removeQuery("q"); + // refine(event.currentTarget.value); + }} + onKeyDown={(event) => { + if (event.key === "Enter") { + event.preventDefault(); + } + }} + limit={5} + style={{ width: '100%', borderRadius: '7px', fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" }} + InputProps={{ + style: { + borderRadius: 8, + }, + endAdornment: ( + + { + searchQuery.length === 0 ? : + } + + ), + }} + /> + {/* */} +
+
+ +
+
+ +
+
+ +
+
+
+
+ {orgApps.map((data, index) => ( + + ))} +
+
+ +
+ ); +}; + +export default Apps2; From 39e32185334b304baab7a166796fe9616b2905e6 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Wed, 13 Nov 2024 22:34:56 +0530 Subject: [PATCH 2/6] Tabs are working except myApps and categories and labels are displayed --- backend/go-app/main.go | 121 ++-- frontend/src/App.jsx | 1 + frontend/src/views/Apps2.jsx | 1115 +++++++++++++++++++++++----------- 3 files changed, 816 insertions(+), 421 deletions(-) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 6b3ba779..1e8dfddf 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -35,8 +35,8 @@ import ( "github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" + gitProxy "github.com/go-git/go-git/v5/plumbing/transport" "github.com/go-git/go-git/v5/storage/memory" - gitProxy "github.com/go-git/go-git/v5/plumbing/transport" // Random xj "github.com/basgys/goxml2json" @@ -257,7 +257,6 @@ type Hook struct { Environment string `json:"environment" datastore:"environment"` } - func GetUsersHandler(w http.ResponseWriter, r *http.Request) { data := map[string]interface{}{ "id": "12345", @@ -398,49 +397,49 @@ func checkUsername(Username string) error { } func isGitNoProxy(rawURL string) bool { - noProxy := os.Getenv("NO_PROXY") - if noProxy == "" { - return false - } - - if noProxy == "*" { - return true - } + noProxy := os.Getenv("NO_PROXY") + if noProxy == "" { + return false + } - noProxyList := strings.Split(noProxy, ",") - parsedURL, err := url.Parse(rawURL) - if err != nil { - return false - } - host := parsedURL.Hostname() + if noProxy == "*" { + return true + } - for _,value := range noProxyList { - value = strings.TrimSpace(value) + noProxyList := strings.Split(noProxy, ",") + parsedURL, err := url.Parse(rawURL) + if err != nil { + return false + } + host := parsedURL.Hostname() - if host == value { - return true - } - if strings.HasPrefix(value, "*.") && strings.HasSuffix(host, value[2:]){ - return true - } - } - return false + for _, value := range noProxyList { + value = strings.TrimSpace(value) + + if host == value { + return true + } + if strings.HasPrefix(value, "*.") && strings.HasSuffix(host, value[2:]) { + return true + } + } + return false } func checkGitProxy(cloneOptions *git.CloneOptions) *git.CloneOptions { - if os.Getenv("HTTP_PROXY") != "" && !isGitNoProxy(cloneOptions.URL){ - cloneOptions.ProxyOptions = gitProxy.ProxyOptions{ - URL: os.Getenv("HTTP_PROXY"), - } - } + if os.Getenv("HTTP_PROXY") != "" && !isGitNoProxy(cloneOptions.URL) { + cloneOptions.ProxyOptions = gitProxy.ProxyOptions{ + URL: os.Getenv("HTTP_PROXY"), + } + } - if os.Getenv("HTTPS_PROXY") != "" && !isGitNoProxy(cloneOptions.URL) { - cloneOptions.ProxyOptions = gitProxy.ProxyOptions{ - URL: os.Getenv("HTTPS_PROXY"), - } - } + if os.Getenv("HTTPS_PROXY") != "" && !isGitNoProxy(cloneOptions.URL) { + cloneOptions.ProxyOptions = gitProxy.ProxyOptions{ + URL: os.Getenv("HTTPS_PROXY"), + } + } - return cloneOptions + return cloneOptions } func createNewUser(username, password, role, apikey string, org shuffle.OrgMini) error { @@ -559,7 +558,6 @@ func createNewUser(username, password, role, apikey string, org shuffle.OrgMini) } } - return nil } @@ -663,7 +661,7 @@ func handleRegister(resp http.ResponseWriter, request *http.Request) { Name: newOrg.Name, } - user.ActiveOrg = currentOrg + user.ActiveOrg = currentOrg } } } @@ -932,7 +930,6 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { log.Printf("[DEBUG] Failed to get org during getinfo: %s", err) } - //if err == nil { if len(org.Id) > 0 { if userInfo.Role == "" { @@ -1069,9 +1066,9 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { ChatDisabled: chatDisabled, Tutorials: tutorialsFinished, - Interests: orgInterests, - Priorities: orgPriorities, - Licensed: licensed, + Interests: orgInterests, + Priorities: orgPriorities, + Licensed: licensed, } returnData, err := json.Marshal(returnValue) @@ -1092,7 +1089,6 @@ type passwordReset struct { Reference string `json:"reference"` } - func checkAdminLogin(resp http.ResponseWriter, request *http.Request) { cors := shuffle.HandleCors(resp, request) if cors { @@ -3338,7 +3334,6 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User, s } } - log.Printf("[DEBUG] Successfully built app %s (%s)", api.Name, api.ID) if len(user.Id) > 0 { resp.WriteHeader(200) @@ -3379,8 +3374,6 @@ func verifySwagger(resp http.ResponseWriter, request *http.Request) { buildSwaggerApp(resp, body, user, false) } - - // Hotloads new apps from a folder func handleAppHotload(ctx context.Context, location string, forceUpdate bool) error { @@ -3727,11 +3720,10 @@ func remoteOrgJobController(org shuffle.Org, body []byte) error { return nil } - func remoteOrgJobHandler(org shuffle.Org, interval int) error { // Check if it's 1 in 10 (10% chance random) - backupJob := shuffle.BackupJob{} + backupJob := shuffle.BackupJob{} // Check if workflow backup is active // Check if app backup is active @@ -3777,7 +3769,6 @@ func remoteOrgJobHandler(org shuffle.Org, interval int) error { backupJobData = []byte{} } - syncUrl := fmt.Sprintf("%s/api/v1/cloud/sync", syncUrl) client := shuffle.GetExternalClient(syncUrl) req, err := http.NewRequest( @@ -3983,7 +3974,7 @@ func runInitEs(ctx context.Context) { } // FIXME: Add a randomized timer to avoid all schedules running at the same time - // Many are at 5 minutes / 1 hour. The point is to spread these out + // Many are at 5 minutes / 1 hour. The point is to spread these out // a bit instead of all of them starting at the exact same time //log.Printf("Schedule: %#v", schedule) @@ -4275,7 +4266,7 @@ func runInitEs(ctx context.Context) { } } - cloneOptions = checkGitProxy(cloneOptions) + cloneOptions = checkGitProxy(cloneOptions) branch := os.Getenv("SHUFFLE_DOWNLOAD_AUTH_BRANCH") if len(branch) > 0 && branch != "master" && branch != "main" { @@ -4322,7 +4313,7 @@ func runInitEs(ctx context.Context) { URL: apis, } - cloneOptions = checkGitProxy(cloneOptions) + cloneOptions = checkGitProxy(cloneOptions) _, err = git.Clone(storer, fs, cloneOptions) if err != nil { @@ -4340,17 +4331,16 @@ func runInitEs(ctx context.Context) { log.Printf("[INFO] Skipping download of extra API samples as %d were found", len(workflowapps)) } - if os.Getenv("SHUFFLE_HEALTHCHECK_DISABLED") != "true" { - healthcheckInterval := 30 + healthcheckInterval := 30 log.Printf("[INFO] Starting healthcheck job every %d minute. Stats available on /api/v1/health/stats. Disable with SHUFFLE_HEALTHCHECK_DISABLED=true", healthcheckInterval) job := func() { - // Prepare a fake http.responsewriter + // Prepare a fake http.responsewriter resp := httptest.NewRecorder() request := http.Request{} // Add the "force=true" query to the fake request - request.URL, err = url.Parse("/api/v1/health/stats?force=true") + request.URL, err = url.Parse("/api/v1/health/stats?force=true") if err != nil { log.Printf("[ERROR] Failed to parse test url for healthstats: %s", err) } @@ -4369,7 +4359,6 @@ func runInitEs(ctx context.Context) { log.Printf("[INFO] Finished INIT (ES)") } - func handleVerifyCloudsync(orgId string) (shuffle.SyncFeatures, error) { ctx := context.Background() org, err := shuffle.GetOrg(ctx, orgId) @@ -4948,8 +4937,6 @@ func makeWorkflowPublic(resp http.ResponseWriter, request *http.Request) { resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) } - - func handleAppZipUpload(resp http.ResponseWriter, request *http.Request) { cors := shuffle.HandleCors(resp, request) if cors { @@ -5008,8 +4995,6 @@ func handleAppZipUpload(resp http.ResponseWriter, request *http.Request) { resp.Write([]byte("OK")) } - - func initHandlers() { var err error ctx := context.Background() @@ -5056,7 +5041,7 @@ func initHandlers() { r.HandleFunc("/api/v1/users/register", handleRegister).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/users/checkusers", checkAdminLogin).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/users/getinfo", handleInfo).Methods("GET", "OPTIONS") - + r.HandleFunc("/api/v1/users/{userId}/apps", shuffle.HandleGetUserApps).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/users/apps", shuffle.HandleGetUserApps).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/users/generateapikey", shuffle.HandleApiGeneration).Methods("GET", "POST", "OPTIONS") r.HandleFunc("/api/v1/users/logout", shuffle.HandleLogout).Methods("POST", "OPTIONS") @@ -5192,7 +5177,7 @@ func initHandlers() { r.HandleFunc("/api/v1/triggers/gmail/register", shuffle.HandleNewGmailRegister).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/triggers/gmail/getFolders", shuffle.HandleGetGmailFolders).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/triggers/pipeline", shuffle.HandleNewPipelineRegister).Methods("POST", "OPTIONS") - //r.HandleFunc("/api/v1/triggers/pipeline/save", shuffle.HandleSavePipelineInfo).Methods("PUT", "OPTIONS") + //r.HandleFunc("/api/v1/triggers/pipeline/save", shuffle.HandleSavePipelineInfo).Methods("PUT", "OPTIONS") r.HandleFunc("/api/v1/pipelines/{key}", handlePipelineCallback).Methods("POST", "GET", "PATCH", "PUT", "DELETE", "OPTIONS") r.HandleFunc("/api/v1/triggers", shuffle.HandleGetTriggers).Methods("GET", "OPTIONS") //r.HandleFunc("/api/v1/triggers/gmail/routing", handleGmailRouting).Methods("POST", "OPTIONS") @@ -5220,7 +5205,7 @@ func initHandlers() { r.HandleFunc("/api/v1/orgs/{orgId}", shuffle.HandleDeleteOrg).Methods("DELETE", "OPTIONS") r.HandleFunc("/api/v1/orgs/{orgId}/suborgs", shuffle.HandleGetSubOrgs).Methods("GET", "OPTIONS") - + // 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. r.HandleFunc("/api/v1/environments/{key}/stop", shuffle.HandleStopExecutions).Methods("GET", "POST", "OPTIONS") @@ -5244,7 +5229,6 @@ func initHandlers() { r.HandleFunc("/api/v1/orgs/{orgId}/datastore", shuffle.HandleSetCacheKey).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/orgs/{orgId}/datastore/{cache_key}", shuffle.HandleDeleteCacheKey).Methods("DELETE", "OPTIONS") - // Docker orborus specific - downloads an image r.HandleFunc("/api/v1/get_docker_image", getDockerImage).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/login_sso", shuffle.HandleSSO).Methods("GET", "POST", "OPTIONS") @@ -5266,15 +5250,14 @@ func initHandlers() { // This structure is horrendous. Needs fixing after we got the prototype up r.HandleFunc("/api/v1/detections/{detectionType}/connect", shuffle.HandleDetectionAutoConnect).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/detections/{detection_type}", shuffle.HandleGetDetectionRules).Methods("GET", "OPTIONS") - r.HandleFunc("/api/v1/detections/{triggerId}/selected_rules", shuffle.HandleGetSelectedRules).Methods("GET","OPTIONS") - r.HandleFunc("/api/v1/detections/{triggerId}/selected_rules/save", shuffle.HandleSaveSelectedRules).Methods("POST","OPTIONS") + r.HandleFunc("/api/v1/detections/{triggerId}/selected_rules", shuffle.HandleGetSelectedRules).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/detections/{triggerId}/selected_rules/save", shuffle.HandleSaveSelectedRules).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/detections/{action}", shuffle.HandleFolderToggle).Methods("PUT", "OPTIONS") // This is weird. r.HandleFunc("/api/v1/detections/{fileId}/{action}", shuffle.HandleToggleRule).Methods("PUT", "OPTIONS") //r.HandleFunc("/api/v1/detections/siem/node_health", shuffle.HandleTenzirHealthUpdate).Methods("POST","OPTIONS") - // Introduced in 0.9.21 to handle notifications for e.g. failed Workflow r.HandleFunc("/api/v1/notifications", shuffle.HandleCreateNotification).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/notifications", shuffle.HandleGetNotifications).Methods("GET", "OPTIONS") diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index c4d118d9..ab71062e 100755 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -417,6 +417,7 @@ const App = (message, props) => { { - const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; + +const searchClient = algoliasearch( + "JNSS5CFDZZ", + "db08e40265e2941b9a7d8f644b6e5240" +); + +const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, deactivatedIndexes }) => { + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" || window.location.host === "localhost:3000"; const appUrl = isCloud ? `/apps/${data.id}` : `https://shuffler.io/apps/${data.id}`; const paperStyle = { @@ -33,23 +42,75 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl } height: 96, borderRadius: 8, boxShadow: "0px 0px 10px 0px rgba(0, 0, 0, 0.1)", - marginBottom: 20, + // marginBottom: 20, }; return ( setMouseHoverIndex(index)} onMouseOut={() => setMouseHoverIndex(-1)}> - - {data.name} -
-
+ + {data.name} +
+
{data.name.replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase())}
-
+
{data.categories ? data.categories.join(", ") : "NA"}
-
+
{data.generated !== true && data.tags && data.tags.slice(0, 2).map((tag, tagIndex) => ( @@ -59,11 +120,44 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl } ))}
{/* Deactivate button logic */} - {data.generated === true ? ( - - ) : null} + : null}
@@ -73,8 +167,364 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl } ); }; + +// Component to fetch all public app from the algolia. +const Hits = memo(({ + userdata, + hits, + setIsAnyAppActivated, + searchQuery, + globalUrl, + setIsLoggedIn, + isLoading, + isLoggedIn, +}) => { + var counted = 0; + const [hoverEffect, setHoverEffect] = useState(-1); + const [allActivatedAppIds, setAllActivatedAppIds] = useState(userdata?.active_apps); + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" || window.location.host === "localhost:3000"; + const [deactivatedIndexes, setDeactivatedIndexes] = React.useState([]); + + const normalizedString = (name) => { + if (typeof name === 'string') { + return name.replace(/_/g, ' '); + } else { + return name; + } + }; + + + // const fetchUserData = useCallback(async () => { + // try { + // const response = await fetch(`${globalUrl}/api/v1/me`, { + // credentials: "include", + // headers: { + // 'Content-Type': 'application/json', + // }, + // }); + // const responseJson = await response.json(); + // console.log("responseJson : user data", responseJson) + // if (responseJson.success) { + // setUserdata(responseJson); + // setAllActivatedAppIds(responseJson.active_apps); + // setIsLoggedIn(true); + // } else { + // setIsLoggedIn(false); + // } + // } catch (error) { + // console.log("Failed login check: ", error); + // } + // }, [globalUrl]); // Added globalUrl as a dependency + + // useEffect(() => { + // fetchUserData(); + // }, [fetchUserData]); // Ensure fetchUserData is stable + + //Function for activation and deactivation of app + const handleActivateButton = (event, data, type) => { + + //use prevent default so it will stop redirection to the app page + event.preventDefault(); + if (!isLoggedIn) { + toast.error("Please log in to your account to activate the app.") + return; + } + if (type === "activate") { + toast.success(`The ${normalizedString(data.name)} app is activating. Please wait...`); + } + if (type === "deactivate") { + toast.success(`The ${normalizedString(data.name)} app is deactivating. Please wait...`); + } + + const baseURL = globalUrl; + const url = `${baseURL}/api/v1/apps/${data.objectID}/${type}`; + + fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => response.json()) + .then((responseJson) => { + if (responseJson.success === false) { + toast.error(responseJson.reason); + } else { + //toast.success(`App ${type}d Successfully!`); + if (type === 'activate') { + setAllActivatedAppIds(prev => [...prev, data.objectID]); + setIsAnyAppActivated(true); + } + if (type === 'deactivate') { + const updatedIds = allActivatedAppIds.filter(id => id !== data.objectID); + setAllActivatedAppIds(updatedIds); + } + } + }) + .catch(error => { + console.log("app error: ", error.toString()); + }); + } + + let workflowDelay = 0; + const isHeader = true; + + const [showNoAppFound, setShowNoAppFound] = useState(false); + + //show some delay to show the "App Not Found." so it doesn't not show while changing tab. + useEffect(() => { + const timer = setTimeout(() => { + setShowNoAppFound(true); + }, 1000); + return () => clearTimeout(timer); + }, []); + + const memoizedHits = useMemo(() => hits, [hits]); + + + return ( +
+ {!isLoading ? ( +
+ {memoizedHits?.length === 0 && searchQuery.length >= 0 && showNoAppFound ? ( + No Apps Found + ) : ( + +
+ {memoizedHits.map((data, index) => { + const appUrl = + isCloud + ? `/apps/${data.objectID}?queryID=${data.__queryID}` + : `https://shuffler.io/apps/${data.objectID}?queryID=${data.__queryID}`; + + return ( + + + + { + setHoverEffect(index); + }} + onMouseLeave={() => { + setHoverEffect(-1); + }} + > + + {data.name} +
+
+ {(allActivatedAppIds && allActivatedAppIds.includes(data.objectID)) && } + {normalizedString(data.name)} +
+ +
+ {data.categories !== null + ? normalizedString(data.categories).join(", ") + : "NA"} +
+
+
+ {hoverEffect === index && isCloud ? ( +
+ {data.tags && ( + + + {data.tags.slice(0, 1).map((tag, tagIndex) => ( + + {normalizedString(tag)} + {tagIndex < 1 ? ", " : ""} + + ))} + + + )} +
+ ) : ( +
+ {data.tags && + data.tags.map((tag, tagIndex) => ( + + {normalizedString(tag)} + {tagIndex < data.tags.length - 1 ? ", " : ""} + + ))} +
+ )} +
+
+ {hoverEffect === index && isCloud && ( +
+ {allActivatedAppIds && allActivatedAppIds.includes(data.objectID) ? ( + + ) : ( + + )} +
+ )} +
+
+
+
+
+
+ + + ); + }) + } +
+ + )} +
+ ) : ( +
+ )} +
+ ); +}); + const Apps2 = (props) => { - const { globalUrl, isLoaded, serverside, userdata } = props; + const { globalUrl, isLoaded, serverside, userdata, isLoggedIn } = props; let navigate = useNavigate(); const { leftSideBarOpenByClick } = useContext(Context); const location = useLocation(); @@ -91,22 +541,13 @@ const Apps2 = (props) => { const [selectedTagsForUserAndOrgApps, setSelectedTagsForUserAndOrgApps] = useState([]); const [appsToShow, setAppsToShow] = useState([]); const [deactivatedIndexes, setDeactivatedIndexes] = React.useState([]); - + const [IsAnyAppActivated, setIsAnyAppActivated] = useState(false); const [mouseHoverIndex, setMouseHoverIndex] = useState(-1); var counted = 0; const [showNoAppFound, setShowNoAppFound] = useState(false); - const isCloud = - window.location.host === "localhost:3002" || - window.location.host === "shuffler.io"; - - useEffect(() => { - const timer = setTimeout(() => { - setShowNoAppFound(true); - }, 1000); - return () => clearTimeout(timer); - }, []); + const CustomHits = connectHits(Hits) useEffect(() => { const queryParams = new URLSearchParams(location.search); @@ -124,45 +565,43 @@ const Apps2 = (props) => { useEffect(() => { - if (currTab === 1) { + const fetchApps = async () => { const baseUrl = globalUrl; - const userAppsUrl = `${baseUrl}/api/v1/users/me/apps`; - fetch(userAppsUrl, { - method: "GET", - credentials: "include", - headers: { - "Content-Type": "application/json", - }, - }) - .then((response) => response.json()) - .then((data) => { + let url; + + const userId = userdata?.id; + if (currTab === 1 && userId) { + url = `${baseUrl}/api/v1/users/${userId}/apps`; + } else if (currTab === 0) { + url = `${baseUrl}/api/v1/apps`; + } else { + return; // No need to fetch if not in the relevant tabs + } + setIsLoading(true); + try { + const response = await fetch(url, { + method: "GET", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + if (currTab === 1) { + console.log("data from userApps", data) setUserApps(data); setIsLoading(false); - }) - .catch((err) => { - console.error("Error fetching user apps:", err); - }); - } else if (currTab === 0) { - const baseUrl = globalUrl; - const appsUrl = `${baseUrl}/api/v1/apps`; - fetch(appsUrl, { - method: "GET", - credentials: "include", - headers: { - "Content-Type": "application/json", - }, - }) - .then((response) => response.json()) - .then((data) => { - console.log("data of orgApps", data) + } else if (currTab === 0) { setOrgApps(data); - setIsLoading(false) - }) - .catch((err) => { - console.error("Error fetching apps:", err); - }); - } - }, [currTab]); + setIsLoading(false); + } + } catch (err) { + console.error("Error fetching apps:", err); + } + }; + + fetchApps(); + }, [currTab, globalUrl]); // Added globalUrl to dependencies to avoid stale closures @@ -173,75 +612,61 @@ const Apps2 = (props) => { } }, [serverside]); + + const findTopCategories = () => { + const categoryCountMap = {}; - useEffect(() => { + // Check if userAndOrgsApp is an array before iterating over it and Find top 10 Category from the apps + if (Array.isArray(appsToShow)) { + appsToShow.forEach((app) => { + const categories = app.categories; - const findTopCategories = () => { - const categoryCountMap = {}; + if (categories && categories.length > 0) { + categories.forEach((category) => { + categoryCountMap[category] = (categoryCountMap[category] || 0) + 1; + }); + } + }); - // Check if userAndOrgsApp is an array before iterating over it and Find top 10 Category from the apps - if (Array.isArray(appsToShow)) { - appsToShow.forEach((app) => { - const categories = app.categories; - - if (categories && categories.length > 0) { - categories.forEach((category) => { - categoryCountMap[category] = (categoryCountMap[category] || 0) + 1; - }); - } - }); - - const categoryArray = Object.keys(categoryCountMap).map((category) => ({ - category, - count: categoryCountMap[category], - })); - - categoryArray.sort((a, b) => b.count - a.count); - - const topCategories = categoryArray.slice(0, 7); - - return topCategories; - } - }; - - const findTopTags = () => { - const tagCountMap = {}; - - if (Array.isArray(appsToShow)) { - appsToShow.forEach((app) => { - const tags = app.tags; - if (tags && tags.length > 0) { - tags.forEach((tag) => { - tagCountMap[tag] = (tagCountMap[tag] || 0) + 1; - }); - } - }); - } - - const tagArray = Object.keys(tagCountMap).map((tag) => ({ - tag, - count: tagCountMap[tag], + const categoryArray = Object.keys(categoryCountMap).map((category) => ({ + category, + count: categoryCountMap[category], })); - tagArray.sort((a, b) => b.count - a.count); + categoryArray.sort((a, b) => b.count - a.count); - const topTags = tagArray.slice(0, 8); + const topCategories = categoryArray.slice(0, 7); - return topTags; - }; - - const categories = findTopCategories(); - if (categories) { - setCategories(categories) + return topCategories; } - console.log(categories) - const tags = findTopTags(); - if (tags) { - setLabels(tags); - } - console.log(tags) - }, [currTab]) + }; + const findTopTags = () => { + const tagCountMap = {}; + + if (Array.isArray(appsToShow)) { + appsToShow.forEach((app) => { + const tags = app.tags; + if (tags && tags.length > 0) { + tags.forEach((tag) => { + tagCountMap[tag] = (tagCountMap[tag] || 0) + 1; + }); + } + }); + } + + const tagArray = Object.keys(tagCountMap).map((tag) => ({ + tag, + count: tagCountMap[tag], + })); + + tagArray.sort((a, b) => b.count - a.count); + + const topTags = tagArray.slice(0, 8); + + return topTags; + }; + const handleCreateApp = () => { navigate('/create-app'); }; @@ -280,11 +705,16 @@ const Apps2 = (props) => { const handleTabChange = (newTab) => { + console.log("Current Tab", newTab) setCurrTab(newTab); - if (currTab === 0) { + if (newTab === 0) { setAppsToShow(orgApps) + const categories = findTopCategories(); + const labels = findTopTags(); + setCategories(categories); + setLabels(labels); } - if (currTab === 1) { + if (newTab === 1) { setAppsToShow(userApps) } const newQueryParam = newTab === 0 ? 'org_apps' : newTab === 1 ? 'my_apps' : 'all_apps'; @@ -292,7 +722,6 @@ const Apps2 = (props) => { queryParams.set('tab', newQueryParam); queryParams.delete('q'); window.history.replaceState({}, '', `${location.pathname}?${queryParams.toString()}`); - console.log("Current Tab", currTab) console.log("Apps to show", appsToShow) }; @@ -315,247 +744,229 @@ const Apps2 = (props) => { }); - const SearchBox = ({ refine, searchQuery, setSearchQuery }) => { - var defaultSearch = ""; - - - //useEffect(() => { - if ( - window !== undefined && - window.location !== undefined && - window.location.search !== undefined && - window.location.search !== null - ) { - const urlSearchParams = new URLSearchParams(window.location.search); - const params = Object.fromEntries(urlSearchParams.entries()); - const foundQuery = params["q"]; - if (foundQuery !== null && foundQuery !== undefined) { - console.log("Got query: ", foundQuery); - refine(foundQuery); - defaultSearch = foundQuery; - searchQuery = foundQuery - } - } - //}, []) - - - const handleSearch = () => { - refine(searchQuery.trim()); - }; - - return ( -
- - - - ), - endAdornment: ( - - {searchQuery.length > 0 && ( - { - setSearchQuery('') - removeQuery("q"); - refine('') - }} - /> - ) - } - - - ), - - }} - autoComplete="off" - color="primary" - placeholder="Search more than 2500 Apps" - id="shuffle_search_field" - onChange={(event) => { - setSearchQuery(event.currentTarget.value); - removeQuery("q"); - refine(event.currentTarget.value); - }} - onKeyDown={(event) => { - if (event.key === "Enter") { - event.preventDefault(); - } - }} - limit={5} - /> - {/*isSearchStalled ? 'My search is stalled' : ''*/} - - ); - }; - - - - - const CustomSearchBox = connectSearchBox(SearchBox); - // const CustomHits = connectHits(Hits); - - return ( -
- - Apps - -
- handleTabChange(newTab)} - TabIndicatorProps={{ style: { height: '3px', borderRadius: 10 } }} - > - - - - -
-
-
- { - setSearchQuery(event.currentTarget.value); - removeQuery("q"); - // refine(event.currentTarget.value); - }} - onKeyDown={(event) => { - if (event.key === "Enter") { - event.preventDefault(); + +
+ + Apps + +
+ handleTabChange(newTab)} + TabIndicatorProps={{ style: { height: '3px', borderRadius: 10 } }} + > + + + + +
+
+
+ { + setSearchQuery(event.currentTarget.value); + removeQuery("q"); + // refine(event.currentTarget.value); + }} + onKeyDown={(event) => { + if (event.key === "Enter") { + event.preventDefault(); + } + }} + limit={5} + style={{ width: '100%', borderRadius: '7px', fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" }} + InputProps={{ + style: { + borderRadius: 8, + }, + endAdornment: ( + + { + searchQuery.length === 0 ? : + } + + ), + }} + /> + {/* */} +
+
+ setSelectedCategory(e.target.value)} - displayEmpty - style={{ width: '100%', borderRadius: '7px', fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" }} - > - - All Categories - - {categories?.map((category) => ( - - {category.category} - - ))} - -
-
- +
+
+ + { + currTab === 0 && + ( + labels?.map((tag) => ( + + {tag.tag} + + )) + ) + } + { + currTab === 2 && + ( +
+ +
+ ) + } + +
+
+ +
-
- +
+ + { + currTab === 0 && + (orgApps.length > 0 ? ( +
+ { + isLoading ?
+ : + ( + filteredUserAppdata?.map((data, index) => ( + + )) + ) + } +
+ ) :
No apps found
+ ) + } + { + currTab === 1 && + (userApps.length > 0 ? ( +
+ { + isLoading ?
+ : + ( + userApps.map((data, index) => ( + + )) + ) + } +
+ ) :
No apps found
+ ) + } + + { + currTab === 2 && + + }
-
-
-
- {orgApps.map((data, index) => ( - - ))} -
-
- -
+ /> */} +
+ ); }; From 2bf969bd14d5a71dfc109a7a90e211a1631bbc3d Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Fri, 15 Nov 2024 12:30:24 +0530 Subject: [PATCH 3/6] done with orgApps section --- frontend/src/views/Apps2.jsx | 831 +++++++++++++++++++++-------------- 1 file changed, 498 insertions(+), 333 deletions(-) diff --git a/frontend/src/views/Apps2.jsx b/frontend/src/views/Apps2.jsx index 828c6bbf..b4573574 100644 --- a/frontend/src/views/Apps2.jsx +++ b/frontend/src/views/Apps2.jsx @@ -29,6 +29,7 @@ const searchClient = algoliasearch( "db08e40265e2941b9a7d8f644b6e5240" ); +// AppCard Component const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, deactivatedIndexes }) => { const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" || window.location.host === "localhost:3000"; const appUrl = isCloud ? `/apps/${data.id}` : `https://shuffler.io/apps/${data.id}`; @@ -42,7 +43,7 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, height: 96, borderRadius: 8, boxShadow: "0px 0px 10px 0px rgba(0, 0, 0, 0.1)", - // marginBottom: 20, + marginBottom: 20, }; return ( @@ -169,13 +170,13 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, // Component to fetch all public app from the algolia. -const Hits = memo(({ +const Hits = ({ userdata, hits, + algoliaSelectedCategories, setIsAnyAppActivated, searchQuery, globalUrl, - setIsLoggedIn, isLoading, isLoggedIn, }) => { @@ -193,6 +194,16 @@ const Hits = memo(({ } }; + useEffect(() => { + console.log("searchQuery", searchQuery) + console.log("hits", hits) + }, [searchQuery, hits]) + + + const filteredHits = hits?.filter(hit => { + if (algoliaSelectedCategories?.length === 0) return true; // If no categories are selected, show all hits + return hit.categories?.some(category => algoliaSelectedCategories.includes(category)); + }); // const fetchUserData = useCallback(async () => { // try { @@ -281,237 +292,242 @@ const Hits = memo(({ return () => clearTimeout(timer); }, []); - const memoizedHits = useMemo(() => hits, [hits]); return (
{!isLoading ? (
- {memoizedHits?.length === 0 && searchQuery.length >= 0 && showNoAppFound ? ( + {filteredHits?.length === 0 && searchQuery.length >= 0 && showNoAppFound ? ( No Apps Found ) : (
- {memoizedHits.map((data, index) => { - const appUrl = - isCloud - ? `/apps/${data.objectID}?queryID=${data.__queryID}` - : `https://shuffler.io/apps/${data.objectID}?queryID=${data.__queryID}`; + }}> +
+ {filteredHits?.map((data, index) => { + const appUrl = + isCloud + ? `/apps/${data.objectID}?queryID=${data.__queryID}` + : `https://shuffler.io/apps/${data.objectID}?queryID=${data.__queryID}`; - return ( - - - - + + { - setHoverEffect(index); - }} - onMouseLeave={() => { - setHoverEffect(-1); + textDecoration: "none", + color: "#f85a3e", }} > - - {data.name} -
-
{ + setHoverEffect(index); + }} + onMouseLeave={() => { + setHoverEffect(-1); + }} + > + + {data.name} - {(allActivatedAppIds && allActivatedAppIds.includes(data.objectID)) && } - {normalizedString(data.name)} -
- -
- {data.categories !== null - ? normalizedString(data.categories).join(", ") - : "NA"} -
+ />
-
- {hoverEffect === index && isCloud ? ( -
- {data.tags && ( - - - {data.tags.slice(0, 1).map((tag, tagIndex) => ( - - {normalizedString(tag)} - {tagIndex < 1 ? ", " : ""} - - ))} - - - )} -
- ) : ( -
- {data.tags && - data.tags.map((tag, tagIndex) => ( - - {normalizedString(tag)} - {tagIndex < data.tags.length - 1 ? ", " : ""} - - ))} -
- )} +
+ {(allActivatedAppIds && allActivatedAppIds.includes(data.objectID)) && } + {normalizedString(data.name)}
-
- {hoverEffect === index && isCloud && ( -
- {allActivatedAppIds && allActivatedAppIds.includes(data.objectID) ? ( - - ) : ( -
+
+
+ {hoverEffect === index && isCloud ? ( +
+ {data.tags && ( + + + {data.tags.slice(0, 1).map((tag, tagIndex) => ( + + {normalizedString(tag)} + {tagIndex < 1 ? ", " : ""} + + ))} + + + )} +
+ ) : ( +
+ {data.tags && + data.tags.map((tag, tagIndex) => ( + + {normalizedString(tag)} + {tagIndex < data.tags.length - 1 ? ", " : ""} + + ))} +
+ )} +
+
+ {hoverEffect === index && isCloud && ( +
+ {allActivatedAppIds && allActivatedAppIds.includes(data.objectID) ? ( + - )} -
- )} + onClick={(event) => { + handleActivateButton(event, data, "deactivate"); + }}> + Deactivate + + ) : ( + + )} +
+ )} +
-
- - -
- - - ); - }) - } + + + + + + ); + }) + } +
)} @@ -521,8 +537,9 @@ const Hits = memo(({ )}
); -}); +} +// Main Apps Component const Apps2 = (props) => { const { globalUrl, isLoaded, serverside, userdata, isLoggedIn } = props; let navigate = useNavigate(); @@ -530,11 +547,13 @@ const Apps2 = (props) => { const location = useLocation(); const [searchQuery, setSearchQuery] = useState(""); const [selectedCategory, setSelectedCategory] = useState(""); + const [algoliaSelectedCategories, setAlgoliaSelectedCategories] = useState([]); const [selectedLabel, setSelectedLabel] = useState(""); + const [algoliaSelectedLabels, setAlgoliaSelectedLabels] = useState([]); const [currTab, setCurrTab] = useState(0); const [categories, setCategories] = useState([]); const [labels, setLabels] = useState([]); - const [isLoading, setIsLoading] = useState(true); + const [isLoading, setIsLoading] = useState(false); const [userApps, setUserApps] = useState([]); const [orgApps, setOrgApps] = useState([]); const [selectedCategoryForUsersAndOgsApps, setselectedCategoryForUsersAndOgsApps] = useState([]); @@ -542,13 +561,12 @@ const Apps2 = (props) => { const [appsToShow, setAppsToShow] = useState([]); const [deactivatedIndexes, setDeactivatedIndexes] = React.useState([]); const [IsAnyAppActivated, setIsAnyAppActivated] = useState(false); - const [mouseHoverIndex, setMouseHoverIndex] = useState(-1); var counted = 0; const [showNoAppFound, setShowNoAppFound] = useState(false); - const CustomHits = connectHits(Hits) + // Set the current tab based on the query parameter useEffect(() => { const queryParams = new URLSearchParams(location.search); const tabParam = queryParams.get('tab'); @@ -560,10 +578,13 @@ const Apps2 = (props) => { } else { setCurrTab(2); } + } else { + setCurrTab(0); } }, [location.search]); + // Fetch apps based on the current tab : 0 -> org_apps, 1 -> my_apps, 2 -> all_apps useEffect(() => { const fetchApps = async () => { const baseUrl = globalUrl; @@ -589,9 +610,12 @@ const Apps2 = (props) => { const data = await response.json(); if (currTab === 1) { console.log("data from userApps", data) + setAppsToShow(data); setUserApps(data); setIsLoading(false); } else if (currTab === 0) { + console.log("data from orgApps", data) + setAppsToShow(data); setOrgApps(data); setIsLoading(false); } @@ -601,9 +625,20 @@ const Apps2 = (props) => { }; fetchApps(); - }, [currTab, globalUrl]); // Added globalUrl to dependencies to avoid stale closures + }, [currTab, globalUrl, location.search]); // Added globalUrl to dependencies to avoid stale closures + + useEffect(() => { + setSearchQuery(""); + }, [currTab]) + // Find top categories and tags based on the current tab + useEffect(() => { + if (currTab === 0) { + setCategories(findTopCategories()); + setLabels(findTopTags()); + } + }, [currTab, appsToShow]) useEffect(() => { @@ -612,13 +647,13 @@ const Apps2 = (props) => { } }, [serverside]); - + const findTopCategories = () => { const categoryCountMap = {}; - + const apps = currTab === 1 ? userApps : orgApps; // Check if userAndOrgsApp is an array before iterating over it and Find top 10 Category from the apps - if (Array.isArray(appsToShow)) { - appsToShow.forEach((app) => { + if (Array.isArray(apps)) { + apps.forEach((app) => { const categories = app.categories; if (categories && categories.length > 0) { @@ -643,9 +678,10 @@ const Apps2 = (props) => { const findTopTags = () => { const tagCountMap = {}; - - if (Array.isArray(appsToShow)) { - appsToShow.forEach((app) => { + const apps = currTab === 1 ? userApps : orgApps; + + if (Array.isArray(apps)) { + apps.forEach((app) => { const tags = app.tags; if (tags && tags.length > 0) { tags.forEach((tag) => { @@ -666,42 +702,47 @@ const Apps2 = (props) => { return topTags; }; - + const handleCreateApp = () => { navigate('/create-app'); }; - //Search app base on app name, category and tag - const filteredUserAppdata = Array.isArray(appsToShow) ? appsToShow.filter((app) => { - const matchesSearchQuery = ( - searchQuery === "" || - app.name.toLowerCase().includes(searchQuery.toLowerCase()) || - (app.tags && app.tags.some(tag => - tag.toLowerCase().includes(searchQuery.toLowerCase()) - )) || - (app.categories && app.categories.some((category) => - category.toLowerCase().includes(searchQuery.toLowerCase()) - )) - ); + useEffect(() => { + const apps = currTab === 1 ? userApps : orgApps; + // Search app based on app name, category, and tag + const filteredUserAppdata = Array.isArray(apps) ? apps.filter((app) => { - const matchesSelectedCategories = ( - selectedCategoryForUsersAndOgsApps.length === 0 || - (app.categories && app.categories.some(category => - selectedCategoryForUsersAndOgsApps.includes(category) - )) - ); + const matchesSearchQuery = ( + searchQuery === "" || // If searchQuery is empty, match all apps + app.name.toLowerCase().includes(searchQuery.toLowerCase()) || + (app.tags && app.tags.some(tag => + tag.toLowerCase().includes(searchQuery.toLowerCase()) + )) || + (app.categories && app.categories.some((category) => + category.toLowerCase().includes(searchQuery.toLowerCase()) + )) + ); - const matchesSelectedTags = ( - selectedTagsForUserAndOrgApps.length === 0 || - (app.tags && selectedTagsForUserAndOrgApps.some(tag => - app.tags.includes(tag) - )) - ); + const matchesSelectedCategories = ( + selectedCategory === "" || // If no category is selected, match all apps + (app.categories && app.categories.some(category => + selectedCategory.includes(category) + )) + ); + const matchesSelectedTags = ( + selectedLabel === "" || // If no label is selected, match all apps + (app.tags && app.tags.some(tag => + tag.toLowerCase().includes(selectedLabel.toLowerCase()) + )) + ); - return matchesSearchQuery && matchesSelectedCategories && matchesSelectedTags; - }) : []; + return matchesSearchQuery && matchesSelectedCategories && matchesSelectedTags; + }) : []; + + setAppsToShow(filteredUserAppdata); + }, [searchQuery, selectedCategory, selectedLabel]); const handleTabChange = (newTab) => { @@ -746,6 +787,104 @@ const Apps2 = (props) => { + const SearchBox = ({ refine, searchQuery, setSearchQuery }) => { + const inputRef = React.useRef(null); // Create a ref for the input field + + // Check for query in URL and set it + React.useEffect(() => { + if ( + window !== undefined && + window.location !== undefined && + window.location.search !== undefined && + window.location.search !== null + ) { + const urlSearchParams = new URLSearchParams(window.location.search); + const params = Object.fromEntries(urlSearchParams.entries()); + const foundQuery = params["q"]; + if (foundQuery !== null && foundQuery !== undefined) { + console.log("Got query: ", foundQuery); + refine(foundQuery); + setSearchQuery(foundQuery); // Use setSearchQuery to update state + } + } + }, [refine, setSearchQuery]); // Add dependencies + + // Use useEffect to focus the input when it mounts or when searchQuery changes + React.useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, [searchQuery]); // Focus whenever searchQuery changes + + console.log("searchQuery", searchQuery); + + return ( + { + setSearchQuery(event.target.value); // Update the search query + removeQuery("q"); + refine(event.target.value); // Refine the search + }} + onKeyDown={(event) => { + if (event.key === "Enter") { + event.preventDefault(); + } + }} + limit={5} + style={{ width: '100%', borderRadius: '7px', fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" }} + InputProps={{ + style: { + borderRadius: 8, + }, + endAdornment: ( + + {searchQuery?.length === 0 ? : ( + { + setSearchQuery(''); // Clear the search query + removeQuery("q"); + refine(''); // Clear the refinement + }} + /> + )} + + ), + }} + /> + ); + }; + + const CustomSearchBox = connectSearchBox(SearchBox); + const CustomHits = connectHits(Hits) + + + const handleCategoryChange = (selectedValue) => { + setAlgoliaSelectedCategories(prev => { + if (prev.includes(selectedValue)) { + // If the category is already selected, remove it + return prev.filter(category => category !== selectedValue); + } else { + // Otherwise, add it to the selected categories + return [...prev, selectedValue]; + } + }); + }; + + useEffect(() => { + console.log("algoliaSelectedCategories", algoliaSelectedCategories) + }, [algoliaSelectedCategories]) + + return (
@@ -765,38 +904,42 @@ const Apps2 = (props) => {
- { - setSearchQuery(event.currentTarget.value); - removeQuery("q"); - // refine(event.currentTarget.value); - }} - onKeyDown={(event) => { - if (event.key === "Enter") { - event.preventDefault(); - } - }} - limit={5} - style={{ width: '100%', borderRadius: '7px', fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" }} - InputProps={{ - style: { - borderRadius: 8, - }, - endAdornment: ( - - { - searchQuery.length === 0 ? : - } - - ), - }} - /> - {/* */} + { + (currTab === 0 || currTab === 1) && + { + setSearchQuery(event.target.value); + }} + onKeyDown={(event) => { + if (event.key === "Enter") { + event.preventDefault(); + } + }} + limit={5} + style={{ width: '100%', borderRadius: '7px', fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" }} + InputProps={{ + style: { + borderRadius: 8, + }, + endAdornment: ( + + { + searchQuery.length === 0 ? : + } + + ), + }} + /> + } + { + currTab === 2 && + + }
@@ -878,74 +1030,86 @@ const Apps2 = (props) => {
{ - currTab === 0 && - (orgApps.length > 0 ? ( -
- { - isLoading ?
- : - ( - filteredUserAppdata?.map((data, index) => ( - - )) - ) - } + currTab === 0 && ( +
+ {isLoading ? ( +
+ +
+ ) : ( + <> + {appsToShow?.length > 0 && appsToShow !== undefined && !isLoading ? ( +
+ {appsToShow.map((data, index) => ( + + ))} +
+ ) : ( +
+ No apps found +
+ )} + + )}
- ) :
No apps found
) } { currTab === 1 && (userApps.length > 0 ? ( -
- { - isLoading ?
- : - ( - userApps.map((data, index) => ( - - )) - ) - } +
+
+ { + isLoading ?
+ : + ( + userApps.map((data, index) => ( + + )) + ) + } +
- ) :
No apps found
+ ) : !isLoading && ( +
No apps found
+ ) ) } { currTab === 2 && + { hitsPerPage={5} globalUrl={globalUrl} searchQuery={searchQuery} + algoliaSelectedCategories={algoliaSelectedCategories} mouseHoverIndex={mouseHoverIndex} setMouseHoverIndex={setMouseHoverIndex} /> From 8e9b1cf1ea56138c1595c26f5225a3b14f0529be Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Fri, 15 Nov 2024 13:08:27 +0530 Subject: [PATCH 4/6] Done with categories and labels in both app and org apps --- frontend/src/views/Apps2.jsx | 198 ++++++++++++++++++----------------- 1 file changed, 104 insertions(+), 94 deletions(-) diff --git a/frontend/src/views/Apps2.jsx b/frontend/src/views/Apps2.jsx index b4573574..a097b800 100644 --- a/frontend/src/views/Apps2.jsx +++ b/frontend/src/views/Apps2.jsx @@ -11,6 +11,7 @@ import { Tooltip, Box, CircularProgress, + Checkbox, } from "@mui/material"; import { Context } from "../context/ContextApi.jsx"; import Add from '@mui/icons-material/Add'; @@ -18,7 +19,7 @@ import InputAdornment from '@mui/material/InputAdornment'; import Search from '@mui/icons-material/Search'; import ClearIcon from '@mui/icons-material/Clear'; -import { ClearRefinements, connectHits, connectSearchBox, connectStateResults, InstantSearch, RefinementList } from "react-instantsearch-dom"; +import { ClearRefinements, connectHits, connectSearchBox, connectStateResults, InstantSearch, RefinementList, connectRefinementList } from "react-instantsearch-dom"; import { removeQuery } from "../components/ScrollToTop.jsx"; import { toast } from "react-toastify"; import algoliasearch from "algoliasearch/lite"; @@ -173,7 +174,6 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, const Hits = ({ userdata, hits, - algoliaSelectedCategories, setIsAnyAppActivated, searchQuery, globalUrl, @@ -200,10 +200,6 @@ const Hits = ({ }, [searchQuery, hits]) - const filteredHits = hits?.filter(hit => { - if (algoliaSelectedCategories?.length === 0) return true; // If no categories are selected, show all hits - return hit.categories?.some(category => algoliaSelectedCategories.includes(category)); - }); // const fetchUserData = useCallback(async () => { // try { @@ -298,7 +294,7 @@ const Hits = ({
{!isLoading ? (
- {filteredHits?.length === 0 && searchQuery.length >= 0 && showNoAppFound ? ( + {hits?.length === 0 && searchQuery.length >= 0 && showNoAppFound ? ( No Apps Found ) : ( @@ -322,7 +318,7 @@ const Hits = ({ scrollbarColor: "#494949 #2f2f2f", }} > - {filteredHits?.map((data, index) => { + {hits?.map((data, index) => { const appUrl = isCloud ? `/apps/${data.objectID}?queryID=${data.__queryID}` @@ -539,6 +535,47 @@ const Hits = ({ ); } +// Custom Category Dropdown Component +const CategoryDropdown = ({ items, currentRefinement, refine }) => { + const handleChange = (event) => { + const value = event.target.value; + refine(value); + }; + + return ( + + ); +}; + +const CustomCategoryDropdown = connectRefinementList(CategoryDropdown); + // Main Apps Component const Apps2 = (props) => { const { globalUrl, isLoaded, serverside, userdata, isLoggedIn } = props; @@ -546,9 +583,8 @@ const Apps2 = (props) => { const { leftSideBarOpenByClick } = useContext(Context); const location = useLocation(); const [searchQuery, setSearchQuery] = useState(""); - const [selectedCategory, setSelectedCategory] = useState(""); - const [algoliaSelectedCategories, setAlgoliaSelectedCategories] = useState([]); - const [selectedLabel, setSelectedLabel] = useState(""); + const [selectedCategory, setSelectedCategory] = useState([]); + const [selectedLabel, setSelectedLabel] = useState([]); const [algoliaSelectedLabels, setAlgoliaSelectedLabels] = useState([]); const [currTab, setCurrTab] = useState(0); const [categories, setCategories] = useState([]); @@ -679,7 +715,7 @@ const Apps2 = (props) => { const findTopTags = () => { const tagCountMap = {}; const apps = currTab === 1 ? userApps : orgApps; - + if (Array.isArray(apps)) { apps.forEach((app) => { const tags = app.tags; @@ -725,22 +761,22 @@ const Apps2 = (props) => { ); const matchesSelectedCategories = ( - selectedCategory === "" || // If no category is selected, match all apps + selectedCategory.length === 0 || // If no category is selected, match all apps (app.categories && app.categories.some(category => selectedCategory.includes(category) )) ); const matchesSelectedTags = ( - selectedLabel === "" || // If no label is selected, match all apps + selectedLabel.length === 0 || // If no label is selected, match all apps (app.tags && app.tags.some(tag => - tag.toLowerCase().includes(selectedLabel.toLowerCase()) + selectedLabel.includes(tag) )) ); return matchesSearchQuery && matchesSelectedCategories && matchesSelectedTags; }) : []; - + setAppsToShow(filteredUserAppdata); }, [searchQuery, selectedCategory, selectedLabel]); @@ -779,13 +815,6 @@ const Apps2 = (props) => { // padding: '20px 380px', }; - const CustomClearRefinements = connectStateResults(({ searchResults, ...rest }) => { - const hasFilters = searchResults && searchResults.nbHits !== searchResults.nbSortedHits; - return Clear All }} {...rest} disabled={!hasFilters} />; - }); - - - const SearchBox = ({ refine, searchQuery, setSearchQuery }) => { const inputRef = React.useRef(null); // Create a ref for the input field @@ -868,21 +897,15 @@ const Apps2 = (props) => { const CustomHits = connectHits(Hits) - const handleCategoryChange = (selectedValue) => { - setAlgoliaSelectedCategories(prev => { - if (prev.includes(selectedValue)) { - // If the category is already selected, remove it - return prev.filter(category => category !== selectedValue); - } else { - // Otherwise, add it to the selected categories - return [...prev, selectedValue]; - } - }); + const handleCategoryChange = (event) => { + const value = event.target.value; + setSelectedCategory(value); }; - useEffect(() => { - console.log("algoliaSelectedCategories", algoliaSelectedCategories) - }, [algoliaSelectedCategories]) + const handleLabelChange = (event) => { + const value = event.target.value; + setSelectedLabel(value); + }; return ( @@ -942,75 +965,63 @@ const Apps2 = (props) => { }
- selected.length ? selected.join(', ') : 'All Categories'} + > + + All Categories + + {categories?.map((category) => ( + + + {category.category} + + ))} + { + selectedCategory.length > 0 && ( + setSelectedCategory([])} style={{ display: 'flex', justifyContent: 'center', padding: '10px', backgroundColor: '#212121', color: '#fff', marginTop: '10px', cursor: 'pointer' }}> + Remove All Categories - )) - ) - } - { - currTab === 2 && - ( -
- { - handleCategoryChange(selectedItems); - }} - transformItems={(items) => { - console.log("items", items) - return items; - }} - /> -
- ) - } - + ) + } + + )}
@@ -1117,7 +1128,6 @@ const Apps2 = (props) => { hitsPerPage={5} globalUrl={globalUrl} searchQuery={searchQuery} - algoliaSelectedCategories={algoliaSelectedCategories} mouseHoverIndex={mouseHoverIndex} setMouseHoverIndex={setMouseHoverIndex} /> From d9b3acd27c6ea8379e8b99e8ff38631e10fd916d Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Fri, 15 Nov 2024 13:39:09 +0530 Subject: [PATCH 5/6] Done with algolia search --- frontend/src/views/Apps2.jsx | 208 ++++++++++++++--------------------- 1 file changed, 85 insertions(+), 123 deletions(-) diff --git a/frontend/src/views/Apps2.jsx b/frontend/src/views/Apps2.jsx index a097b800..9ccbd95e 100644 --- a/frontend/src/views/Apps2.jsx +++ b/frontend/src/views/Apps2.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useContext, useCallback, memo, useMemo } from "react"; +import React, { useState, useEffect, useContext, useCallback, memo, useMemo, useRef } from "react"; import theme from "../theme.jsx"; import { isMobile } from "react-device-detect"; import AppGrid from "../components/AppGrid.jsx"; @@ -19,10 +19,11 @@ import InputAdornment from '@mui/material/InputAdornment'; import Search from '@mui/icons-material/Search'; import ClearIcon from '@mui/icons-material/Clear'; -import { ClearRefinements, connectHits, connectSearchBox, connectStateResults, InstantSearch, RefinementList, connectRefinementList } from "react-instantsearch-dom"; +import { ClearRefinements, connectHits, connectSearchBox, connectStateResults, InstantSearch, RefinementList, connectRefinementList, Configure } from "react-instantsearch-dom"; import { removeQuery } from "../components/ScrollToTop.jsx"; import { toast } from "react-toastify"; import algoliasearch from "algoliasearch/lite"; +import { debounce } from "lodash"; const searchClient = algoliasearch( @@ -180,7 +181,6 @@ const Hits = ({ isLoading, isLoggedIn, }) => { - var counted = 0; const [hoverEffect, setHoverEffect] = useState(-1); const [allActivatedAppIds, setAllActivatedAppIds] = useState(userdata?.active_apps); const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" || window.location.host === "localhost:3000"; @@ -201,31 +201,6 @@ const Hits = ({ - // const fetchUserData = useCallback(async () => { - // try { - // const response = await fetch(`${globalUrl}/api/v1/me`, { - // credentials: "include", - // headers: { - // 'Content-Type': 'application/json', - // }, - // }); - // const responseJson = await response.json(); - // console.log("responseJson : user data", responseJson) - // if (responseJson.success) { - // setUserdata(responseJson); - // setAllActivatedAppIds(responseJson.active_apps); - // setIsLoggedIn(true); - // } else { - // setIsLoggedIn(false); - // } - // } catch (error) { - // console.log("Failed login check: ", error); - // } - // }, [globalUrl]); // Added globalUrl as a dependency - - // useEffect(() => { - // fetchUserData(); - // }, [fetchUserData]); // Ensure fetchUserData is stable //Function for activation and deactivation of app const handleActivateButton = (event, data, type) => { @@ -312,7 +287,6 @@ const Hits = ({ display: "flex", flexWrap: "wrap", justifyContent: "flex-start", - // marginLeft: 24, maxHeight: 570, scrollbarWidth: "thin", scrollbarColor: "#494949 #2f2f2f", @@ -521,11 +495,10 @@ const Hits = ({ ); - }) - } + })}
- + )}
) : ( @@ -576,6 +549,85 @@ const CategoryDropdown = ({ items, currentRefinement, refine }) => { const CustomCategoryDropdown = connectRefinementList(CategoryDropdown); +// Custom SearchBox Component +const SearchBox = ({ refine, searchQuery, setSearchQuery }) => { + const inputRef = useRef(null); + const [localQuery, setLocalQuery] = useState(searchQuery); + + // Debounced function to refine search + const debouncedRefine = useRef( + debounce((value) => { + setSearchQuery(value); + removeQuery("q"); + refine(value); + }, 300) // Adjust the delay as needed + ).current; + + useEffect(() => { + const urlSearchParams = new URLSearchParams(window.location.search); + const params = Object.fromEntries(urlSearchParams.entries()); + const foundQuery = params["q"]; + if (foundQuery) { + setLocalQuery(foundQuery); + debouncedRefine(foundQuery); + } + }, [debouncedRefine]); + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, [searchQuery]); + + const handleChange = (event) => { + const value = event.target.value; + setLocalQuery(value); + debouncedRefine(value); + }; + + return ( + { + if (event.key === "Enter") { + event.preventDefault(); + } + }} + style={{ width: '100%', borderRadius: '7px', fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" }} + InputProps={{ + style: { + borderRadius: 8, + }, + endAdornment: ( + + {localQuery?.length === 0 ? : ( + { + setLocalQuery(''); + debouncedRefine(''); + }} + /> + )} + + ), + }} + /> + ); +}; + +const CustomSearchBox = connectSearchBox(SearchBox); +const CustomHits = connectHits(Hits); + // Main Apps Component const Apps2 = (props) => { const { globalUrl, isLoaded, serverside, userdata, isLoggedIn } = props; @@ -631,8 +683,6 @@ const Apps2 = (props) => { url = `${baseUrl}/api/v1/users/${userId}/apps`; } else if (currTab === 0) { url = `${baseUrl}/api/v1/apps`; - } else { - return; // No need to fetch if not in the relevant tabs } setIsLoading(true); try { @@ -816,87 +866,6 @@ const Apps2 = (props) => { }; - const SearchBox = ({ refine, searchQuery, setSearchQuery }) => { - const inputRef = React.useRef(null); // Create a ref for the input field - - // Check for query in URL and set it - React.useEffect(() => { - if ( - window !== undefined && - window.location !== undefined && - window.location.search !== undefined && - window.location.search !== null - ) { - const urlSearchParams = new URLSearchParams(window.location.search); - const params = Object.fromEntries(urlSearchParams.entries()); - const foundQuery = params["q"]; - if (foundQuery !== null && foundQuery !== undefined) { - console.log("Got query: ", foundQuery); - refine(foundQuery); - setSearchQuery(foundQuery); // Use setSearchQuery to update state - } - } - }, [refine, setSearchQuery]); // Add dependencies - - // Use useEffect to focus the input when it mounts or when searchQuery changes - React.useEffect(() => { - if (inputRef.current) { - inputRef.current.focus(); - } - }, [searchQuery]); // Focus whenever searchQuery changes - - console.log("searchQuery", searchQuery); - - return ( - { - setSearchQuery(event.target.value); // Update the search query - removeQuery("q"); - refine(event.target.value); // Refine the search - }} - onKeyDown={(event) => { - if (event.key === "Enter") { - event.preventDefault(); - } - }} - limit={5} - style={{ width: '100%', borderRadius: '7px', fontFamily: "var(--zds-typography-base,Inter,Helvetica,arial,sans-serif)" }} - InputProps={{ - style: { - borderRadius: 8, - }, - endAdornment: ( - - {searchQuery?.length === 0 ? : ( - { - setSearchQuery(''); // Clear the search query - removeQuery("q"); - refine(''); // Clear the refinement - }} - /> - )} - - ), - }} - /> - ); - }; - - const CustomSearchBox = connectSearchBox(SearchBox); - const CustomHits = connectHits(Hits) - - const handleCategoryChange = (event) => { const value = event.target.value; setSelectedCategory(value); @@ -1120,7 +1089,6 @@ const Apps2 = (props) => { { currTab === 2 && - { /> }
- {/* */}
+ ); }; From 0e1a86c90ba4540f97f68052e0871cf3698406f6 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Fri, 15 Nov 2024 17:02:43 +0530 Subject: [PATCH 6/6] Done with myApps section --- frontend/src/App.jsx | 1 + frontend/src/components/AppSelection.jsx | 32 +- frontend/src/views/Apps2.jsx | 466 +++++++++++++---------- 3 files changed, 285 insertions(+), 214 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index ab71062e..04cdd17c 100755 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -418,6 +418,7 @@ const App = (message, props) => { serverside={false} isLoaded={isLoaded} isLoggedIn={isLoggedIn} + checkLogin={checkLogin} userdata={userdata} globalUrl={globalUrl} surfaceColor={theme.palette.surfaceColor} diff --git a/frontend/src/components/AppSelection.jsx b/frontend/src/components/AppSelection.jsx index 5bbb5ff0..df78af8e 100644 --- a/frontend/src/components/AppSelection.jsx +++ b/frontend/src/components/AppSelection.jsx @@ -47,6 +47,7 @@ const AppSelection = props => { defaultSearch, setDefaultSearch, checkLogin, + isAppPage=false } = props; const [discoveryData, setDiscoveryData] = React.useState({}) @@ -507,26 +508,31 @@ const AppSelection = props => { })}
- {!moreButton ? ( -
- { + { + !isAppPage && ( + <> + {!moreButton ? ( +
+ { setMoreButton(true) - setTimeout(() => { navigate("/welcome?tab=2") }, 250) }} >See More Apps -
) : ""} +
) : ""} -
- -
+
+ +
+ + ) + }
) diff --git a/frontend/src/views/Apps2.jsx b/frontend/src/views/Apps2.jsx index 9ccbd95e..079d4b2e 100644 --- a/frontend/src/views/Apps2.jsx +++ b/frontend/src/views/Apps2.jsx @@ -24,6 +24,7 @@ import { removeQuery } from "../components/ScrollToTop.jsx"; import { toast } from "react-toastify"; import algoliasearch from "algoliasearch/lite"; import { debounce } from "lodash"; +import AppSelection from "../components/AppSelection.jsx"; const searchClient = algoliasearch( @@ -32,7 +33,7 @@ const searchClient = algoliasearch( ); // AppCard Component -const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, deactivatedIndexes }) => { +const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, deactivatedIndexes, currTab }) => { const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" || window.location.host === "localhost:3000"; const appUrl = isCloud ? `/apps/${data.id}` : `https://shuffler.io/apps/${data.id}`; @@ -50,9 +51,13 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, return ( - - setMouseHoverIndex(index)} onMouseOut={() => setMouseHoverIndex(-1)}> - setMouseHoverIndex(index)} + onMouseOut={() => setMouseHoverIndex(-1)} + > + { + // if (!event.target.closest('.deactivate-button')) { + // window.location.href = appUrl; + // } + console.log("clicked"); + }} + > + {data.name} +
- {data.name}
-
- {data.name.replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase())} + {data.name.replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase())} +
+
+ {data.categories ? data.categories.join(", ") : "NA"} +
+
+
+ {data.generated !== true && data.tags && data.tags.slice(0, 2).map((tag, tagIndex) => ( + + {tag} + {tagIndex < data.tags.length - 1 ? ", " : ""} + + ))}
-
- {data.categories ? data.categories.join(", ") : "NA"} -
-
-
- {data.generated !== true && data.tags && data.tags.slice(0, 2).map((tag, tagIndex) => ( - - {tag} - {tagIndex < data.tags.length - 1 ? ", " : ""} - - ))} -
- {/* Deactivate button logic */} - {mouseHoverIndex === index ? - - : null} -
+ .catch(error => { + console.log("app error: ", error.toString()); + }); + }} + > + Deactivate + + )}
- - -
+
+ + ); }; @@ -183,7 +196,7 @@ const Hits = ({ }) => { const [hoverEffect, setHoverEffect] = useState(-1); const [allActivatedAppIds, setAllActivatedAppIds] = useState(userdata?.active_apps); - const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" || window.location.host === "localhost:3000"; + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; const [deactivatedIndexes, setDeactivatedIndexes] = React.useState([]); const normalizedString = (name) => { @@ -194,12 +207,6 @@ const Hits = ({ } }; - useEffect(() => { - console.log("searchQuery", searchQuery) - console.log("hits", hits) - }, [searchQuery, hits]) - - //Function for activation and deactivation of app @@ -270,7 +277,9 @@ const Hits = ({ {!isLoading ? (
{hits?.length === 0 && searchQuery.length >= 0 && showNoAppFound ? ( - No Apps Found +
+ No Apps Found +
) : (
{ }, [debouncedRefine]); useEffect(() => { + if (searchQuery === "") { + return; + } if (inputRef.current) { inputRef.current.focus(); } @@ -628,24 +640,62 @@ const SearchBox = ({ refine, searchQuery, setSearchQuery }) => { const CustomSearchBox = connectSearchBox(SearchBox); const CustomHits = connectHits(Hits); +// Custom Label Dropdown Component +const LabelDropdown = ({ items, currentRefinement, refine }) => { + const handleChange = (event) => { + const value = event.target.value; + refine(value); + }; + + return ( + + ); +}; + +const CustomLabelDropdown = connectRefinementList(LabelDropdown); + // Main Apps Component const Apps2 = (props) => { - const { globalUrl, isLoaded, serverside, userdata, isLoggedIn } = props; + const { globalUrl, isLoaded, serverside, userdata, isLoggedIn, checkLogin } = props; let navigate = useNavigate(); const { leftSideBarOpenByClick } = useContext(Context); const location = useLocation(); const [searchQuery, setSearchQuery] = useState(""); const [selectedCategory, setSelectedCategory] = useState([]); const [selectedLabel, setSelectedLabel] = useState([]); - const [algoliaSelectedLabels, setAlgoliaSelectedLabels] = useState([]); const [currTab, setCurrTab] = useState(0); const [categories, setCategories] = useState([]); const [labels, setLabels] = useState([]); const [isLoading, setIsLoading] = useState(false); const [userApps, setUserApps] = useState([]); const [orgApps, setOrgApps] = useState([]); - const [selectedCategoryForUsersAndOgsApps, setselectedCategoryForUsersAndOgsApps] = useState([]); - const [selectedTagsForUserAndOrgApps, setSelectedTagsForUserAndOrgApps] = useState([]); const [appsToShow, setAppsToShow] = useState([]); const [deactivatedIndexes, setDeactivatedIndexes] = React.useState([]); const [IsAnyAppActivated, setIsAnyAppActivated] = useState(false); @@ -654,6 +704,8 @@ const Apps2 = (props) => { const [showNoAppFound, setShowNoAppFound] = useState(false); + const [appFramework, setAppFramework] = useState(undefined); + const [defaultSearch, setDefaultSearch] = useState(""); // Set the current tab based on the query parameter useEffect(() => { const queryParams = new URLSearchParams(location.search); @@ -666,11 +718,47 @@ const Apps2 = (props) => { } else { setCurrTab(2); } - } else { - setCurrTab(0); } }, [location.search]); + useEffect(() => { + + const getFramework = () => { + fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for framework!"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === false) { + setAppFramework({}) + + if (responseJson.reason !== undefined) { + //toast("Failed loading: " + responseJson.reason) + } else { + //toast("Failed to load framework for your org.") + } + } else { + setAppFramework(responseJson) + } + }) + .catch((error) => { + console.log("err in framework: ", error.toString()); + }) + } + + getFramework(); + }, []); // Fetch apps based on the current tab : 0 -> org_apps, 1 -> my_apps, 2 -> all_apps useEffect(() => { @@ -695,23 +783,27 @@ const Apps2 = (props) => { }); const data = await response.json(); if (currTab === 1) { - console.log("data from userApps", data) setAppsToShow(data); setUserApps(data); - setIsLoading(false); } else if (currTab === 0) { - console.log("data from orgApps", data) setAppsToShow(data); setOrgApps(data); - setIsLoading(false); + // For testing the empty state + // setAppsToShow([]); + // setOrgApps([]); } + setIsLoading(false); } catch (err) { console.error("Error fetching apps:", err); + setIsLoading(false); } }; - fetchApps(); - }, [currTab, globalUrl, location.search]); // Added globalUrl to dependencies to avoid stale closures + // Only fetch if we have required data + if (globalUrl && (currTab === 0 || (currTab === 1 && userdata?.id))) { + fetchApps(); + } + }, [currTab, globalUrl, userdata?.id]); // Remove location.search dependency useEffect(() => { setSearchQuery(""); @@ -789,8 +881,9 @@ const Apps2 = (props) => { return topTags; }; - const handleCreateApp = () => { - navigate('/create-app'); + const handleCreateApp = (e) => { + e.preventDefault(); + console.log("Create app clicked") }; @@ -832,7 +925,6 @@ const Apps2 = (props) => { const handleTabChange = (newTab) => { - console.log("Current Tab", newTab) setCurrTab(newTab); if (newTab === 0) { setAppsToShow(orgApps) @@ -956,44 +1048,34 @@ const Apps2 = (props) => { {category.category} ))} - { - selectedCategory.length > 0 && ( - setSelectedCategory([])} style={{ display: 'flex', justifyContent: 'center', padding: '10px', backgroundColor: '#212121', color: '#fff', marginTop: '10px', cursor: 'pointer' }}> - Remove All Categories - - ) - } )}
- selected.length ? selected.join(', ') : 'All Labels'} + > + + All Labels - ))} - { - selectedLabel.length > 0 && ( - setSelectedCategory([])} style={{ display: 'flex', justifyContent: 'center', padding: '10px', backgroundColor: '#212121', color: '#fff', marginTop: '10px', cursor: 'pointer' }}> - Remove All Labels + {labels?.map((tag) => ( + + + {tag.tag} - ) - } - + ))} + + )}