Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create the Analysis plugin and implement the new menu app registration method #484

Open
3 tasks
Tracked by #412
asteriscos opened this issue Jan 14, 2025 · 5 comments · May be fixed by wazuh/wazuh-dashboard-plugins#7242
Open
3 tasks
Tracked by #412
Assignees
Labels
level/task Task issue type/enhancement New feature or request

Comments

@asteriscos
Copy link
Member

asteriscos commented Jan 14, 2025

Description

We have to create the plugin "Analysis" which will include all the applications related to data analysis dashboards. The main goal is to register the Analysis plugin and its applications to create the scaffolding which will be used later on to create the dashboards.

Menu structure

  • Analysis
    • Endpoint security
      • Configuration assessment
      • Malware detection
      • FIM
    • Threat intelligence
      • Threat hunting
      • Vulnerability detection
      • Mitre ATT&CK
    • Security operations
      • Regulatory compliance (unify)
      • IT Hygiene
      • Incident response
    • Cloud Security
      • Docker
      • AWS
      • Google Cloud
      • Github
      • Office365

Functional requirements

  • The applications must be registered in the legacy navigation menu as well as the new navigation menu.

Additional information

The new navigation menu is enabled with the advanced setting useNewHomePage. The OpenSearch native plugin dev_tools already has the new navigation menu implementation.
This can be used as an example:
src/plugins/dev_tools/public/plugin.ts

Tasks

  • Separate static application configurations into a dedicated file to improve code organization and simplify future maintenance
  • Move shared logic between different plugins to wazuh-core
  • Add titles to the navigation groups in the sidebar
@guidomodarelli
Copy link

The dynamically controlled system for hiding/showing subapps

The system for hiding/showing subapps in an OpenSearch Dashboards plugin uses a combination of plugin lifecycle methods, rxjs for handling observables and reactive state (updater$), and navLinkStatus configurations to control the visibility of sub-applications in the interface. Step-by-step explanation of the process:


1. General Architecture

Main apps and subapps

  • A main app (for example, ENDPOINT_SECURITY) represents a group of functionalities within the plugin.
  • The subapps are specific functionalities within a main app (such as CONFIGURATION_ASSESSMENT, MALWARE_DETECTION, or FIM).

These subapps are linked to their main app and their visibility is controlled dynamically.


2. Subapp Visibility Control

Visibility is controlled through these key elements:

navLinkStatus

  • Each app or subapp has a navLinkStatus property, which can be:
    • AppNavLinkStatus.visible: The subapp is visible in the navigation bar.
    • AppNavLinkStatus.hidden: The subapp is hidden in the navigation bar.

updater$

  • It's an observable (Subject) that serves to partially update any app. In this specific context, it's linked to each main app to dynamically update the visibility state (navLinkStatus) of its subapps.
  • Each subapp has its own updater$ connected to its main app's observable.

Example:

updater$: this.appStatusUpdater$[ENDPOINT_SECURITY],

When the plugin emits a value in this updater$, the associated subapps update their state (navLinkStatus).


3. Helper Methods for Changing Visibility

The plugin uses two main functions:

makeNavLinkStatusVisible

Returns a partial object indicating that the subapp should be visible:

function makeNavLinkStatusVisible(): Partial<App> {
  return {
    navLinkStatus: AppNavLinkStatus.visible,
  };
}

makeNavLinkStatusHidden

Returns a partial object indicating that the subapp should be hidden:

function makeNavLinkStatusHidden(): Partial<App> {
  return {
    navLinkStatus: AppNavLinkStatus.hidden,
  };
}

4. Lifecycle Flow

The visibility system is primarily activated in the plugin lifecycle methods: setup, start, and mount.

In setup

  • All main apps and subapps are registered using registerApps.
  • Each subapp defines an updater$ connected to a Subject of the main app.

Example:

this.appStatusUpdater$[ENDPOINT_SECURITY] = new Subject();
  • Navigation links (navLinks) are registered for each subapp, initially hidden (navLinkStatus: AppNavLinkStatus.hidden).

In start

  • Subscribes to the appStartup$ observable, which activates when a main app is mounted.
  • When activating a main app, all associated subapps become visible as they share the same observable, and navigation proceeds to the first one.

