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) => ( - - ))} -
-
- -
+ /> */} +
+ ); };