wazuh / wazuh-dashboard

Wazuh dashboard, the Wazuh UI platform
https://wazuh.com
Apache License 2.0
36 stars 56 forks source link

Global menu #83

Closed gdiazlo closed 1 year ago

gdiazlo commented 1 year ago

Introduction

This UX redesign tier 1 scope involves mostly a new general menu for the whole interface.

Now that we have control over the complete dashboard, we want to improve the user experience, making it easier to access Wazuh features. Our first step will be to create a new menu experience in line with what the platform is designed to work with: the left navigation drawer.

Our current menu is embedded into our main application in the top bar:

image

We want to incorporate this accesses into the global navigation drawer, transforming the current look (on the left) to something like:

image

Of these, we want to pursue the 1st one, as it allows us to implement the global navigation drawer in a simple and fast pace manner, and allows us to evolve the main plugin with time, iteratively, instead of a big bang devel approach, unlike option 2 and 3.

It is important that we leverage the built-in styles and looks so that our menu can evolve with the new upstream changes which are on their way (for example https://github.com/opensearch-project/OpenSearch-Dashboards/issues/4298).

We must customize the icons for each category. The UI library provides a set of icons we could use.

It's also important to study what would happen in the case of newer applications arriving from upstream that use the same categories as we propose here.

Functional requirements

Non-functional requirements

Plan

Desvelao commented 1 year ago

Research

Investigate: menu as a standalone plugin

The platform menu displays all the registered and visible applications. A plugin can register any number of applications in the platform menu. The app registration needs to define what will be rendered when the user access to the menu.

This means that if we do a plugin to register all the applications, then it must have access to the render methods of them. The rendering method can come from other plugins or itself. If they come from other plugins, this creates more dependency between these plugins.

The menu as a standalone plugin can need more logic than if each plugin manages the application registration.

I think that @asteriscos was working in a POC to register multiple applications. It would be interesting to know what approach followed.

Adding multiple applications breaks some features related to:

Other problems related to the proposition:

Desvelao commented 1 year ago

Research

The current plugin is a monolith and this can generate multiple problems if we want to decouple into different applications if this architecture is kept as mentioned ones:

Health check page

This is a page that does some checks and creates or updates some elements that are needed to work the plugin.

Each route of the current plugin can do some checks. If some of them fail, then a redirection to the health check page will be done. This means we should decide how to accommodate the health check in the different applications. It could be a different application or a route in each future application if we want to keep the current logic.

The angular routing problem

I am not sure what it is referring to.

The current plugin uses as base AngularJS, despite part of the application being migrated to ReactJS. The routing is done with AngularJS.

The embedded discover urls

The Events tab of each module uses the URL to set the filters.

Desvelao commented 1 year ago

Possible approach: applications redirect to the monolith application

@asteriscos and I were researching how to create the applications in the side menu and clicking on them redirects to them.

This approach does the new applications redirect to the monolith plugin when the link in the side menu is clicked on. The monolith plugin should be hidden.

Pros:

Cons:

Problems

Screenshots

image

Implementation

Create the application that redirects to the monolith application:

core.application.register({
  id,
  title,
  mount: async (params: AppMountParameters) => {
    window.location.href = href; // e.g.: /app/wazuh#/agents
  },
  category,
});

Hide the monolith application:

import {
  AppNavLinkStatus,
} from '../../../src/core/public';
core.application.register({
  id: `wazuh`,
  title: 'Wazuh',
  navLinkStatus: AppNavLinkStatus.hidden,
 // rest of properties
})

Add applications to base Management category:

import { DEFAULT_APP_CATEGORIES,} from '../../../src/core/public';

core.application.register({
  id,
  title,
  mount: async (params: AppMountParameters) => {
    window.location.href = href;
  },
  category: DEFAULT_APP_CATEGORIES.management,
})
Desvelao commented 1 year ago

Possible approach: generate the application routes according to the selected application

This approach generates the routes of each application dynamically. The default route is changed to the current route that should be visible.

Pros:

Cons:

Implementation

Change the routes dynamically:

// public/services/routes.ts
const redirectTo: getApplicationRedirectTo()
// router definition
.when('/', {
      redirectTo,
      outerAngularWrapperRoute: true,
    })
    .when('', {
      redirectTo,
      outerAngularWrapperRoute: true,
    })
    .otherwise({
      redirectTo,
      outerAngularWrapperRoute: true,
    });

getRootRedirectTo is a getter. Use a setter when the application is mounted in the mount method:

// public/plugin.ts
mount: (params){
  setApplicationRedirectTo(redirectToRoot);
  // rest of logic
}

Create the getter/setter:

// public/kibana_services.js
export const [getApplicationRedirectTo, setApplicationRedirectTo] =
  createGetterSetter<any>('WzApplicationRedirectTo');

I was testing this trying to keep the shared state of the selected agent that is stored in Redux store. When I change from a Wazuh application to another one, despite the agentId parameter being added to the URL, it disappears due to the page being refreshed https://github.com/wazuh/wazuh-kibana-app/blob/4.6.0/plugins/main/public/plugin.ts#L96

window.location.reload()

Reloading the page is used in other situations. So, fixing is not easy because the Redux store is kept, and the page can't be refreshed. Both situations are mutually exclusive.

If we need to want to use the Redux store to share critical state, then it seems the page should not be refreshed. As the user can refresh manually the page, this could cause some problems, so it seems these states should be managed in another way, such as local storage/session storage or URL.

Update 2023/08/21 In a meeting with @asteriscos , we tried to use the session store for the selected agent. So the selected agent is stored in the Redux store and session storage. If the page is refreshed (by the user or the application itself), then the Redux store will use the value of the selected agent in the session storage to initiate the Redux store. A mechanism that exists in the current application will set the agentId query parameter in the URL if the agent data is stored in the Redux store. We will continue with this approach.

// plugins/main/public/redux/reducers/appStateReducers.js
const initialState = {
  currentAPI: '',
  showMenu: false,
  wazuhNotReadyYet: '',
  currentTab: '',
  extensions: {},
  selected_settings_section: '',
  currentPlatform: false,
  currentAgentData: JSON.parse(
    window.sessionStorage.getItem('wz-shared-selected-agent') || '{}',
  ),
  showExploreAgentModal: false,
  showExploreAgentModalGlobal: false,
  userPermissions: false,
  userRoles: [],
  toastNotification: false,
  withUserLogged: false,
  allowedAgents: [],
  logtestToken: '',
};
// ....
 if (action.type === 'UPDATE_SELECTED_AGENT_DATA') {
    window.sessionStorage.setItem(
      'wz-shared-selected-agent',
      JSON.stringify(action.currentAgentData),
    );
    return {
      ...state,
      currentAgentData: action.currentAgentData,
    };
  }
Desvelao commented 1 year ago

Design

Following the approach: https://github.com/wazuh/wazuh-dashboard/issues/83#issuecomment-1683978025 Base development branch: feat/83-move-menu-items-to-applications

A plugin registers each application in the side menu. The initial solution uses the same plugin, and we don't consider to be needed another plugin to register the application in the side menu because it creates more complexity. Each application mounts the monolith application whose entry point is modified to display the expected view.

JuanGarriuz commented 1 year ago

Tabs in Server Management - RBAC don't work image image image image

yenienserrano commented 1 year ago

When you want to navigate between the Endpoint security, Thread intelligence, Security operations and Cloud security modules and go through the heath check instead of redirecting to the desired view, it redirects to the overview.

On the other hand, changing views is changing the plugin, as the URL changes and the application takes longer to load the views, so it slows down the application and in my environment it doesn't have much data to load.

Desvelao commented 1 year ago

Tabs in Server Management - RBAC don't work image image image image

Fixed here: https://github.com/wazuh/wazuh-kibana-app/commit/bc7739fc3554614a2c896e1d50c818f515286fc1

Desvelao commented 1 year ago

Meeting

Discussions

Decisions

Desvelao commented 1 year ago

Changes

Category Name Section in monolith plugin
Management Modules Settings > Modules
Management Miscellaneous Settings > Miscellaneous

Notes: Settings > Miscellaneus should be moved to Configuration Modules app only contains the visibility of some applications. This should be refactored to support all the applications.

Desvelao commented 1 year ago

Changes

JuanGarriuz commented 1 year ago

Server Management Issues

Groups

When we click on the eye icon to navigate to Agent-preview, we go to /app/wz-groups#/agents?_g=(filters, using monolithic logic. The function that works over the eye button is showAgent() which works like:

image

image

The expected result is to navigate to the Endpoints summary:

image

But keep into Groups

image

We can find that code in → plugins/main/public/controllers/management/groups.js That code modified the URL after "#" to navigate a new web section, but it doesn't work in the new menu bar logic, because we navigate across the menu bar.

To fix it, there are 2 main branches; create a new function that works in all redirection code lines, or change old logic code lines with a HREF.

Statitics

When we click on the settings button, we go to /app/wz-statistics#/settings?tab=configuration&category=statistics, using monolithic logic. The function that works over the button is navigateToModule() which works like:

image

image

The expected result is to navigate to Configuration:

image

But keep in Statistics

image

We can find that code in → plugins/main/public/controllers/management/components/management/statistics/statistics-overview.js That code modified the URL after "#" to navigate a new web section, but it doesn't work in the new menu bar logic, because we navigate across the menu bar.

To fix it, there are 2 main branches; create a new function that works in all redirection code lines, or change old logic code lines with a href.

Monitoring

When we click on the Agents button, we go to /app/wz-cluster#/agents-preview/?_g=(filters:, using monolithic logic. The function that works over the button is a href.

The expected result is to navigate to Configuration:

image

But keep in Statistics

image

We can find that code in → plugins/main/public/controllers/management/monitoring.js That code modified the URL with a href. but that means that there are at least 3 different ways to navigate over the different parts of the app.

To fix it there are 2 main branches; create a new function that works in all redirection code lines, or change old logic code lines on the href.

Conclusion

If we fix those changes all navigates code lines to hrefs the code will be too much dirt, making it more difficult to fix bugs in the future, and making the code less reusable. Conversely, it means that we have to take more time to make reusable and cleaner code.

yenienserrano commented 1 year ago

Research the impact of: API hosts and valid index patterns are updated when opening or closing the current menu. Should it behavior keep and how?

Requests are made to update the index pattern and apis selectors. This is not very reliable because if the user adds an index pattern or a new api and opens the selector, he will not see the changes until he opens the menu or refreshes the page. With the index pattern it would only affect if you have more than 1 window open with Wazuh, because if the user only handles 1 window when adding an index pattern and then navigates to another view, the requests would be executed when mounting the component. For me, it is better to add to the documentation that after adding a new api or index pattern you have to restart the browser, and save us from making those requests every so often or when the user makes a specific action. We can also use the onMouseEnter function to execute the request when the user puts the mouse over the selector, but this may cause unnecessary requests.

Desvelao commented 1 year ago

Changes

Category Name Section in monolith plugin
Endpoint security Overview Modules directory
Desvelao commented 1 year ago

Meeting

Discussions:

Desvelao commented 1 year ago

Meeting

Current status

Action items