5. Dynamic Runtime Execution

Mounting a subapp

When a subapp is mounted, its mount executes. During this process:

  1. The updater$ of the main app emits to make the subapp visible:

    this.appStatusUpdater$[ENDPOINT_SECURITY].next(makeNavLinkStatusVisible);
  2. When unmounting the subapp, a change is emitted to hide it:

    this.appStatusUpdater$[ENDPOINT_SECURITY].next(makeNavLinkStatusHidden);

Navigation to a subapp

If a main app is active, the system automatically navigates to the first visible subapp through:

  • navigateToFirstAppInNavGroup: Navigates to the first link (navLink) in the app group.

6. Concrete Example: Endpoint Security

For the ENDPOINT_SECURITY app:

  1. The subapps (CONFIGURATION_ASSESSMENT, MALWARE_DETECTION, FIM) are initially registered as hidden.
  2. When selecting "Endpoint Security", the updater$ makes the associated subapps visible.
  3. When unmounting a subapp, the updater$ hides it.

7. Component Interaction Summary

  1. Initial Registration:
    • Subapps are registered as hidden.
    • An updater$ is associated to update them dynamically.
  2. Visibility Change:
    • When selecting a main app, the updater$ modifies its subapps' visibility.
    • Visibility adjusts according to the mount/unmount lifecycle.
  3. Navigation:
    • The system automatically navigates to the first visible subapp when selecting a main app.

@guidomodarelli
Copy link

What does getNavGroupEnabled() do?

The core.chrome.navGroup.getNavGroupEnabled() method verifies if navigation groups are enabled in the system configuration.


The NavGroupEnabled acts as a mechanism to verify if navigation groups (or Nav Groups) are enabled in the OpenSearch Dashboards user interface.


What is a Nav Group?

A Nav Group (navigation group) is a set of links or apps grouped under a common category in the OpenSearch Dashboards sidebar. For example:

  • "Endpoint Security" (ID: ENDPOINT_SECURITY_ID) can contain subapps like "Configuration Assessment" or "Malware Detection".

Why is NavGroupEnabled relevant?

  1. Conditional for certain actions: In the code, getNavGroupEnabled() acts as a condition before executing group actions, such as:

    • Changing the current group:

      if (core.chrome.navGroup.getNavGroupEnabled()) {
        core.chrome.navGroup.setCurrentNavGroup(navGroupId);
      }
    • Emitting visibility updates for subapps:

      if (core.chrome.navGroup.getNavGroupEnabled()) {
        this.appStatusUpdater$[ENDPOINT_SECURITY_ID].next(makeNavLinkStatusVisible);
      }
  2. Some OpenSearch Dashboards implementations may have Nav Groups disabled. In these cases:

    • The plugin must function safely, avoiding navigation to non-existent groups or updating their states.
    • Compatibility is ensured with configurations that have Nav Groups both enabled and disabled.
  3. When groups are disabled, the plugin avoids unnecessary operations such as:

    • Subscribing to group changes (getCurrentNavGroup).
    • Updating subapps that won't be visible.

Practical usage example

When selecting the main app ENDPOINT_SECURITY_ID:

  • If NavGroupEnabled is true, the system:
    1. Sets ENDPOINT_SECURITY_ID as the current group using setCurrentNavGroup.
    2. Navigates to the first visible subapp with navigateToFirstAppInNavGroup.
  • If NavGroupEnabled is false, the system skips these actions.

Conclusion

getNavGroupEnabled() ensures that navigation group functionalities are executed only when enabled, providing compatibility with both the legacy menu system and newer configurations.

@guidomodarelli
Copy link

type: NavGroupType.SYSTEM

The problem with NavGroupType.SYSTEM type and why it "doesn't work" in this context lies in how the OpenSearch Dashboards navigation system processes application categories.


What is NavGroupType.SYSTEM?

In OpenSearch Dashboards, types like NavGroupType.SYSTEM are used to classify and group applications within specific categories, such as "Manage". This type indicates that an app belongs to a predefined internal or administrative category. This can be interpreted as a system decision to enforce these apps to be grouped under the "Manage" category, ignoring the custom category property that you define.

Main problem

