diff --git a/dashboards-observability/common/constants/application_analytics.ts b/dashboards-observability/common/constants/application_analytics.ts index df675a704..4c47eab2d 100644 --- a/dashboards-observability/common/constants/application_analytics.ts +++ b/dashboards-observability/common/constants/application_analytics.ts @@ -13,7 +13,15 @@ export const TAB_OVERVIEW_TITLE = 'Overview'; export const TAB_SERVICE_TITLE = 'Services'; export const TAB_TRACE_TITLE = 'Traces & Spans'; export const TAB_LOG_TITLE = 'Log Events'; -export const TAB_PANEL_TITLE = 'Panel'; +export const TAB_PANEL_TITLE = 'Panel(demo)'; export const TAB_CONFIG_TITLE = 'Configuration'; +export const TAB_INTEGRATION_ID = 'request-response'; +export const TAB_INTEGRATION_TITLE = 'Request/Response'; +export const TAB_UPSTREAM_ID = 'upstreams'; +export const TAB_UPSTREAM_TITLE = 'HTTP Upstreams'; +export const TAB_TCP_ZONES_ID = 'tcp-udp-zones'; +export const TAB_TCP_ZONES_TITLE = 'TCP/UDP Zones'; +export const TAB_TCP_UPSTREAMS_ID = 'tcp-udp-upstreams'; +export const TAB_TCP_UPSTREAMS_TITLE = 'TCP/UDP Upstreams'; export const APP_ANALYTICS_API_PREFIX = '/api/observability/application'; diff --git a/dashboards-observability/common/constants/shared.ts b/dashboards-observability/common/constants/shared.ts index 00b6b3f14..4de462734 100644 --- a/dashboards-observability/common/constants/shared.ts +++ b/dashboards-observability/common/constants/shared.ts @@ -72,7 +72,6 @@ export const pageStyles: CSS.Properties = { maxWidth: '1130px', }; - export enum visChartTypes { Bar = 'bar', HorizontalBar = 'horizontal_bar', @@ -82,13 +81,13 @@ export enum visChartTypes { Text = 'text', Gauge = 'gauge', Histogram = 'histogram', - TreeMap = 'tree_map' + TreeMap = 'tree_map', } export interface ValueOptionsAxes { - xaxis ?: IField[]; - yaxis ?: IField[]; - zaxis ?: IField[]; + xaxis?: IField[]; + yaxis?: IField[]; + zaxis?: IField[]; childField?: IField[]; valueField?: IField[]; series?: IField[]; @@ -97,67 +96,75 @@ export interface ValueOptionsAxes { export const NUMERICAL_FIELDS = ['short', 'integer', 'long', 'float', 'double']; -export const ENABLED_VIS_TYPES = [visChartTypes.Bar, visChartTypes.HorizontalBar, visChartTypes.Line, visChartTypes.Pie, visChartTypes.HeatMap, visChartTypes.Text, visChartTypes.TreeMap]; +export const ENABLED_VIS_TYPES = [ + visChartTypes.Bar, + visChartTypes.HorizontalBar, + visChartTypes.Line, + visChartTypes.Pie, + visChartTypes.HeatMap, + visChartTypes.Text, + visChartTypes.TreeMap, +]; //Live tail constants export const LIVE_OPTIONS = [ { - label:'5s', + label: '5s', startTime: 'now-5s', delayTime: 5000, }, { - label:'10s', + label: '10s', startTime: 'now-10s', delayTime: 10000, }, { - label:'30s', + label: '30s', startTime: 'now-30s', delayTime: 30000, }, { - label:'1m', + label: '1m', startTime: 'now-1m', delayTime: 60000, }, { - label:'5m', + label: '5m', startTime: 'now-5m', delayTime: 60000 * 5, }, { - label:'15m', + label: '15m', startTime: 'now-15m', delayTime: 60000 * 15, }, { - label:'30m', + label: '30m', startTime: 'now-30m', delayTime: 60000 * 30, }, { - label:'1h', + label: '1h', startTime: 'now-1h', delayTime: 60000 * 60, }, { - label:'2h', + label: '2h', startTime: 'now-2h', delayTime: 60000 * 120, }, ]; -export const LIVE_END_TIME ='now'; +export const LIVE_END_TIME = 'now'; export interface DefaultChartStylesProps { - DefaultMode: string, - Interpolation: string, - LineWidth: number, - FillOpacity: number, - MarkerSize: number, - ShowLegend: string, - LegendPosition: string -}; + DefaultMode: string; + Interpolation: string; + LineWidth: number; + FillOpacity: number; + MarkerSize: number; + ShowLegend: string; + LegendPosition: string; +} export const DefaultChartStyles: DefaultChartStylesProps = { DefaultMode: 'lines', @@ -166,7 +173,39 @@ export const DefaultChartStyles: DefaultChartStylesProps = { FillOpacity: 40, MarkerSize: 5, ShowLegend: 'show', - LegendPosition: 'v' -} + LegendPosition: 'v', +}; -export const FILLOPACITY_DIV_FACTOR = 200; +export const FILLOPACITY_DIV_FACTOR = 200; + +export const INTEGRATION = 'integration'; + +export const QUERY_VIS_TYPES = [ + { + type: visChartTypes.Line, + query: + 'source = nginx_2 | stats avg( upstream_response_time )as Response_Time , count() as response_count by span( @timestamp ,1h)', + }, + { + type: visChartTypes.Line, + query: 'source = nginx_2 | stats max(bytes_sent), avg(bytes_sent) by remote_addr', + }, + { + type: visChartTypes.Bar, + query: 'source = nginx_2 | stats count() by request_method', + }, + { + type: visChartTypes.Bar, + query: + 'source = nginx_2 | where status > 400 or status < 599 | stats count() by span( @timestamp , 1d)', + }, + { + type: visChartTypes.Line, + query: 'source = nginx_2 | stats avg( bytes_sent ) by span( @timestamp ,1d)', + }, + { + type: visChartTypes.Bar, + query: + 'source = nginx_2 | stats avg( upstream_response_time ) as response_time by span( @timestamp , 1h)', + }, +]; diff --git a/dashboards-observability/common/types/application_analytics.ts b/dashboards-observability/common/types/application_analytics.ts index 11f42c236..6d8045aea 100644 --- a/dashboards-observability/common/types/application_analytics.ts +++ b/dashboards-observability/common/types/application_analytics.ts @@ -28,6 +28,7 @@ export interface ApplicationRequestType { traceGroups: string[]; panelId: string; availabilityVisId: string; + // appType: string | null; TODO Uncomment this line when api if fixed to accept this field } export interface AvailabilityType { diff --git a/dashboards-observability/common/types/explorer.ts b/dashboards-observability/common/types/explorer.ts index 2204a6ab4..20314c527 100644 --- a/dashboards-observability/common/types/explorer.ts +++ b/dashboards-observability/common/types/explorer.ts @@ -86,6 +86,7 @@ export interface ILogExplorerProps { } export interface IExplorerProps { + appType?: string; pplService: PPLService; dslService: DSLService; tabId: string; diff --git a/dashboards-observability/public/components/app.tsx b/dashboards-observability/public/components/app.tsx index b4d164331..38446e701 100644 --- a/dashboards-observability/public/components/app.tsx +++ b/dashboards-observability/public/components/app.tsx @@ -8,9 +8,10 @@ import React from 'react'; import { Provider } from 'react-redux'; import { HashRouter, Route, Switch } from 'react-router-dom'; import { CoreStart } from '../../../../src/core/public'; -import { observabilityID, observabilityTitle } from '../../common/constants/shared'; +import { INTEGRATION, observabilityID, observabilityTitle } from '../../common/constants/shared'; import store from '../framework/redux/store'; import { AppPluginStartDependencies } from '../types'; +// import { Home as ApplicationAnalyticsHome } from './integrations/plugins/application_analytics/home'; import { Home as ApplicationAnalyticsHome } from './application_analytics/home'; import { Home as CustomPanelsHome } from './custom_panels/home'; import { EventAnalytics } from './event_analytics'; @@ -70,6 +71,26 @@ export const App = ({ dslService={dslService} savedObjects={savedObjects} timestampUtils={timestampUtils} + appType={'application'} + /> + ); + }} + /> + { + return ( + ); }} diff --git a/dashboards-observability/public/components/application_analytics/components/app_table.tsx b/dashboards-observability/public/components/application_analytics/components/app_table.tsx index 75fe228af..c6a496bd1 100644 --- a/dashboards-observability/public/components/application_analytics/components/app_table.tsx +++ b/dashboards-observability/public/components/application_analytics/components/app_table.tsx @@ -37,12 +37,13 @@ import moment from 'moment'; import { DeleteModal } from '../../common/helpers/delete_modal'; import { AppAnalyticsComponentDeps } from '../home'; import { getCustomModal } from '../../custom_panels/helpers/modal_containers'; -import { pageStyles, UI_DATE_FORMAT } from '../../../../common/constants/shared'; +import { INTEGRATION, pageStyles, UI_DATE_FORMAT } from '../../../../common/constants/shared'; import { ApplicationType, AvailabilityType } from '../../../../common/types/application_analytics'; interface AppTableProps extends AppAnalyticsComponentDeps { loading: boolean; applications: ApplicationType[]; + appType: string; fetchApplications: () => void; renameApplication: (newAppName: string, appId: string) => void; deleteApplication: (appList: string[], panelIdList: string[], toastMessage?: string) => void; @@ -55,6 +56,7 @@ export function AppTable(props: AppTableProps) { chrome, applications, parentBreadcrumbs, + appType, fetchApplications, renameApplication, deleteApplication, @@ -66,16 +68,23 @@ export function AppTable(props: AppTableProps) { const [isActionsPopoverOpen, setIsActionsPopoverOpen] = useState(false); const [modalLayout, setModalLayout] = useState(); const [selectedApplications, setSelectedApplications] = useState([]); - const createButtonText = 'Create application'; + const createButtonText = appType === INTEGRATION ? 'Add Integration' : 'Create application'; + const breadCrumbs = + appType === INTEGRATION + ? { + text: 'Integrations', + href: '#/integrations/plugins', + } + : { + text: 'Application analytics', + href: '#/application_analytics', + }; + + useEffect(() => { + chrome.setBreadcrumbs([...parentBreadcrumbs, breadCrumbs]); + }, [appType]); useEffect(() => { - chrome.setBreadcrumbs([ - ...parentBreadcrumbs, - { - text: 'Application analytics', - href: '#/application_analytics', - }, - ]); clear(); fetchApplications(); }, []); @@ -209,6 +218,7 @@ export function AppTable(props: AppTableProps) { } }; + const appLink = appType === INTEGRATION ? '#/integrations/plugins/' : '#/application_analytics/'; const tableColumns = [ { field: 'name', @@ -216,10 +226,7 @@ export function AppTable(props: AppTableProps) { sortable: true, truncateText: true, render: (value, record) => ( - + {_.truncate(record.name, { length: 100 })} ), @@ -251,6 +258,9 @@ export function AppTable(props: AppTableProps) { }, ] as Array>; + const allApps = + appType === INTEGRATION ? '#/integrations/plugins/all_apps' : '#/application_analytics/create'; + return (
@@ -258,18 +268,21 @@ export function AppTable(props: AppTableProps) { -

Overview

+ {appType === INTEGRATION ?

Integrations

:

Overview

}
- -

- Applications ({applications.length}) -

-
+ {appType !== INTEGRATION && ( + +

+ Applications + ({applications.length}) +

+
+ )}
@@ -284,7 +297,7 @@ export function AppTable(props: AppTableProps) { - + {createButtonText} @@ -324,7 +337,7 @@ export function AppTable(props: AppTableProps) { - + {createButtonText} diff --git a/dashboards-observability/public/components/application_analytics/components/application.tsx b/dashboards-observability/public/components/application_analytics/components/application.tsx index ba5132be7..2ec675ec5 100644 --- a/dashboards-observability/public/components/application_analytics/components/application.tsx +++ b/dashboards-observability/public/components/application_analytics/components/application.tsx @@ -41,6 +41,8 @@ import { Configuration } from './configuration'; import { TAB_CONFIG_ID, TAB_CONFIG_TITLE, + TAB_INTEGRATION_ID, + TAB_INTEGRATION_TITLE, TAB_LOG_ID, TAB_LOG_TITLE, TAB_OVERVIEW_ID, @@ -49,8 +51,14 @@ import { TAB_PANEL_TITLE, TAB_SERVICE_ID, TAB_SERVICE_TITLE, + TAB_TCP_UPSTREAMS_ID, + TAB_TCP_UPSTREAMS_TITLE, + TAB_TCP_ZONES_ID, + TAB_TCP_ZONES_TITLE, TAB_TRACE_ID, TAB_TRACE_TITLE, + TAB_UPSTREAM_ID, + TAB_UPSTREAM_TITLE, } from '../../../../common/constants/application_analytics'; import { TAB_EVENT_ID, TAB_CHART_ID, NEW_TAB } from '../../../../common/constants/explorer'; import { IQueryTab } from '../../../../common/types/explorer'; @@ -66,6 +74,8 @@ import { ServiceDetailFlyout } from './flyout_components/service_detail_flyout'; import { SpanDetailFlyout } from '../../../../public/components/trace_analytics/components/traces/span_detail_flyout'; import { TraceDetailFlyout } from './flyout_components/trace_detail_flyout'; import { fetchAppById, initializeTabData } from '../helpers/utils'; +import { Tabs } from '../../integrations/plugins/nginx/tabs'; +import { INTEGRATION } from '../../../../common/constants/shared'; const searchBarConfigs = { [TAB_EVENT_ID]: { @@ -86,7 +96,14 @@ interface AppDetailProps extends AppAnalyticsComponentDeps { savedObjects: SavedObjects; timestampUtils: TimestampUtils; notifications: NotificationsStart; - updateApp: (appId: string, updateAppData: Partial, type: string) => void; + appType: string; + updateApp: ( + appId: string, + updateAppData: Partial, + type: string, + appName?: string, + appType?: string | null + ) => void; setToasts: (title: string, color?: string, text?: ReactChild) => void; callback: (childfunction: () => void) => void; } @@ -105,6 +122,7 @@ export function Application(props: AppDetailProps) { query, filters, appConfigs, + appType, updateApp, setAppConfigs, setToasts, @@ -210,18 +228,31 @@ export function Application(props: AppDetailProps) { callback(switchToEvent); }, [appId]); + const breadCrumbs = + appType === INTEGRATION + ? [ + { + text: 'Integrations', + href: '#/integrations/plugins', + }, + { + text: application.name, + href: `${last(parentBreadcrumbs)!.href}integrations/plugins/${appId}`, + }, + ] + : [ + { + text: 'Application analytics', + href: '#/application_analytics', + }, + { + text: application.name, + href: `${last(parentBreadcrumbs)!.href}application_analytics/${appId}`, + }, + ]; + useEffect(() => { - chrome.setBreadcrumbs([ - ...parentBreadcrumbs, - { - text: 'Application analytics', - href: '#/application_analytics', - }, - { - text: application.name, - href: `${last(parentBreadcrumbs)!.href}application_analytics/${appId}`, - }, - ]); + chrome.setBreadcrumbs([...parentBreadcrumbs, ...breadCrumbs]); setStartTimeForApp(sessionStorage.getItem(`${application.name}StartTime`) || 'now-24h'); setEndTimeForApp(sessionStorage.getItem(`${application.name}EndTime`) || 'now'); }, [appId, application.name]); @@ -267,16 +298,61 @@ export function Application(props: AppDetailProps) { setTraceFlyoutId(''); }; - const childBreadcrumbs = [ - { - text: 'Application analytics', - href: '#/application_analytics', - }, - { - text: `${application.name}`, - href: `#/application_analytics/${appId}`, - }, - ]; + const childBreadcrumbs = + appType === INTEGRATION + ? [ + { + text: 'Integrations', + href: '#/integrations/plugins', + }, + { + text: `${application.name}`, + href: `#/integrations/plugins/application_analytics/${appId}`, + }, + ] + : [ + { + text: 'Application analytics', + href: '#/application_analytics', + }, + { + text: `${application.name}`, + href: `#/application_analytics/${appId}`, + }, + ]; + + const getNginxTab = (tabId: string) => { + return ( + <> + + undefined} + cloneCustomPanel={async () => Promise.reject()} + deleteCustomPanel={async () => Promise.reject()} + setToast={setToasts} + page="app" + appId={appId} + updateAvailabilityVizId={updateAvailabilityVizId} + startTime={appStartTime} + endTime={appEndTime} + setStartTime={setStartTimeForApp} + setEndTime={setEndTimeForApp} + onAddClick={switchToEvent} + onEditClick={onEditClick} + tabId={tabId} + appType={appType} + /> + + ); + }; const getOverview = () => { return ( @@ -356,6 +432,7 @@ export function Application(props: AppDetailProps) { const getLog = () => { return ( getNginxTab(TAB_INTEGRATION_ID), + }), + getAppAnalyticsTab({ + tabId: TAB_LOG_ID, + tabTitle: TAB_LOG_TITLE, + getContent: () => getLog(), + }), + getAppAnalyticsTab({ + tabId: TAB_PANEL_ID, + tabTitle: TAB_PANEL_TITLE, + getContent: () => getPanel(), + }), + getAppAnalyticsTab({ + tabId: TAB_CONFIG_ID, + tabTitle: TAB_CONFIG_TITLE, + getContent: () => getConfig(), + }), + ]; + const analyticsTabs = [ getAppAnalyticsTab({ tabId: TAB_OVERVIEW_ID, tabTitle: TAB_OVERVIEW_TITLE, @@ -517,6 +618,8 @@ export function Application(props: AppDetailProps) { }), ]; + appAnalyticsTabs = appType === INTEGRATION ? integrationTabs : analyticsTabs; + return (
diff --git a/dashboards-observability/public/components/application_analytics/components/create.tsx b/dashboards-observability/public/components/application_analytics/components/create.tsx index 084d53b17..175fe2af3 100644 --- a/dashboards-observability/public/components/application_analytics/components/create.tsx +++ b/dashboards-observability/public/components/application_analytics/components/create.tsx @@ -6,12 +6,16 @@ import { EuiButton, + EuiButtonEmpty, EuiFieldText, EuiFlexGroup, EuiFlexItem, + EuiFlyoutBody, + EuiFlyoutFooter, EuiForm, EuiFormRow, EuiHorizontalRule, + EuiLink, EuiPage, EuiPageBody, EuiPageContent, @@ -20,6 +24,7 @@ import { EuiPageHeader, EuiPageHeaderSection, EuiSpacer, + EuiText, EuiTitle, EuiToolTip, } from '@elastic/eui'; @@ -38,15 +43,26 @@ import { OptionType, } from '../../../../common/types/application_analytics'; import { fetchAppById } from '../helpers/utils'; +import { FlyoutContainers } from '../../common/flyout_containers'; +import { NginxDocument } from '../../integrations/plugins/nginx/doc'; +import { INTEGRATION } from '../../../../common/constants/shared'; interface CreateAppProps extends AppAnalyticsComponentDeps { dslService: DSLService; pplService: PPLService; setToasts: (title: string, color?: string, text?: ReactChild) => void; - createApp: (app: ApplicationRequestType, type: string) => void; - updateApp: (appId: string, updateAppData: Partial, type: string) => void; + createApp: (app: ApplicationRequestType, type: string, appType?: string | null) => void; + updateApp: ( + appId: string, + updateAppData: Partial, + type: string, + appName: string, + appType?: string | null + ) => void; clearStorage: () => void; existingAppId: string; + appName: string | null; + appType: string | null; } export const CreateApp = (props: CreateAppProps) => { @@ -67,8 +83,11 @@ export const CreateApp = (props: CreateAppProps) => { setFilters, clearStorage, existingAppId, + appType, + appName, } = props; const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); + const [isFlyoutVisibleIntegration, setIsFlyoutVisibleIntegration] = useState(false); const [selectedServices, setSelectedServices] = useState([]); const [selectedTraces, setSelectedTraces] = useState([]); @@ -86,13 +105,29 @@ export const CreateApp = (props: CreateAppProps) => { availability: { name: '', color: '', availabilityVisId: '' }, }); + const breadCrumbs = + appType === INTEGRATION + ? [ + { + text: 'Integrations', + href: '#/integrations/plugins', + }, + { + text: 'All Integrations', + href: '#/integrations/plugins/all_apps', + }, + ] + : [ + { + text: 'Application analytics', + href: '#/application_analytics', + }, + ]; + useEffect(() => { chrome.setBreadcrumbs([ ...parentBreadcrumbs, - { - text: 'Application analytics', - href: '#/application_analytics', - }, + ...breadCrumbs, { text: editMode ? 'Edit' : 'Create', href: `#/application_analytics/${editMode ? 'edit' : 'create'}`, @@ -126,6 +161,64 @@ export const CreateApp = (props: CreateAppProps) => { setIsFlyoutVisible(false); }; + const closeIntegrationFlyout = () => { + setIsFlyoutVisibleIntegration(false); + }; + + const openIntegrationFlyout = () => { + setIsFlyoutVisibleIntegration(true); + }; + + let integrationFlyout; + if (isFlyoutVisibleIntegration) { + integrationFlyout = ( + + + {/* +

{appName} Doc

+
*/} +
+ + } + flyoutBody={ + + + + +

{appName} information

+
+
+
+ + + NGINX is open source software for web serving, reverse proxying, caching, load + balancing, media streaming, and more. It started out as a web server designed for + maximum performance and stability. In addition to its HTTP server capabilities, NGINX + can also function as a proxy server for email (IMAP, POP3, and SMTP) and a reverse + proxy and load balancer for HTTP, TCP, and UDP servers. + + + Click here to know more about NGINX + +
+ } + flyoutFooter={ + + + + Close + + + + } + ariaLabel="pplReferenceFlyout" + /> + ); + } + let flyout; if (isFlyoutVisible) { flyout = ; @@ -156,8 +249,9 @@ export const CreateApp = (props: CreateAppProps) => { traceGroups: selectedTraces.map((option) => option.label), panelId: '', availabilityVisId: '', + // appType: appType, TODO uncomment this when backend api is fixed to accept this new field }; - createApp(appData, type); + createApp(appData, type, appType); }; const onUpdate = () => { @@ -167,18 +261,20 @@ export const CreateApp = (props: CreateAppProps) => { servicesEntities: selectedServices.map((option) => option.label), traceGroups: selectedTraces.map((option) => option.label), }; - updateApp(existingAppId, appData, 'update'); + updateApp(existingAppId, appData, 'update', appName, appType); }; + const redirectOnCancel = + appType === INTEGRATION ? 'integrations/plugins' : 'application_analytics'; const onCancel = () => { clearStorage(); - window.location.assign(`${last(parentBreadcrumbs)!.href}application_analytics`); + window.location.assign(`${last(parentBreadcrumbs)!.href}${redirectOnCancel}`); }; return ( -
+
- + @@ -271,7 +367,9 @@ export const CreateApp = (props: CreateAppProps) => { )} + {appType === INTEGRATION && appName === 'Nginx' && } + {integrationFlyout} {flyout}
); diff --git a/dashboards-observability/public/components/application_analytics/home.tsx b/dashboards-observability/public/components/application_analytics/home.tsx index 95ab3d978..9d09a6752 100644 --- a/dashboards-observability/public/components/application_analytics/home.tsx +++ b/dashboards-observability/public/components/application_analytics/home.tsx @@ -5,16 +5,16 @@ /* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable no-console */ -import React, { ReactChild, useEffect, useState } from 'react'; +import React, { ReactChild, useEffect, useState, useRef } from 'react'; import { Route, RouteComponentProps, Switch } from 'react-router-dom'; import DSLService from 'public/services/requests/dsl'; import PPLService from 'public/services/requests/ppl'; import SavedObjects from 'public/services/saved_objects/event_analytics/saved_objects'; import TimestampUtils from 'public/services/timestamp/timestamp'; -import { EuiGlobalToastList, EuiLink } from '@elastic/eui'; +import { EuiGlobalToastList, EuiLink, EuiSelectOption } from '@elastic/eui'; import { Toast } from '@elastic/eui/src/components/toast/global_toast_list'; import { isEmpty, last } from 'lodash'; -import { useDispatch } from 'react-redux'; +import { batch, useDispatch } from 'react-redux'; import { AppTable } from './components/app_table'; import { Application } from './components/application'; import { CreateApp } from './components/create'; @@ -23,11 +23,19 @@ import { FilterType } from '../trace_analytics/components/common/filters/filters import { handleIndicesExistRequest } from '../trace_analytics/requests/request_handler'; import { ObservabilitySideBar } from '../common/side_nav'; import { NotificationsStart } from '../../../../../src/core/public'; +import { changeQuery } from '../event_analytics/redux/slices/query_slice'; +import { updateTabName } from '../event_analytics/redux/slices/query_tab_slice'; import { APP_ANALYTICS_API_PREFIX } from '../../../common/constants/application_analytics'; import { ApplicationRequestType, ApplicationType, } from '../../../common/types/application_analytics'; +import { + SAVED_OBJECT_ID, + SAVED_OBJECT_TYPE, + SAVED_QUERY, + SAVED_VISUALIZATION, +} from '../../../common/constants/explorer'; import { calculateAvailability, fetchPanelsVizIdList, @@ -38,6 +46,9 @@ import { CUSTOM_PANELS_API_PREFIX, CUSTOM_PANELS_DOCUMENTATION_URL, } from '../../../common/constants/custom_panels'; +import { AllApps } from '../integrations/plugins/all_apps'; +import { fetchAppById } from '../application_analytics/helpers/utils'; +import { INTEGRATION, QUERY_VIS_TYPES } from '../../../common/constants/shared'; export type AppAnalyticsCoreDeps = TraceAnalyticsCoreDeps; @@ -47,6 +58,7 @@ interface HomeProps extends RouteComponentProps, AppAnalyticsCoreDeps { savedObjects: SavedObjects; timestampUtils: TimestampUtils; notifications: NotificationsStart; + appType: string; } export interface AppAnalyticsComponentDeps extends TraceAnalyticsComponentDeps { @@ -69,9 +81,11 @@ export const Home = (props: HomeProps) => { http, chrome, notifications, + appType, } = props; const [triggerSwitchToEvent, setTriggerSwitchToEvent] = useState(0); const dispatch = useDispatch(); + const selectedPanelNameRef = useRef(''); const [applicationList, setApplicationList] = useState([]); const [toasts, setToasts] = useState([]); const [indicesExist, setIndicesExist] = useState(true); @@ -80,6 +94,21 @@ export const Home = (props: HomeProps) => { const [filters, setFilters] = useState( storedFilters ? JSON.parse(storedFilters) : [] ); + const [visWithAvailability, setVisWithAvailability] = useState([]); + + const [application, setApplication] = useState({ + id: '', + dateCreated: '', + dateModified: '', + name: '', + description: '', + baseQuery: '', + servicesEntities: [], + traceGroups: [], + panelId: '', + availability: { name: '', color: '', availabilityVisId: '' }, + }); + const [name, setName] = useState(sessionStorage.getItem('AppAnalyticsName') || ''); const [description, setDescription] = useState( sessionStorage.getItem('AppAnalyticsDescription') || '' @@ -149,14 +178,21 @@ export const Home = (props: HomeProps) => { setQueryWithStorage(''); }; - const moveToApp = (id: string, type: string) => { - window.location.assign(`${last(parentBreadcrumbs)!.href}application_analytics/${id}`); + const moveToApp = (id: string, type: string, appType?: string | null) => { + appType === INTEGRATION + ? window.location.assign(`${last(parentBreadcrumbs)!.href}integrations/plugins/${id}`) + : window.location.assign(`${last(parentBreadcrumbs)!.href}application_analytics/${id}`); if (type === 'createSetAvailability') { setTriggerSwitchToEvent(2); } }; - const createPanelForApp = (applicationId: string, appName: string, type: string) => { + const createPanelForApp = ( + applicationId: string, + appName: string, + type: string, + appType?: string | null + ) => { return http .post(`${CUSTOM_PANELS_API_PREFIX}/panels`, { body: JSON.stringify({ @@ -165,7 +201,7 @@ export const Home = (props: HomeProps) => { }), }) .then((res) => { - updateApp(applicationId, { panelId: res.newPanelId }, type); + updateApp(applicationId, { panelId: res.newPanelId }, type, appName, appType); }) .catch((err) => { setToast( @@ -238,8 +274,94 @@ export const Home = (props: HomeProps) => { }); }; + // Add visualization to application's panel + const addVisualizationToPanel = async ( + appId: string, + visualizationId: string, + panelId: string + ) => { + return http + .post(`${CUSTOM_PANELS_API_PREFIX}/visualizations`, { + body: JSON.stringify({ + panelId, + savedVisualizationId: visualizationId, + }), + }) + .then(() => { + fetchAppById( + http, + pplService, + appId, + setApplication, + setAppConfigs, + setVisWithAvailability, + setToasts, + ); + }) + .catch((err) => { + // setToasts(`Error in adding ${visualizationName} visualization to the panel`, 'danger'); + console.error(err); + }); + }; + const setStartTimeForApp = (appName: string, newStartTime: string) => { + sessionStorage.setItem(`${appName}StartTime`, newStartTime); + }; + const setEndTimeForApp = (appName: string, newEndTime: string) => { + sessionStorage.setItem(`${appName}EndTime`, newEndTime); + }; + + // need to move to common , copied from explorer + const handleSavingObject = (appId, appName, type, panelId, chartObj) => { + // create new saved visualization + savedObjects + .createSavedVisualization({ + query: chartObj.query, + fields: [], + dateRange: ['now/y', 'now'], + type: chartObj.type, + name: `${chartObj.type} chart ${appName} ${type}`, + timestamp: '@timestamp', + applicationId: appId, + userConfigs: JSON.stringify({}), + description: '', + }) + .then((res: any) => { + batch(() => { + addVisualizationToPanel(appId, res.objectId, panelId); + setStartTimeForApp(appName, 'now/y'); + setEndTimeForApp(appName, 'now'); + dispatch( + changeQuery({ + undefined, + query: { + [SAVED_OBJECT_ID]: res.objectId, + [SAVED_OBJECT_TYPE]: SAVED_VISUALIZATION, + }, + }) + ); + dispatch( + updateTabName({ + undefined, + tabName: selectedPanelNameRef.current, + }) + ); + }); + setToast('New visualization'); + return res; + }) + .catch((error: any) => { + notifications.toasts.addError(error, { + title: `Cannot save Visualization '${selectedPanelNameRef.current}'`, + }); + }); + }; + // Create a new application - const createApp = (application: ApplicationRequestType, type: string) => { + const createApp = ( + application: ApplicationRequestType, + type: string, + appTypeIntegration?: string | null + ) => { const toast = isNameValid( application.name, applicationList.map((obj) => obj.name) @@ -256,6 +378,7 @@ export const Home = (props: HomeProps) => { servicesEntities: application.servicesEntities, traceGroups: application.traceGroups, availabilityVisId: '', + // appType: application.appType, TODO uncomment this when backend api is fixed to accept this new field }; return http @@ -263,8 +386,8 @@ export const Home = (props: HomeProps) => { body: JSON.stringify(requestBody), }) .then(async (res) => { - createPanelForApp(res.newAppId, application.name, type); - setToast(`Application "${application.name}" successfully created!`); + createPanelForApp(res.newAppId, application.name, type, appTypeIntegration); + // setToast(`Application "${application.name}" successfully created!`); clearStorage(); }) .catch((err) => { @@ -314,7 +437,9 @@ export const Home = (props: HomeProps) => { const updateApp = ( appId: string, updateAppData: Partial, - type: string + type: string, + appName?: string, + appType?: string | null ) => { const requestBody = { appId, @@ -326,13 +451,18 @@ export const Home = (props: HomeProps) => { body: JSON.stringify(requestBody), }) .then((res) => { + if (appType === INTEGRATION) { + for (let i = 0; i < QUERY_VIS_TYPES.length; i++) { + handleSavingObject(appId, appName, type, updateAppData.panelId, QUERY_VIS_TYPES[i]); + } + } if (type === 'update') { setToast('Application successfully updated.'); clearStorage(); - moveToApp(res.updatedAppId, type); + moveToApp(res.updatedAppId, type, appType); } if (type.startsWith('create')) { - moveToApp(res.updatedAppId, type); + moveToApp(res.updatedAppId, type, appType); } }) .catch((err) => { @@ -388,7 +518,7 @@ export const Home = (props: HomeProps) => { ( { deleteApplication={deleteApp} clearStorage={clearStorage} moveToApp={moveToApp} + appType={appType} {...commonProps} /> @@ -406,23 +537,39 @@ export const Home = (props: HomeProps) => { /> { + const query = new URLSearchParams(routerProps.location.search); + return ( + + ); + }} + /> + ( - + )} /> ( { setToasts={setToast} updateApp={updateApp} callback={callback} + appType={appType} {...commonProps} /> )} diff --git a/dashboards-observability/public/components/common/side_nav.tsx b/dashboards-observability/public/components/common/side_nav.tsx index 0f63ed243..6477e55ec 100644 --- a/dashboards-observability/public/components/common/side_nav.tsx +++ b/dashboards-observability/public/components/common/side_nav.tsx @@ -52,7 +52,7 @@ export function ObservabilitySideBar(props: { children: React.ReactNode }) { id: 0, items: [ { - name: 'Application analytics', + name: 'Application Analytics', id: 1, href: '#/application_analytics', }, @@ -88,6 +88,11 @@ export function ObservabilitySideBar(props: { children: React.ReactNode }) { id: 5, href: '#/notebooks', }, + { + name: 'Integrations', + id: 6, + href: '#/integrations/plugins', + }, ], }, ]; diff --git a/dashboards-observability/public/components/custom_panels/custom_panel_view.tsx b/dashboards-observability/public/components/custom_panels/custom_panel_view.tsx index be6ab6ae9..653c03f0c 100644 --- a/dashboards-observability/public/components/custom_panels/custom_panel_view.tsx +++ b/dashboards-observability/public/components/custom_panels/custom_panel_view.tsx @@ -48,7 +48,7 @@ import { isPPLFilterValid, fetchVisualizationById, } from './helpers/utils'; -import { UI_DATE_FORMAT } from '../../../common/constants/shared'; +import { INTEGRATION, UI_DATE_FORMAT } from '../../../common/constants/shared'; import { VisaulizationFlyout } from './panel_modules/visualization_flyout'; import { uiSettingsService } from '../../../common/utils'; import { PPLReferenceFlyout } from '../common/helpers'; @@ -112,6 +112,7 @@ interface CustomPanelViewProps { appId?: string; updateAvailabilityVizId?: any; onAddClick?: any; + appType?: string; } export const CustomPanelView = (props: CustomPanelViewProps) => { @@ -136,6 +137,7 @@ export const CustomPanelView = (props: CustomPanelViewProps) => { setToast, onEditClick, onAddClick, + appType, } = props; const [openPanelName, setOpenPanelName] = useState(''); const [panelCreatedTime, setPanelCreatedTime] = useState(''); @@ -583,34 +585,36 @@ export const CustomPanelView = (props: CustomPanelViewProps) => { - - onRefreshFilters(startTime, endTime)} - dslService={dslService} - getSuggestions={parseGetSuggestions} - onItemSelect={onItemSelect} - isDisabled={inputDisabled} - tabId={'panels-filter'} - placeholder={ - "Use PPL 'where' clauses to add filters on all visualizations [where Carrier = 'OpenSearch-Air']" - } - possibleCommands={[{ label: 'where' }]} - append={ - - PPL - - } - /> - + {appType !== INTEGRATION && ( + + onRefreshFilters(startTime, endTime)} + dslService={dslService} + getSuggestions={parseGetSuggestions} + onItemSelect={onItemSelect} + isDisabled={inputDisabled} + tabId={'panels-filter'} + placeholder={ + "Use PPL 'where' clauses to add filters on all visualizations [where Carrier = 'OpenSearch-Air']" + } + possibleCommands={[{ label: 'where' }]} + append={ + + PPL + + } + /> + + )} void; editActionType: string; setEditVizId?: any; + appType?: string; } export const PanelGrid = (props: PanelGridProps) => { @@ -79,6 +82,7 @@ export const PanelGrid = (props: PanelGridProps) => { pplFilterValue, showFlyout, editActionType, + appType, } = props; const [currentLayout, setCurrentLayout] = useState([]); const [postEditLayout, setPostEditLayout] = useState([]); @@ -196,17 +200,30 @@ export const PanelGrid = (props: PanelGridProps) => { loadVizComponents(); }, []); - return ( - - {panelVisualizations.map((panelVisualization: VisualizationType, index) => ( -
{gridData[index]}
- ))} -
- ); + const visualizationData = + appType === INTEGRATION ? ( + + {panelVisualizations.map((panelVisualization: VisualizationType, index) => ( + +
+ {gridData[index]} +
+
+ ))} +
+ ) : ( + + {panelVisualizations.map((panelVisualization: VisualizationType, index) => ( +
{gridData[index]}
+ ))} +
+ ); + + return visualizationData; }; diff --git a/dashboards-observability/public/components/event_analytics/explorer/explorer.tsx b/dashboards-observability/public/components/event_analytics/explorer/explorer.tsx index 015bf48f5..1bd578f14 100644 --- a/dashboards-observability/public/components/event_analytics/explorer/explorer.tsx +++ b/dashboards-observability/public/components/event_analytics/explorer/explorer.tsx @@ -57,6 +57,7 @@ import { PPL_NEWLINE_REGEX, LIVE_OPTIONS, LIVE_END_TIME, + INTEGRATION, } from '../../../../common/constants/shared'; import { getIndexPatternFromRawQuery, preprocessQuery, buildQuery } from '../../../../common/utils'; import { useFetchEvents, useFetchVisualizations } from '../hooks'; @@ -105,6 +106,7 @@ export const Explorer = ({ setEndTime, callback, callbackInApp, + appType, }: IExplorerProps) => { const dispatch = useDispatch(); const requestParams = { tabId }; @@ -141,7 +143,9 @@ export const Explorer = ({ const [browserTabFocus, setBrowserTabFocus] = useState(true); const [liveTimestamp, setLiveTimestamp] = useState(DATE_PICKER_FORMAT); const [triggerAvailability, setTriggerAvailability] = useState(false); - const [isValidDataConfigOptionSelected, setIsValidDataConfigOptionSelected] = useState(false); + const [isValidDataConfigOptionSelected, setIsValidDataConfigOptionSelected] = useState( + false + ); const queryRef = useRef(); const appBasedRef = useRef(''); @@ -332,7 +336,6 @@ export const Explorer = ({ startingTime = curQuery![SELECTED_DATE_RANGE][0]; endingTime = curQuery![SELECTED_DATE_RANGE][1]; } - // compose final query const finalQuery = composeFinalQuery( curQuery, @@ -729,7 +732,7 @@ export const Explorer = ({ }; const changeIsValidConfigOptionState = (isValidConfig: Boolean) => - setIsValidDataConfigOptionSelected(isValidConfig); + setIsValidDataConfigOptionSelected(isValidConfig); const getExplorerVis = () => { return ( @@ -750,7 +753,17 @@ export const Explorer = ({ ); }; - const getMainContentTabs = () => { + const getIntegrationTab = () => { + return [ + getMainContentTab({ + tabID: TAB_EVENT_ID, + tabTitle: TAB_EVENT_TITLE, + getContent: () => getMainContent(), + }), + ]; + }; + + const getApplicationTab = () => { return [ getMainContentTab({ tabID: TAB_EVENT_ID, @@ -766,7 +779,24 @@ export const Explorer = ({ }; const memorizedMainContentTabs = useMemo(() => { - return getMainContentTabs(); + return getApplicationTab(); + }, [ + curVisId, + isPanelTextFieldInvalid, + explorerData, + explorerFields, + isSidebarClosed, + countDistribution, + explorerVisualizations, + selectedContentTabId, + isOverridingTimestamp, + visualizations, + query, + isLiveTailOnRef.current, + ]); + + const memorizedIntegrationContentTabs = useMemo(() => { + return getIntegrationTab(); }, [ curVisId, isPanelTextFieldInvalid, @@ -828,7 +858,53 @@ export const Explorer = ({ setTempQuery(newQuery); }; + // need to move to common , copied from explorer + const handleCreatingObject = () => { + // create new saved visualization + savedObjects + .createSavedQuery({ + query: 'source = opensearch_dashboards_sample_data_logs | stats count() , max( memory ) ', + fields: [], + dateRange: ['now/y', 'now/y'], + type, + name: appName, + timestamp: 'timestamp', + applicationId: appId, + userConfigs: {}, + description: '', + }) + .then((res: any) => { + batch(() => { + addVisualizationToPanel(res.objectId, selectedPanelNameRef.current); + dispatch( + changeQuery({ + undefined, + query: { + [SAVED_OBJECT_ID]: res.objectId, + [SAVED_OBJECT_TYPE]: SAVED_VISUALIZATION, + }, + }) + ); + dispatch( + updateTabName({ + undefined, + tabName: selectedPanelNameRef.current, + }) + ); + }); + setToast('New visualization'); + return res; + }) + .catch((error: any) => { + notifications.toasts.addError(error, { + title: `Cannot save Visualization '${selectedPanelNameRef.current}'`, + }); + }); + }; const handleSavingObject = async () => { + // if (appType === INTEGRATION) { + // handleCreatingObject(iAppId, iAppName, itype); + // } else { const currQuery = queryRef.current; const currFields = explorerFieldsRef.current; if (isEmpty(currQuery![RAW_QUERY]) && isEmpty(appBaseQuery)) { @@ -1044,6 +1120,7 @@ export const Explorer = ({ }); } } + //} }; const liveTailLoop = async ( @@ -1172,13 +1249,21 @@ export const Explorer = ({ setIsLiveTailPopoverOpen={setIsLiveTailPopoverOpen} liveTailName={liveTailNameRef.current} /> - tab.id === selectedContentTabId)} - onTabClick={(selectedTab: EuiTabbedContentTab) => handleContentTabClick(selectedTab)} - tabs={memorizedMainContentTabs} - /> + {appType === INTEGRATION ? ( + + ) : ( + tab.id === selectedContentTabId)} + onTabClick={(selectedTab: EuiTabbedContentTab) => handleContentTabClick(selectedTab)} + tabs={memorizedMainContentTabs} + /> + )}
); diff --git a/dashboards-observability/public/components/event_analytics/hooks/use_fetch_visualizations.ts b/dashboards-observability/public/components/event_analytics/hooks/use_fetch_visualizations.ts index 866921201..bd031d003 100644 --- a/dashboards-observability/public/components/event_analytics/hooks/use_fetch_visualizations.ts +++ b/dashboards-observability/public/components/event_analytics/hooks/use_fetch_visualizations.ts @@ -40,7 +40,6 @@ export const useFetchVisualizations = ({ handler: (res: any) => void ) => { setIsVisLoading(true); - await pplService .fetch({ query, diff --git a/dashboards-observability/public/components/integrations/plugins/all_apps.tsx b/dashboards-observability/public/components/integrations/plugins/all_apps.tsx new file mode 100644 index 000000000..da9623c08 --- /dev/null +++ b/dashboards-observability/public/components/integrations/plugins/all_apps.tsx @@ -0,0 +1,105 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +/* eslint-disable react-hooks/exhaustive-deps */ + +import { + EuiCard, + EuiFlexGrid, + EuiFlexItem, + EuiHorizontalRule, + EuiIcon, + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentHeader, + EuiPageContentHeaderSection, + EuiPageHeader, + EuiTitle, +} from '@elastic/eui'; +import { TraceAnalyticsCoreDeps } from 'public/components/trace_analytics/home'; +import React, { useEffect } from 'react'; +import { pageStyles } from '../../../../common/constants/shared'; + +export function AllApps(props: TraceAnalyticsCoreDeps) { + const { parentBreadcrumbs, http, chrome } = props; + + useEffect(() => { + chrome.setBreadcrumbs([ + ...parentBreadcrumbs, + { + text: 'Integrations', + href: '#/integrations/plugins', + }, + { + text: 'All Integrations', + href: '#/integrations/plugins/all_apps', + }, + ]); + }, []); + + const icons = [ + { + name: 'Sql', + icon: 'MySQL', + path: 'create?type=integration', + description: 'Collect performance schema metrics, query throughput, custom metrics, and more', + }, + { + name: 'Nginx', + icon: 'Nginx', + path: 'create?type=integration&app=Nginx', + description: 'Monitor connection and request metrics with NGINX', + }, + { + name: 'Kibana', + icon: 'Kibana', + path: 'create?type=integration', + description: 'Monitor connection and request metrics with Kibana', + }, + { + name: 'Metrics', + icon: 'Metrics', + path: 'create?type=integration', + description: 'Monitor connection and request metrics with Kibana', + }, + ]; + + const cardNodes = icons.map(function (item, index) { + return ( + + } + title={item.name} + href={`#/application_analytics/${item.path}`} + description={item.description} + /> + + ); + }); + return ( +
+ + + + + + + +

All Integrations

+
+
+
+ +
+ + {cardNodes} + +
+
+
+
+
+ ); +} diff --git a/dashboards-observability/public/components/integrations/plugins/nginx/doc/constant.tsx b/dashboards-observability/public/components/integrations/plugins/nginx/doc/constant.tsx new file mode 100644 index 000000000..3379143d1 --- /dev/null +++ b/dashboards-observability/public/components/integrations/plugins/nginx/doc/constant.tsx @@ -0,0 +1,168 @@ +export enum NginxConfig { + STEP_1 = 'Step 1', + STEP_2 = 'Step 2', + STEP_3 = 'Step 3', + STEP_4 = 'Step 4', + STEP1_DESC = 'In this step we have added the custom log that we want to retrieve in the log.', + STEP2_DESC = 'In this step we need to provide the location of the access file.', + STEP3_DESC = 'In this step we need to update the fluent configuration file (fluent.conf)', + STEP4_DESC = 'In this step we need to provide the location of the access file.', + STEP5_DESC = 'In this setting we have created nginx_json_format as index to fetch data.', + CONFIGURATION_STEP1 = `'{' + '"msec": "$msec", ' # request unixtime in seconds with a milliseconds resolution + '"connection": "$connection", ' # connection serial number + '"connection_requests": "$connection_requests", ' # number of requests made in connection + '"pid": "$pid", ' # process pid + '"request_id": "$request_id", ' # the unique request id + '"request_length": "$request_length", ' # request length (including headers and body) + '"remote_addr": "$remote_addr", ' # client IP + '"remote_user": "$remote_user", ' # client HTTP username + '"remote_port": "$remote_port", ' # client port + '"time_local": "$time_local", ' + '"time_iso8601": "$time_iso8601", ' # local time in the ISO 8601 standard format + '"request": "$request", ' # full path no arguments if the request + '"request_uri": "$request_uri", ' # full path and arguments if the request + '"args": "$args", ' # args + '"status": "$status", ' # response status code + '"body_bytes_sent": "$body_bytes_sent", ' # the number of body bytes exclude headers sent to a client + '"bytes_sent": "$bytes_sent", ' # the number of bytes sent to a client + '"http_referer": "$http_referer", ' # HTTP referer + '"http_user_agent": "$http_user_agent", ' # user agent + '"http_x_forwarded_for": "$http_x_forwarded_for", ' # http_x_forwarded_for + '"http_host": "$http_host", ' # the request Host: header + '"server_name": "$server_name", ' # the name of the vhost serving the request + '"request_time": "$request_time", ' # request processing time in seconds with msec resolution + '"upstream": "$upstream_addr", ' # upstream backend server for proxied requests + '"upstream_connect_time": "$upstream_connect_time", ' # upstream handshake time incl. TLS + '"upstream_header_time": "$upstream_header_time", ' # time spent receiving upstream headers + '"upstream_response_time": "$upstream_response_time", ' # time spent receiving upstream body + '"upstream_response_length": "$upstream_response_length", ' # upstream response length + '"upstream_cache_status": "$upstream_cache_status", ' # cache HIT/MISS where applicable + '"ssl_protocol": "$ssl_protocol", ' # TLS protocol + '"ssl_cipher": "$ssl_cipher", ' # TLS cipher + '"scheme": "$scheme", ' # http or https + '"request_method": "$request_method", ' # request method + '"server_protocol": "$server_protocol", ' # request protocol, like HTTP/1.1 or HTTP/2.0 + '"pipe": "$pipe", ' # "p" if request was pipelined, "." otherwise + '"gzip_ratio": "$gzip_ratio"' + '}'`, + CONFIGURATION_STEP2 = `##this uses the our custom log format access_log /var/log/nginx/json_format.log custom_format`, + CONFIGURATION_STEP3 = ` + @type tail + + @type json + time_key time + time_format %time_iso8601 + typescode:integer,size:integer,connections_active:integer,request_time:float,upstream_connect_time:float,bytes_sent_body:integer,upstream_header_time:float,upstream_connect_time_1:float,upstream_header_time1:float,upstream_response_length:integer,upstream_response_time1:float,upstream_status:integer,bytes_sent:integer,connection:integer,content_length:integer,msec:float,pid:integer,realip_remote_port:integer,remote_port:integer,request_length:integer,tcpinfo_rtt:integer,tcpinfo_rttvar:integer,tcpinfo_rcv_space:integer,tcpinfo_snd_cwnd:integer,connection_requests:integer,connections_reading:integer,connections_waiting:integer,connections_writing:integer,gzip_ratio:float + # time_format %d/%b/%Y:%H:%M:%S %z + + path /var/log/nginx/json_format.log + tag nginx + `, + CONFIGURATION_STEP4 = ` + @type opensearch + + chunk_limit_size 1GB + total_limit_size 2GB + flush_interval 3s + flush_thread_count 8 + + ssl_verify false + host 10.53.97.136 #Enter your host name + port 9200 + index_name nginx_json_format # Enter your index name + verify_os_version_at_startup false + suppress_type_name true + include_timestamp true + `, + CONFIGURATION_STEP5 = ` "_source" : { + "msec" : 1.654592648156E9, + "connection" : 365, + "connection_requests" : 1, + "connections_active" : 2, + "pid" : 110407, + "request_id" : "758077d2f3f26a297fe9242d661e074a", + "request_length" : 1128, + "remote_addr" : "10.37.3.22", + "remote_user" : "", + "remote_port" : 51919, + "time_local" : "07/Jun/2022:14:34:08 +0530", + "time_iso8601" : "2022-06-07T14:34:08+05:30", + "request" : "POST /wp-admin/admin-ajax.php HTTP/1.1", + "request_uri" : "/wp-admin/admin-ajax.php", + "args" : "", + "status" : 200, + "body_bytes_sent" : 58, + "bytes_sent" : 562, + "http_referer" : "http://10.53.97.136/wp-admin/edit.php", + "http_user_agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36", + "http_x_forwarded_for" : "", + "http_host" : "10.53.97.136", + "server_name" : "_", + "request_time" : 0.029, + "upstream" : "unix:/run/php/php7.4-fpm.sock", + "upstream_connect_time" : 0.0, + "upstream_header_time" : 0.032, + "upstream_response_time" : 0.032, + "upstream_response_length" : 65, + "upstream_cache_status" : "", + "ssl_protocol" : "", + "ssl_cipher" : "", + "scheme" : "http", + "request_method" : "POST", + "server_protocol" : "HTTP/1.1", + "pipe" : ".", + "gzip_ratio" : 0.0, + "content_type" : "application/x-www-form-urlencoded; charset=UTF-8", + "hostname" : "hj-datdpe13901.persistent.co.in", + "content_length" : 127, + "uri" : "/wp-admin/admin-ajax.php", + "request_body" : "data%5Bwp-check-locked-posts%5D%5B%5D=post-1&interval=15&_nonce=3f2a20e904&action=heartbeat&screen_id=edit-post&has_focus=false", + "https" : "", + "modern_browser" : "", + "msie" : "", + "nginx_version" : "1.18.0", + "connections_reading" : 0, + "connections_waiting" : 1, + "connections_writing" : 0, + "date_gmt" : "Tuesday, 07-Jun-2022 09:04:08 GMT", + "document_root" : "/var/www/wordpress", + "document_uri" : "/wp-admin/admin-ajax.php", + "tcpinfo_rttvar" : 2945, + "tcpinfo_snd_cwnd" : 10, + "tcpinfo_rcv_space" : 14600, + "tcpinfo_rtt" : 5890, + "upstream_addr" : "unix:/run/php/php7.4-fpm.sock", + "query_string" : "", + "request_body_file" : "", + "request_completion" : "OK", + "request_filename" : "/var/www/wordpress/wp-admin/admin-ajax.php", + "realpath_root" : "/var/www/wordpress", + "ssl_session_id" : "", + "server_addr" : "10.53.97.136", + "is_args" : "", + "realip_remote_addr" : "10.37.3.22", + "realip_remote_port" : 51919, + "ssl_server_name" : "", + "ssl_session_reused" : "", + "ssl_client_verify" : "", + "ssl_client_serial" : "", + "ssl_client_s_dn" : "", + "upstream_status" : 200, + "uid_got" : "", + "uid_reset" : "", + "uid_set" : "", + "proxy_host" : "", + "proxy_port" : "", + "proxy_protocol_addr" : "", + "date_local" : "Tuesday, 07-Jun-2022 14:34:08 IST", + "proxy_protocol_port" : "", + "limit_rate" : 0, + "server_port" : 80, + "ssl_client_cert" : "", + "ssl_client_fingerprint" : "", + "ssl_client_i_dn" : "", + "ssl_client_raw_cert" : "", + "@timestamp" : "2022-06-07T14:34:08.157536533+05:30" + }`, +} diff --git a/dashboards-observability/public/components/integrations/plugins/nginx/doc/index.tsx b/dashboards-observability/public/components/integrations/plugins/nginx/doc/index.tsx new file mode 100644 index 000000000..24dc7a95c --- /dev/null +++ b/dashboards-observability/public/components/integrations/plugins/nginx/doc/index.tsx @@ -0,0 +1,197 @@ +import { + EuiButton, + EuiButtonEmpty, + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiFlyoutFooter, + EuiHorizontalRule, + EuiLink, + EuiPageBody, + EuiPageContent, + EuiPageContentHeader, + EuiPageContentHeaderSection, + EuiPageHeader, + EuiPageHeaderSection, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import React, { useState } from 'react'; +import { FlyoutContainers } from '../../../../common/flyout_containers'; +import { NginxConfig } from './constant'; + +type Props = { + appName: string; +}; + +export function NginxDocument({ appName }: Props) { + const [isFlyoutVisibleIntegration, setIsFlyoutVisibleIntegration] = useState(false); + const closeIntegrationFlyout = () => { + setIsFlyoutVisibleIntegration(false); + }; + + const openIntegrationFlyout = () => { + setIsFlyoutVisibleIntegration(true); + }; + let integrationFlyout; + if (isFlyoutVisibleIntegration) { + integrationFlyout = ( + + + {/* +

{appName} Doc

+
*/} +
+ + } + flyoutBody={ + + + + +

{appName} Configurations

+
+
+
+ + +
{NginxConfig.STEP_1}
+
+
+ + {NginxConfig.STEP1_DESC} + +
+ + {NginxConfig.CONFIGURATION_STEP1} + +
+ +
{NginxConfig.STEP_2}
+
+
+ + {NginxConfig.STEP2_DESC} + +
+ + {NginxConfig.CONFIGURATION_STEP2} + +
+ +
{NginxConfig.STEP_3}
+
+
+ + {NginxConfig.STEP3_DESC} + +
+ + {NginxConfig.CONFIGURATION_STEP3} + +
+ + {NginxConfig.STEP5_DESC} + +
+ + {NginxConfig.CONFIGURATION_STEP4} + +
+ +
{NginxConfig.STEP_4}
+
+
+ + {NginxConfig.STEP4_DESC} + +
+ + {NginxConfig.CONFIGURATION_STEP5} + +
+ + Click here for more details + +
+
+ } + flyoutFooter={ + + + + Close + + + + } + ariaLabel="configurationLayout" + /> + ); + } + + return ( +
+ + + + +

Document panel

+
+
+
+ + + + +

{appName} information

+
+
+
+ + +
Installation
+
+
+ + NGINX is open source software for web serving, reverse proxying, caching, load + balancing, media streaming, and more. It started out as a web server designed for + maximum performance and stability. + +
+ +
{NginxConfig.STEP_1}
+
+ +

sudo apt update

+
+
+ +

{NginxConfig.STEP_2}

+
+ +

sudo apt install nginx

+
+ + + Configurations + + {/* +

I am a transparent box simply for padding

+
*/} +
+
+ {integrationFlyout} +
+ ); +} diff --git a/dashboards-observability/public/components/integrations/plugins/nginx/nginx.tsx b/dashboards-observability/public/components/integrations/plugins/nginx/nginx.tsx new file mode 100644 index 000000000..0feabb7a1 --- /dev/null +++ b/dashboards-observability/public/components/integrations/plugins/nginx/nginx.tsx @@ -0,0 +1,41 @@ +import React, { useEffect } from 'react'; +import { + EuiCard, + EuiFlexGrid, + EuiFlexItem, + EuiHorizontalRule, + EuiIcon, + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentHeader, + EuiPageContentHeaderSection, + EuiPageHeader, + EuiTitle, +} from '@elastic/eui'; +export function Nginx() { + const Title = 'Nginx App'; + + return ( + + + + + + + +

{Title}

+
+
+
+ +
+ + {'Nginx'} + +
+
+
+
+ ); +} diff --git a/dashboards-observability/public/components/integrations/plugins/nginx/schema/schema.ts b/dashboards-observability/public/components/integrations/plugins/nginx/schema/schema.ts new file mode 100644 index 000000000..4918a9fff --- /dev/null +++ b/dashboards-observability/public/components/integrations/plugins/nginx/schema/schema.ts @@ -0,0 +1,71 @@ +export interface NginxSource { + msec: number; + connection: number; + connection_requests: number; + pid: number; + request_id: string; + request_length: number; + remote_addr: string; + remote_user: string; + remote_port: number; + time_local: string; + time_iso8601: string; + request: string; + request_uri: string; + args: string; + status: number; + body_bytes_sent: number; + bytes_sent: number; + http_referer: string; + http_user_agent: string; + http_x_forwarded_for: string; + http_host: string; + server_name: string; + request_time: number; + upstream: string; + upstream_connect_time: number; + upstream_header_time: number; + upstream_response_time: string; + upstream_response_length: number; + upstream_cache_status: string; + ssl_protocol: string; + ssl_cipher: string; + scheme: string; + request_method: string; + server_protocol: string; + pipe: string; + gzip_ratio: number; + '@timestamp': string; +} + +export interface Total { + value: number; + relation: string; +} + +export interface Hits { + _index: string; + _id: string; + _score: number; + _source: NginxSource; +} + +export interface HitsObject { + total: Total; + max_score: number; + hits: Hits[]; +} + +export interface Shards { + total: number; + successful: number; + skipped: number; + failed: number; +} + +export interface NginxSchema { + took: number; + timed_out: boolean; + _shards: Shards; + hits: HitsObject; +} diff --git a/dashboards-observability/public/components/integrations/plugins/nginx/tabs/index.tsx b/dashboards-observability/public/components/integrations/plugins/nginx/tabs/index.tsx new file mode 100644 index 000000000..3778bdd12 --- /dev/null +++ b/dashboards-observability/public/components/integrations/plugins/nginx/tabs/index.tsx @@ -0,0 +1,677 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +/* eslint-disable no-console */ +/* eslint-disable react-hooks/exhaustive-deps */ + +import { + EuiBreadcrumb, + EuiButton, + EuiContextMenu, + EuiContextMenuPanelDescriptor, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiOverlayMask, + EuiPage, + EuiPageBody, + EuiPageContentBody, + EuiPageHeader, + EuiPageHeaderSection, + EuiPopover, + EuiSpacer, + EuiSuperDatePicker, + EuiTitle, + OnTimeChangeProps, + ShortDate, +} from '@elastic/eui'; +import { last } from 'lodash'; +import React, { useEffect, useState } from 'react'; +import { DurationRange } from '@elastic/eui/src/components/date_picker/types'; +import moment from 'moment'; +import DSLService from '../../../../../services/requests/dsl'; //../../../../services/requests/dsl +import { CoreStart } from '../../../../../../../../src/core/public'; +import { EmptyPanelView } from '../../../../custom_panels/panel_modules/empty_panel'; +import { + CREATE_PANEL_MESSAGE, + CUSTOM_PANELS_API_PREFIX, +} from '../../../../../../common/constants/custom_panels'; +import { SavedVisualizationType, VisualizationType } from '../../../../../../common/types/custom_panels'; +import { PanelGrid } from '../../../../custom_panels/panel_modules/panel_grid'; +import { getCustomModal } from '../../../../custom_panels/helpers/modal_containers'; +import PPLService from '../../../../../services/requests/ppl'; +import { + isDateValid, + convertDateTime, + onTimeChange, + isPPLFilterValid, + fetchVisualizationById, +} from '../../../../custom_panels/helpers/utils'; +import { UI_DATE_FORMAT } from '../../../../../../common/constants/shared'; +import { VisaulizationFlyout } from '../../../../custom_panels/panel_modules/visualization_flyout'; +import { uiSettingsService } from '../../../../../../common/utils'; +import { PPLReferenceFlyout } from '../../../../common/helpers'; +import { Autocomplete } from '../../../../common/search/autocomplete'; +import { + parseGetSuggestions, + onItemSelect, + parseForIndices, +} from '../../../../common/search/autocomplete_logic'; +import { AddVisualizationPopover } from '../../../../custom_panels/helpers/add_visualization_popover'; +import { DeleteModal } from '../../../../common/helpers/delete_modal'; + +/* + * "CustomPanelsView" module used to render an Operational Panel + * + * Props taken in as params are: + * panelId: Name of the panel opened + * page: Page where component is called + * http: http core service + * pplService: ppl requestor service + * dslService: dsl requestor service + * chrome: chrome core service + * parentBreadcrumb: parent breadcrumb + * renameCustomPanel: Rename function for the panel + * deleteCustomPanel: Delete function for the panel + * cloneCustomPanel: Clone function for the panel + * setToast: create Toast function + * onEditClick: Edit function for visualization + * startTime: Starting time + * endTime: Ending time + * setStartTime: Function to change start time + * setEndTime: Function to change end time + * childBreadcrumbs: Breadcrumbs to extend + * appId: id of application that panel belongs to + * onAddClick: Function for add button instead of add visualization popover + */ + +interface CustomPanelViewProps { + panelId: string; + page: 'app' | 'operationalPanels'; + http: CoreStart['http']; + pplService: PPLService; + dslService: DSLService; + chrome: CoreStart['chrome']; + parentBreadcrumbs: EuiBreadcrumb[]; + renameCustomPanel: (editedCustomPanelName: string, editedCustomPanelId: string) => Promise; + deleteCustomPanel: (customPanelId: string, customPanelName: string) => Promise; + cloneCustomPanel: (clonedCustomPanelName: string, clonedCustomPanelId: string) => Promise; + setToast: ( + title: string, + color?: string, + text?: React.ReactChild | undefined, + side?: string | undefined + ) => void; + onEditClick: (savedVisualizationId: string) => any; + startTime: string; + endTime: string; + setStartTime: any; + setEndTime: any; + childBreadcrumbs?: EuiBreadcrumb[]; + appId?: string; + updateAvailabilityVizId?: any; + onAddClick?: any; + tabId?: string; + appType: string; +} + +export const Tabs = (props: CustomPanelViewProps) => { + const { + panelId, + page, + appId, + http, + pplService, + dslService, + chrome, + parentBreadcrumbs, + childBreadcrumbs, + startTime, + endTime, + setStartTime, + setEndTime, + updateAvailabilityVizId, + tabId, + appType, + renameCustomPanel, + deleteCustomPanel, + cloneCustomPanel, + setToast, + onEditClick, + onAddClick, + } = props; + const [openPanelName, setOpenPanelName] = useState(''); + const [panelCreatedTime, setPanelCreatedTime] = useState(''); + const [pplFilterValue, setPPLFilterValue] = useState(''); + const [baseQuery, setBaseQuery] = useState(''); + const [onRefresh, setOnRefresh] = useState(false); + + const [inputDisabled, setInputDisabled] = useState(true); + const [addVizDisabled, setAddVizDisabled] = useState(false); + const [editDisabled, setEditDisabled] = useState(false); + const [dateDisabled, setDateDisabled] = useState(false); + const [panelVisualizations, setPanelVisualizations] = useState([]); + const [editMode, setEditMode] = useState(false); + const [isModalVisible, setIsModalVisible] = useState(false); // Modal Toggle + const [modalLayout, setModalLayout] = useState(); // Modal Layout + const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); // Add Visualization Flyout + const [isFlyoutReplacement, setisFlyoutReplacement] = useState(false); + const [replaceVisualizationId, setReplaceVisualizationId] = useState(''); + const [panelsMenuPopover, setPanelsMenuPopover] = useState(false); + const [editActionType, setEditActionType] = useState(''); + const [isHelpFlyoutVisible, setHelpIsFlyoutVisible] = useState(false); + + const appPanel = page === 'app'; + + const closeHelpFlyout = () => { + setAddVizDisabled(false); + setHelpIsFlyoutVisible(false); + }; + + const showHelpFlyout = () => { + setAddVizDisabled(true); + setHelpIsFlyoutVisible(true); + }; + + // DateTimePicker States + const [recentlyUsedRanges, setRecentlyUsedRanges] = useState([]); + + // Fetch Panel by id + const fetchCustomPanel = async () => { + return http + .get(`${CUSTOM_PANELS_API_PREFIX}/panels/${panelId}`) + .then((res) => { + setOpenPanelName(res.operationalPanel.name); + setPanelCreatedTime(res.createdTimeMs); + setPPLFilterValue(res.operationalPanel.queryFilter.query); + setStartTime(startTime ? startTime : res.operationalPanel.timeRange.from); + setEndTime(endTime ? endTime : res.operationalPanel.timeRange.to); + setPanelVisualizations(res.operationalPanel.visualizations); + }) + .catch((err) => { + console.error('Issue in fetching the operational panels', err); + }); + }; + + const handleQueryChange = (newQuery: string) => { + setPPLFilterValue(newQuery); + }; + + const closeModal = () => { + setIsModalVisible(false); + }; + + const showModal = () => { + setIsModalVisible(true); + }; + + const onDatePickerChange = (timeProps: OnTimeChangeProps) => { + onTimeChange( + timeProps.start, + timeProps.end, + recentlyUsedRanges, + setRecentlyUsedRanges, + setStartTime, + setEndTime + ); + onRefreshFilters(timeProps.start, timeProps.end); + }; + + const onDelete = async () => { + deleteCustomPanel(panelId, openPanelName).then((res) => { + setTimeout(() => { + window.location.assign(`${last(parentBreadcrumbs)!.href}`); + }, 1000); + }); + closeModal(); + }; + + const deletePanel = () => { + setModalLayout( + + ); + showModal(); + }; + + const onRename = async (newCustomPanelName: string) => { + renameCustomPanel(newCustomPanelName, panelId).then(() => { + setOpenPanelName(newCustomPanelName); + }); + closeModal(); + }; + + const renamePanel = () => { + setModalLayout( + getCustomModal( + onRename, + closeModal, + 'Name', + 'Rename Panel', + 'Cancel', + 'Rename', + openPanelName, + CREATE_PANEL_MESSAGE + ) + ); + showModal(); + }; + + const onClone = async (newCustomPanelName: string) => { + cloneCustomPanel(newCustomPanelName, panelId).then((id: string) => { + window.location.assign(`${last(parentBreadcrumbs)!.href}${id}`); + }); + closeModal(); + }; + + const clonePanel = () => { + setModalLayout( + getCustomModal( + onClone, + closeModal, + 'Name', + 'Duplicate Panel', + 'Cancel', + 'Duplicate', + openPanelName + ' (copy)', + CREATE_PANEL_MESSAGE + ) + ); + showModal(); + }; + + // toggle between panel edit mode + const editPanel = (editType: string) => { + setEditMode(!editMode); + if (editType === 'cancel') fetchCustomPanel(); + setEditActionType(editType); + }; + + const closeFlyout = () => { + setIsFlyoutVisible(false); + setAddVizDisabled(false); + checkDisabledInputs(); + }; + + const showFlyout = (isReplacement?: boolean, replaceVizId?: string) => { + setisFlyoutReplacement(isReplacement); + setReplaceVisualizationId(replaceVizId); + setIsFlyoutVisible(true); + setAddVizDisabled(true); + setInputDisabled(true); + }; + + const checkDisabledInputs = () => { + // When not in edit mode and panel has no visualizations + if (panelVisualizations.length === 0 && !editMode) { + setEditDisabled(true); + setInputDisabled(true); + setAddVizDisabled(false); + setDateDisabled(false); + } + + // When panel has visualizations + if (panelVisualizations.length > 0) { + setEditDisabled(false); + setInputDisabled(false); + setAddVizDisabled(false); + setDateDisabled(false); + } + + // When in edit mode + if (editMode) { + setEditDisabled(false); + setInputDisabled(true); + setAddVizDisabled(true); + setDateDisabled(true); + } + }; + + const buildBaseQuery = async () => { + const indices: string[] = []; + for (let i = 0; i < panelVisualizations.length; i++) { + const visualizationId = panelVisualizations[i].savedVisualizationId; + // TODO: create route to get list of visualizations in one call + const visData: SavedVisualizationType = await fetchVisualizationById( + http, + visualizationId, + (value: string) => setToast(value, 'danger') + ); + const moreIndices = parseForIndices(visData.query); + for (let j = 0; j < moreIndices.length; j++) { + if (!indices.includes(moreIndices[j])) { + indices.push(moreIndices[j]); + } + } + } + setBaseQuery('source = ' + indices.join(', ')); + return; + }; + + const onRefreshFilters = (start: ShortDate, end: ShortDate) => { + if (!isDateValid(convertDateTime(start), convertDateTime(end, false), setToast)) { + return; + } + + if (!isPPLFilterValid(pplFilterValue, setToast)) { + return; + } + + const panelFilterBody = { + panelId, + query: pplFilterValue, + language: 'ppl', + to: end, + from: start, + }; + + http + .post(`${CUSTOM_PANELS_API_PREFIX}/panels/filter`, { + body: JSON.stringify(panelFilterBody), + }) + .then((res) => { + setOnRefresh(!onRefresh); + }) + .catch((err) => { + setToast('Error is adding filters to the operational panel', 'danger'); + console.error(err.body.message); + }); + }; + + const cloneVisualization = (visualzationTitle: string, savedVisualizationId: string) => { + http + .post(`${CUSTOM_PANELS_API_PREFIX}/visualizations`, { + body: JSON.stringify({ + panelId, + savedVisualizationId, + }), + }) + .then(async (res) => { + setPanelVisualizations(res.visualizations); + setToast(`Visualization ${visualzationTitle} successfully added!`, 'success'); + }) + .catch((err) => { + setToast(`Error in adding ${visualzationTitle} visualization to the panel`, 'danger'); + console.error(err); + }); + }; + + const cancelButton = ( + editPanel('cancel')}> + Cancel + + ); + + const saveButton = ( + editPanel('save')}> + Save + + ); + + const editButton = ( + editPanel('edit')} disabled={editDisabled}> + Edit + + ); + + const addButton = ( + + Add + + ); + + // Panel Actions Button + const panelActionsButton = ( + setPanelsMenuPopover(true)} + disabled={addVizDisabled} + > + Panel actions + + ); + + let flyout; + if (isFlyoutVisible) { + flyout = ( + + ); + } + + let helpFlyout; + if (isHelpFlyoutVisible) { + helpFlyout = ; + } + + const panelActionsMenu: EuiContextMenuPanelDescriptor[] = [ + { + id: 0, + title: 'Panel actions', + items: [ + { + name: 'Reload panel', + onClick: () => { + setPanelsMenuPopover(false); + fetchCustomPanel(); + }, + }, + { + name: 'Rename panel', + onClick: () => { + setPanelsMenuPopover(false); + renamePanel(); + }, + }, + { + name: 'Duplicate panel', + onClick: () => { + setPanelsMenuPopover(false); + clonePanel(); + }, + }, + { + name: 'Delete panel', + onClick: () => { + setPanelsMenuPopover(false); + deletePanel(); + }, + }, + ], + }, + ]; + + // Fetch the custom panel on Initial Mount + useEffect(() => { + fetchCustomPanel(); + }, [panelId]); + + // Toggle input type (disabled or not disabled) + // Disabled when there no visualizations in panels or when the panel is in edit mode + useEffect(() => { + checkDisabledInputs(); + }, [editMode]); + + // Build base query with all of the indices included in the current visualizations + useEffect(() => { + checkDisabledInputs(); + buildBaseQuery(); + }, [panelVisualizations]); + + // Edit the breadcrumb when panel name changes + useEffect(() => { + let newBreadcrumb; + if (childBreadcrumbs) { + newBreadcrumb = childBreadcrumbs; + } else { + newBreadcrumb = [ + { + text: openPanelName, + href: `${last(parentBreadcrumbs)!.href}${panelId}`, + }, + ]; + } + chrome.setBreadcrumbs([...parentBreadcrumbs, ...newBreadcrumb]); + }, [panelId, openPanelName]); + + return ( +
+ + + + {appPanel || ( + <> + + +

{openPanelName}

+
+ + + + Created on {moment(panelCreatedTime).format(UI_DATE_FORMAT)} +
+ + + {editMode ? ( + <> + {cancelButton} + {saveButton} + + ) : ( + {editButton} + )} + + setPanelsMenuPopover(false)} + > + + + + + + + + + + )} +
+ + + + onRefreshFilters(startTime, endTime)} + dslService={dslService} + getSuggestions={parseGetSuggestions} + onItemSelect={onItemSelect} + isDisabled={inputDisabled} + tabId={'panels-filter'} + placeholder={ + "Use PPL 'where' clauses to add filters on all visualizations [where Carrier = 'OpenSearch-Air']" + } + possibleCommands={[{ label: 'where' }]} + append={ + + PPL + + } + /> + + + + + {appPanel && ( + <> + {editMode ? ( + <> + {cancelButton} + {saveButton} + + ) : ( + {editButton} + )} + {addButton} + + )} + + + {panelVisualizations.length === 0 && ( + + )} + + +
+
+ {isModalVisible && modalLayout} + {flyout} + {helpFlyout} +
+ ); +}; diff --git a/dashboards-observability/public/components/integrations/plugins/sql/sql.tsx b/dashboards-observability/public/components/integrations/plugins/sql/sql.tsx new file mode 100644 index 000000000..29328420d --- /dev/null +++ b/dashboards-observability/public/components/integrations/plugins/sql/sql.tsx @@ -0,0 +1,41 @@ +import React, { useEffect } from 'react'; +import { + EuiCard, + EuiFlexGrid, + EuiFlexItem, + EuiHorizontalRule, + EuiIcon, + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentHeader, + EuiPageContentHeaderSection, + EuiPageHeader, + EuiTitle, +} from '@elastic/eui'; +export function Sql() { + const Title = 'SQL App'; + + return ( + + + + + + + +

{Title}

+
+
+
+ +
+ + {'cardNodes'} + +
+
+
+
+ ); +} diff --git a/dashboards-observability/public/services/saved_objects/event_analytics/saved_objects.ts b/dashboards-observability/public/services/saved_objects/event_analytics/saved_objects.ts index 880aad65c..05ce04c33 100644 --- a/dashboards-observability/public/services/saved_objects/event_analytics/saved_objects.ts +++ b/dashboards-observability/public/services/saved_objects/event_analytics/saved_objects.ts @@ -207,7 +207,6 @@ export default class SavedObjects { name: params.name, timestamp: params.timestamp, }); - return await this.http.post( `${OBSERVABILITY_BASE}${EVENT_ANALYTICS}${SAVED_OBJECTS}${SAVED_QUERY}`, {