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

[7.x] [Fleet] Support for showing an Integration Detail Custom (UI Extension) tab (#83805) #84539

Merged
merged 1 commit into from
Nov 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion x-pack/plugins/fleet/common/types/models/epm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export type InstallSource = 'registry' | 'upload';

export type EpmPackageInstallStatus = 'installed' | 'installing';

export type DetailViewPanelName = 'overview' | 'policies' | 'settings';
export type DetailViewPanelName = 'overview' | 'policies' | 'settings' | 'custom';
export type ServiceName = 'kibana' | 'elasticsearch';
export type AgentAssetType = typeof agentAssetTypes;
export type AssetType = KibanaAssetType | ElasticsearchAssetType | ValueOf<AgentAssetType>;
Expand Down
261 changes: 261 additions & 0 deletions x-pack/plugins/fleet/public/applications/fleet/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React, { memo, useEffect, useState } from 'react';
import { AppMountParameters } from 'kibana/public';
import { EuiCode, EuiEmptyPrompt, EuiErrorBoundary, EuiPanel } from '@elastic/eui';
import { createHashHistory, History } from 'history';
import { Router, Redirect, Route, Switch } from 'react-router-dom';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import styled from 'styled-components';
import useObservable from 'react-use/lib/useObservable';
import {
ConfigContext,
FleetStatusProvider,
KibanaVersionContext,
sendGetPermissionsCheck,
sendSetup,
useBreadcrumbs,
useConfig,
} from './hooks';
import { Error, Loading } from './components';
import { IntraAppStateProvider } from './hooks/use_intra_app_state';
import { PackageInstallProvider } from './sections/epm/hooks';
import { PAGE_ROUTING_PATHS } from './constants';
import { DefaultLayout, WithoutHeaderLayout } from './layouts';
import { EPMApp } from './sections/epm';
import { AgentPolicyApp } from './sections/agent_policy';
import { DataStreamApp } from './sections/data_stream';
import { FleetApp } from './sections/agents';
import { IngestManagerOverview } from './sections/overview';
import { ProtectedRoute } from './index';
import { FleetConfigType, FleetStartServices } from '../../plugin';
import { UIExtensionsStorage } from './types';
import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public';
import { EuiThemeProvider } from '../../../../xpack_legacy/common';
import { UIExtensionsContext } from './hooks/use_ui_extension';

const ErrorLayout = ({ children }: { children: JSX.Element }) => (
<EuiErrorBoundary>
<DefaultLayout showSettings={false}>
<WithoutHeaderLayout>{children}</WithoutHeaderLayout>
</DefaultLayout>
</EuiErrorBoundary>
);

const Panel = styled(EuiPanel)`
max-width: 500px;
margin-right: auto;
margin-left: auto;
`;

export const WithPermissionsAndSetup: React.FC = memo(({ children }) => {
useBreadcrumbs('base');

const [isPermissionsLoading, setIsPermissionsLoading] = useState<boolean>(false);
const [permissionsError, setPermissionsError] = useState<string>();
const [isInitialized, setIsInitialized] = useState(false);
const [initializationError, setInitializationError] = useState<Error | null>(null);

useEffect(() => {
(async () => {
setIsPermissionsLoading(false);
setPermissionsError(undefined);
setIsInitialized(false);
setInitializationError(null);
try {
setIsPermissionsLoading(true);
const permissionsResponse = await sendGetPermissionsCheck();
setIsPermissionsLoading(false);
if (permissionsResponse.data?.success) {
try {
const setupResponse = await sendSetup();
if (setupResponse.error) {
setInitializationError(setupResponse.error);
}
} catch (err) {
setInitializationError(err);
}
setIsInitialized(true);
} else {
setPermissionsError(permissionsResponse.data?.error || 'REQUEST_ERROR');
}
} catch (err) {
setPermissionsError('REQUEST_ERROR');
}
})();
}, []);

if (isPermissionsLoading || permissionsError) {
return (
<ErrorLayout>
{isPermissionsLoading ? (
<Loading />
) : permissionsError === 'REQUEST_ERROR' ? (
<Error
title={
<FormattedMessage
id="xpack.fleet.permissionsRequestErrorMessageTitle"
defaultMessage="Unable to check permissions"
/>
}
error={i18n.translate('xpack.fleet.permissionsRequestErrorMessageDescription', {
defaultMessage: 'There was a problem checking Fleet permissions',
})}
/>
) : (
<Panel>
<EuiEmptyPrompt
iconType="securityApp"
title={
<h2>
{permissionsError === 'MISSING_SUPERUSER_ROLE' ? (
<FormattedMessage
id="xpack.fleet.permissionDeniedErrorTitle"
defaultMessage="Permission denied"
/>
) : (
<FormattedMessage
id="xpack.fleet.securityRequiredErrorTitle"
defaultMessage="Security is not enabled"
/>
)}
</h2>
}
body={
<p>
{permissionsError === 'MISSING_SUPERUSER_ROLE' ? (
<FormattedMessage
id="xpack.fleet.permissionDeniedErrorMessage"
defaultMessage="You are not authorized to access Fleet. Fleet requires {roleName} privileges."
values={{ roleName: <EuiCode>superuser</EuiCode> }}
/>
) : (
<FormattedMessage
id="xpack.fleet.securityRequiredErrorMessage"
defaultMessage="You must enable security in Kibana and Elasticsearch to use Fleet."
/>
)}
</p>
}
/>
</Panel>
)}
</ErrorLayout>
);
}

if (!isInitialized || initializationError) {
return (
<ErrorLayout>
{initializationError ? (
<Error
title={
<FormattedMessage
id="xpack.fleet.initializationErrorMessageTitle"
defaultMessage="Unable to initialize Fleet"
/>
}
error={initializationError}
/>
) : (
<Loading />
)}
</ErrorLayout>
);
}

return <>{children}</>;
});

/**
* Fleet Application context all the way down to the Router, but with no permissions or setup checks
* and no routes defined
*/
export const FleetAppContext: React.FC<{
basepath: string;
startServices: FleetStartServices;
config: FleetConfigType;
history: AppMountParameters['history'];
kibanaVersion: string;
extensions: UIExtensionsStorage;
/** For testing purposes only */
routerHistory?: History<any>;
}> = memo(
({
children,
startServices,
config,
history,
kibanaVersion,
extensions,
routerHistory = createHashHistory(),
}) => {
const isDarkMode = useObservable<boolean>(startServices.uiSettings.get$('theme:darkMode'));

return (
<startServices.i18n.Context>
<KibanaContextProvider services={{ ...startServices }}>
<EuiErrorBoundary>
<ConfigContext.Provider value={config}>
<KibanaVersionContext.Provider value={kibanaVersion}>
<EuiThemeProvider darkMode={isDarkMode}>
<UIExtensionsContext.Provider value={extensions}>
<FleetStatusProvider>
<IntraAppStateProvider kibanaScopedHistory={history}>
<Router history={routerHistory}>
<PackageInstallProvider notifications={startServices.notifications}>
{children}
</PackageInstallProvider>
</Router>
</IntraAppStateProvider>
</FleetStatusProvider>
</UIExtensionsContext.Provider>
</EuiThemeProvider>
</KibanaVersionContext.Provider>
</ConfigContext.Provider>
</EuiErrorBoundary>
</KibanaContextProvider>
</startServices.i18n.Context>
);
}
);

export const AppRoutes = memo(() => {
const { agents } = useConfig();

return (
<Switch>
<Route path={PAGE_ROUTING_PATHS.integrations}>
<DefaultLayout section="epm">
<EPMApp />
</DefaultLayout>
</Route>
<Route path={PAGE_ROUTING_PATHS.policies}>
<DefaultLayout section="agent_policy">
<AgentPolicyApp />
</DefaultLayout>
</Route>
<Route path={PAGE_ROUTING_PATHS.data_streams}>
<DefaultLayout section="data_stream">
<DataStreamApp />
</DefaultLayout>
</Route>
<ProtectedRoute path={PAGE_ROUTING_PATHS.fleet} isAllowed={agents.enabled}>
<DefaultLayout section="fleet">
<FleetApp />
</DefaultLayout>
</ProtectedRoute>
<Route exact path={PAGE_ROUTING_PATHS.overview}>
<DefaultLayout section="overview">
<IngestManagerOverview />
</DefaultLayout>
</Route>
<Redirect to="/" />
</Switch>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,13 @@ const paginationFromUrlParams = (urlParams: UrlPaginationParams): Pagination =>
// Search params can appear multiple times in the URL, in which case the value for them,
// once parsed, would be an array. In these case, we take the last value defined
pagination.currentPage = Number(
(Array.isArray(urlParams.currentPage)
? urlParams.currentPage[urlParams.currentPage.length - 1]
: urlParams.currentPage) ?? pagination.currentPage
(Array.isArray(urlParams.currentPage) ? urlParams.currentPage.pop() : urlParams.currentPage) ??
pagination.currentPage
);
pagination.pageSize =
Number(
(Array.isArray(urlParams.pageSize)
? urlParams.pageSize[urlParams.pageSize.length - 1]
: urlParams.pageSize) ?? pagination.pageSize
(Array.isArray(urlParams.pageSize) ? urlParams.pageSize.pop() : urlParams.pageSize) ??
pagination.pageSize
) ?? pagination.pageSize;

// If Current Page is not a valid positive integer, set it to 1
Expand Down
Loading