When you define the app type as NavGroupType.SYSTEM, the system:

  1. Automatically groups these applications in the predefined "Manage" category.
  2. Ignores the custom category property defined for the app. This means you cannot assign a different category (for example, "Analysis" or "Security") for that app.

Why does this happen?

The behavior is due to how the system handles dynamic categories:

  • When the system detects that an app has the NavGroupType.SYSTEM type, it prioritizes its internal logic and forces it to be included in a predefined category ("Manage"), without respecting the category property.
  • This ensures that all "system" apps are grouped in a common place, but at the cost of limiting flexibility to customize their location.

This design makes sense in cases where apps are strictly related to system administration or need to be grouped to ensure coherence, but it can be an obstacle if you need apps to be categorized in a custom way.

What doesn't NavGroupType.SYSTEM allow you to do?

  1. Category customization:
    • You cannot assign an app with type: NavGroupType.SYSTEM to a different category using the category property. The app will always be in the "Manage" category, regardless of what you define.
  2. Flexible app visualization:
    • If you need an app to appear in both "Manage" and another category (like "Analysis"), it's not possible. The system forces the app to be only in "Manage".
  3. Consistency in app organization:
    • If you're trying to maintain a custom and coherent category scheme for your apps (for example, all those related to "Security" under the same category), this behavior breaks that consistency.

How to solve this problem

To prevent apps from being forced into the "Manage" category, you can do the following:

  1. Avoid using NavGroupType.SYSTEM:

    • If you don't need the app to be treated as a system app, simply remove the type: NavGroupType.SYSTEM property when registering the app. Instead, use the category property to customize the app's location.
    core.application.register({
      id: 'my_custom_app',
      title: 'My Custom App',
      category: CATEGORY, // Your custom category
      mount: async (params) => {
        const { renderApp } = await import('./application');
        return renderApp(params, {});
      },
    });
  2. Review dynamic categories logic:

    • If you need some apps to remain part of "Manage" but also have custom categories, you can create a manual mapping to duplicate links in both categories (although this requires implementing additional logic).
  3. Create an equivalent custom category:

    • If you need apps to appear "system-like" but within another category, you could replicate the "Manage" category properties in your own custom category.

Conclusion

The use of NavGroupType.SYSTEM is specifically designed to group apps in the "Manage" category by default, ignoring customization. This is useful for internal or administrative apps, but limits flexibility if you need to organize your apps in custom categories. The solution involves avoiding the use of this type if you need more control over your apps' organization.

@guidomodarelli
Copy link

showInAllNavGroup

    core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.all, [
      {
        id: this.ENDPOINT_SECURITY_ID,
        title: this.translationMessages.ENDPOINT_SECURITY,
        order: 0,
        category: this.CATEGORY,
      },
      {
        id: this.THREAT_INTELLIGENCE_ID,
        title: this.translationMessages.THREAT_INTELLIGENCE,
        order: 1,
        category: this.CATEGORY,
      },
      {
        id: this.SECURITY_OPERATIONS_ID,
        title: this.translationMessages.SECURITY_OPERATIONS,
        order: 2,
        category: this.CATEGORY,
      },
      {
        id: this.CLOUD_SECURITY_ID,
        title: this.translationMessages.CLOUD_SECURITY,
        order: 3,
        category: this.CATEGORY,
      },
    ]);

Image

Explanation

  1. core.chrome.navGroup.addNavLinksToGroup:
    • This method adds navigation links to a specific navigation group.
    • DEFAULT_NAV_GROUPS.all indicates that the links are being added to a general navigation group containing all categories.
  2. Links array ([ ... ]):
    • Each object in the array represents a link that will appear in the side navigation menu, as shown in the image.
    • Each link has the following properties:
      • id: Unique link identifier.
      • title: Text displayed in the interface.
      • order: Determines the link display order. Lower values appear first.
      • category: Groups links under a specific category, such as "Analysis" (visible in the image).
  3. Interface result:
    • The added links appear under the "Analysis" header in the left sidebar, with titles including:
      • Endpoint Security
      • Threat Intelligence
      • Security Operations
      • Cloud Security
    • The links are ordered according to their order property.
    core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.all, [
      {
        id: this.ENDPOINT_SECURITY_ID,
        title: this.translationMessages.ENDPOINT_SECURITY,
        order: 0,
        category: this.CATEGORY,
+       showInAllNavGroup: true,
      },
      {
        id: this.THREAT_INTELLIGENCE_ID,
        title: this.translationMessages.THREAT_INTELLIGENCE,
        order: 1,
        category: this.CATEGORY,
+       showInAllNavGroup: true,
      },
      {
        id: this.SECURITY_OPERATIONS_ID,
        title: this.translationMessages.SECURITY_OPERATIONS,
        order: 2,
        category: this.CATEGORY,
+       showInAllNavGroup: true,
      },
      {
        id: this.CLOUD_SECURITY_ID,
        title: this.translationMessages.CLOUD_SECURITY,
        order: 3,
        category: this.CATEGORY,
+       showInAllNavGroup: true,
      },
    ]);

