diff --git a/apps/dashboard/src/main/java/com/akto/action/AccessTokenAction.java b/apps/dashboard/src/main/java/com/akto/action/AccessTokenAction.java index 865b2ba81d..87ff229f69 100644 --- a/apps/dashboard/src/main/java/com/akto/action/AccessTokenAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/AccessTokenAction.java @@ -22,6 +22,7 @@ public class AccessTokenAction implements Action, ServletResponseAware, ServletR public static final String ACCESS_TOKEN_HEADER_NAME = "access-token"; public static final String CONTEXT_SOURCE_HEADER = "x-context-source"; public static final String AKTO_SESSION_TOKEN = "x-akto-session-token"; + public static final String SUB_CATEGORY_HEADER = "x-sub-category"; @Override public String execute() { diff --git a/apps/dashboard/src/main/java/com/akto/action/ApiCollectionsAction.java b/apps/dashboard/src/main/java/com/akto/action/ApiCollectionsAction.java index e791b63a6b..18f4c2a8aa 100644 --- a/apps/dashboard/src/main/java/com/akto/action/ApiCollectionsAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/ApiCollectionsAction.java @@ -95,6 +95,8 @@ public class ApiCollectionsAction extends UserAction { int mcpDataCount; @Setter String type; + @Setter + String contextType; @Getter List auditAlerts; @@ -116,10 +118,15 @@ public void setApiList(List apiList) { * Only populates MCP URLs when dashboard context is not API security. */ private void populateCollectionUrls(ApiCollection apiCollection) { - // Do not populate MCP URLs if dashboard context is API security - if (Context.contextSource.get() != null && Context.contextSource.get() == GlobalEnums.CONTEXT_SOURCE.MCP) { - apiCollectionUrlService.populateMcpCollectionUrls(apiCollection); - }else { + try { + // Do not populate MCP URLs if dashboard context is API security + GlobalEnums.CONTEXT_SOURCE contextSource = Context.contextSource.get(); + if (contextSource != null && contextSource == GlobalEnums.CONTEXT_SOURCE.MCP) { + apiCollectionUrlService.populateMcpCollectionUrls(apiCollection); + } else { + apiCollection.setUrls(new HashSet<>()); + } + } catch (Exception e) { apiCollection.setUrls(new HashSet<>()); } } @@ -237,7 +244,18 @@ public String fetchApiStats() { } public String fetchAllCollectionsBasic() { - UsersCollectionsList.deleteContextCollectionsForUser(Context.accountId.get(), Context.contextSource.get()); + try { + // Safely delete context collections for user - handle potential null context source + Integer accountId = Context.accountId.get(); + GlobalEnums.CONTEXT_SOURCE contextSource = Context.contextSource.get(); + if (accountId != null) { + UsersCollectionsList.deleteContextCollectionsForUser(accountId, contextSource, Context.subCategory.get()); + } + } catch (Exception e) { + // Log the error but don't fail the entire request + loggerMaker.errorAndAddToDb(e, "Error deleting context collections for user", LogDb.DASHBOARD); + } + this.apiCollections = ApiCollectionsDao.instance.findAll(Filters.empty(), Projections.exclude("urls")); this.apiCollections = fillApiCollectionsUrlCount(this.apiCollections, Filters.nin(SingleTypeInfo._API_COLLECTION_ID, deactivatedCollections)); return Action.SUCCESS.toUpperCase(); @@ -316,7 +334,7 @@ public String createCollection() { UsersCollectionsList.deleteCollectionIdsFromCache(userId, accountId); // remove the cache of context collections for account - UsersCollectionsList.deleteContextCollectionsForUser(Context.accountId.get(), Context.contextSource.get()); + UsersCollectionsList.deleteContextCollectionsForUser(Context.accountId.get(), Context.contextSource.get(), Context.subCategory.get()); } catch(Exception e){ } @@ -385,7 +403,7 @@ public String deleteMultipleCollections() { UsersCollectionsList.deleteCollectionIdsFromCache(userId, accountId); // remove the cache of context collections for account - UsersCollectionsList.deleteContextCollectionsForUser(Context.accountId.get(), Context.contextSource.get()); + UsersCollectionsList.deleteContextCollectionsForUser(Context.accountId.get(), Context.contextSource.get(), Context.subCategory.get()); } catch (Exception e) { } @@ -1171,7 +1189,9 @@ public String fetchMcpdata() { Bson mcpTagFilter = Filters.elemMatch(ApiCollection.TAGS_STRING, Filters.eq("keyName", com.akto.util.Constants.AKTO_MCP_SERVER_TAG) ); + List mcpCollections = ApiCollectionsDao.instance.findAll(mcpTagFilter, null); + List mcpCollectionIds = mcpCollections.stream().map(ApiCollection::getId).collect(Collectors.toList()); switch (filterType) { @@ -1314,11 +1334,13 @@ public String fetchMcpdata() { Bson guardRailTagFilter = Filters.elemMatch(ApiCollection.TAGS_STRING, Filters.eq("keyName", Constants.AKTO_GUARD_RAIL_TAG) ); + // Use projection to only fetch IDs, reducing memory usage List guardRailCollections = ApiCollectionsDao.instance.findAll( - guardRailTagFilter, - Projections.include(ApiCollection.ID) + guardRailTagFilter, + Projections.include(ApiCollection.ID) ); + List guardRailCollectionIds = guardRailCollections.stream() .map(ApiCollection::getId) .collect(Collectors.toList()); diff --git a/apps/dashboard/src/main/java/com/akto/action/threat_detection/utils/ThreatsUtils.java b/apps/dashboard/src/main/java/com/akto/action/threat_detection/utils/ThreatsUtils.java index 3f2f86ad1d..23a813f6da 100644 --- a/apps/dashboard/src/main/java/com/akto/action/threat_detection/utils/ThreatsUtils.java +++ b/apps/dashboard/src/main/java/com/akto/action/threat_detection/utils/ThreatsUtils.java @@ -2,6 +2,8 @@ import com.akto.dao.context.Context; import com.akto.dao.monitoring.FilterYamlTemplateDao; +import com.akto.util.enums.GlobalEnums.CONTEXT_SOURCE; +import com.akto.util.enums.GlobalEnums.SUB_CATEGORY_SOURCE; import java.util.ArrayList; import java.util.List; @@ -12,12 +14,56 @@ public class ThreatsUtils { public static List getTemplates(List latestAttack) { Set contextTemplatesForAccount = FilterYamlTemplateDao.getContextTemplatesForAccount(Context.accountId.get(), Context.contextSource.get()); +// Set contextTemplatesForAccount = getContextTemplatesWithSubCategoryFilter( +// Context.accountId.get(), +// Context.contextSource.get(), +// Context.getSubCategory() +// ); +// +// System.out.println("ThreatsUtils.getTemplates: Context=" + Context.contextSource.get() + +// ", SubCategory=" + Context.getSubCategory() + +// ", Templates count=" + contextTemplatesForAccount.size()); if(latestAttack == null || latestAttack.isEmpty()) { return new ArrayList<>(contextTemplatesForAccount); } - return latestAttack.stream().filter(contextTemplatesForAccount::contains).collect(Collectors.toList()); } + + /** + * Get templates filtered by context and subcategory. + * Implements the same filtering logic as UsersCollectionsList.getContextCollections() + */ +// private static Set getContextTemplatesWithSubCategoryFilter(int accountId, CONTEXT_SOURCE contextSource, SUB_CATEGORY_SOURCE subCategory) { +// // For non-AGENTIC contexts, use existing logic without subcategory filtering +// if (contextSource != CONTEXT_SOURCE.AGENTIC) { +// System.out.println("ThreatsUtils: Using standard context filtering for " + contextSource); +// return FilterYamlTemplateDao.getContextTemplatesForAccount(accountId, contextSource,subCategory); +// } +// +// // Handle null subcategory +// if (subCategory == null) { +// subCategory = SUB_CATEGORY_SOURCE.DEFAULT; +// } +// +// System.out.println("ThreatsUtils: Processing AGENTIC context with SubCategory: " + subCategory); +// +// // Apply subcategory-based filtering similar to UsersCollectionsList.getContextCollections() +// if (subCategory == SUB_CATEGORY_SOURCE.ENDPOINT_SECURITY) { +// // For endpoint security, use only MCP templates +// System.out.println("ThreatsUtils: Using MCP templates for Endpoint Security"); +// return FilterYamlTemplateDao.getContextTemplatesForAccount(accountId, CONTEXT_SOURCE.MCP,subCategory); +// +// } else if (subCategory == SUB_CATEGORY_SOURCE.CLOUD_SECURITY) { +// // For cloud security, use only GenAI templates +// System.out.println("ThreatsUtils: Using GenAI templates for Cloud Security"); +// return FilterYamlTemplateDao.getContextTemplatesForAccount(accountId, CONTEXT_SOURCE.GEN_AI,subCategory); +// +// } else { +// // For DEFAULT subcategory or other cases, use full AGENTIC templates (both MCP and GenAI) +// System.out.println("ThreatsUtils: Using all AGENTIC templates (DEFAULT subcategory)"); +// return FilterYamlTemplateDao.getContextTemplatesForAccount(accountId, CONTEXT_SOURCE.AGENTIC,subCategory); +// } +// } } diff --git a/apps/dashboard/src/main/java/com/akto/filter/UserDetailsFilter.java b/apps/dashboard/src/main/java/com/akto/filter/UserDetailsFilter.java index 71502ceb2b..942c3e5c45 100644 --- a/apps/dashboard/src/main/java/com/akto/filter/UserDetailsFilter.java +++ b/apps/dashboard/src/main/java/com/akto/filter/UserDetailsFilter.java @@ -83,6 +83,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo String accessTokenFromResponse = httpServletResponse.getHeader(AccessTokenAction.ACCESS_TOKEN_HEADER_NAME); String accessTokenFromRequest = httpServletRequest.getHeader(AccessTokenAction.ACCESS_TOKEN_HEADER_NAME); String contextSourceFromRequest = httpServletRequest.getHeader(AccessTokenAction.CONTEXT_SOURCE_HEADER); + String subCategoryFromRequest = httpServletRequest.getHeader(AccessTokenAction.SUB_CATEGORY_HEADER); String aktoSessionTokenFromRequest = httpServletRequest.getHeader(AccessTokenAction.AKTO_SESSION_TOKEN); @@ -108,6 +109,18 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo } } + if(!StringUtils.isEmpty(subCategoryFromRequest)) { + if(StringUtils.isEmpty(subCategoryFromRequest)){ + Context.subCategory.set(GlobalEnums.SUB_CATEGORY_SOURCE.DEFAULT); + } else { + try { + Context.subCategory.set(GlobalEnums.SUB_CATEGORY_SOURCE.valueOf(subCategoryFromRequest.toUpperCase())); + } catch (Exception e) { + Context.subCategory.set(GlobalEnums.SUB_CATEGORY_SOURCE.DEFAULT); + } + } + } + if(StringUtils.isNotEmpty(aktoSessionTokenFromRequest) && httpServletRequest.getRequestURI().contains("agent")){ try { Jws claims = JwtAuthenticator.authenticate(aktoSessionTokenFromRequest); diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/layouts/header/Headers.js b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/layouts/header/Headers.js index df3df0134f..1a66c20b73 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/layouts/header/Headers.js +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/layouts/header/Headers.js @@ -118,6 +118,9 @@ export default function Header() { } const handleDashboardChange = (value) => { + // Preserve current subcategory selection + const currentSubCategory = PersistStore.getState().subCategory || 'Cloud Security'; + PersistStore.getState().setAllCollections([]); PersistStore.getState().setCollectionsMap({}); PersistStore.getState().setHostNameMap({}); @@ -126,6 +129,20 @@ export default function Header() { LocalStore.getState().setCategoryMap({}); LocalStore.getState().setSubCategoryMap({}); SessionStore.getState().setThreatFiltersMap({}); + + // Set appropriate default subcategory for each dashboard category + if (value === "Agentic Security") { + // For Agentic Security, use Cloud Security as default on first load + const defaultSubCategory = currentSubCategory === 'Default' ? 'Cloud Security' : currentSubCategory; + PersistStore.getState().setSubCategory(defaultSubCategory); + } else if (value === "API Security") { + // For API Security, use Default subcategory (no filtering) + PersistStore.getState().setSubCategory('Default'); + } else { + // For other categories, use Default subcategory + PersistStore.getState().setSubCategory('Default'); + } + setDashboardCategory(value); window.location.reload(); window.location.href("/dashboard/observe/inventory") @@ -244,7 +261,6 @@ export default function Header() { state.resetStore); const resetSession = SessionStore(state => state.resetStore); const resetFields = IssuesStore(state => state.resetStore); + const dashboardCategory = PersistStore((state) => state.dashboardCategory) || "API Security"; + const subcategory = PersistStore((state) => state.subCategory); + const setSubCategory = PersistStore((state) => state.setSubCategory); - const handleSelect = (selectedId) => { - setLeftNavSelected(selectedId); - }; + // Set initial selection based on current path and subcategory + useEffect(() => { + const pathString = func.transformString(location.pathname); + const subCategoryIndex = subcategory === "Endpoint Security" ? "1" : "0"; + const keyWithIndex = pathString + subCategoryIndex; + + // Only update if the current selection doesn't match + if (leftNavSelected !== keyWithIndex && leftNavSelected !== pathString) { + setLeftNavSelected(keyWithIndex); + } + }, [subcategory, location.pathname]); + + const handleClick = (key, path, activeState, subcategoryValue = "Default") => { + const finalKey = subcategoryValue === "Endpoint Security" ? key + "_1" : key + "_0"; + setLeftNavSelected(finalKey); + navigate(path); + setActive(activeState); + if (subcategoryValue && subcategoryValue !== subcategory) { + setSubCategory(subcategoryValue); + // Clear collections cache to trigger refresh when subcategory changes + PersistStore.getState().setAllCollections([]); + } + } const handleAccountChange = async (selected) => { resetAll(); @@ -52,63 +74,47 @@ export default function LeftNav() { await api.goToAccount(selected); func.setToast(true, false, `Switched to account ${accounts[selected]}`); window.location.href = '/dashboard/observe/inventory'; - }; + } const accountOptions = Object.keys(accounts).map(accountId => ({ label: accounts[accountId], value: accountId })); - let reportsSubNavigationItems = [ - { - label: "Issues", - onClick: () => { - navigate("/dashboard/reports/issues"); - handleSelect("dashboard_reports_issues"); - setActive("active"); - }, - selected: leftNavSelected === "dashboard_reports_issues", - } - ] - - if (window.USER_NAME.indexOf("@akto.io")) { - reportsSubNavigationItems.push({ - label: "Compliance", - onClick: () => { - navigate("/dashboard/reports/compliance"); - handleSelect("dashboard_reports_compliance"); - setActive("active"); - }, - selected: leftNavSelected === "dashboard_reports_compliance", - }) - } - - const dashboardCategory = PersistStore((state) => state.dashboardCategory) || "API Security"; + - const navItems = useMemo(() => { - let items = [ + const getMainNavItems = (subCategoryValue) => { + const subCategoryIndex = subCategoryValue === "Endpoint Security" ? "1" : "0"; + const isCurrentSubCategory = subcategory === subCategoryValue; + + let reportsSubNavigationItems = [ { - label: (!func.checkLocal()) ? ( - - accounts[activeAccount]} - selected={(type) => handleAccountChange(type)} - /> - - - ) : null + label: "Issues", + onClick: () => handleClick("dashboard_reports_issues", "/dashboard/reports/issues", "active", subCategoryValue), + selected: isCurrentSubCategory && (leftNavSelected === "dashboard_reports_issues" + subCategoryIndex || leftNavSelected === "dashboard_reports_issues"), + } + ] + + if (window.USER_NAME.indexOf("@akto.io")) { + reportsSubNavigationItems.push({ + label: "Compliance", + onClick: () => handleClick("dashboard_reports_compliance", "/dashboard/reports/compliance", "active", subCategoryValue), + selected: isCurrentSubCategory && (leftNavSelected === "dashboard_reports_compliance" + subCategoryIndex || leftNavSelected === "dashboard_reports_compliance"), + }) + } + return [ + { + label: "Quick Start", + icon: AppsFilledMajor, + onClick: () => handleClick("dashboard_quick_start", "/dashboard/quick-start", "normal", subCategoryValue), + selected: isCurrentSubCategory && (leftNavSelected === "dashboard_quick_start" + subCategoryIndex || leftNavSelected === "dashboard_quick_start"), + key: "quick_start", }, { label: mapLabel("API Security Posture", dashboardCategory), icon: ReportFilledMinor, - onClick: () => { - handleSelect("dashboard_home"); - navigate("/dashboard/home"); - setActive("normal"); - }, - selected: leftNavSelected === "dashboard_home", + onClick: () => handleClick("dashboard_home", "/dashboard/home", "normal", subCategoryValue), + selected: isCurrentSubCategory && (leftNavSelected === "dashboard_home" + subCategoryIndex || leftNavSelected === "dashboard_home"), key: "2", }, { @@ -118,7 +124,7 @@ export default function LeftNav() { variant="bodyMd" fontWeight="medium" color={ - leftNavSelected.includes("observe") + leftNavSelected.includes(subCategoryIndex) && leftNavSelected.includes("observe") ? active === "active" ? "subdued" : "" @@ -129,66 +135,38 @@ export default function LeftNav() { ), icon: InventoryFilledMajor, - onClick: () => { - handleSelect("dashboard_observe_inventory"); - navigate("/dashboard/observe/inventory"); - setActive("normal"); - }, - selected: leftNavSelected.includes("_observe"), + onClick: () => handleClick("dashboard_observe_inventory", "/dashboard/observe/inventory", "normal", subCategoryValue), + selected: isCurrentSubCategory && leftNavSelected.includes("_observe"), subNavigationItems: [ { label: "Collections", - onClick: () => { - navigate("/dashboard/observe/inventory"); - handleSelect("dashboard_observe_inventory"); - setActive("active"); - }, - selected: leftNavSelected === "dashboard_observe_inventory", + onClick: () => handleClick("dashboard_observe_inventory", "/dashboard/observe/inventory", "active", subCategoryValue), + selected: isCurrentSubCategory && (leftNavSelected === "dashboard_observe_inventory" + subCategoryIndex || leftNavSelected === "dashboard_observe_inventory"), }, { label: "Recent Changes", - onClick: () => { - navigate("/dashboard/observe/changes"); - handleSelect("dashboard_observe_changes"); - setActive("active"); - }, - selected: leftNavSelected === "dashboard_observe_changes", + onClick: () => handleClick("dashboard_observe_changes", "/dashboard/observe/changes", "active", subCategoryValue), + selected: isCurrentSubCategory && (leftNavSelected === "dashboard_observe_changes" + subCategoryIndex || leftNavSelected === "dashboard_observe_changes"), }, { label: "Sensitive Data", - onClick: () => { - navigate("/dashboard/observe/sensitive"); - handleSelect("dashboard_observe_sensitive"); - setActive("active"); - }, - selected: leftNavSelected === "dashboard_observe_sensitive", + onClick: () => handleClick("dashboard_observe_sensitive", "/dashboard/observe/sensitive", "active", subCategoryValue), + selected: isCurrentSubCategory && (leftNavSelected === "dashboard_observe_sensitive" + subCategoryIndex || leftNavSelected === "dashboard_observe_sensitive"), }, - ...(window?.STIGG_FEATURE_WISE_ALLOWED?.AKTO_DAST?.isGranted && dashboardCategory == CATEGORY_API_SECURITY ? [{ + ...(window?.STIGG_FEATURE_WISE_ALLOWED?.AKTO_DAST?.isGranted && dashboardCategory === CATEGORY_API_SECURITY ? [{ label: "DAST scans", - onClick: () => { - navigate("/dashboard/observe/dast-progress"); - handleSelect("dashboard_observe_dast_progress"); - setActive("active"); - }, - selected: leftNavSelected === "dashboard_observe_dast_progress" + onClick: () => handleClick("dashboard_observe_dast_progress", "/dashboard/observe/dast-progress", "active", subCategoryValue), + selected: leftNavSelected === "dashboard_observe_dast_progress" + subCategoryIndex, }] : []), ...((dashboardCategory === "MCP Security" || dashboardCategory === "Agentic Security") ? [{ label: "Audit Data", - onClick: () => { - navigate("/dashboard/observe/audit"); - handleSelect("dashboard_observe_audit"); - setActive("active"); - }, - selected: leftNavSelected === "dashboard_observe_audit", + onClick: () => handleClick("dashboard_observe_audit", "/dashboard/observe/audit", "active", subCategoryValue), + selected: leftNavSelected === "dashboard_observe_audit" + subCategoryIndex, }] : []), ...((dashboardCategory === "MCP Security" || dashboardCategory === "Agentic Security") ? [{ label: "Endpoint Shield", - onClick: () => { - navigate("/dashboard/observe/endpoint-shield"); - handleSelect("dashboard_observe_endpoint_shield"); - setActive("active"); - }, - selected: leftNavSelected === "dashboard_observe_endpoint_shield", + onClick: () => handleClick("dashboard_observe_endpoint_shield", "/dashboard/observe/endpoint-shield", "active", subCategoryValue), + selected: leftNavSelected === "dashboard_observe_endpoint_shield" + subCategoryIndex, }] : []), ], key: "3", @@ -200,7 +178,7 @@ export default function LeftNav() { variant="bodyMd" fontWeight="medium" color={ - leftNavSelected.includes("testing") + leftNavSelected.includes(subCategoryIndex) && leftNavSelected.includes("testing") ? active === "active" ? "subdued" : "" @@ -211,48 +189,28 @@ export default function LeftNav() { ), icon: MarketingFilledMinor, - onClick: () => { - navigate("/dashboard/testing/"); - handleSelect("dashboard_testing"); - setActive("normal"); - }, - selected: leftNavSelected.includes("_testing"), + onClick: () => handleClick("dashboard_testing", "/dashboard/testing/", "normal", subCategoryValue), + selected: leftNavSelected.includes(subCategoryIndex) && leftNavSelected.includes("_testing"), subNavigationItems: [ { label: "Results", - onClick: () => { - navigate("/dashboard/testing/"); - handleSelect("dashboard_testing"); - setActive("active"); - }, - selected: leftNavSelected === "dashboard_testing", + onClick: () => handleClick("dashboard_testing", "/dashboard/testing/", "active", subCategoryValue), + selected: leftNavSelected === "dashboard_testing" + subCategoryIndex, }, { label: mapLabel("Test", dashboardCategory) + " Roles", - onClick: () => { - navigate("/dashboard/testing/roles"); - handleSelect("dashboard_testing_roles"); - setActive("active"); - }, - selected: leftNavSelected === "dashboard_testing_roles", + onClick: () => handleClick("dashboard_testing_roles", "/dashboard/testing/roles", "active", subCategoryValue), + selected: leftNavSelected === "dashboard_testing_roles" + subCategoryIndex, }, { label: "User Config", - onClick: () => { - navigate("/dashboard/testing/user-config"); - handleSelect("dashboard_testing_user_config"); - setActive("active"); - }, - selected: leftNavSelected === "dashboard_testing_user_config", + onClick: () => handleClick("dashboard_testing_user_config", "/dashboard/testing/user-config", "active", subCategoryValue), + selected: leftNavSelected === "dashboard_testing_user_config" + subCategoryIndex, }, { label:mapLabel("Test", dashboardCategory) + " Suite", - onClick:()=>{ - navigate("/dashboard/testing/test-suite"); - handleSelect("dashboard_testing_test_suite"); - setActive("active"); - }, - selected: leftNavSelected === "dashboard_testing_test_suite", + onClick: () => handleClick("dashboard_testing_test_suite", "/dashboard/testing/test-suite", "active", subCategoryValue), + selected: leftNavSelected === "dashboard_testing_test_suite" + subCategoryIndex, } ], key: "4", @@ -265,30 +223,18 @@ export default function LeftNav() { ), icon: FinancesMinor, - onClick: () => { - handleSelect("dashboard_test_library_tests"); - navigate("/dashboard/test-library/tests"); - setActive("normal"); - }, - selected: leftNavSelected.includes("_test_library"), + onClick: () => handleClick("dashboard_test_library_tests", "/dashboard/test-library/tests", "normal", subCategoryValue), + selected: leftNavSelected.includes(subCategoryIndex) && leftNavSelected.includes("_test_library"), subNavigationItems: [ { label: mapLabel("More Tests", dashboardCategory), - onClick: () => { - navigate("/dashboard/test-library/tests"); - handleSelect("dashboard_test_library_tests"); - setActive("active"); - }, - selected: leftNavSelected === "dashboard_test_library_tests", + onClick: () => handleClick("dashboard_test_library_tests", "/dashboard/test-library/tests", "active", subCategoryValue), + selected: leftNavSelected === "dashboard_test_library_tests" + subCategoryIndex, }, { label: "Editor", - onClick: () => { - navigate("/dashboard/test-editor"); - handleSelect("dashboard_test_library_test_editor"); - setActive("active"); - }, - selected: leftNavSelected === "dashboard_test_library_test_editor", + onClick: () => handleClick("dashboard_test_library_test_editor", "/dashboard/test-editor", "active", subCategoryValue), + selected: leftNavSelected === "dashboard_test_library_test_editor" + subCategoryIndex, }, ], key: "5", @@ -300,12 +246,8 @@ export default function LeftNav() { ), icon: AutomationFilledMajor, - onClick: () => { - handleSelect("dashboard_prompt_hardening"); - navigate("/dashboard/prompt-hardening"); - setActive("normal"); - }, - selected: leftNavSelected === "dashboard_prompt_hardening", + onClick: () => handleClick("dashboard_prompt_hardening", "/dashboard/prompt-hardening", "normal", subCategoryValue), + selected: leftNavSelected === "dashboard_prompt_hardening" + subCategoryIndex, key: "prompt_hardening", }] : []), { @@ -315,7 +257,7 @@ export default function LeftNav() { variant="bodyMd" fontWeight="medium" color={ - leftNavSelected.includes("reports") + leftNavSelected.includes(subCategoryIndex) && leftNavSelected.includes("reports") ? active === "active" ? "subdued" : "" @@ -326,78 +268,50 @@ export default function LeftNav() { ), icon: ReportFilledMinor, - onClick: () => { - navigate("/dashboard/reports/issues"); - handleSelect("dashboard_reports_issues"); - setActive("normal"); - }, - selected: leftNavSelected.includes("_reports"), + onClick: () => handleClick("dashboard_reports_issues", "/dashboard/reports/issues", "normal", subCategoryValue), + selected: leftNavSelected.includes(subCategoryIndex) && leftNavSelected.includes("_reports"), subNavigationItems: reportsSubNavigationItems, key: "6", }, - ...(window?.STIGG_FEATURE_WISE_ALLOWED?.THREAT_DETECTION?.isGranted ? [{ + ...( dashboardCategory === "API Security" ? [{ label: ( {mapLabel("Threat Detection", dashboardCategory)} ), icon: DiamondAlertMinor, - onClick: () => { - handleSelect("dashboard_threat_actor"); - navigate("/dashboard/protection/threat-actor"); - setActive("normal"); - }, - selected: leftNavSelected.includes("_threat"), + onClick: () => handleClick("dashboard_threat_actor", "/dashboard/protection/threat-actor", "normal", subCategoryValue), + selected: leftNavSelected.includes(subCategoryIndex) && leftNavSelected.includes("_threat"), url: "#", key: "7", subNavigationItems: [ ...(dashboardCategory === "API Security" && func.isDemoAccount() ? [{ label: "Dashboard", - onClick: () => { - navigate("/dashboard/protection/threat-dashboard"); - handleSelect("dashboard_threat_dashboard"); - setActive("active"); - }, - selected: leftNavSelected === "dashboard_threat_dashboard", + onClick: () => handleClick("dashboard_threat_dashboard", "/dashboard/protection/threat-dashboard", "active", subCategoryValue), + selected: leftNavSelected === "dashboard_threat_dashboard" + subCategoryIndex, }] : []), { label: "Threat Actors", - onClick: () => { - navigate("/dashboard/protection/threat-actor"); - handleSelect("dashboard_threat_actor"); - setActive("active"); - }, - selected: leftNavSelected === "dashboard_threat_actor", + onClick: () => handleClick("dashboard_threat_actor", "/dashboard/protection/threat-actor", "active", subCategoryValue), + selected: leftNavSelected === "dashboard_threat_actor" + subCategoryIndex, }, { label: "Threat Activity", - onClick: () => { - navigate("/dashboard/protection/threat-activity"); - handleSelect("dashboard_threat_activity"); - setActive("active"); - }, + onClick: () => handleClick("dashboard_threat_activity", "/dashboard/protection/threat-activity", "active", subCategoryValue), selected: - leftNavSelected === "dashboard_threat_activity", + leftNavSelected === "dashboard_threat_activity" + subCategoryIndex, }, { label: `${mapLabel("APIs", dashboardCategory)} Under Threat`, - onClick: () => { - navigate("/dashboard/protection/threat-api"); - handleSelect("dashboard_threat_api"); - setActive("active"); - }, + onClick: () => handleClick("dashboard_threat_api", "/dashboard/protection/threat-api", "active", subCategoryValue), selected: - leftNavSelected === "dashboard_threat_api", + leftNavSelected === "dashboard_threat_api" + subCategoryIndex, }, { label: "Threat Policy", - onClick: () => { - navigate("/dashboard/protection/threat-policy"); - handleSelect("dashboard_threat_policy"); - setActive("active"); - }, + onClick: () => handleClick("dashboard_threat_policy", "/dashboard/protection/threat-policy", "active", subCategoryValue), selected: - leftNavSelected === "dashboard_threat_policy", + leftNavSelected === "dashboard_threat_policy" + subCategoryIndex, }, ], }] : []), @@ -411,9 +325,9 @@ export default function LeftNav() { // onClick: () => { // handleSelect("agent_team_members"); // navigate("/dashboard/agent-team/members"); - // setActive("normal"); + // setActive("normal", subCategoryValue); // }, - // selected: leftNavSelected.includes("agent_team"), + // selected: leftNavSelected.includes(subCategoryIndex) && leftNavSelected.includes("agent_team"), // url: "#", // key: "8", // }] : []), @@ -424,33 +338,32 @@ export default function LeftNav() { ), icon: LockMajor, - onClick: () => { - handleSelect("dashboard_mcp_guardrails"); - navigate("/dashboard/guardrails/activity"); - setActive("normal"); - }, - selected: leftNavSelected.includes("_guardrails"), + onClick: () => handleClick("dashboard_mcp_guardrails", "/dashboard/guardrails/activity", "normal", subCategoryValue), + selected: leftNavSelected.includes(subCategoryIndex) && (leftNavSelected.includes("_guardrails") || leftNavSelected.includes("_threat")), url: "#", key: "9", subNavigationItems: [ { - label: "Guardrails Activity", - onClick: () => { - navigate("/dashboard/guardrails/activity"); - handleSelect("dashboard_guardrails_activity"); - setActive("active"); - }, - selected: leftNavSelected === "dashboard_guardrails_activity", + label: "Guardrail Activity", + onClick: () => handleClick("dashboard_threat_activity", "/dashboard/protection/threat-activity", "active", subCategoryValue), + selected: + leftNavSelected === "dashboard_threat_activity" + subCategoryIndex, }, { label: "Guardrails Policies", - onClick: () => { - navigate("/dashboard/guardrails/policies"); - handleSelect("dashboard_guardrails_policies"); - setActive("active"); - }, + onClick: () => handleClick("dashboard_guardrails_policies", "/dashboard/guardrails/policies", "active", subCategoryValue), selected: - leftNavSelected === "dashboard_guardrails_policies", + leftNavSelected === "dashboard_guardrails_policies" + subCategoryIndex, + }, + { + label: "Guardrail Actors", + onClick: () => handleClick("dashboard_threat_actor", "/dashboard/protection/threat-actor", "active", subCategoryValue), + selected: leftNavSelected === "dashboard_threat_actor" + subCategoryIndex, + }, + { + label: `${mapLabel("APIs", dashboardCategory)} Under Guardrail`, + onClick: () => handleClick("dashboard_threat_api", "/dashboard/protection/threat-api", "active", subCategoryValue), + selected: leftNavSelected === "dashboard_threat_api" + subCategoryIndex, } ] @@ -462,33 +375,21 @@ export default function LeftNav() { ), icon: LockMajor, - onClick: () => { - handleSelect("dashboard_ai_agent_guardrails"); - navigate("/dashboard/guardrails/activity"); - setActive("normal"); - }, - selected: leftNavSelected.includes("_guardrails"), + onClick: () => handleClick("dashboard_ai_agent_guardrails", "/dashboard/guardrails/activity", "normal", subCategoryValue), + selected: leftNavSelected.includes(subCategoryIndex) && leftNavSelected.includes("_guardrails"), url: "#", key: "10", subNavigationItems: [ { label: "Guardrails Activity", - onClick: () => { - navigate("/dashboard/guardrails/activity"); - handleSelect("dashboard_guardrails_activity"); - setActive("active"); - }, - selected: leftNavSelected === "dashboard_guardrails_activity", + onClick: () => handleClick("dashboard_guardrails_activity", "/dashboard/guardrails/activity", "active", subCategoryValue), + selected: leftNavSelected === "dashboard_guardrails_activity" + subCategoryIndex, }, { label: "Guardrails Policies", - onClick: () => { - navigate("/dashboard/guardrails/policies"); - handleSelect("dashboard_guardrails_policies"); - setActive("active"); - }, + onClick: () => handleClick("dashboard_guardrails_policies", "/dashboard/guardrails/policies", "active", subCategoryValue), selected: - leftNavSelected === "dashboard_guardrails_policies", + leftNavSelected === "dashboard_guardrails_policies" + subCategoryIndex, } ] }] : []), @@ -499,55 +400,99 @@ export default function LeftNav() { ), icon: LockMajor, - onClick: () => { - handleSelect("dashboard_agentic_guardrails"); - navigate("/dashboard/guardrails/activity"); - setActive("normal"); - }, - selected: leftNavSelected.includes("_guardrails"), + onClick: () => handleClick("dashboard_threat_activity", "/dashboard/protection/threat-activity", "normal", subCategoryValue), + selected: leftNavSelected.includes(subCategoryIndex) && (leftNavSelected.includes("_guardrails") || leftNavSelected.includes("_threat")), url: "#", key: "11", subNavigationItems: [ { - label: "Guardrails Activity", - onClick: () => { - navigate("/dashboard/guardrails/activity"); - handleSelect("dashboard_guardrails_activity"); - setActive("active"); - }, - selected: leftNavSelected === "dashboard_guardrails_activity", + label: "Guardrail Activity", + onClick: () => handleClick("dashboard_threat_activity", "/dashboard/protection/threat-activity", "active", subCategoryValue), + selected: + leftNavSelected === "dashboard_threat_activity" + subCategoryIndex, }, { label: "Guardrails Policies", - onClick: () => { - navigate("/dashboard/guardrails/policies"); - handleSelect("dashboard_guardrails_policies"); - setActive("active"); - }, + onClick: () => handleClick("dashboard_guardrails_policies", "/dashboard/guardrails/policies", "active", subCategoryValue), selected: - leftNavSelected === "dashboard_guardrails_policies", + leftNavSelected === "dashboard_guardrails_policies" + subCategoryIndex, + }, + { + label: "Guardrail Actors", + onClick: () => handleClick("dashboard_threat_actor", "/dashboard/protection/threat-actor", "active", subCategoryValue), + selected: leftNavSelected === "dashboard_threat_actor" + subCategoryIndex, + }, + { + label: `${mapLabel("APIs", dashboardCategory)} Under Guardrail`, + onClick: () => handleClick("dashboard_threat_api", "/dashboard/protection/threat-api", "active", subCategoryValue), + selected: leftNavSelected === "dashboard_threat_api" + subCategoryIndex, } + ] }] : []) + ]; + } + + const navItems = useMemo(() => { + + const accountSection = [ + { + label: (!func.checkLocal()) ? ( + + accounts[activeAccount]} + selected={(type) => handleAccountChange(type)} + /> + + + ) : null + } ] - const exists = items.find(item => item.key === "quick_start") - if (!exists) { - items.splice(1, 0, { - label: "Quick Start", - icon: AppsFilledMajor, - onClick: () => { - handleSelect("dashboard_quick_start") - navigate("/dashboard/quick-start") - setActive("normal") + const mainNavItems = getMainNavItems("Cloud Security"); + const shouldShowDivisions = dashboardCategory === "MCP Security" || dashboardCategory === "Agentic Security"; + + let allItems; + if (shouldShowDivisions) { + const apiSecurityItems = getMainNavItems("Endpoint Security"); + allItems = [ + ...accountSection, + { + label: ( + +
+ Cloud Security +
+
+ ), + key: "cloud_header", + disabled: true, }, - selected: leftNavSelected === "dashboard_quick_start", - key: "quick_start", - }) + ...mainNavItems, + { + label: ( + +
+ Endpoint Security +
+
+ ), + key: "endpoint_header", + disabled: true, + }, + ...apiSecurityItems + ]; + }else{ + allItems = [ + ...accountSection, + ...mainNavItems + ]; } - return items - }, [dashboardCategory, leftNavSelected]) + return allItems + }, [dashboardCategory, leftNavSelected, subcategory]) const navigationMarkup = (
diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/dashboard/HomeDashboard.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/dashboard/HomeDashboard.jsx index fc9c6712a6..44a6f07526 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/dashboard/HomeDashboard.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/dashboard/HomeDashboard.jsx @@ -71,6 +71,7 @@ function HomeDashboard() { const allCollections = PersistStore(state => state.allCollections) const hostNameMap = PersistStore(state => state.hostNameMap) const coverageMap = PersistStore(state => state.coverageMap) + const subCategory = PersistStore(state => state.subCategory) const [authMap, setAuthMap] = useState({}) const [apiTypesData, setApiTypesData] = useState([{ "data": [], "color": "#D6BBFB" }]) const [riskScoreData, setRiskScoreData] = useState([]) @@ -448,21 +449,21 @@ function HomeDashboard() { useEffect(() => { fetchData() - }, [startTimestamp, endTimestamp]) + }, [startTimestamp, endTimestamp, subCategory]) // Fetch MCP API call stats when time range changes useEffect(() => { if (allCollections && allCollections.length > 0) { fetchMcpApiCallStats(mcpStatsTimeRange, func.timeNow()); } - }, [mcpStatsTimeRange, allCollections]) + }, [mcpStatsTimeRange, allCollections, subCategory]) // Fetch Policy Guardrail stats when time range changes useEffect(() => { if (allCollections && allCollections.length > 0) { fetchPolicyGuardrailStats(policyGuardrailStatsTimeRange, func.timeNow()); } - }, [policyGuardrailStatsTimeRange, allCollections]) + }, [policyGuardrailStatsTimeRange, allCollections, subCategory]) async function getActionItemsDataAndCount() { const data = await fetchActionItemsData(); diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/observe/api_collections/ApiCollections.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/observe/api_collections/ApiCollections.jsx index d6544f6dfe..81d2f0ecc3 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/observe/api_collections/ApiCollections.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/observe/api_collections/ApiCollections.jsx @@ -35,8 +35,8 @@ import ReactFlow, { } from 'react-flow-renderer'; import SetUserEnvPopupComponent from "./component/SetUserEnvPopupComponent"; -import { getDashboardCategory, mapLabel, isMCPSecurityCategory, isAgenticSecurityCategory, isGenAISecurityCategory } from "../../../../main/labelHelper"; - +import { getDashboardCategory, mapLabel, isMCPSecurityCategory, isAgenticSecurityCategory, isGenAISecurityCategory, shouldShowLeftNavSwitch, isEndpointSecurityLeftNav, getSubCategory } from "../../../../main/labelHelper"; + const CenterViewType = { Table: 0, Tree: 1, @@ -45,7 +45,7 @@ const CenterViewType = { const headers = [ - ...((isMCPSecurityCategory() || isAgenticSecurityCategory()) && func.isDemoAccount() ? [{ + ...(shouldShowLeftNavSwitch() ? [{ title: "", text: "", value: "iconComp", @@ -200,6 +200,7 @@ const resourceName = { plural: 'collections', }; + const convertToNewData = (collectionsArr, sensitiveInfoMap, severityInfoMap, coverageMap, trafficInfoMap, riskScoreMap, isLoading) => { // Ensure collectionsArr is an array @@ -214,14 +215,15 @@ const convertToNewData = (collectionsArr, sensitiveInfoMap, severityInfoMap, cov c.disableClick = true } const tagsList = JSON.stringify(c?.tagsList || "") + const currentSubCategory = PersistStore.getState().subCategory return{ ...c, icon: CircleTickMajor, nextUrl: "/dashboard/observe/inventory/"+ c.id, envTypeOriginal: c?.envType, envType: c?.envType?.map(func.formatCollectionType), - ...((isMCPSecurityCategory() || isAgenticSecurityCategory()) && func.isDemoAccount() && tagsList.includes("mcp-server") ? { - iconComp: (icon) + ...(shouldShowLeftNavSwitch() ? { + iconComp: (icon) } : {}), ...((isGenAISecurityCategory() || isAgenticSecurityCategory()) && func.isDemoAccount() && tagsList.includes("gen-ai") ? { iconComp: () @@ -252,12 +254,12 @@ const categorizeCollections = (prettifyArray) => { const activeCollections = []; const deactivatedCollectionsData = []; const collectionMap = new Map(); - + prettifyArray.forEach((c) => { // Build environment map envTypeObj[c.id] = c.envTypeOriginal; collectionMap.set(c.id, c); - + // Categorize collections in single pass if (!c.deactivated) { activeCollections.push(c); @@ -298,7 +300,7 @@ function ApiCollections(props) { const [data, setData] = useState({'all': [], 'hostname':[], 'groups': [], 'custom': [], 'deactivated': [], 'untracked': []}) const [active, setActive] = useState(false); const [loading, setLoading] = useState(false) - + const [summaryData, setSummaryData] = useState({totalEndpoints:0 , totalTestedEndpoints: 0, totalSensitiveEndpoints: 0, totalCriticalEndpoints: 0, totalAllowedForTesting: 0}) const [hasUsageEndpoints, setHasUsageEndpoints] = useState(true) const [envTypeMap, setEnvTypeMap] = useState({}) @@ -347,6 +349,7 @@ function ApiCollections(props) { } const allCollections = PersistStore(state => state.allCollections) + const subCategory = PersistStore(state => state.subCategory) // const allCollections = dummyData.allCollections; const setAllCollections = PersistStore(state => state.setAllCollections) const setCollectionsMap = PersistStore(state => state.setCollectionsMap) @@ -484,7 +487,7 @@ function ApiCollections(props) { if(customCollectionDataFilter){ finalArr = finalArr.filter(customCollectionDataFilter) } - + const dataObj = convertToNewData(finalArr, sensitiveInfoMap, severityInfoMap, coverageMap, trafficInfoMap, riskScoreMap, false); setNormalData(dataObj.normal) @@ -546,7 +549,7 @@ function ApiCollections(props) { setData(res); setEnvTypeMap(envTypeObj); setAllCollections(apiCollectionsResp.apiCollections || []); - + // Fetch endpoints count and sensitive info asynchronously Promise.all([ dashboardApi.fetchEndpointsCount(0, 0), @@ -556,7 +559,7 @@ function ApiCollections(props) { if (endpointsResponse) { setTotalAPIs(endpointsResponse.newCount); } - + // Update sensitive info if available if(sensitiveResponse == null || sensitiveResponse === undefined){ sensitiveResponse = { @@ -574,7 +577,7 @@ function ApiCollections(props) { // Update the store with new sensitive info setLastFetchedSensitiveResp(newSensitiveInfo); - // Re-calculate data with new sensitive info + // Re-calculate data with new sensitive info (use same filtered array) const updatedDataObj = convertToNewData( finalArr, newSensitiveInfo.sensitiveInfoMap || {}, @@ -603,7 +606,7 @@ function ApiCollections(props) { updatedCategorized['untracked'] = untrackedCollections; setData(updatedCategorized); - + // Update summary with new sensitive endpoints count const updatedSummary = transform.getSummaryData(updatedDataObj.normal); updatedSummary.totalCriticalEndpoints = riskScoreObj.criticalUrls; @@ -647,7 +650,7 @@ function ApiCollections(props) { useEffect(() => { fetchData() resetFunc() - }, []) + }, [subCategory]) const createCollectionModalActivatorRef = useRef(); const resetResourcesSelected = () => { TableStore.getState().setSelectedItems([]) diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/ThreatActorPage.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/ThreatActorPage.jsx index 67d056dd53..4c5aa813d6 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/ThreatActorPage.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/ThreatActorPage.jsx @@ -14,6 +14,7 @@ import { HorizontalGrid, VerticalStack } from "@shopify/polaris"; import { ThreatSummary } from "./components/ThreatSummary"; import ThreatActivityTimeline from "./components/ThreatActivityTimeline"; import React from "react"; +import { getDashboardCategory, mapLabel } from "../../../main/labelHelper"; const ChartComponent = ({ onSubCategoryClick, currDateRange }) => { return ( @@ -79,7 +80,7 @@ function ThreatActorPage() { return ( } + title={} isFirstPage={true} primaryAction={ } + title={} isFirstPage={true} primaryAction={ } - title="Threat Status" - titleToolTip="Distribution of threats by their current status" + title={`${mapLabel("Threat", getDashboardCategory())} Status`} + titleToolTip={`Distribution of ${mapLabel("Threat", getDashboardCategory())} by their current status`} /> ) @@ -337,8 +338,8 @@ function ThreatDashboardPage() { pieInnerSize="50%" /> } - title="Threat Actors by Severity" - titleToolTip="Distribution of threat actors categorized by severity level" + title={`${mapLabel("Threat", getDashboardCategory())} Actors by Severity`} + titleToolTip={`Distribution of ${mapLabel("Threat", getDashboardCategory())} actors categorized by severity level`} /> ) diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/ThreatDetectionPage.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/ThreatDetectionPage.jsx index 7508277051..8aeac7eb79 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/ThreatDetectionPage.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/ThreatDetectionPage.jsx @@ -18,7 +18,8 @@ import threatDetectionFunc from "./transform"; import InfoCard from "../dashboard/new_components/InfoCard"; import BarGraph from "../../components/charts/BarGraph"; import SessionStore from "../../../main/SessionStore"; -import { getDashboardCategory, isApiSecurityCategory, mapLabel } from "../../../main/labelHelper"; +import { getDashboardCategory, isApiSecurityCategory, mapLabel, getSubCategory, SUB_CATEGORY_ENDPOINT_SECURITY } from "../../../main/labelHelper"; +import PersistStore from "../../../main/PersistStore"; import { useNavigate } from "react-router-dom"; import LineChart from "../../components/charts/LineChart"; import P95LatencyGraph from "../../components/charts/P95LatencyGraph"; @@ -84,6 +85,40 @@ const directionData = [ } ] +const directionData2 = [ + { + name: 'Request', + data: [ + [1722556800000, 9], // Aug 1 + [1725235200000, 16], // Sept 1 + [1725580800000, 13], // Sept 5 + [1725926400000, 6], // Sept 9 + [1726272000000, 19], // Sept 13 + [1726617600000, 10], // Sept 17 + [1726963200000, 17], // Sept 21 + [1727308800000, 8], // Sept 25 + [1727654400000, 15], // Sept 29 + ], + color: '#6200EA' + }, + { + name: 'Response', + data: [ + [1722556800000, 9], // Aug 1 + [1725235200000, 8], // Sept 1 + [1725580800000, 17], // Sept 5 + [1725926400000, 12], // Sept 9 + [1726272000000, 5], // Sept 13 + [1726617600000, 15], // Sept 17 + [1726963200000, 9], // Sept 21 + [1727308800000, 19], // Sept 25 + [1727654400000, 10], // Sept 29 + ], + color: '#AF6CF6' + } +] + + const flaggedData = [ { name: 'Safe', @@ -131,7 +166,68 @@ const flaggedData = [ } ] +const flaggedData2 = [ + { + name: 'Safe', + data: [ + [1722556800000, 53], // Aug 1 + [1725235200000, 72], // Sept 1 + [1725580800000, 70], // Sept 5 + [1725926400000, 74], // Sept 9 + [1726272000000, 32], // Sept 13 + [1726617600000, 85], // Sept 17 + [1726963200000, 88], // Sept 21 + [1727308800000, 81], // Sept 25 + [1727654400000, 95], // Sept 29 + ], + color: '#AEE9D1' + }, + { + name: 'Flagged', + data: [ + [1722556800000, 21], // Aug 1 (12+9 from directionData) + [1725235200000, 24], // Sept 1 (16+8) + [1725580800000, 30], // Sept 5 (13+17) + [1725926400000, 18], // Sept 9 (6+12) + [1726272000000, 24], // Sept 13 (19+5) + [1726617600000, 25], // Sept 17 (10+15) + [1726963200000, 26], // Sept 21 (17+9) + [1727308800000, 27], // Sept 25 (8+19) + [1727654400000, 25], // Sept 29 (15+10) + ], + color: '#E45357' + } +] + const ChartComponent = ({ subCategoryCount, severityCountMap }) => { + // Get current subcategory to determine which dataset to use (reactive) + const currentSubCategory = PersistStore(state => state.subCategory); + const isEndpointSecurity = currentSubCategory === SUB_CATEGORY_ENDPOINT_SECURITY; + + // Add useEffect to track when component updates + useEffect(() => { + }, [currentSubCategory]); + + // Temporary alert to ensure we see the values + if (typeof window !== 'undefined') { + console.warn('CHART COMPONENT RENDER - SubCategory:', currentSubCategory, 'IsEndpoint:', isEndpointSecurity); + } + + // Select appropriate data based on subcategory + const selectedDirectionData = isEndpointSecurity ? directionData2 : directionData; + const selectedFlaggedData = isEndpointSecurity ? flaggedData2 : flaggedData; + + // Debug logging for development + if (process.env.NODE_ENV === 'development') { + console.log('ThreatDetectionPage chart data selection:', { + currentSubCategory, + isEndpointSecurity, + usingAlternateData: isEndpointSecurity, + directionDataFirstPoint: selectedDirectionData[0]?.data[0], + flaggedDataFirstPoint: selectedFlaggedData[0]?.data[0] + }); + } + return ( @@ -140,10 +236,11 @@ const ChartComponent = ({ subCategoryCount, severityCountMap }) => { data={subCategoryCount} /> { /> { - func.isDemoAccount() && !isApiSecurityCategory() ? + !isApiSecurityCategory() ? { } /> state.threatFiltersMap); + const currentSubCategory = PersistStore((state) => state.subCategory); + + // Add useEffect to track subcategory changes in main component + useEffect(() => { + console.log('Main component useEffect - subcategory changed to:', currentSubCategory); + }, [currentSubCategory]); const startTimestamp = parseInt(currDateRange.period.since.getTime()/1000) const endTimestamp = parseInt(currDateRange.period.until.getTime()/1000) @@ -238,6 +343,9 @@ function ThreatDetectionPage() { const intervalDays = 3; // Every 3 days const dataPoints = Math.floor(totalDays / intervalDays) + 1; // +1 to include today + // Use the reactive subcategory from main component + const isEndpointSecurity = currentSubCategory === SUB_CATEGORY_ENDPOINT_SECURITY; + // Predefined latency values for consistent demo data (total 20-30ms) const latencyValues = [ { incoming: 12.5, output: 8.2, total: 21.4 }, @@ -261,14 +369,33 @@ function ThreatDetectionPage() { { incoming: 12.1, output: 14.8, total: 28.1 }, { incoming: 14.9, output: 8.7, total: 24.9 } ]; + + const latencyValues2 = [ + { incoming: 12.5, output: 8.2, total: 21.4 }, + { incoming: 15.3, output: 9.8, total: 25.7 }, + { incoming: 11.7, output: 7.4, total: 20.3 }, + { incoming: 10.3, output: 16.1, total: 27.9 }, + { incoming: 13.8, output: 8.6, total: 23.1 }, + { incoming: 14.6, output: 9.9, total: 25.5 }, + { incoming: 12.2, output: 7.5, total: 20.8 }, + { incoming: 11.4, output: 13.7, total: 26.4 }, + { incoming: 16.1, output: 9.5, total: 27.2 }, + { incoming: 13.5, output: 11.2, total: 25.8 }, + { incoming: 15.8, output: 9.6, total: 26.7 }, + { incoming: 12.1, output: 14.8, total: 28.1 }, + { incoming: 14.9, output: 8.7, total: 24.9 } + ]; + + // Select appropriate latency values based on subcategory + const selectedLatencyValues = isEndpointSecurity ? latencyValues2 : latencyValues; for (let i = 0; i < dataPoints; i++) { const daysAgo = i * intervalDays; const timestamp = now - (daysAgo * 24 * 60 * 60 * 1000); // Use deterministic values based on data point index - const latencyIndex = i % latencyValues.length; - const latency = latencyValues[latencyIndex]; + const latencyIndex = i % selectedLatencyValues.length; + const latency = selectedLatencyValues[latencyIndex]; data.push({ timestamp: Math.floor(timestamp / 1000), // Convert to seconds @@ -283,7 +410,7 @@ function ThreatDetectionPage() { console.error('Error generating latency data:', error); return []; } - }, []); + }, [currentSubCategory]); /** * Handle latency click events from the P95LatencyGraph component @@ -343,33 +470,77 @@ function ThreatDetectionPage() { useEffect(() => { const fetchThreatCategoryCount = async () => { setLoading(true); - const res = await api.fetchThreatCategoryCount(startTimestamp, endTimestamp); - const finalObj = threatDetectionFunc.getGraphsData(res); - setSubCategoryCount(finalObj.subCategoryCount); - setLoading(false); - }; + + if (getDashboardCategory() === "Agentic Security") { + // Provide dummy data for subCategoryCount (TopThreatTypeChart will use its own dummy data for Agentic Security) + const dummySubCategoryCount = [ + { text: "Dummy Category", value: 1, color: "#ccc" } + ]; + setSubCategoryCount(dummySubCategoryCount); + setLoading(false); + } else { + // Use real API data for API Security and other dashboard categories + const res = await api.fetchThreatCategoryCount(startTimestamp, endTimestamp); + const finalObj = threatDetectionFunc.getGraphsData(res); + setSubCategoryCount(finalObj.subCategoryCount); + setLoading(false); + } + }; const fetchCountBySeverity = async () => { setLoading(true); - let severityMap = { - CRITICAL: 0, - HIGH: 0, - MEDIUM: 0, - LOW: 0, + + // Check if we're in Agentic Security to use dummy data + if (getDashboardCategory() === "Agentic Security") { + console.log("Using dummy severity data for Agentic Security"); + + // Dummy severity data for Cloud Security (AI/ML threats tend to have more critical issues) + // Pattern: Higher critical/high counts due to AI model vulnerabilities + const cloudSeverityMap = { + CRITICAL: 8, // Model poisoning, prompt injection attacks + HIGH: 15, // Data exposure, adversarial examples + MEDIUM: 12, // Model extraction, inference manipulation + LOW: 5, // Minor configuration issues + }; + + // Dummy severity data for Endpoint Security (More distributed across severities) + // Pattern: More medium/low threats due to system-level monitoring + const endpointSeverityMap = { + CRITICAL: 4, // System compromises, privilege escalation + HIGH: 9, // Unauthorized access, command injection + MEDIUM: 18, // Resource exhaustion, file tampering + LOW: 13, // Configuration drift, minor violations + }; + + const isEndpointSecurity = currentSubCategory === SUB_CATEGORY_ENDPOINT_SECURITY; + const selectedSeverityMap = isEndpointSecurity ? endpointSeverityMap : cloudSeverityMap; + + console.log(`Using ${isEndpointSecurity ? 'Endpoint' : 'Cloud'} Security severity data:`, selectedSeverityMap); + + setSeverityCountMap(convertToGraphData(selectedSeverityMap)); + setLoading(false); + } else { + // Use real API data for non-Agentic categories + let severityMap = { + CRITICAL: 0, + HIGH: 0, + MEDIUM: 0, + LOW: 0, + } + const res = await api.fetchCountBySeverity(startTimestamp, endTimestamp); + res.categoryCounts.forEach(({ subCategory, count }) => { + severityMap[subCategory] = count; + }); + setSeverityCountMap(convertToGraphData(severityMap)); + setLoading(false); } - const res = await api.fetchCountBySeverity(startTimestamp, endTimestamp); - res.categoryCounts.forEach(({ subCategory, count }) => { - severityMap[subCategory] = count; - }); - setSeverityCountMap(convertToGraphData(severityMap)); - setLoading(false); }; fetchThreatCategoryCount(); fetchCountBySeverity(); // Generate latency data for demo mode - if (isDemoMode) { + if (!isApiSecurityCategory()) { try { const latency = generateLatencyData(); setLatencyData(latency); @@ -378,16 +549,16 @@ function ThreatDetectionPage() { setLatencyData([]); } } - }, [startTimestamp, endTimestamp, isDemoMode]); + }, [startTimestamp, endTimestamp, currentSubCategory]); const components = [ - , + , // Add P95 latency graphs for MCP and AI Agent security in demo mode - ...(isDemoMode && !isApiSecurityCategory() ? [ + ...(!isApiSecurityCategory() ? [ } isFirstPage={true} diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/ThreatPolicyPage.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/ThreatPolicyPage.jsx index c8cb1c2425..03921310db 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/ThreatPolicyPage.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/ThreatPolicyPage.jsx @@ -7,6 +7,7 @@ import values from "@/util/values"; import {produce} from "immer" import func from "@/util/func"; import {HorizontalGrid} from "@shopify/polaris"; +import { mapLabel, getDashboardCategory } from '../../../main/labelHelper'; function ThreatPolicyPage() { @@ -24,8 +25,8 @@ function ThreatPolicyPage() { return } isFirstPage={true} diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/components/ActorDetails.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/components/ActorDetails.jsx index ded131c735..5d23641d16 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/components/ActorDetails.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/components/ActorDetails.jsx @@ -73,7 +73,7 @@ export const ActorDetails = ({ actorDetails, setShowActorDetails }) => { ] return ( } /> ) diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/components/ThreatApiSubcategoryCount.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/components/ThreatApiSubcategoryCount.jsx index e958db3c41..11bb32a400 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/components/ThreatApiSubcategoryCount.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/components/ThreatApiSubcategoryCount.jsx @@ -1,13 +1,50 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import { DataTable } from "@shopify/polaris"; import InfoCard from "../../dashboard/new_components/InfoCard"; +import { getDashboardCategory, mapLabel, SUB_CATEGORY_ENDPOINT_SECURITY } from "../../../../main/labelHelper"; +import PersistStore from "../../../../main/PersistStore"; + +// Dummy data for Cloud Security (GenAI threat categories) +const cloudSecurityDummyData = [ + { text: "Model Poisoning", value: 22 }, + { text: "Prompt Injection", value: 18 }, + { text: "Data Leakage", value: 16 }, + { text: "Adversarial Attacks", value: 14 }, + { text: "Bias Amplification", value: 12 }, + { text: "Privacy Violations", value: 10 }, + { text: "Hallucination Risks", value: 8 } +]; + +// Dummy data for Endpoint Security (MCP threat categories) +const endpointSecurityDummyData = [ + { text: "File System Abuse", value: 15 }, + { text: "Network Intrusion", value: 13 }, + { text: "Privilege Escalation", value: 11 }, + { text: "System Compromise", value: 9 } +]; const ThreatApiSubcategoryCount = ({ data }) => { + const [categoryData, setCategoryData] = useState([]); + const currentSubCategory = PersistStore(state => state.subCategory); + const isEndpointSecurity = currentSubCategory === SUB_CATEGORY_ENDPOINT_SECURITY; + + useEffect(() => { + // Use dummy data based on subcategory for Agentic Security + if (getDashboardCategory() === "Agentic Security") { + const selectedDummyData = isEndpointSecurity ? endpointSecurityDummyData : cloudSecurityDummyData; + console.log(`ThreatApiSubcategoryCount: Using ${isEndpointSecurity ? 'Endpoint' : 'Cloud'} Security category dummy data`); + setCategoryData(selectedDummyData); + } else { + // Use real data for other categories + setCategoryData(data || []); + } + }, [data, currentSubCategory, isEndpointSecurity]); + const Data = () => ( [x.text, x.value]) .sort((a, b) => b[0].localeCompare(a[0]))} /> @@ -15,8 +52,8 @@ const ThreatApiSubcategoryCount = ({ data }) => { return ( } /> ); diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/components/ThreatWorldMap.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/components/ThreatWorldMap.jsx index cdd06790b6..ff01d42097 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/components/ThreatWorldMap.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/components/ThreatWorldMap.jsx @@ -6,6 +6,7 @@ import FullScreen from "highcharts/modules/full-screen"; import InfoCard from "../../dashboard/new_components/InfoCard"; import { Spinner } from "@shopify/polaris"; import api from "../api"; +import { getDashboardCategory, mapLabel } from "../../../../main/labelHelper"; // Initialize modules Exporting(Highcharts); @@ -144,8 +145,8 @@ function ThreatWorldMap({ startTimestamp, endTimestamp, style}) { return (
} /> ); diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/components/TopThreatTypeChart.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/components/TopThreatTypeChart.jsx index 865094c582..646bb328fd 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/components/TopThreatTypeChart.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/components/TopThreatTypeChart.jsx @@ -1,26 +1,58 @@ import React, { useEffect, useState } from "react"; import InfoCard from "../../dashboard/new_components/InfoCard"; import BarGraph from "../../../components/charts/BarGraph"; -import { getDashboardCategory, mapLabel } from "../../../../main/labelHelper"; +import { getDashboardCategory, mapLabel, SUB_CATEGORY_ENDPOINT_SECURITY } from "../../../../main/labelHelper"; +import PersistStore from "../../../../main/PersistStore"; + +// Dummy data for Cloud Security (GenAI threats) +const cloudSecurityDummyData = [ + { text: "Prompt Injection", value: 15, color: "#4e76cd" }, + { text: "Data Poisoning", value: 8, color: "#4e76cd" }, + { text: "Model Extraction", value: 12, color: "#4e76cd" }, + { text: "Adversarial Examples", value: 6, color: "#4e76cd" }, + { text: "Training Data Exposure", value: 10, color: "#4e76cd" }, + { text: "Inference Manipulation", value: 4, color: "#4e76cd" }, + { text: "Model Inversion", value: 7, color: "#4e76cd" }, +]; + +// Dummy data for Endpoint Security (MCP threats) +const endpointSecurityDummyData = [ + { text: "File System Tampering", value: 13, color: "#4e76cd" }, + { text: "Memory Corruption", value: 5, color: "#4e76cd" }, + { text: "Network Intrusion", value: 16, color: "#4e76cd" }, + { text: "Service Hijacking", value: 7, color: "#4e76cd" }, +]; const TopThreatTypeChart = ({ data }) => { const [chartData, setChartData] = useState([]); + const currentSubCategory = PersistStore(state => state.subCategory); + const isEndpointSecurity = currentSubCategory === SUB_CATEGORY_ENDPOINT_SECURITY; + useEffect(() => { - const chartData = data - .sort((a, b) => a.text.localeCompare(b.text)) - .map((x) => ({ - text: x.text.replaceAll("_", " "), - value: x.value, - color: x.color, - })); - setChartData(chartData); - }, [data]); + // Use dummy data based on subcategory for Agentic Security + if (getDashboardCategory() === "Agentic Security") { + const selectedDummyData = isEndpointSecurity ? endpointSecurityDummyData : cloudSecurityDummyData; + console.log(`TopThreatTypeChart: Using ${isEndpointSecurity ? 'Endpoint' : 'Cloud'} Security dummy data`); + setChartData(selectedDummyData); + } else { + // Use real data for other categories + const processedData = data + .sort((a, b) => a.text.localeCompare(b.text)) + .map((x) => ({ + text: x.text.replaceAll("_", " "), + value: x.value, + color: x.color, + })); + setChartData(processedData); + } + }, [data, currentSubCategory, isEndpointSecurity]); return ( { }} showYAxis={true} yAxisTitle={`# of ${mapLabel("APIs", getDashboardCategory())}`} - barWidth={100 - (data.length * 6)} + barWidth={100 - (chartData.length * 6)} barGap={12} showGridLines={true} /> diff --git a/apps/dashboard/web/polaris_web/web/src/apps/main/PersistStore.js b/apps/dashboard/web/polaris_web/web/src/apps/main/PersistStore.js index 008efe32cc..c8a4b4a97b 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/main/PersistStore.js +++ b/apps/dashboard/web/polaris_web/web/src/apps/main/PersistStore.js @@ -61,6 +61,7 @@ const initialState = { sendEventOnLogin: false, tableSelectedTab: {}, dashboardCategory: 'API Security', + subCategory: 'Default', }; let persistStore = (set, get) => ({ @@ -221,6 +222,14 @@ let persistStore = (set, get) => ({ console.error("Error setting dashboardCategory:", error); } }, + setSubCategory: (subCategory) => { + try { + set({ subCategory }); + } catch (error) { + console.error("Error setting subCategory:", error); + } + }, + }); persistStore = devtools(persistStore); @@ -241,7 +250,8 @@ persistStore = persist(persistStore, { trafficAlerts: state.trafficAlerts, sendEventOnLogin: state.sendEventOnLogin, tableSelectedTab: state.tableSelectedTab, - dashboardCategory: state.dashboardCategory + dashboardCategory: state.dashboardCategory, + subCategory: state.subCategory }) }); diff --git a/apps/dashboard/web/polaris_web/web/src/apps/main/labelHelper.js b/apps/dashboard/web/polaris_web/web/src/apps/main/labelHelper.js index 81bbc26451..dfe35a11ec 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/main/labelHelper.js +++ b/apps/dashboard/web/polaris_web/web/src/apps/main/labelHelper.js @@ -15,6 +15,8 @@ export const CATEGORY_MCP_SECURITY = 'MCP Security'; export const CATEGORY_GEN_AI = 'Gen AI'; export const CATEGORY_API_SECURITY = 'API Security'; export const CATEGORY_AGENTIC_SECURITY = 'Agentic Security'; +export const SUB_CATEGORY_CLOUD_SECURITY = 'Cloud Security'; +export const SUB_CATEGORY_ENDPOINT_SECURITY = 'Endpoint Security'; export function getDashboardCategory() { try { @@ -44,3 +46,20 @@ export function isApiSecurityCategory() { export function isAgenticSecurityCategory() { return isCategory(CATEGORY_AGENTIC_SECURITY); } + +export function getSubCategory() { + try { + const category = PersistStore.getState().subCategory + return category + } catch(e){ + return SUB_CATEGORY_CLOUD_SECURITY + } +} + +export function isSubCategory(category) { + return getSubCategory() === category; +} + +export function shouldShowLeftNavSwitch() { + return isMCPSecurityCategory() || isAgenticSecurityCategory(); +} diff --git a/apps/dashboard/web/polaris_web/web/src/apps/main/labelHelperMap.js b/apps/dashboard/web/polaris_web/web/src/apps/main/labelHelperMap.js index b0f304bd88..779326b89a 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/main/labelHelperMap.js +++ b/apps/dashboard/web/polaris_web/web/src/apps/main/labelHelperMap.js @@ -98,7 +98,7 @@ export const labelMap = { "API Risk Score": "MCP components Risk Score", "Endpoint": "MCP component", "Threat Policy": "Guardrails Policy", - "API Threat Activity": "MCP Threat Activity", + "API Threat Activity": "MCP Guardrail Activity", "API changes": "Recent MCP components", "APIs Tested": "Tools tested", "APIs with most open issues": "MCP components with most open issues", @@ -137,7 +137,8 @@ export const labelMap = { "tests selected": "probes selected", "testing": "scanning", "Test library":"Probe library", - "Endpoint Shield": "MCP Endpoint Shield" + "Endpoint Shield": "MCP Endpoint Shield", + "Threat": "Guardrail" }, "Gen AI": { "API Security Posture": "AI Agent Security Posture", @@ -237,7 +238,7 @@ export const labelMap = { "API Risk Score": "Agentic Risk Score", "Endpoint": "Agentic Component", "Threat Policy": "Agentic Guardrails Policy", - "API Threat Activity": "Agentic Threat Activity", + "API Threat Activity": "Agentic Guardrail Activity", "API changes": "Recent Agentic Components", "APIs Tested": "Agentic Components Tested", "APIs with most open issues": "Agentic Components with most open issues", @@ -276,6 +277,7 @@ export const labelMap = { "tests selected": "probes selected", "testing": "scanning", "Test library":"Probe library", - "Endpoint Shield": "Agentic Endpoint Shield" + "Endpoint Shield": "Agentic Endpoint Shield", + "Threat": "Guardrail" }, } \ No newline at end of file diff --git a/apps/dashboard/web/polaris_web/web/src/util/request.js b/apps/dashboard/web/polaris_web/web/src/util/request.js index d83c57d793..650bc64bd3 100644 --- a/apps/dashboard/web/polaris_web/web/src/util/request.js +++ b/apps/dashboard/web/polaris_web/web/src/util/request.js @@ -101,15 +101,27 @@ service.interceptors.request.use((config) => { config.headers['Content-Type'] = 'application/json' config.headers["access-token"] = SessionStore.getState().accessToken const currentCategory = PersistStore.getState().dashboardCategory || "API Security"; + let subCategory = PersistStore.getState().subCategory; let contextSource = "API"; if (currentCategory === "API Security") { contextSource = "API"; + subCategory = subCategory || "Default"; } else if (currentCategory === "MCP Security") { contextSource = "MCP"; + subCategory = subCategory || "Cloud Security"; } else if (currentCategory === "Agentic Security") { contextSource = "AGENTIC"; + subCategory = subCategory || "Cloud Security"; } + + if(subCategory === "Cloud Security"){ + subCategory = "CLOUD_SECURITY"; + } else if(subCategory === "Endpoint Security"){ + subCategory = "ENDPOINT_SECURITY"; + } + config.headers['x-context-source'] = contextSource; + config.headers['x-sub-category'] = subCategory; if (window.ACTIVE_ACCOUNT) { diff --git a/apps/data-ingestion-service/src/main/java/com/akto/action/IngestionAction.java b/apps/data-ingestion-service/src/main/java/com/akto/action/IngestionAction.java index b448ce37d6..a589eadf03 100644 --- a/apps/data-ingestion-service/src/main/java/com/akto/action/IngestionAction.java +++ b/apps/data-ingestion-service/src/main/java/com/akto/action/IngestionAction.java @@ -53,6 +53,7 @@ public String ingestData() { try { if(sendLogsToCustomAccount(batchData)){ System.setProperty("DATABASE_ABSTRACTOR_SERVICE_TOKEN", "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJBa3RvIiwic3ViIjoiaW52aXRlX3VzZXIiLCJhY2NvdW50SWQiOjE2NjI2ODA0NjMsImlhdCI6MTc2MDU5NzM0OCwiZXhwIjoxNzc2MzIyMTQ4fQ.b-aqZEiTinzE1tavKDe6t7Ec7TsnsGoVRdxCiMmeOM20JcJ7aEgOZaJxD7O9zyoD6AEXmpEghd04wGhGCECBOKWivDS8Y_fdatLw8R7hH0Y-pu8QEMC1whbXXJrNhsRGXihLIiQ80nDKbrv6ObbyDwy4NPYoCFK8Mpu2i4W8qZHBJXnxmVkCp8Cp_LyeDLotXvc8DAp9huHASil0BSOxiUwHsw3Efk4BkRlHADfAwGFz4j-ozdbiK0SHHvOZNicl1wgpvDk0nHRLhIg3Ynx-Fk4Pp0agb0MCpS55-CRMBbx3zy9xRdkhIGdOydEzZKK5p311hwPnxxeL6Dp1C2f89g"); + } printLogs("ingestData batch size " + batchData.size()); diff --git a/libs/dao/src/main/java/com/akto/DaoInit.java b/libs/dao/src/main/java/com/akto/DaoInit.java index cfef934603..23aa4b392a 100644 --- a/libs/dao/src/main/java/com/akto/DaoInit.java +++ b/libs/dao/src/main/java/com/akto/DaoInit.java @@ -399,7 +399,8 @@ public static CodecRegistry createCodecRegistry(){ new EnumCodec<>(ModelType.class), new EnumCodec<>(ModuleInfo.ModuleType.class), new EnumCodec<>(TLSAuthParam.CertificateType.class), - new EnumCodec<>(TicketSource.class) + new EnumCodec<>(TicketSource.class), + new EnumCodec<>(CollectionTags.TagSource.class) ); return fromRegistries(MongoClientSettings.getDefaultCodecRegistry(), pojoCodecRegistry, diff --git a/libs/dao/src/main/java/com/akto/dao/context/Context.java b/libs/dao/src/main/java/com/akto/dao/context/Context.java index f618a24268..5498a2ce71 100644 --- a/libs/dao/src/main/java/com/akto/dao/context/Context.java +++ b/libs/dao/src/main/java/com/akto/dao/context/Context.java @@ -2,7 +2,9 @@ import com.akto.dao.AccountsDao; import com.akto.dto.Account; +import com.akto.util.enums.GlobalEnums; import com.akto.util.enums.GlobalEnums.CONTEXT_SOURCE; +import com.akto.util.enums.GlobalEnums.SUB_CATEGORY_SOURCE; import java.math.BigDecimal; import java.time.*; @@ -12,11 +14,13 @@ public class Context { public static ThreadLocal accountId = new ThreadLocal(); public static ThreadLocal userId = new ThreadLocal(); public static ThreadLocal contextSource = new ThreadLocal(); +public static ThreadLocal subCategory = new ThreadLocal<>(); public static void resetContextThreadLocals() { accountId.remove(); userId.remove(); contextSource.remove(); + subCategory.remove(); } public static int getId() { @@ -29,6 +33,14 @@ public static void dummy() { } + public static SUB_CATEGORY_SOURCE getSubCategory() { + try { + return subCategory.get(); + } catch (Exception e) { + return null; + } + } + public static int today() { DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd"); LocalDateTime now = LocalDateTime.now(); diff --git a/libs/dao/src/main/java/com/akto/dto/rbac/UsersCollectionsList.java b/libs/dao/src/main/java/com/akto/dto/rbac/UsersCollectionsList.java index 32b5222931..49b8293129 100644 --- a/libs/dao/src/main/java/com/akto/dto/rbac/UsersCollectionsList.java +++ b/libs/dao/src/main/java/com/akto/dto/rbac/UsersCollectionsList.java @@ -9,6 +9,7 @@ import com.akto.dto.traffic.CollectionTags; import com.akto.util.Constants; import com.akto.util.Pair; +import com.akto.util.enums.GlobalEnums; import com.akto.util.enums.GlobalEnums.CONTEXT_SOURCE; import com.mongodb.client.MongoCursor; import com.mongodb.client.model.Filters; @@ -27,7 +28,7 @@ public class UsersCollectionsList { private static final ConcurrentHashMap, Pair, Integer>> usersCollectionMap = new ConcurrentHashMap<>(); - private static final ConcurrentHashMap, Pair, Integer>> contextCollectionsMap = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap, Integer>> contextCollectionsMap = new ConcurrentHashMap<>(); private static final int EXPIRY_TIME = 15 * 60; private static final int CONTEXT_EXPIRY_TIME = 120; @@ -40,6 +41,8 @@ public static void deleteCollectionIdsFromCache(int userId, int accountId) { public static final String RBAC_FEATURE = "RBAC_FEATURE"; + private static final Bson tagFilter = Filters.elemMatch(ApiCollection.TAGS_STRING, Filters.eq(CollectionTags.SOURCE, CollectionTags.TagSource.ENDPOINT)); + /* * Cases: * 1. For admin we save the list as null and @@ -82,8 +85,9 @@ public static List getCollectionsIdForUser(int userId, int accountId) { collectionList = collectionIdEntry.getFirst(); } + GlobalEnums.SUB_CATEGORY_SOURCE subCategory = Context.getSubCategory(); // since this function is used everywhere for the queries, taking context collections into account here - Set contextCollections = getContextCollectionsForUser(accountId, Context.contextSource.get()); + Set contextCollections = getContextCollectionsForUser(accountId, Context.contextSource.get(), subCategory); if(collectionList == null) { collectionList = contextCollections.stream() .collect(Collectors.toList()); @@ -95,27 +99,39 @@ public static List getCollectionsIdForUser(int userId, int accountId) { return collectionList; } - public static void deleteContextCollectionsForUser(int accountId, CONTEXT_SOURCE source) { + public static void deleteContextCollectionsForUser(int accountId, CONTEXT_SOURCE source, GlobalEnums.SUB_CATEGORY_SOURCE subCategory) { if(source == null) { source = CONTEXT_SOURCE.API; } + + if(subCategory == null) { + subCategory = GlobalEnums.SUB_CATEGORY_SOURCE.DEFAULT; + } if(contextCollectionsMap.isEmpty()) { return; } - Pair key = new Pair<>(accountId, source); - contextCollectionsMap.remove(key); + // Remove all cache entries for this accountId and source (regardless of leftNavCategory) + String keyPrefix = accountId + "_" + source.name() + "_" + subCategory.name() ; + contextCollectionsMap.remove(keyPrefix); } - public static Set getContextCollectionsForUser(int accountId, CONTEXT_SOURCE source) { + public static Set getContextCollectionsForUser(int accountId, CONTEXT_SOURCE source, GlobalEnums.SUB_CATEGORY_SOURCE subCategory) { if(source == null) { source = CONTEXT_SOURCE.API; } - Pair key = new Pair<>(accountId, source); - Pair, Integer> collectionIdEntry = contextCollectionsMap.get(key); - Set collectionList = new HashSet<>(); + + if(subCategory == null) { + subCategory = GlobalEnums.SUB_CATEGORY_SOURCE.DEFAULT; + } + // Create cache key that includes leftNavCategory to ensure proper filtering + String cacheKey = accountId + "_" + source.name() + "_" + subCategory.name() ; + Pair, Integer> collectionIdEntry = contextCollectionsMap.get(cacheKey); + Set collectionList; if (collectionIdEntry == null || (Context.now() - collectionIdEntry.getSecond() > CONTEXT_EXPIRY_TIME)) { - collectionList = getContextCollections(source); + collectionList = getContextCollections(source, subCategory); + // Cache the result with the new key + contextCollectionsMap.put(cacheKey, new Pair<>(collectionList, Context.now())); } else { collectionList = collectionIdEntry.getFirst(); } @@ -123,7 +139,7 @@ public static Set getContextCollectionsForUser(int accountId, CONTEXT_S return collectionList; } - public static Set getContextCollections(CONTEXT_SOURCE source) { + public static Set getContextCollections(CONTEXT_SOURCE source, GlobalEnums.SUB_CATEGORY_SOURCE subCategory ) { Set collectionIds = new HashSet<>(); Bson finalFilter = Filters.or( Filters.exists(ApiCollection.TAGS_STRING, false), @@ -132,11 +148,24 @@ public static Set getContextCollections(CONTEXT_SOURCE source) { Filters.elemMatch(ApiCollection.TAGS_STRING, Filters.eq(CollectionTags.KEY_NAME, Constants.AKTO_GEN_AI_TAG)) ) ); + + Bson subCategoryFilter = Filters.empty(); + if (!subCategory.equals(GlobalEnums.SUB_CATEGORY_SOURCE.DEFAULT)){ + if ((subCategory.equals(GlobalEnums.SUB_CATEGORY_SOURCE.ENDPOINT_SECURITY))) { + subCategoryFilter = tagFilter; + } else if (subCategory.equals(GlobalEnums.SUB_CATEGORY_SOURCE.CLOUD_SECURITY)) { + subCategoryFilter = Filters.not( + tagFilter + ); + } + } + switch (source) { case MCP: finalFilter = Filters.and( Filters.exists(ApiCollection.TAGS_STRING), - Filters.elemMatch(ApiCollection.TAGS_STRING, Filters.eq(CollectionTags.KEY_NAME, Constants.AKTO_MCP_SERVER_TAG)) + Filters.elemMatch(ApiCollection.TAGS_STRING, Filters.eq(CollectionTags.KEY_NAME, Constants.AKTO_MCP_SERVER_TAG)), + subCategoryFilter ); break; case GEN_AI: @@ -153,12 +182,14 @@ public static Set getContextCollections(CONTEXT_SOURCE source) { Filters.or( Filters.elemMatch(ApiCollection.TAGS_STRING, Filters.eq(CollectionTags.KEY_NAME, Constants.AKTO_MCP_SERVER_TAG)), Filters.elemMatch(ApiCollection.TAGS_STRING, Filters.eq(CollectionTags.KEY_NAME, Constants.AKTO_GEN_AI_TAG)) - ) + ), + subCategoryFilter ); break; default: break; } + MongoCursor cursor = ApiCollectionsDao.instance.getMCollection().find(finalFilter).projection(Projections.include(Constants.ID)).iterator(); while (cursor.hasNext()) { collectionIds.add(cursor.next().getId()); diff --git a/libs/dao/src/main/java/com/akto/dto/traffic/CollectionTags.java b/libs/dao/src/main/java/com/akto/dto/traffic/CollectionTags.java index 86355fcb57..74c42b8943 100644 --- a/libs/dao/src/main/java/com/akto/dto/traffic/CollectionTags.java +++ b/libs/dao/src/main/java/com/akto/dto/traffic/CollectionTags.java @@ -24,7 +24,8 @@ public class CollectionTags { public enum TagSource { KUBERNETES, - USER + USER, + ENDPOINT } TagSource source; diff --git a/libs/dao/src/main/java/com/akto/util/enums/GlobalEnums.java b/libs/dao/src/main/java/com/akto/util/enums/GlobalEnums.java index 54125e6445..3eab02ddcc 100644 --- a/libs/dao/src/main/java/com/akto/util/enums/GlobalEnums.java +++ b/libs/dao/src/main/java/com/akto/util/enums/GlobalEnums.java @@ -159,5 +159,9 @@ public enum CONTEXT_SOURCE { API, MCP, GEN_AI, AGENTIC } + public enum SUB_CATEGORY_SOURCE { + DEFAULT, CLOUD_SECURITY, ENDPOINT_SECURITY; + } + /* ********************************************************************** */ } diff --git a/libs/utils/src/main/java/com/akto/usage/UsageMetricCalculator.java b/libs/utils/src/main/java/com/akto/usage/UsageMetricCalculator.java index 337841c506..48696507a7 100644 --- a/libs/utils/src/main/java/com/akto/usage/UsageMetricCalculator.java +++ b/libs/utils/src/main/java/com/akto/usage/UsageMetricCalculator.java @@ -122,19 +122,19 @@ public static Set getDemosAndDeactivated() { } public static Set getMcpCollections() { - return UsersCollectionsList.getContextCollections(CONTEXT_SOURCE.MCP); + return UsersCollectionsList.getContextCollections(CONTEXT_SOURCE.MCP, Context.getSubCategory()); } public static Set getGenAiCollections() { - return UsersCollectionsList.getContextCollections(CONTEXT_SOURCE.GEN_AI); + return UsersCollectionsList.getContextCollections(CONTEXT_SOURCE.GEN_AI, Context.getSubCategory()); } public static Set getApiCollections() { - return UsersCollectionsList.getContextCollections(CONTEXT_SOURCE.API); + return UsersCollectionsList.getContextCollections(CONTEXT_SOURCE.API, null); } public static Set getAgenticCollections() { - return UsersCollectionsList.getContextCollections(CONTEXT_SOURCE.AGENTIC); + return UsersCollectionsList.getContextCollections(CONTEXT_SOURCE.AGENTIC, Context.getSubCategory()); } public static List getInvalidTestErrors() {