Image

In this new code, an additional property (showInAllNavGroup) is added to each navigation group link. Below is an explanation of what's happening and how it affects the interface:

Original vs. Modified Code

In the original code, each link had properties like id, title, order, and category. The modified code introduces a new property:

+ showInAllNavGroup: true,

This enables the visibility of these links in the navigation group "Analytics".


Effect of the showInAllNavGroup Property

  • Purpose: The showInAllNavGroup: true property indicates that the associated links will display in the "Analytics" navigation group (DEFAULT_NAV_GROUPS.all), even if they belong to another category or section.
  • Visual Result:
    • Before: Links only appeared under a specific section, such as Analysis.
    • Now: The same links appear in multiple locations within the navigation menu.
      • For example, links such as Endpoint Security, Threat Intelligence, Security Operations, and Cloud Security now appear under both Analytics and Analysis, as shown in the provided image.

@guidomodarelli
Copy link

guidomodarelli commented Jan 22, 2025

Global SearchBar

Peek.2025-01-22.11-54.mp4

A SearchCommand is registered using the core.chrome.globalSearch.registerSearchCommand function. This function allows adding custom searches to the OpenSearch Dashboards navigation system. Let's look in detail at how this registration works:


1. Availability Verification

if (core.chrome.navGroup.getNavGroupEnabled()) {

First, it verifies if navigation groups (navGroup) are enabled in core.chrome, ensuring that the navigation functionality is available.


2. Search Command Registration

core.chrome.globalSearch.registerSearchCommand({
  id: 'wz-analysis',
  type: 'PAGES',
  run: async (query: string, done?: () => void) =>
    searchPages(
      query,
      applications.map(app => app.id),
      this.coreStart,
      done,
    ),
});

Registration Components:

  • id: Unique identifier for the command, in this case 'wz-analysis', which distinguishes it from other registrations.
  • type: Defines the type of element to search for. Here it's 'PAGES', indicating that the search will be performed on pages within applications.
  • run: Function that executes when performing a search. It receives:
    • query: The search term entered by the user.
    • done: (Optional) Function that is called when execution is complete.

3. Logic of the run Function

The run property executes the searchPages function, which performs the search:

searchPages(
  query,
  applications.map(app => app.id),
  this.coreStart,
  done,
)

Parameters:

  1. The term entered by the user for the search (query).
  2. An array of registered application IDs (applications.map(app => app.id)) to search in their navigation links (navLinks).
  3. The this.coreStart instance to interact with the OpenSearch Dashboards API.
  4. The optional done function that executes when the search is complete.

4. Search in Detail: searchPages

The searchPages function performs these operations:

  1. Gets the navigation groups through:

    const navGroupMap = await coreStart.chrome.navGroup
      .getNavGroupsMap$()
      .pipe(first())
      .toPromise();
  2. Filters relevant links using:

    links.filter(link => {
        const titleMatch = match(title, query);
        return !link.disabled && titleMatch;
    });
  3. Transforms the filtered results into React components (<GlobalSearchPageItem />) for visualization.


5. Conclusion

The registration of a SearchCommand extends the search capability of OpenSearch Dashboards. When registered, the command uses the run logic to locate relevant elements in the configured applications.

This implementation allows users to find navigation system elements quickly and intuitively.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
level/task Task issue type/enhancement New feature or request
Projects
Status: Backlog
Development

Successfully merging a pull request may close this issue.

2 participants